use std::collections::HashMap;
use loadsmith_core::{Checksum, Dependency, FileUrl, PackageId, PackageRef};
use serde::{Deserialize, Serialize};
use crate::{Diff, Diffable, PackageStoreEntry};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Lockfile {
packages: Vec<LockedPackage>,
}
impl Lockfile {
pub fn new(packages: Vec<LockedPackage>) -> Self {
Self { packages }
}
pub fn packages(&self) -> &[LockedPackage] {
&self.packages
}
pub fn package_by_id(&self, id: &PackageId) -> Option<&LockedPackage> {
self.packages.iter().find(|locked| locked.ref_.id() == id)
}
pub(crate) fn id_to_package_map(&self) -> HashMap<&PackageId, &LockedPackage> {
self.packages.iter().map(|p| (p.ref_.id(), p)).collect()
}
pub fn diff<'a>(&'a self, new: &'a Lockfile) -> Diff<'a, LockedPackage, LockedPackage> {
Diff::compute(self.id_to_package_map(), new.id_to_package_map())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LockedPackage {
#[serde(rename = "package")]
pub ref_: PackageRef,
pub source: String,
pub url: FileUrl,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub transitive: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub registry_metadata: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checksum: Option<Checksum>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub deps: Vec<Dependency>,
}
impl LockedPackage {
pub fn new(
package: impl Into<PackageRef>,
source: impl Into<String>,
url: impl Into<FileUrl>,
) -> Self {
Self {
ref_: package.into(),
source: source.into(),
url: url.into(),
transitive: false,
registry_metadata: None,
size: None,
checksum: None,
deps: Vec::new(),
}
}
pub fn with_transitive(mut self, transitive: bool) -> Self {
self.transitive = transitive;
self
}
pub fn with_size(mut self, size: u64) -> Self {
self.size = Some(size);
self
}
pub fn with_checksum(mut self, checksum: impl Into<Checksum>) -> Self {
self.checksum = Some(checksum.into());
self
}
pub fn with_deps(mut self, deps: Vec<Dependency>) -> Self {
self.deps = deps;
self
}
pub fn with_registry_metadata(mut self, metadata: serde_json::Value) -> Self {
self.registry_metadata = Some(metadata);
self
}
pub fn store_entry(&self) -> PackageStoreEntry {
PackageStoreEntry::new(self.ref_.clone(), self.checksum.clone())
}
pub fn into_store_entry(self) -> PackageStoreEntry {
PackageStoreEntry::new(self.ref_, self.checksum)
}
}
impl Diffable for LockedPackage {
fn checksum(&self) -> Option<&Checksum> {
self.checksum.as_ref()
}
fn version(&self) -> &loadsmith_core::Version {
self.ref_.version()
}
}
#[cfg(test)]
mod tests {
use super::*;
use loadsmith_core::{PackageRef, Version};
#[test]
fn diff() {
let example_com = FileUrl::try_from_url("https://example.com").unwrap();
let a = Lockfile::new(vec![
LockedPackage::new(
PackageRef::new("A".to_string(), Version::new(1, 0, 0)),
"local",
example_com.clone(),
),
LockedPackage::new(
PackageRef::new("B".to_string(), Version::new(1, 0, 0)),
"local",
example_com.clone(),
),
]);
let b = Lockfile::new(vec![
LockedPackage::new(
PackageRef::new("B".to_string(), Version::new(2, 0, 0)),
"local",
example_com.clone(),
),
LockedPackage::new(
PackageRef::new("C".to_string(), Version::new(1, 0, 0)),
"local",
example_com.clone(),
),
]);
let diff = a.diff(&b);
assert_eq!(
diff.added,
vec![&LockedPackage::new(
PackageRef::new("C".to_string(), Version::new(1, 0, 0)),
"local",
example_com.clone(),
)]
);
assert_eq!(
diff.removed,
vec![&LockedPackage::new(
PackageRef::new("A".to_string(), Version::new(1, 0, 0)),
"local",
example_com.clone(),
)]
);
assert_eq!(
diff.changed,
vec![(
&LockedPackage::new(
PackageRef::new("B".to_string(), Version::new(1, 0, 0)),
"local",
example_com.clone(),
),
&LockedPackage::new(
PackageRef::new("B".to_string(), Version::new(2, 0, 0)),
"local",
example_com.clone(),
)
)]
);
}
}