microsandbox_core/config/
reference_path.rs1use std::{
2 fmt::{self, Display},
3 path::PathBuf,
4 str::FromStr,
5};
6
7use crate::{oci::Reference, MicrosandboxError};
8
9#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19#[serde(try_from = "String")]
20#[serde(into = "String")]
21pub enum ReferenceOrPath {
22 Reference(Reference),
25
26 Path(PathBuf),
29}
30
31impl FromStr for ReferenceOrPath {
36 type Err = MicrosandboxError;
37
38 fn from_str(s: &str) -> Result<Self, Self::Err> {
58 if s.starts_with('.') || s.starts_with('/') {
60 Ok(ReferenceOrPath::Path(PathBuf::from(s)))
61 } else {
62 let reference = Reference::from_str(s)?;
64 Ok(ReferenceOrPath::Reference(reference))
65 }
66 }
67}
68
69impl Display for ReferenceOrPath {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 match self {
72 ReferenceOrPath::Path(path) => write!(f, "{}", path.display()),
73 ReferenceOrPath::Reference(reference) => write!(f, "{}", reference),
74 }
75 }
76}
77
78impl TryFrom<String> for ReferenceOrPath {
79 type Error = MicrosandboxError;
80
81 fn try_from(s: String) -> Result<Self, Self::Error> {
82 s.parse()
83 }
84}
85
86impl Into<String> for ReferenceOrPath {
87 fn into(self) -> String {
88 self.to_string()
89 }
90}
91
92#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn test_path_relative() {
102 let cases = vec![
104 "./path/to/file",
105 "./single",
106 ".",
107 "./path/with/multiple/segments",
108 "./path.with.dots",
109 "./path-with-dashes",
110 "./path_with_underscores",
111 ];
112
113 for case in cases {
114 let reference = ReferenceOrPath::from_str(case).unwrap();
115 match &reference {
116 ReferenceOrPath::Path(path) => {
117 assert_eq!(path, &PathBuf::from(case));
118 assert_eq!(reference.to_string(), case);
119 }
120 _ => panic!("Expected Path variant for {}", case),
121 }
122 }
123 }
124
125 #[test]
126 fn test_path_absolute() {
127 let cases = vec![
129 "/absolute/path",
130 "/root",
131 "/path/with/multiple/segments",
132 "/path.with.dots",
133 "/path-with-dashes",
134 "/path_with_underscores",
135 ];
136
137 for case in cases {
138 let reference = ReferenceOrPath::from_str(case).unwrap();
139 match &reference {
140 ReferenceOrPath::Path(path) => {
141 assert_eq!(path, &PathBuf::from(case));
142 assert_eq!(reference.to_string(), case);
143 }
144 _ => panic!("Expected Path variant for {}", case),
145 }
146 }
147 }
148
149 #[test]
150 fn test_image_reference_simple() {
151 let cases = vec![
153 "alpine:latest",
154 "ubuntu:20.04",
155 "nginx:1.19",
156 "redis:6",
157 "postgres:13-alpine",
158 ];
159
160 for case in cases {
161 let reference = ReferenceOrPath::from_str(case).unwrap();
162 match &reference {
163 ReferenceOrPath::Reference(ref_) => {
164 assert_eq!(reference.to_string(), ref_.to_string());
165 }
166 _ => panic!("Expected Reference variant for {}", case),
167 }
168 }
169 }
170
171 #[test]
172 fn test_image_reference_with_registry() {
173 let cases = vec![
175 "docker.io/library/alpine:latest",
176 "registry.example.com/myapp:v1.0",
177 "ghcr.io/owner/repo:tag",
178 "k8s.gcr.io/pause:3.2",
179 "quay.io/organization/image:1.0",
180 ];
181
182 for case in cases {
183 let reference = ReferenceOrPath::from_str(case).unwrap();
184 match &reference {
185 ReferenceOrPath::Reference(ref_) => {
186 assert_eq!(reference.to_string(), ref_.to_string());
187 }
188 _ => panic!("Expected Reference variant for {}", case),
189 }
190 }
191 }
192
193 #[test]
194 fn test_image_reference_with_digest() {
195 let valid_digest = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
196 let cases = vec![
197 format!("alpine@sha256:{}", valid_digest),
198 format!("docker.io/library/ubuntu@sha256:{}", valid_digest),
199 format!("registry.example.com/myapp:v1.0@sha256:{}", valid_digest),
200 ];
201
202 for case in cases {
203 let reference = ReferenceOrPath::from_str(&case).unwrap();
204 match &reference {
205 ReferenceOrPath::Reference(ref_) => {
206 assert_eq!(reference.to_string(), ref_.to_string());
207 }
208 _ => panic!("Expected Reference variant for {}", case),
209 }
210 }
211 }
212
213 #[test]
214 fn test_image_reference_with_port() {
215 let cases = vec![
217 "localhost:5000/myapp:latest",
218 "registry.example.com:5000/app:v1",
219 "192.168.1.1:5000/image:tag",
220 ];
221
222 for case in cases {
223 let reference = ReferenceOrPath::from_str(case).unwrap();
224 match &reference {
225 ReferenceOrPath::Reference(ref_) => {
226 assert_eq!(reference.to_string(), ref_.to_string());
227 }
228 _ => panic!("Expected Reference variant for {}", case),
229 }
230 }
231 }
232
233 #[test]
234 fn test_empty_input() {
235 assert!(ReferenceOrPath::from_str("").is_err());
237 }
238
239 #[test]
240 fn test_display_formatting() {
241 let test_cases = vec![
243 ("./local/path", "./local/path"),
244 ("/absolute/path", "/absolute/path"),
245 ("alpine:latest", "sandboxes.io/library/alpine:latest"),
246 (
247 "registry.example.com/app:v1.0",
248 "registry.example.com/library/app:v1.0",
249 ),
250 ];
251
252 for (input, expected) in test_cases {
253 let reference = ReferenceOrPath::from_str(input).unwrap();
254 assert_eq!(reference.to_string(), expected);
255 }
256 }
257
258 #[test]
259 fn test_serde_path_roundtrip() {
260 let test_cases = vec![
261 ReferenceOrPath::Path(PathBuf::from("./local/rootfs")),
262 ReferenceOrPath::Path(PathBuf::from("/absolute/path/to/rootfs")),
263 ReferenceOrPath::Path(PathBuf::from(".")),
264 ReferenceOrPath::Path(PathBuf::from("/root")),
265 ];
266
267 for case in test_cases {
268 let serialized = serde_yaml::to_string(&case).unwrap();
269 let deserialized: ReferenceOrPath = serde_yaml::from_str(&serialized).unwrap();
270 assert_eq!(case, deserialized);
271 }
272 }
273
274 #[test]
275 fn test_serde_reference_roundtrip() {
276 let test_cases = vec![
277 "alpine:latest",
278 "docker.io/library/ubuntu:20.04",
279 "registry.example.com:5000/myapp:v1.0",
280 "ghcr.io/owner/repo:tag",
281 ];
282
283 for case in test_cases {
284 let reference = ReferenceOrPath::from_str(case).unwrap();
285 let serialized = serde_yaml::to_string(&reference).unwrap();
286 let deserialized: ReferenceOrPath = serde_yaml::from_str(&serialized).unwrap();
287 assert_eq!(reference, deserialized);
288 }
289 }
290
291 #[test]
292 fn test_serde_yaml_format() {
293 let path = ReferenceOrPath::Path(PathBuf::from("/test/rootfs"));
295 let serialized = serde_yaml::to_string(&path).unwrap();
296 assert_eq!(serialized.trim(), "/test/rootfs");
297
298 let reference = ReferenceOrPath::from_str("ubuntu:latest").unwrap();
300 let serialized = serde_yaml::to_string(&reference).unwrap();
301 assert!(serialized.trim().contains("ubuntu:latest"));
302 }
303
304 #[test]
305 fn test_serde_invalid_input() {
306 let invalid_yaml = "- not a valid reference path";
308 assert!(serde_yaml::from_str::<ReferenceOrPath>(invalid_yaml).is_err());
309
310 let invalid_reference = "invalid!reference:format";
312 assert!(serde_yaml::from_str::<ReferenceOrPath>(invalid_reference).is_err());
313 }
314}