microsandbox_core/config/
path_pair.rs1use std::{fmt, str::FromStr};
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use typed_path::Utf8UnixPathBuf;
5
6use crate::MicrosandboxError;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum PathPair {
41 Distinct {
43 host: Utf8UnixPathBuf,
45
46 guest: Utf8UnixPathBuf,
48 },
49
50 Same(Utf8UnixPathBuf),
52}
53
54impl PathPair {
59 pub fn with_same(path: Utf8UnixPathBuf) -> Self {
61 Self::Same(path)
62 }
63
64 pub fn with_distinct(host: Utf8UnixPathBuf, guest: Utf8UnixPathBuf) -> Self {
66 Self::Distinct { host, guest }
67 }
68
69 pub fn get_host(&self) -> &Utf8UnixPathBuf {
71 match self {
72 Self::Distinct { host, .. } | Self::Same(host) => host,
73 }
74 }
75
76 pub fn get_guest(&self) -> &Utf8UnixPathBuf {
78 match self {
79 Self::Distinct { guest, .. } | Self::Same(guest) => guest,
80 }
81 }
82}
83
84impl FromStr for PathPair {
89 type Err = MicrosandboxError;
90
91 fn from_str(s: &str) -> Result<Self, Self::Err> {
92 if s.is_empty() {
93 return Err(MicrosandboxError::InvalidPathPair(s.to_string()));
94 }
95
96 if s.contains(':') {
97 let (host, guest) = s.split_once(':').unwrap();
98 if guest.is_empty() || host.is_empty() {
99 return Err(MicrosandboxError::InvalidPathPair(s.to_string()));
100 }
101
102 if guest == host {
103 return Ok(Self::Same(host.into()));
104 } else {
105 return Ok(Self::Distinct {
106 host: host.into(),
107 guest: guest.into(),
108 });
109 }
110 }
111
112 Ok(Self::Same(s.into()))
113 }
114}
115
116impl fmt::Display for PathPair {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 match self {
120 Self::Distinct { host, guest } => {
121 write!(f, "{}:{}", host, guest)
122 }
123 Self::Same(path) => write!(f, "{}:{}", path, path),
124 }
125 }
126}
127
128impl Serialize for PathPair {
129 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
130 where
131 S: Serializer,
132 {
133 serializer.serialize_str(&self.to_string())
134 }
135}
136
137impl<'de> Deserialize<'de> for PathPair {
138 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
139 where
140 D: Deserializer<'de>,
141 {
142 let s = String::deserialize(deserializer)?;
143 Self::from_str(&s).map_err(serde::de::Error::custom)
144 }
145}
146
147#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_path_pair_from_str() {
157 assert_eq!(
159 "/data".parse::<PathPair>().unwrap(),
160 PathPair::Same("/data".into())
161 );
162 assert_eq!(
163 "/data:/data".parse::<PathPair>().unwrap(),
164 PathPair::Same("/data".into())
165 );
166
167 assert_eq!(
169 "/host/data:/container/data".parse::<PathPair>().unwrap(),
170 PathPair::Distinct {
171 host: "/host/data".into(),
172 guest: "/container/data".into()
173 }
174 );
175
176 assert!("".parse::<PathPair>().is_err());
178 assert!(":".parse::<PathPair>().is_err());
179 assert!(":/data".parse::<PathPair>().is_err());
180 assert!("/data:".parse::<PathPair>().is_err());
181 }
182
183 #[test]
184 fn test_path_pair_display() {
185 assert_eq!(PathPair::Same("/data".into()).to_string(), "/data:/data");
187
188 assert_eq!(
190 PathPair::Distinct {
191 host: "/host/data".into(),
192 guest: "/container/data".into()
193 }
194 .to_string(),
195 "/host/data:/container/data"
196 );
197 }
198
199 #[test]
200 fn test_path_pair_getters() {
201 let same = PathPair::Same("/data".into());
203 assert_eq!(same.get_host().as_str(), "/data");
204 assert_eq!(same.get_guest().as_str(), "/data");
205
206 let distinct = PathPair::Distinct {
208 host: "/host/data".into(),
209 guest: "/container/data".into(),
210 };
211 assert_eq!(distinct.get_host().as_str(), "/host/data");
212 assert_eq!(distinct.get_guest().as_str(), "/container/data");
213 }
214
215 #[test]
216 fn test_path_pair_constructors() {
217 assert_eq!(
218 PathPair::with_same("/data".into()),
219 PathPair::Same("/data".into())
220 );
221 assert_eq!(
222 PathPair::with_distinct("/host/data".into(), "/container/data".into()),
223 PathPair::Distinct {
224 host: "/host/data".into(),
225 guest: "/container/data".into()
226 }
227 );
228 }
229}