From 535ab32d2a86bff229f94a58bb439367c9258411 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Thu, 20 Jul 2023 10:51:30 +0200 Subject: [PATCH] Change type of Package.rust_version from VersionReq to Version As per the Cargo Book[1] the `rust-version` must: > be a bare version number with two or three components; > it cannot include semver operators or pre-release identifiers. VersionReq therefore obviously is the wrong type since it describes[2]: > the intersection of some version comparators such as `>=1.2.3, <1.8` It doesn't make much sense to deserialize strings that explicitly "cannot include semver operators" as a vector of comparison operators. While the Version type's Deserialize implementation requires three components, we can just use a custom deserialize function to deserialize 1.2 as 1.2.0. This commit introduces such. [1]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field [2]: https://docs.rs/semver/1.0.7/semver/struct.VersionReq.html --- src/lib.rs | 86 +++++++++++++++++++++++++- tests/all/Cargo.toml | 2 +- tests/all/bare-rust-version/Cargo.toml | 7 +++ tests/all/bare-rust-version/src/lib.rs | 1 + tests/test_samples.rs | 2 +- 5 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 tests/all/bare-rust-version/Cargo.toml create mode 100644 tests/all/bare-rust-version/src/lib.rs diff --git a/src/lib.rs b/src/lib.rs index 80068a9d..c222c130 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,7 +92,7 @@ use std::str::from_utf8; pub use camino; pub use semver; -use semver::{Version, VersionReq}; +use semver::Version; #[cfg(feature = "builder")] pub use dependency::DependencyBuilder; @@ -110,7 +110,7 @@ pub use messages::{ ArtifactBuilder, ArtifactProfileBuilder, BuildFinishedBuilder, BuildScriptBuilder, CompilerMessageBuilder, }; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; mod dependency; pub mod diagnostic; @@ -381,7 +381,9 @@ pub struct Package { /// The minimum supported Rust version of this package. /// /// This is always `None` if running with a version of Cargo older than 1.58. - pub rust_version: Option, + #[serde(default)] + #[serde(deserialize_with = "deserialize_rust_version")] + pub rust_version: Option, } impl Package { @@ -802,3 +804,81 @@ impl MetadataCommand { Self::parse(stdout) } } + +/// As per the Cargo Book the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) must: +/// +/// > be a bare version number with two or three components; +/// > it cannot include semver operators or pre-release identifiers. +/// +/// [`semver::Version`] however requires three components. This function takes +/// care of appending `.0` if the provided version number only has two components +/// and ensuring that it does not contain a pre-release version or build metadata. +fn deserialize_rust_version<'de, D>( + deserializer: D, +) -> std::result::Result, D::Error> +where + D: Deserializer<'de>, +{ + let mut buf = match Option::::deserialize(deserializer)? { + None => return Ok(None), + Some(buf) => buf, + }; + + for char in buf.chars() { + if char == '-' { + return Err(serde::de::Error::custom( + "pre-release identifiers are not supported in rust-version", + )); + } else if char == '+' { + return Err(serde::de::Error::custom( + "build metadata is not supported in rust-version", + )); + } + } + + if buf.matches('.').count() == 1 { + // e.g. 1.0 -> 1.0.0 + buf.push_str(".0"); + } + + Ok(Some( + Version::parse(&buf).map_err(serde::de::Error::custom)?, + )) +} + +#[cfg(test)] +mod test { + use semver::Version; + + #[derive(Debug, serde::Deserialize)] + struct BareVersion( + #[serde(deserialize_with = "super::deserialize_rust_version")] Option, + ); + + fn bare_version(str: &str) -> Version { + serde_json::from_str::(&format!(r#""{}""#, str)) + .unwrap() + .0 + .unwrap() + } + + fn bare_version_err(str: &str) -> String { + serde_json::from_str::(&format!(r#""{}""#, str)) + .unwrap_err() + .to_string() + } + + #[test] + fn test_deserialize_rust_version() { + assert_eq!(bare_version("1.2"), Version::new(1, 2, 0)); + assert_eq!(bare_version("1.2.0"), Version::new(1, 2, 0)); + assert_eq!( + bare_version_err("1.2.0-alpha"), + "pre-release identifiers are not supported in rust-version" + ); + assert_eq!( + bare_version_err("1.2.0+123"), + "build metadata is not supported in rust-version" + ); + } +} diff --git a/tests/all/Cargo.toml b/tests/all/Cargo.toml index 9077aa42..95a6a17b 100644 --- a/tests/all/Cargo.toml +++ b/tests/all/Cargo.toml @@ -57,7 +57,7 @@ name = "reqfeat" required-features = ["feat2"] [workspace] -exclude = ["bdep", "benches", "devdep", "examples", "featdep", "namedep", "oldname", "path-dep", "windep"] +exclude = ["bare-rust-version", "bdep", "benches", "devdep", "examples", "featdep", "namedep", "oldname", "path-dep", "windep"] [workspace.metadata.testobject] myvalue = "abc" diff --git a/tests/all/bare-rust-version/Cargo.toml b/tests/all/bare-rust-version/Cargo.toml new file mode 100644 index 00000000..34ce8fde --- /dev/null +++ b/tests/all/bare-rust-version/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bare-rust-version" +version = "0.1.0" +edition = "2021" +rust-version = "1.60" + +[dependencies] diff --git a/tests/all/bare-rust-version/src/lib.rs b/tests/all/bare-rust-version/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/tests/all/bare-rust-version/src/lib.rs @@ -0,0 +1 @@ + diff --git a/tests/test_samples.rs b/tests/test_samples.rs index 91d06d65..15128b07 100644 --- a/tests/test_samples.rs +++ b/tests/test_samples.rs @@ -212,7 +212,7 @@ fn all_the_fields() { if ver >= semver::Version::parse("1.58.0").unwrap() { assert_eq!( all.rust_version, - Some(semver::VersionReq::parse("1.56").unwrap()) + Some(semver::Version::parse("1.56.0").unwrap()) ); }