soma/problem/configs/
common.rs1use 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 }
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 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#[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 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}