use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Platform {
pub os: String,
pub architecture: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub variant: Option<String>,
}
impl Platform {
pub fn new(os: impl Into<String>, architecture: impl Into<String>) -> Self {
Self {
os: os.into(),
architecture: architecture.into(),
variant: None,
}
}
pub fn with_variant(
os: impl Into<String>,
architecture: impl Into<String>,
variant: impl Into<String>,
) -> Self {
Self {
os: os.into(),
architecture: architecture.into(),
variant: Some(variant.into()),
}
}
pub fn linux_amd64() -> Self {
Self::new("linux", "amd64")
}
pub fn linux_arm64() -> Self {
Self::new("linux", "arm64")
}
pub fn host() -> Self {
let arch = match std::env::consts::ARCH {
"x86_64" => "amd64",
"aarch64" => "arm64",
other => other,
};
Self::new("linux", arch)
}
pub fn parse(s: &str) -> Result<Self, String> {
let parts: Vec<&str> = s.split('/').collect();
match parts.len() {
2 => {
let arch = normalize_arch(parts[1]);
Ok(Self::new(parts[0], arch))
}
3 => {
let arch = normalize_arch(parts[1]);
Ok(Self::with_variant(parts[0], arch, parts[2]))
}
_ => Err(format!(
"Invalid platform '{}': expected 'os/arch' or 'os/arch/variant'",
s
)),
}
}
pub fn parse_list(s: &str) -> Result<Vec<Self>, String> {
s.split(',').map(|p| Self::parse(p.trim())).collect()
}
pub fn is_native(&self) -> bool {
*self == Self::host()
}
pub fn oci_arch(&self) -> &str {
&self.architecture
}
}
impl fmt::Display for Platform {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.os, self.architecture)?;
if let Some(ref v) = self.variant {
write!(f, "/{}", v)?;
}
Ok(())
}
}
fn normalize_arch(arch: &str) -> String {
match arch {
"x86_64" | "x86-64" => "amd64".to_string(),
"aarch64" | "arm64v8" => "arm64".to_string(),
"armhf" | "armv7l" => "arm".to_string(),
"i386" | "i686" | "x86" => "386".to_string(),
other => other.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_platform_new() {
let p = Platform::new("linux", "amd64");
assert_eq!(p.os, "linux");
assert_eq!(p.architecture, "amd64");
assert!(p.variant.is_none());
}
#[test]
fn test_platform_with_variant() {
let p = Platform::with_variant("linux", "arm", "v7");
assert_eq!(p.variant, Some("v7".to_string()));
}
#[test]
fn test_platform_display() {
assert_eq!(Platform::linux_amd64().to_string(), "linux/amd64");
assert_eq!(Platform::linux_arm64().to_string(), "linux/arm64");
assert_eq!(
Platform::with_variant("linux", "arm", "v7").to_string(),
"linux/arm/v7"
);
}
#[test]
fn test_platform_parse() {
let p = Platform::parse("linux/amd64").unwrap();
assert_eq!(p, Platform::linux_amd64());
let p = Platform::parse("linux/arm64").unwrap();
assert_eq!(p, Platform::linux_arm64());
let p = Platform::parse("linux/arm/v7").unwrap();
assert_eq!(p.architecture, "arm");
assert_eq!(p.variant, Some("v7".to_string()));
}
#[test]
fn test_platform_parse_normalizes() {
let p = Platform::parse("linux/x86_64").unwrap();
assert_eq!(p.architecture, "amd64");
let p = Platform::parse("linux/aarch64").unwrap();
assert_eq!(p.architecture, "arm64");
}
#[test]
fn test_platform_parse_invalid() {
assert!(Platform::parse("linux").is_err());
assert!(Platform::parse("a/b/c/d").is_err());
}
#[test]
fn test_platform_parse_list() {
let platforms = Platform::parse_list("linux/amd64,linux/arm64").unwrap();
assert_eq!(platforms.len(), 2);
assert_eq!(platforms[0], Platform::linux_amd64());
assert_eq!(platforms[1], Platform::linux_arm64());
}
#[test]
fn test_platform_parse_list_with_spaces() {
let platforms = Platform::parse_list("linux/amd64, linux/arm64").unwrap();
assert_eq!(platforms.len(), 2);
}
#[test]
fn test_platform_host() {
let host = Platform::host();
assert_eq!(host.os, "linux");
assert!(host.architecture == "amd64" || host.architecture == "arm64");
}
#[test]
fn test_platform_is_native() {
let host = Platform::host();
assert!(host.is_native());
let other = if host.architecture == "amd64" {
Platform::linux_arm64()
} else {
Platform::linux_amd64()
};
assert!(!other.is_native());
}
#[test]
fn test_platform_serde_roundtrip() {
let p = Platform::linux_amd64();
let json = serde_json::to_string(&p).unwrap();
let parsed: Platform = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, p);
}
#[test]
fn test_platform_serde_with_variant() {
let p = Platform::with_variant("linux", "arm", "v7");
let json = serde_json::to_string(&p).unwrap();
let parsed: Platform = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, p);
assert_eq!(parsed.variant, Some("v7".to_string()));
}
#[test]
fn test_normalize_arch() {
assert_eq!(normalize_arch("x86_64"), "amd64");
assert_eq!(normalize_arch("aarch64"), "arm64");
assert_eq!(normalize_arch("armhf"), "arm");
assert_eq!(normalize_arch("i386"), "386");
assert_eq!(normalize_arch("riscv64"), "riscv64");
}
#[test]
fn test_platform_equality() {
assert_eq!(Platform::linux_amd64(), Platform::new("linux", "amd64"));
assert_ne!(Platform::linux_amd64(), Platform::linux_arm64());
}
}