1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
//! Lockfile versions

use super::encoding::EncodablePackage;
use crate::{
    error::{Error, ErrorKind},
    metadata::Metadata,
};
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, str::FromStr};

/// Lockfile versions
#[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)]
#[non_exhaustive]
#[repr(u32)]
pub enum ResolveVersion {
    /// Original `Cargo.lock` format which places checksums in the
    /// `[[metadata]]` table.
    V1 = 1,

    /// Revised `Cargo.lock` format which is optimized to prevent merge
    /// conflicts.
    ///
    /// For more information, see:
    /// <https://github.com/rust-lang/cargo/pull/7070>
    V2 = 2,

    /// Encodes Git dependencies with `branch = 'master'` in the manifest as
    /// `?branch=master` in their URLs.
    ///
    /// For more information, see:
    /// <https://internals.rust-lang.org/t/upcoming-changes-to-cargo-lock/14017>
    V3 = 3,
}

impl ResolveVersion {
    /// Autodetect the version of a lockfile from the packages
    pub(super) fn detect(
        packages: &[EncodablePackage],
        metadata: &Metadata,
    ) -> Result<Self, Error> {
        // V1: look for [[metadata]] keys beginning with checksum
        let is_v1 = metadata.keys().any(|key| key.is_checksum());

        // V2: look for `checksum` fields in `[package]`
        let is_v2 = packages.iter().any(|package| package.checksum.is_some());

        if is_v1 && is_v2 {
            fail!(ErrorKind::Parse, "malformed lockfile: contains checksums in both [[package]] and [[metadata]] sections");
        }

        if is_v1 {
            Ok(ResolveVersion::V1)
        } else {
            // Default to V2
            Ok(ResolveVersion::V2)
        }
    }

    /// Should this version be explicitly encoded?
    pub(super) fn is_explicit(self) -> bool {
        u32::from(self) > 3
    }
}

/// V2 format is now the default.
///
///See: <https://github.com/rust-lang/cargo/pull/7579>
impl Default for ResolveVersion {
    fn default() -> Self {
        ResolveVersion::V2
    }
}

impl From<ResolveVersion> for u32 {
    fn from(version: ResolveVersion) -> u32 {
        version as u32
    }
}

impl FromStr for ResolveVersion {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Error> {
        u32::from_str(s)
            .map_err(|_| {
                format_err!(
                    ErrorKind::Parse,
                    "invalid Cargo.lock format version: `{}`",
                    s
                )
            })
            .and_then(Self::try_from)
    }
}

impl TryFrom<u32> for ResolveVersion {
    type Error = Error;

    fn try_from(num: u32) -> Result<Self, Error> {
        match num {
            1 => Ok(ResolveVersion::V1),
            2 => Ok(ResolveVersion::V2),
            3 => Ok(ResolveVersion::V3),
            _ => fail!(
                ErrorKind::Parse,
                "invalid Cargo.lock format version: `{}`",
                num
            ),
        }
    }
}