#
cargo/util/toml_mut/
upgrade.rs

    1use std::fmt::Display;
    2
    3use crate::CargoResult;
    4
    5/// Upgrade an existing requirement to a new version.
    6/// Copied from cargo-edit.
    7pub(crate) fn upgrade_requirement(
    8    req: &str,
    9    version: &semver::Version,
    10) -> CargoResult<Option<(String, semver::VersionReq)>> {
    11    let req_text = req.to_string();
    12    let raw_req = semver::VersionReq::parse(&req_text)
    13        .expect("semver to generate valid version requirements");
    14    if raw_req.comparators.is_empty() {
    15        // Empty matches everything, no-change.
    16        Ok(None)
    17    } else {
    18        let comparators: Vec<_> = raw_req
    19            .comparators
    20            .into_iter()
    21            // Don't downgrade if pre-release was used, see https://github.com/rust-lang/cargo/issues/14178 and https://github.com/rust-lang/cargo/issues/13290.
    22            .filter(|p| p.pre.is_empty() || matches_greater(p, version))
    23            .map(|p| set_comparator(p, version))
    24            .collect::<CargoResult<_>>()?;
    25        if comparators.is_empty() {
    26            return Ok(None);
    27        }
    28        let new_req = semver::VersionReq { comparators };
    29        let mut new_req_text = new_req.to_string();
    30        if new_req_text.starts_with('^') && !req.starts_with('^') {
    31            new_req_text.remove(0);
    32        }
    33        // Validate contract
    34        #[cfg(debug_assertions)]
    35        {
    36            assert!(
    37                new_req.matches(version),
    38                "New req {} is invalid, because {} does not match {}",
    39                new_req_text,
    40                new_req,
    41                version
    42            )
    43        }
    44        if new_req_text == req_text {
    45            Ok(None)
    46        } else {
    47            Ok(Some((new_req_text, new_req)))
    48        }
    49    }
    50}
    51
    52fn set_comparator(
    53    mut pred: semver::Comparator,
    54    version: &semver::Version,
    55) -> CargoResult<semver::Comparator> {
    56    match pred.op {
    57        semver::Op::Wildcard => {
    58            pred.major = version.major;
    59            if pred.minor.is_some() {
    60                pred.minor = Some(version.minor);
    61            }
    62            if pred.patch.is_some() {
    63                pred.patch = Some(version.patch);
    64            }
    65            Ok(pred)
    66        }
    67        semver::Op::Exact => Ok(assign_partial_req(version, pred)),
    68        semver::Op::Greater | semver::Op::GreaterEq | semver::Op::Less | semver::Op::LessEq => {
    69            let user_pred = pred.to_string();
    70            Err(unsupported_version_req(user_pred))
    71        }
    72        semver::Op::Tilde => Ok(assign_partial_req(version, pred)),
    73        semver::Op::Caret => Ok(assign_partial_req(version, pred)),
    74        _ => {
    75            let user_pred = pred.to_string();
    76            Err(unsupported_version_req(user_pred))
    77        }
    78    }
    79}
    80
    81// See https://github.com/dtolnay/semver/blob/69efd3cc770ead273a06ad1788477b3092996d29/src/eval.rs#L64-L88
    82fn matches_greater(cmp: &semver::Comparator, ver: &semver::Version) -> bool {
    83    if ver.major != cmp.major {
    84        return ver.major > cmp.major;
    85    }
    86
    87    match cmp.minor {
    88        None => return false,
    89        Some(minor) => {
    90            if ver.minor != minor {
    91                return ver.minor > minor;
    92            }
    93        }
    94    }
    95
    96    match cmp.patch {
    97        None => return false,
    98        Some(patch) => {
    99            if ver.patch != patch {
    100                return ver.patch > patch;
    101            }
    102        }
    103    }
    104
    105    ver.pre > cmp.pre
    106}
    107
    108fn assign_partial_req(
    109    version: &semver::Version,
    110    mut pred: semver::Comparator,
    111) -> semver::Comparator {
    112    pred.major = version.major;
    113    if pred.minor.is_some() {
    114        pred.minor = Some(version.minor);
    115    }
    116    if pred.patch.is_some() {
    117        pred.patch = Some(version.patch);
    118    }
    119    pred.pre = version.pre.clone();
    120    pred
    121}
    122
    123fn unsupported_version_req(req: impl Display) -> anyhow::Error {
    124    anyhow::format_err!("Support for modifying {} is currently unsupported", req)
    125}
    126
    127#[cfg(test)]
    128mod test {
    129    use super::*;
    130
    131    mod upgrade_requirement {
    132        use super::*;
    133
    134        #[track_caller]
    135        fn assert_req_bump<'a, O: Into<Option<&'a str>>>(version: &str, req: &str, expected: O) {
    136            let version = semver::Version::parse(version).unwrap();
    137            let actual = upgrade_requirement(req, &version)
    138                .unwrap()
    139                .map(|(actual, _req)| actual);
    140            let expected = expected.into();
    141            assert_eq!(actual.as_deref(), expected);
    142        }
    143
    144        #[test]
    145        fn wildcard_major() {
    146            assert_req_bump("1.0.0", "*", None);
    147        }
    148
    149        #[test]
    150        fn wildcard_minor() {
    151            assert_req_bump("1.0.0", "1.*", None);
    152            assert_req_bump("1.1.0", "1.*", None);
    153            assert_req_bump("2.0.0", "1.*", "2.*");
    154        }
    155
    156        #[test]
    157        fn wildcard_patch() {
    158            assert_req_bump("1.0.0", "1.0.*", None);
    159            assert_req_bump("1.1.0", "1.0.*", "1.1.*");
    160            assert_req_bump("1.1.1", "1.0.*", "1.1.*");
    161            assert_req_bump("2.0.0", "1.0.*", "2.0.*");
    162        }
    163
    164        #[test]
    165        fn caret_major() {
    166            assert_req_bump("1.0.0", "1", None);
    167            assert_req_bump("1.0.0", "^1", None);
    168
    169            assert_req_bump("1.1.0", "1", None);
    170            assert_req_bump("1.1.0", "^1", None);
    171
    172            assert_req_bump("2.0.0", "1", "2");
    173            assert_req_bump("2.0.0", "^1", "^2");
    174        }
    175
    176        #[test]
    177        fn caret_minor() {
    178            assert_req_bump("1.0.0", "1.0", None);
    179            assert_req_bump("1.0.0", "^1.0", None);
    180
    181            assert_req_bump("1.1.0", "1.0", "1.1");
    182            assert_req_bump("1.1.0", "^1.0", "^1.1");
    183
    184            assert_req_bump("1.1.1", "1.0", "1.1");
    185            assert_req_bump("1.1.1", "^1.0", "^1.1");
    186
    187            assert_req_bump("2.0.0", "1.0", "2.0");
    188            assert_req_bump("2.0.0", "^1.0", "^2.0");
    189        }
    190
    191        #[test]
    192        fn caret_patch() {
    193            assert_req_bump("1.0.0", "1.0.0", None);
    194            assert_req_bump("1.0.0", "^1.0.0", None);
    195
    196            assert_req_bump("1.1.0", "1.0.0", "1.1.0");
    197            assert_req_bump("1.1.0", "^1.0.0", "^1.1.0");
    198
    199            assert_req_bump("1.1.1", "1.0.0", "1.1.1");
    200            assert_req_bump("1.1.1", "^1.0.0", "^1.1.1");
    201
    202            assert_req_bump("2.0.0", "1.0.0", "2.0.0");
    203            assert_req_bump("2.0.0", "^1.0.0", "^2.0.0");
    204        }
    205
    206        #[test]
    207        fn tilde_major() {
    208            assert_req_bump("1.0.0", "~1", None);
    209            assert_req_bump("1.1.0", "~1", None);
    210            assert_req_bump("2.0.0", "~1", "~2");
    211        }
    212
    213        #[test]
    214        fn tilde_minor() {
    215            assert_req_bump("1.0.0", "~1.0", None);
    216            assert_req_bump("1.1.0", "~1.0", "~1.1");
    217            assert_req_bump("1.1.1", "~1.0", "~1.1");
    218            assert_req_bump("2.0.0", "~1.0", "~2.0");
    219        }
    220
    221        #[test]
    222        fn tilde_patch() {
    223            assert_req_bump("1.0.0", "~1.0.0", None);
    224            assert_req_bump("1.1.0", "~1.0.0", "~1.1.0");
    225            assert_req_bump("1.1.1", "~1.0.0", "~1.1.1");
    226            assert_req_bump("2.0.0", "~1.0.0", "~2.0.0");
    227        }
    228
    229        #[test]
    230        fn equal_major() {
    231            assert_req_bump("1.0.0", "=1", None);
    232            assert_req_bump("1.1.0", "=1", None);
    233            assert_req_bump("2.0.0", "=1", "=2");
    234        }
    235
    236        #[test]
    237        fn equal_minor() {
    238            assert_req_bump("1.0.0", "=1.0", None);
    239            assert_req_bump("1.1.0", "=1.0", "=1.1");
    240            assert_req_bump("1.1.1", "=1.0", "=1.1");
    241            assert_req_bump("2.0.0", "=1.0", "=2.0");
    242        }
    243
    244        #[test]
    245        fn equal_patch() {
    246            assert_req_bump("1.0.0", "=1.0.0", None);
    247            assert_req_bump("1.1.0", "=1.0.0", "=1.1.0");
    248            assert_req_bump("1.1.1", "=1.0.0", "=1.1.1");
    249            assert_req_bump("2.0.0", "=1.0.0", "=2.0.0");
    250        }
    251
    252        #[test]
    253        fn greater_prerelease() {
    254            assert_req_bump("1.7.0", "2.0.0-beta.21", None);
    255            assert_req_bump("1.7.0", "=2.0.0-beta.21", None);
    256            assert_req_bump("1.7.0", "~2.0.0-beta.21", None);
    257            assert_req_bump("2.0.0-beta.20", "2.0.0-beta.21", None);
    258            assert_req_bump("2.0.0-beta.21", "2.0.0-beta.21", None);
    259            assert_req_bump("2.0.0-beta.22", "2.0.0-beta.21", "2.0.0-beta.22");
    260            assert_req_bump("2.0.0", "2.0.0-beta.21", "2.0.0");
    261            assert_req_bump("3.0.0", "2.0.0-beta.21", "3.0.0");
    262        }
    263    }
    264}