use camino::Utf8PathBuf;
use cabin_core::PackageName;
pub const LOCKFILE_VERSION: u32 = 1;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Lockfile {
pub version: u32,
pub packages: Vec<LockedPackage>,
pub patches: Vec<LockedPatch>,
pub source_replacements: Vec<LockedSourceReplacement>,
}
impl Default for Lockfile {
fn default() -> Self {
Self::empty()
}
}
impl Lockfile {
pub fn empty() -> Self {
Self {
version: LOCKFILE_VERSION,
packages: Vec::new(),
patches: Vec::new(),
source_replacements: Vec::new(),
}
}
pub fn find(&self, name: &PackageName) -> Option<&LockedPackage> {
self.packages.iter().find(|p| &p.name == name)
}
pub fn matches_patch_state(
&self,
active_patches: &[LockedPatch],
active_source_replacements: &[LockedSourceReplacement],
) -> bool {
active_patches == self.patches.as_slice()
&& active_source_replacements == self.source_replacements.as_slice()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LockedPackage {
pub name: PackageName,
pub version: semver::Version,
pub source: LockedSource,
pub checksum: Option<String>,
pub dependencies: Vec<PackageName>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockedSource {
Index,
}
impl LockedSource {
pub fn as_str(self) -> &'static str {
match self {
LockedSource::Index => "index",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LockedPatch {
pub package: PackageName,
pub version: semver::Version,
pub kind: LockedPatchKind,
pub provenance: String,
pub path: Utf8PathBuf,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockedPatchKind {
Path,
}
impl LockedPatchKind {
pub fn as_str(self) -> &'static str {
match self {
LockedPatchKind::Path => "path",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LockedSourceReplacement {
pub original: String,
pub original_kind: LockedSourceLocatorKind,
pub replacement: String,
pub replacement_kind: LockedSourceLocatorKind,
pub provenance: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockedSourceLocatorKind {
IndexPath,
IndexUrl,
}
impl LockedSourceLocatorKind {
pub fn as_str(self) -> &'static str {
match self {
LockedSourceLocatorKind::IndexPath => "index-path",
LockedSourceLocatorKind::IndexUrl => "index-url",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pkg(name: &str) -> PackageName {
PackageName::new(name).unwrap()
}
fn ver(s: &str) -> semver::Version {
semver::Version::parse(s).unwrap()
}
fn patch(name: &str, version: &str) -> LockedPatch {
LockedPatch {
package: pkg(name),
version: ver(version),
kind: LockedPatchKind::Path,
provenance: "manifest".into(),
path: Utf8PathBuf::from("../").join(name),
}
}
fn replacement(original: &str, replacement: &str) -> LockedSourceReplacement {
LockedSourceReplacement {
original: original.into(),
original_kind: LockedSourceLocatorKind::IndexUrl,
replacement: replacement.into(),
replacement_kind: LockedSourceLocatorKind::IndexPath,
provenance: "user-config".into(),
}
}
#[test]
fn matches_patch_state_returns_true_for_equal_slices() {
let lock = Lockfile {
version: LOCKFILE_VERSION,
packages: Vec::new(),
patches: vec![patch("fmt", "10.2.1")],
source_replacements: vec![replacement("https://example.com/index", "../mirror")],
};
assert!(lock.matches_patch_state(
&[patch("fmt", "10.2.1")],
&[replacement("https://example.com/index", "../mirror")],
));
}
#[test]
fn matches_patch_state_detects_added_patch() {
let lock = Lockfile {
version: LOCKFILE_VERSION,
packages: Vec::new(),
patches: Vec::new(),
source_replacements: Vec::new(),
};
assert!(!lock.matches_patch_state(&[patch("fmt", "10.2.1")], &[]));
}
#[test]
fn matches_patch_state_detects_removed_replacement() {
let lock = Lockfile {
version: LOCKFILE_VERSION,
packages: Vec::new(),
patches: Vec::new(),
source_replacements: vec![replacement("https://example.com/index", "../mirror")],
};
assert!(!lock.matches_patch_state(&[], &[]));
}
#[test]
fn matches_patch_state_is_order_sensitive() {
let lock = Lockfile {
version: LOCKFILE_VERSION,
packages: Vec::new(),
patches: vec![patch("a", "1.0.0"), patch("b", "1.0.0")],
source_replacements: Vec::new(),
};
assert!(!lock.matches_patch_state(&[patch("b", "1.0.0"), patch("a", "1.0.0")], &[],));
}
}