Skip to main content
Glama

microsandbox

by microsandbox
reference_path.rs10.8 kB
use std::{ fmt::{self, Display}, path::PathBuf, str::FromStr, }; use crate::{oci::Reference, MicrosandboxError}; //-------------------------------------------------------------------------------------------------- // Types //-------------------------------------------------------------------------------------------------- /// Represents either an OCI image reference or a path to a rootfs on disk. /// /// This type is used to specify the source of a container's root filesystem: /// - For OCI images (e.g., "docker.io/library/ubuntu:latest"), use `Reference` variant /// - For local rootfs directories (e.g., "/path/to/rootfs" or "./rootfs"), use `Path` variant #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(try_from = "String")] #[serde(into = "String")] pub enum ReferenceOrPath { /// An OCI-compliant image reference (e.g., "docker.io/library/ubuntu:latest"). /// This is used when the rootfs should be pulled from a container registry. Reference(Reference), /// A path to a rootfs directory on the local filesystem. /// This can be either absolute (e.g., "/path/to/rootfs") or relative (e.g., "./rootfs"). Path(PathBuf), } //-------------------------------------------------------------------------------------------------- // Trait Implementations //-------------------------------------------------------------------------------------------------- impl FromStr for ReferenceOrPath { type Err = MicrosandboxError; /// Parses a string into a ReferenceOrPath. /// /// The parsing rules are: /// - If the string starts with "." or "/", it is interpreted as a path to a local rootfs /// - Otherwise, it is interpreted as an OCI image reference /// /// # Examples /// /// ``` /// # use std::str::FromStr; /// # use microsandbox_core::config::ReferenceOrPath; /// // Parse as local rootfs path /// let local = ReferenceOrPath::from_str("./my-rootfs").unwrap(); /// let absolute = ReferenceOrPath::from_str("/var/lib/my-rootfs").unwrap(); /// /// // Parse as OCI image reference /// let image = ReferenceOrPath::from_str("ubuntu:latest").unwrap(); /// let full = ReferenceOrPath::from_str("docker.io/library/debian:11").unwrap(); /// ``` fn from_str(s: &str) -> Result<Self, Self::Err> { // Check if the string starts with "." or "/" to determine if it's a path if s.starts_with('.') || s.starts_with('/') { Ok(ReferenceOrPath::Path(PathBuf::from(s))) } else { // Parse as an image reference let reference = Reference::from_str(s)?; Ok(ReferenceOrPath::Reference(reference)) } } } impl Display for ReferenceOrPath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ReferenceOrPath::Path(path) => write!(f, "{}", path.display()), ReferenceOrPath::Reference(reference) => write!(f, "{}", reference), } } } impl TryFrom<String> for ReferenceOrPath { type Error = MicrosandboxError; fn try_from(s: String) -> Result<Self, Self::Error> { s.parse() } } impl Into<String> for ReferenceOrPath { fn into(self) -> String { self.to_string() } } //-------------------------------------------------------------------------------------------------- // Tests //-------------------------------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; #[test] fn test_path_relative() { // Test relative paths with different formats let cases = vec![ "./path/to/file", "./single", ".", "./path/with/multiple/segments", "./path.with.dots", "./path-with-dashes", "./path_with_underscores", ]; for case in cases { let reference = ReferenceOrPath::from_str(case).unwrap(); match &reference { ReferenceOrPath::Path(path) => { assert_eq!(path, &PathBuf::from(case)); assert_eq!(reference.to_string(), case); } _ => panic!("Expected Path variant for {}", case), } } } #[test] fn test_path_absolute() { // Test absolute paths with different formats let cases = vec![ "/absolute/path", "/root", "/path/with/multiple/segments", "/path.with.dots", "/path-with-dashes", "/path_with_underscores", ]; for case in cases { let reference = ReferenceOrPath::from_str(case).unwrap(); match &reference { ReferenceOrPath::Path(path) => { assert_eq!(path, &PathBuf::from(case)); assert_eq!(reference.to_string(), case); } _ => panic!("Expected Path variant for {}", case), } } } #[test] fn test_image_reference_simple() { // Test simple image references let cases = vec![ "alpine:latest", "ubuntu:20.04", "nginx:1.19", "redis:6", "postgres:13-alpine", ]; for case in cases { let reference = ReferenceOrPath::from_str(case).unwrap(); match &reference { ReferenceOrPath::Reference(ref_) => { assert_eq!(reference.to_string(), ref_.to_string()); } _ => panic!("Expected Reference variant for {}", case), } } } #[test] fn test_image_reference_with_registry() { // Test image references with registry let cases = vec![ "docker.io/library/alpine:latest", "registry.example.com/myapp:v1.0", "ghcr.io/owner/repo:tag", "k8s.gcr.io/pause:3.2", "quay.io/organization/image:1.0", ]; for case in cases { let reference = ReferenceOrPath::from_str(case).unwrap(); match &reference { ReferenceOrPath::Reference(ref_) => { assert_eq!(reference.to_string(), ref_.to_string()); } _ => panic!("Expected Reference variant for {}", case), } } } #[test] fn test_image_reference_with_digest() { let valid_digest = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; let cases = vec![ format!("alpine@sha256:{}", valid_digest), format!("docker.io/library/ubuntu@sha256:{}", valid_digest), format!("registry.example.com/myapp:v1.0@sha256:{}", valid_digest), ]; for case in cases { let reference = ReferenceOrPath::from_str(&case).unwrap(); match &reference { ReferenceOrPath::Reference(ref_) => { assert_eq!(reference.to_string(), ref_.to_string()); } _ => panic!("Expected Reference variant for {}", case), } } } #[test] fn test_image_reference_with_port() { // Test image references with registry port let cases = vec![ "localhost:5000/myapp:latest", "registry.example.com:5000/app:v1", "192.168.1.1:5000/image:tag", ]; for case in cases { let reference = ReferenceOrPath::from_str(case).unwrap(); match &reference { ReferenceOrPath::Reference(ref_) => { assert_eq!(reference.to_string(), ref_.to_string()); } _ => panic!("Expected Reference variant for {}", case), } } } #[test] fn test_empty_input() { // Test empty input assert!(ReferenceOrPath::from_str("").is_err()); } #[test] fn test_display_formatting() { // Test display formatting for both variants let test_cases = vec![ ("./local/path", "./local/path"), ("/absolute/path", "/absolute/path"), ("alpine:latest", "docker.io/library/alpine:latest"), ( "registry.example.com/app:v1.0", "registry.example.com/library/app:v1.0", ), ]; for (input, expected) in test_cases { let reference = ReferenceOrPath::from_str(input).unwrap(); assert_eq!(reference.to_string(), expected); } } #[test] fn test_serde_path_roundtrip() { let test_cases = vec![ ReferenceOrPath::Path(PathBuf::from("./local/rootfs")), ReferenceOrPath::Path(PathBuf::from("/absolute/path/to/rootfs")), ReferenceOrPath::Path(PathBuf::from(".")), ReferenceOrPath::Path(PathBuf::from("/root")), ]; for case in test_cases { let serialized = serde_yaml::to_string(&case).unwrap(); let deserialized: ReferenceOrPath = serde_yaml::from_str(&serialized).unwrap(); assert_eq!(case, deserialized); } } #[test] fn test_serde_reference_roundtrip() { let test_cases = vec![ "alpine:latest", "docker.io/library/ubuntu:20.04", "registry.example.com:5000/myapp:v1.0", "ghcr.io/owner/repo:tag", ]; for case in test_cases { let reference = ReferenceOrPath::from_str(case).unwrap(); let serialized = serde_yaml::to_string(&reference).unwrap(); let deserialized: ReferenceOrPath = serde_yaml::from_str(&serialized).unwrap(); assert_eq!(reference, deserialized); } } #[test] fn test_serde_yaml_format() { // Test Path variant serialization format let path = ReferenceOrPath::Path(PathBuf::from("/test/rootfs")); let serialized = serde_yaml::to_string(&path).unwrap(); assert_eq!(serialized.trim(), "/test/rootfs"); // Test Reference variant serialization format let reference = ReferenceOrPath::from_str("ubuntu:latest").unwrap(); let serialized = serde_yaml::to_string(&reference).unwrap(); assert!(serialized.trim().contains("ubuntu:latest")); } #[test] fn test_serde_invalid_input() { // Test deserializing invalid YAML let invalid_yaml = "- not a valid reference path"; assert!(serde_yaml::from_str::<ReferenceOrPath>(invalid_yaml).is_err()); // Test deserializing invalid reference format let invalid_reference = "invalid!reference:format"; assert!(serde_yaml::from_str::<ReferenceOrPath>(invalid_reference).is_err()); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/microsandbox/microsandbox'

If you have feedback or need assistance with the MCP directory API, please join our Discord server