Skip to main content

cuenv_tools_oci/
platform.rs

1//! Platform detection and normalization.
2//!
3//! Handles mapping between:
4//! - cuenv platform strings (e.g., "darwin-arm64", "linux-x86_64")
5//! - OCI platform specs (os/arch, e.g., "darwin/arm64", "linux/amd64")
6
7use std::fmt;
8
9/// A normalized platform specification.
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct Platform {
12    /// Operating system (darwin, linux, windows).
13    pub os: String,
14    /// Architecture (arm64, x86_64).
15    pub arch: String,
16}
17
18impl Platform {
19    /// Create a new platform.
20    #[must_use]
21    pub fn new(os: impl Into<String>, arch: impl Into<String>) -> Self {
22        Self {
23            os: os.into(),
24            arch: arch.into(),
25        }
26    }
27
28    /// Parse a cuenv platform string (e.g., "darwin-arm64").
29    #[must_use]
30    pub fn parse(s: &str) -> Option<Self> {
31        let normalized = normalize_platform(s);
32        let parts: Vec<&str> = normalized.split('-').collect();
33        if parts.len() == 2 {
34            Some(Self::new(parts[0], parts[1]))
35        } else {
36            None
37        }
38    }
39
40    /// Convert to OCI platform format (os/arch).
41    #[must_use]
42    pub fn to_oci_platform(&self) -> String {
43        let arch = match self.arch.as_str() {
44            "x86_64" => "amd64",
45            "arm64" => "arm64",
46            other => other,
47        };
48        format!("{}/{}", self.os, arch)
49    }
50}
51
52impl fmt::Display for Platform {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        write!(f, "{}-{}", self.os, self.arch)
55    }
56}
57
58/// Get the current platform.
59#[must_use]
60pub fn current_platform() -> Platform {
61    let os = match std::env::consts::OS {
62        "macos" => "darwin",
63        other => other,
64    };
65    let arch = match std::env::consts::ARCH {
66        "aarch64" => "arm64",
67        other => other,
68    };
69    Platform::new(os, arch)
70}
71
72/// Normalize a platform string to canonical format.
73///
74/// Handles various platform representations:
75/// - "macos-amd64" -> "darwin-x86_64"
76/// - "linux-aarch64" -> "linux-arm64"
77/// - "Darwin-ARM64" -> "darwin-arm64"
78#[must_use]
79pub fn normalize_platform(platform: &str) -> String {
80    let platform = platform.to_lowercase();
81
82    platform
83        .replace("macos", "darwin")
84        .replace("osx", "darwin")
85        .replace("amd64", "x86_64")
86        .replace("aarch64", "arm64")
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_platform_parse() {
95        let p = Platform::parse("darwin-arm64").unwrap();
96        assert_eq!(p.os, "darwin");
97        assert_eq!(p.arch, "arm64");
98    }
99
100    #[test]
101    fn test_platform_to_oci() {
102        let p = Platform::new("darwin", "arm64");
103        assert_eq!(p.to_oci_platform(), "darwin/arm64");
104
105        let p = Platform::new("linux", "x86_64");
106        assert_eq!(p.to_oci_platform(), "linux/amd64");
107    }
108
109    #[test]
110    fn test_normalize_platform() {
111        assert_eq!(normalize_platform("macos-amd64"), "darwin-x86_64");
112        assert_eq!(normalize_platform("linux-aarch64"), "linux-arm64");
113        assert_eq!(normalize_platform("Darwin-ARM64"), "darwin-arm64");
114    }
115
116    #[test]
117    fn test_current_platform() {
118        let p = current_platform();
119        assert!(!p.os.is_empty());
120        assert!(!p.arch.is_empty());
121    }
122
123    #[test]
124    fn test_platform_new() {
125        let p = Platform::new("linux", "arm64");
126        assert_eq!(p.os, "linux");
127        assert_eq!(p.arch, "arm64");
128    }
129
130    #[test]
131    fn test_platform_new_with_strings() {
132        let p = Platform::new(String::from("darwin"), String::from("x86_64"));
133        assert_eq!(p.os, "darwin");
134        assert_eq!(p.arch, "x86_64");
135    }
136
137    #[test]
138    fn test_platform_display() {
139        let p = Platform::new("linux", "arm64");
140        assert_eq!(format!("{p}"), "linux-arm64");
141    }
142
143    #[test]
144    fn test_platform_parse_invalid() {
145        assert!(Platform::parse("just-one").is_some()); // Two parts
146        assert!(Platform::parse("single").is_none()); // Only one part
147        assert!(Platform::parse("too-many-parts").is_none()); // Three parts
148    }
149
150    #[test]
151    fn test_platform_parse_normalized() {
152        // Parse should normalize macos to darwin
153        let p = Platform::parse("macos-arm64").unwrap();
154        assert_eq!(p.os, "darwin");
155        assert_eq!(p.arch, "arm64");
156    }
157
158    #[test]
159    fn test_platform_parse_with_amd64() {
160        let p = Platform::parse("linux-amd64").unwrap();
161        assert_eq!(p.os, "linux");
162        assert_eq!(p.arch, "x86_64"); // amd64 normalized to x86_64
163    }
164
165    #[test]
166    fn test_platform_equality() {
167        let p1 = Platform::new("darwin", "arm64");
168        let p2 = Platform::new("darwin", "arm64");
169        let p3 = Platform::new("linux", "arm64");
170        assert_eq!(p1, p2);
171        assert_ne!(p1, p3);
172    }
173
174    #[test]
175    fn test_normalize_platform_osx() {
176        assert_eq!(normalize_platform("osx-arm64"), "darwin-arm64");
177    }
178
179    #[test]
180    fn test_normalize_platform_already_normalized() {
181        assert_eq!(normalize_platform("darwin-arm64"), "darwin-arm64");
182        assert_eq!(normalize_platform("linux-x86_64"), "linux-x86_64");
183    }
184
185    #[test]
186    fn test_to_oci_platform_unknown_arch() {
187        let p = Platform::new("linux", "riscv64");
188        // Unknown arch should pass through unchanged
189        assert_eq!(p.to_oci_platform(), "linux/riscv64");
190    }
191}