use serde::{Deserialize, Serialize};
use crate::PackageType;
pub const LOCKFILE_VERSION: u32 = 2;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[must_use]
pub struct Lockfile {
pub lockfile_version: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub components: Vec<Package>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub interfaces: Vec<Package>,
}
impl Default for Lockfile {
fn default() -> Self {
Self {
lockfile_version: LOCKFILE_VERSION,
components: Default::default(),
interfaces: Default::default(),
}
}
}
impl Lockfile {
pub fn all_packages(&self) -> impl Iterator<Item = (&Package, PackageType)> {
self.components
.iter()
.map(|p| (p, PackageType::Component))
.chain(self.interfaces.iter().map(|p| (p, PackageType::Interface)))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[must_use]
pub struct Package {
pub name: String,
pub version: String,
pub registry: String,
pub digest: String,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub dependencies: Vec<PackageDependency>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[must_use]
pub struct PackageDependency {
pub name: String,
pub version: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_lockfile() {
let toml = r#"
lockfile_version = 2
[[interfaces]]
name = "wasi:logging"
version = "1.0.0"
registry = "ghcr.io/webassembly/wasi-logging"
digest = "sha256:a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"
[[interfaces]]
name = "wasi:key-value"
version = "2.0.0"
registry = "ghcr.io/webassembly/wasi-key-value"
digest = "sha256:b2c3d4e5f67890123456789012345678901abcdef2345678901abcdef2345678"
[[interfaces.dependencies]]
name = "wasi:logging"
version = "1.0.0"
"#;
let lockfile: Lockfile = toml::from_str(toml).expect("Failed to parse lockfile");
assert_eq!(lockfile.lockfile_version, 2);
assert_eq!(lockfile.interfaces.len(), 2);
let logging = &lockfile.interfaces[0];
assert_eq!(logging.name, "wasi:logging");
assert_eq!(logging.version, "1.0.0");
assert_eq!(logging.registry, "ghcr.io/webassembly/wasi-logging");
assert!(logging.digest.starts_with("sha256:"));
let key_value = &lockfile.interfaces[1];
assert_eq!(key_value.name, "wasi:key-value");
assert_eq!(key_value.version, "2.0.0");
assert_eq!(key_value.dependencies.len(), 1);
assert_eq!(key_value.dependencies[0].name, "wasi:logging");
assert_eq!(key_value.dependencies[0].version, "1.0.0");
}
#[test]
fn test_serialize_lockfile() {
let lockfile = Lockfile {
lockfile_version: 2,
components: vec![],
interfaces: vec![
Package {
name: "wasi:logging".to_string(),
version: "1.0.0".to_string(),
registry: "ghcr.io/webassembly/wasi-logging".to_string(),
digest: "sha256:abc123".to_string(),
dependencies: vec![],
},
Package {
name: "wasi:key-value".to_string(),
version: "2.0.0".to_string(),
registry: "ghcr.io/webassembly/wasi-key-value".to_string(),
digest: "sha256:def456".to_string(),
dependencies: vec![PackageDependency {
name: "wasi:logging".to_string(),
version: "1.0.0".to_string(),
}],
},
],
};
let toml = toml::to_string(&lockfile).expect("Failed to serialize lockfile");
assert!(toml.contains("version = 2"));
assert!(toml.contains("wasi:logging"));
assert!(toml.contains("wasi:key-value"));
assert!(toml.contains("sha256:abc123"));
}
#[test]
fn test_package_without_dependencies() {
let toml = r#"
lockfile_version = 2
[[interfaces]]
name = "wasi:logging"
version = "1.0.0"
registry = "ghcr.io/webassembly/wasi-logging"
digest = "sha256:abc123"
"#;
let lockfile: Lockfile = toml::from_str(toml).expect("Failed to parse lockfile");
assert_eq!(lockfile.interfaces.len(), 1);
assert_eq!(lockfile.interfaces[0].dependencies.len(), 0);
}
#[test]
fn test_serialize_package_without_dependencies() {
let package = Package {
name: "wasi:logging".to_string(),
version: "1.0.0".to_string(),
registry: "ghcr.io/webassembly/wasi-logging".to_string(),
digest: "sha256:abc123".to_string(),
dependencies: vec![],
};
let toml = toml::to_string(&package).expect("Failed to serialize package");
assert!(!toml.contains("dependencies"));
}
#[test]
fn test_components_and_interfaces() {
let toml = r#"
lockfile_version = 2
[[components]]
name = "root:component"
version = "0.1.0"
registry = "ghcr.io/example/component"
digest = "sha256:comp123"
[[interfaces]]
name = "wasi:clocks"
version = "0.2.5"
registry = "ghcr.io/webassembly/wasi/clocks"
digest = "sha256:iface456"
"#;
let lockfile: Lockfile = toml::from_str(toml).expect("Failed to parse lockfile");
assert_eq!(lockfile.components.len(), 1);
assert_eq!(lockfile.interfaces.len(), 1);
assert_eq!(lockfile.components[0].name, "root:component");
assert_eq!(lockfile.interfaces[0].name, "wasi:clocks");
}
#[test]
fn test_all_packages() {
let lockfile = Lockfile {
lockfile_version: 2,
components: vec![Package {
name: "root:component".to_string(),
version: "0.1.0".to_string(),
registry: "ghcr.io/example/component".to_string(),
digest: "sha256:comp123".to_string(),
dependencies: vec![],
}],
interfaces: vec![Package {
name: "wasi:clocks".to_string(),
version: "0.2.5".to_string(),
registry: "ghcr.io/webassembly/wasi/clocks".to_string(),
digest: "sha256:iface456".to_string(),
dependencies: vec![],
}],
};
let all: Vec<_> = lockfile.all_packages().collect();
assert_eq!(all.len(), 2);
let has_component = all.iter().any(|(_, pt)| *pt == PackageType::Component);
let has_interface = all.iter().any(|(_, pt)| *pt == PackageType::Interface);
assert!(has_component);
assert!(has_interface);
}
}