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).is_some_and(|perm| perm.is_granted(kind))
41 }
42
43 pub fn find_permission(&self, path: &Path) -> Option<FsAccessPermission> {
66 let mut max_path_len = 0;
67 let mut highest_permission = FsAccessPermission::None;
68
69 for perm in &self.permissions {
71 let permission_path = dunce::canonicalize(&perm.path).unwrap_or(perm.path.clone());
72 if path.starts_with(&permission_path) {
73 let path_len = permission_path.components().count();
74 if path_len > max_path_len {
75 max_path_len = path_len;
77 highest_permission = perm.access;
78 } else if path_len == max_path_len {
79 highest_permission = match (highest_permission, perm.access) {
81 (FsAccessPermission::ReadWrite, _)
82 | (FsAccessPermission::Read, FsAccessPermission::Write)
83 | (FsAccessPermission::Write, FsAccessPermission::Read) => {
84 FsAccessPermission::ReadWrite
85 }
86 (FsAccessPermission::None, perm) => perm,
87 (existing_perm, _) => existing_perm,
88 }
89 }
90 }
91 }
92
93 if max_path_len > 0 { Some(highest_permission) } else { None }
94 }
95
96 pub fn join_all(&mut self, root: &Path) {
98 self.permissions.iter_mut().for_each(|perm| {
99 perm.path = root.join(&perm.path);
100 })
101 }
102
103 pub fn joined(mut self, root: &Path) -> Self {
105 self.join_all(root);
106 self
107 }
108
109 pub fn remove(&mut self, path: &Path) {
111 self.permissions.retain(|permission| permission.path != path)
112 }
113
114 pub fn is_empty(&self) -> bool {
116 self.permissions.is_empty()
117 }
118
119 pub fn len(&self) -> usize {
121 self.permissions.len()
122 }
123}
124
125#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
127pub struct PathPermission {
128 pub access: FsAccessPermission,
130 pub path: PathBuf,
132}
133
134impl PathPermission {
135 pub fn new(path: impl Into<PathBuf>, access: FsAccessPermission) -> Self {
137 Self { path: path.into(), access }
138 }
139
140 pub fn read(path: impl Into<PathBuf>) -> Self {
142 Self::new(path, FsAccessPermission::Read)
143 }
144
145 pub fn read_write(path: impl Into<PathBuf>) -> Self {
147 Self::new(path, FsAccessPermission::ReadWrite)
148 }
149
150 pub fn write(path: impl Into<PathBuf>) -> Self {
152 Self::new(path, FsAccessPermission::Write)
153 }
154
155 pub fn none(path: impl Into<PathBuf>) -> Self {
157 Self::new(path, FsAccessPermission::None)
158 }
159
160 pub fn is_granted(&self, kind: FsAccessKind) -> bool {
162 self.access.is_granted(kind)
163 }
164}
165
166#[derive(Clone, Copy, Debug, PartialEq, Eq)]
168pub enum FsAccessKind {
169 Read,
171 Write,
173}
174
175impl fmt::Display for FsAccessKind {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 match self {
178 Self::Read => f.write_str("read"),
179 Self::Write => f.write_str("write"),
180 }
181 }
182}
183
184#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
186pub enum FsAccessPermission {
187 #[default]
189 None,
190 Read,
192 Write,
194 ReadWrite,
196}
197
198impl FsAccessPermission {
199 pub fn is_granted(&self, kind: FsAccessKind) -> bool {
201 match (self, kind) {
202 (Self::ReadWrite, _) => true,
203 (Self::Write, FsAccessKind::Write) => true,
204 (Self::Read, FsAccessKind::Read) => true,
205 (Self::None, _) => false,
206 _ => false,
207 }
208 }
209}
210
211impl FromStr for FsAccessPermission {
212 type Err = String;
213
214 fn from_str(s: &str) -> Result<Self, Self::Err> {
215 match s {
216 "true" | "read-write" | "readwrite" => Ok(Self::ReadWrite),
217 "false" | "none" => Ok(Self::None),
218 "read" => Ok(Self::Read),
219 "write" => Ok(Self::Write),
220 _ => Err(format!("Unknown variant {s}")),
221 }
222 }
223}
224
225impl fmt::Display for FsAccessPermission {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 match self {
228 Self::ReadWrite => f.write_str("read-write"),
229 Self::None => f.write_str("none"),
230 Self::Read => f.write_str("read"),
231 Self::Write => f.write_str("write"),
232 }
233 }
234}
235
236impl Serialize for FsAccessPermission {
237 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
238 where
239 S: Serializer,
240 {
241 match self {
242 Self::ReadWrite => serializer.serialize_bool(true),
243 Self::None => serializer.serialize_bool(false),
244 Self::Read => serializer.serialize_str("read"),
245 Self::Write => serializer.serialize_str("write"),
246 }
247 }
248}
249
250impl<'de> Deserialize<'de> for FsAccessPermission {
251 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
252 where
253 D: Deserializer<'de>,
254 {
255 #[derive(Deserialize)]
256 #[serde(untagged)]
257 enum Status {
258 Bool(bool),
259 String(String),
260 }
261 match Status::deserialize(deserializer)? {
262 Status::Bool(enabled) => {
263 let status = if enabled { Self::ReadWrite } else { Self::None };
264 Ok(status)
265 }
266 Status::String(val) => val.parse().map_err(serde::de::Error::custom),
267 }
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn can_parse_permission() {
277 assert_eq!(FsAccessPermission::ReadWrite, "true".parse().unwrap());
278 assert_eq!(FsAccessPermission::ReadWrite, "readwrite".parse().unwrap());
279 assert_eq!(FsAccessPermission::ReadWrite, "read-write".parse().unwrap());
280 assert_eq!(FsAccessPermission::None, "false".parse().unwrap());
281 assert_eq!(FsAccessPermission::None, "none".parse().unwrap());
282 assert_eq!(FsAccessPermission::Read, "read".parse().unwrap());
283 assert_eq!(FsAccessPermission::Write, "write".parse().unwrap());
284 }
285
286 #[test]
287 fn nested_permissions() {
288 let permissions = FsPermissions::new(vec![
289 PathPermission::read("./"),
290 PathPermission::write("./out"),
291 PathPermission::read_write("./out/contracts"),
292 ]);
293
294 let permission =
295 permissions.find_permission(Path::new("./out/contracts/MyContract.sol")).unwrap();
296 assert_eq!(FsAccessPermission::ReadWrite, permission);
297 let permission = permissions.find_permission(Path::new("./out/MyContract.sol")).unwrap();
298 assert_eq!(FsAccessPermission::Write, permission);
299 }
300
301 #[test]
302 fn read_write_permission_combination() {
303 let permissions = FsPermissions::new(vec![
305 PathPermission::read("./out/contracts"),
306 PathPermission::write("./out/contracts"),
307 ]);
308
309 let permission =
310 permissions.find_permission(Path::new("./out/contracts/MyContract.sol")).unwrap();
311 assert_eq!(FsAccessPermission::ReadWrite, permission);
312 }
313
314 #[test]
315 fn longest_path_takes_precedence() {
316 let permissions = FsPermissions::new(vec![
317 PathPermission::read_write("./out"),
318 PathPermission::read("./out/contracts"),
319 ]);
320
321 let permission =
323 permissions.find_permission(Path::new("./out/contracts/MyContract.sol")).unwrap();
324 assert_eq!(FsAccessPermission::Read, permission);
325
326 let permission = permissions.find_permission(Path::new("./out/other.sol")).unwrap();
328 assert_eq!(FsAccessPermission::ReadWrite, permission);
329 }
330}