soma/problem/configs/
common.rs

1use std::fmt;
2use std::path::{Path, PathBuf};
3
4use path_slash::PathBufExt;
5use serde::de::{self, Deserializer, Unexpected, Visitor};
6use serde::ser::Serializer;
7use serde::{Deserialize, Serialize};
8
9use crate::prelude::*;
10
11#[derive(Debug, PartialEq)]
12pub enum FilePermissions {
13    Custom(u16),
14    Executable,
15    ReadOnly,
16    // Reserved: FetchOnly, ReadWrite
17}
18
19impl Serialize for FilePermissions {
20    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
21    where
22        S: Serializer,
23    {
24        let permissions_string = match self {
25            FilePermissions::Custom(permissions) => format!("{:o}", permissions),
26            FilePermissions::Executable => "550".to_owned(),
27            FilePermissions::ReadOnly => "440".to_owned(),
28        };
29        serializer.serialize_str(&permissions_string)
30    }
31}
32
33impl<'de> Deserialize<'de> for FilePermissions {
34    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
35    where
36        D: Deserializer<'de>,
37    {
38        deserializer.deserialize_str(FilePermissionsVisitor)
39    }
40}
41
42struct FilePermissionsVisitor;
43
44impl<'de> Visitor<'de> for FilePermissionsVisitor {
45    type Value = FilePermissions;
46
47    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
48        write!(
49            formatter,
50            "a file permissions string in octal number format"
51        )
52    }
53
54    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
55    where
56        E: de::Error,
57    {
58        let permissions = u16::from_str_radix(s, 8);
59        match permissions {
60            // Support sticky bits later
61            Ok(permissions) if permissions <= 0o777 => Ok(FilePermissions::Custom(permissions)),
62            _ => Err(de::Error::invalid_value(Unexpected::Str(s), &self)),
63        }
64    }
65}
66
67fn serialize_as_slash_path<S>(path_buf: &PathBuf, serializer: S) -> Result<S::Ok, S::Error>
68where
69    S: Serializer,
70{
71    match path_buf.to_slash() {
72        Some(s) => serializer.serialize_str(&s),
73        None => Err(serde::ser::Error::custom(
74            "path contains invalid UTF-8 characters",
75        )),
76    }
77}
78
79// target_path is defined as String instead of PathBuf to support Windows
80#[derive(Deserialize)]
81pub struct FileEntry {
82    path: PathBuf,
83    public: Option<bool>,
84    target_path: Option<PathBuf>,
85}
86
87#[derive(Serialize)]
88pub struct SolidFileEntry {
89    path: PathBuf,
90    public: bool,
91    #[serde(serialize_with = "serialize_as_slash_path")]
92    target_path: PathBuf,
93    permissions: FilePermissions,
94}
95
96impl FileEntry {
97    pub fn path(&self) -> &PathBuf {
98        &self.path
99    }
100
101    pub fn public(&self) -> bool {
102        self.public.unwrap_or(false)
103    }
104
105    pub fn solidify(
106        &self,
107        work_dir: impl AsRef<Path>,
108        permissions: FilePermissions,
109    ) -> SomaResult<SolidFileEntry> {
110        let target_path = match &self.target_path {
111            Some(path) => path.clone(),
112            None => {
113                let file_name = self.path.file_name().ok_or(SomaError::FileNameNotFound)?;
114                work_dir.as_ref().join(file_name)
115            }
116        };
117
118        // TODO: More descriptive error
119        if !target_path.has_root() {
120            Err(SomaError::InvalidManifest)?;
121        }
122
123        Ok(SolidFileEntry {
124            path: self.path.clone(),
125            public: self.public.unwrap_or(false),
126            target_path,
127            permissions,
128        })
129    }
130}
131
132impl SolidFileEntry {
133    pub fn path_map(&self) -> (&PathBuf, &PathBuf) {
134        (&self.path, &self.target_path)
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use serde_test::{assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, Token};
142
143    #[test]
144    fn test_file_permissions_ser() {
145        assert_ser_tokens(&FilePermissions::Executable, &[Token::Str("550")]);
146        assert_ser_tokens(&FilePermissions::ReadOnly, &[Token::Str("440")]);
147        assert_ser_tokens(&FilePermissions::Custom(0o777), &[Token::Str("777")]);
148    }
149
150    #[test]
151    fn test_file_permissions_de() {
152        assert_de_tokens(&FilePermissions::Custom(0o550), &[Token::Str("550")]);
153        assert_de_tokens(&FilePermissions::Custom(0o440), &[Token::Str("440")]);
154        assert_de_tokens(&FilePermissions::Custom(0o777), &[Token::Str("777")]);
155        assert_de_tokens_error::<FilePermissions>(&[
156                Token::Str("1000")
157            ], "invalid value: string \"1000\", expected a file permissions string in octal number format"
158        );
159    }
160}