cargo_lock/lockfile/
version.rs

1//! Lockfile versions
2
3use super::encoding::EncodablePackage;
4use crate::{
5    error::{Error, Result},
6    metadata::Metadata,
7};
8use serde::{Deserialize, Serialize};
9use std::str::FromStr;
10
11/// Lockfile versions
12#[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)]
13#[non_exhaustive]
14#[repr(u32)]
15pub enum ResolveVersion {
16    /// Original `Cargo.lock` format which places checksums in the
17    /// `[[metadata]]` table.
18    V1 = 1,
19
20    /// Revised `Cargo.lock` format which is optimized to prevent merge
21    /// conflicts.
22    ///
23    /// For more information, see:
24    /// <https://github.com/rust-lang/cargo/pull/7070>
25    V2 = 2,
26
27    /// Encodes Git dependencies with `branch = 'master'` in the manifest as
28    /// `?branch=master` in their URLs.
29    ///
30    /// For more information, see:
31    /// <https://internals.rust-lang.org/t/upcoming-changes-to-cargo-lock/14017>
32    V3 = 3,
33
34    /// SourceId URL serialization is aware of URL encoding.
35    ///
36    /// For more information, see:
37    /// <https://github.com/rust-lang/cargo/pull/12852>
38    V4 = 4,
39}
40
41impl ResolveVersion {
42    /// Autodetect the version of a lockfile from the packages
43    pub(super) fn detect(packages: &[EncodablePackage], metadata: &Metadata) -> Result<Self> {
44        // V1: look for [[metadata]] keys beginning with checksum
45        let is_v1 = metadata.keys().any(|key| key.is_checksum());
46
47        // V2: look for `checksum` fields in `[package]`
48        let is_v2 = packages.iter().any(|package| package.checksum.is_some());
49
50        if is_v1 && is_v2 {
51            return Err(Error::Parse("malformed lockfile: contains checksums in both [[package]] and [[metadata]] sections".to_string()));
52        }
53
54        if is_v1 {
55            Ok(ResolveVersion::V1)
56        } else {
57            // Default to V2
58            Ok(ResolveVersion::V2)
59        }
60    }
61
62    /// Should this version be explicitly encoded?
63    pub(super) fn is_explicit(self) -> bool {
64        u32::from(self) >= 3
65    }
66}
67
68/// V3 format is now the default.
69impl Default for ResolveVersion {
70    fn default() -> Self {
71        ResolveVersion::V3
72    }
73}
74
75impl From<ResolveVersion> for u32 {
76    fn from(version: ResolveVersion) -> u32 {
77        version as u32
78    }
79}
80
81impl FromStr for ResolveVersion {
82    type Err = Error;
83
84    fn from_str(s: &str) -> Result<Self> {
85        u32::from_str(s)
86            .map_err(|_| Error::Parse(format!("invalid Cargo.lock format version: `{s}`")))
87            .and_then(Self::try_from)
88    }
89}
90
91impl TryFrom<u32> for ResolveVersion {
92    type Error = Error;
93
94    fn try_from(num: u32) -> Result<Self> {
95        match num {
96            1 => Ok(ResolveVersion::V1),
97            2 => Ok(ResolveVersion::V2),
98            3 => Ok(ResolveVersion::V3),
99            4 => Ok(ResolveVersion::V4),
100            _ => Err(Error::Parse(format!(
101                "invalid Cargo.lock format version: `{num}`"
102            ))),
103        }
104    }
105}