1use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use std::{
5 fmt,
6 path::{Path, PathBuf},
7 str::FromStr,
8};
9
10#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(transparent)]
15pub struct FsPermissions {
16 pub permissions: Vec<PathPermission>,
18}
19
20impl FsPermissions {
21 pub fn new(permissions: impl IntoIterator<Item = PathPermission>) -> Self {
23 Self { permissions: permissions.into_iter().collect() }
24 }
25
26 pub fn add(&mut self, permission: PathPermission) {
28 self.permissions.push(permission)
29 }
30
31 pub fn is_path_allowed(&self, path: &Path, kind: FsAccessKind) -> bool {
40 self.find_permission(path).map(|perm| perm.is_granted(kind)).unwrap_or_default()
41 }
42
43 pub fn find_permission(&self, path: &Path) -> Option<FsAccessPermission> {
53 let mut permission: Option<&PathPermission> = None;
54 for perm in &self.permissions {
55 let permission_path = dunce::canonicalize(&perm.path).unwrap_or(perm.path.clone());
56 if path.starts_with(permission_path) {
57 if let Some(active_perm) = permission.as_ref() {
58 if perm.path < active_perm.path {
60 continue;
61 }
62 }
63 permission = Some(perm);
64 }
65 }
66 permission.map(|perm| perm.access)
67 }
68
69 pub fn join_all(&mut self, root: &Path) {
71 self.permissions.iter_mut().for_each(|perm| {
72 perm.path = root.join(&perm.path);
73 })
74 }
75
76 pub fn joined(mut self, root: &Path) -> Self {
78 self.join_all(root);
79 self
80 }
81
82 pub fn remove(&mut self, path: &Path) {
84 self.permissions.retain(|permission| permission.path != path)
85 }
86
87 pub fn is_empty(&self) -> bool {
89 self.permissions.is_empty()
90 }
91
92 pub fn len(&self) -> usize {
94 self.permissions.len()
95 }
96}
97
98#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
100pub struct PathPermission {
101 pub access: FsAccessPermission,
103 pub path: PathBuf,
105}
106
107impl PathPermission {
108 pub fn new(path: impl Into<PathBuf>, access: FsAccessPermission) -> Self {
110 Self { path: path.into(), access }
111 }
112
113 pub fn read(path: impl Into<PathBuf>) -> Self {
115 Self::new(path, FsAccessPermission::Read)
116 }
117
118 pub fn read_write(path: impl Into<PathBuf>) -> Self {
120 Self::new(path, FsAccessPermission::ReadWrite)
121 }
122
123 pub fn write(path: impl Into<PathBuf>) -> Self {
125 Self::new(path, FsAccessPermission::Write)
126 }
127
128 pub fn none(path: impl Into<PathBuf>) -> Self {
130 Self::new(path, FsAccessPermission::None)
131 }
132
133 pub fn is_granted(&self, kind: FsAccessKind) -> bool {
135 self.access.is_granted(kind)
136 }
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141pub enum FsAccessKind {
142 Read,
144 Write,
146}
147
148impl fmt::Display for FsAccessKind {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 match self {
151 Self::Read => f.write_str("read"),
152 Self::Write => f.write_str("write"),
153 }
154 }
155}
156
157#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
159pub enum FsAccessPermission {
160 #[default]
162 None,
163 ReadWrite,
165 Read,
167 Write,
169}
170
171impl FsAccessPermission {
172 pub fn is_granted(&self, kind: FsAccessKind) -> bool {
174 match (self, kind) {
175 (Self::ReadWrite, _) => true,
176 (Self::None, _) => false,
177 (Self::Read, FsAccessKind::Read) => true,
178 (Self::Write, FsAccessKind::Write) => true,
179 _ => false,
180 }
181 }
182}
183
184impl FromStr for FsAccessPermission {
185 type Err = String;
186
187 fn from_str(s: &str) -> Result<Self, Self::Err> {
188 match s {
189 "true" | "read-write" | "readwrite" => Ok(Self::ReadWrite),
190 "false" | "none" => Ok(Self::None),
191 "read" => Ok(Self::Read),
192 "write" => Ok(Self::Write),
193 _ => Err(format!("Unknown variant {s}")),
194 }
195 }
196}
197
198impl fmt::Display for FsAccessPermission {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 match self {
201 Self::ReadWrite => f.write_str("read-write"),
202 Self::None => f.write_str("none"),
203 Self::Read => f.write_str("read"),
204 Self::Write => f.write_str("write"),
205 }
206 }
207}
208
209impl Serialize for FsAccessPermission {
210 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
211 where
212 S: Serializer,
213 {
214 match self {
215 Self::ReadWrite => serializer.serialize_bool(true),
216 Self::None => serializer.serialize_bool(false),
217 Self::Read => serializer.serialize_str("read"),
218 Self::Write => serializer.serialize_str("write"),
219 }
220 }
221}
222
223impl<'de> Deserialize<'de> for FsAccessPermission {
224 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
225 where
226 D: Deserializer<'de>,
227 {
228 #[derive(Deserialize)]
229 #[serde(untagged)]
230 enum Status {
231 Bool(bool),
232 String(String),
233 }
234 match Status::deserialize(deserializer)? {
235 Status::Bool(enabled) => {
236 let status = if enabled { Self::ReadWrite } else { Self::None };
237 Ok(status)
238 }
239 Status::String(val) => val.parse().map_err(serde::de::Error::custom),
240 }
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn can_parse_permission() {
250 assert_eq!(FsAccessPermission::ReadWrite, "true".parse().unwrap());
251 assert_eq!(FsAccessPermission::ReadWrite, "readwrite".parse().unwrap());
252 assert_eq!(FsAccessPermission::ReadWrite, "read-write".parse().unwrap());
253 assert_eq!(FsAccessPermission::None, "false".parse().unwrap());
254 assert_eq!(FsAccessPermission::None, "none".parse().unwrap());
255 assert_eq!(FsAccessPermission::Read, "read".parse().unwrap());
256 assert_eq!(FsAccessPermission::Write, "write".parse().unwrap());
257 }
258
259 #[test]
260 fn nested_permissions() {
261 let permissions = FsPermissions::new(vec![
262 PathPermission::read("./"),
263 PathPermission::write("./out"),
264 PathPermission::read_write("./out/contracts"),
265 ]);
266
267 let permission =
268 permissions.find_permission(Path::new("./out/contracts/MyContract.sol")).unwrap();
269 assert_eq!(FsAccessPermission::ReadWrite, permission);
270 let permission = permissions.find_permission(Path::new("./out/MyContract.sol")).unwrap();
271 assert_eq!(FsAccessPermission::Write, permission);
272 }
273}