Skip to content

Commit

Permalink
Change type of Package.rust_version from VersionReq to Version
Browse files Browse the repository at this point in the history
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
  • Loading branch information
not-my-profile committed Jul 20, 2023
1 parent 14a8b64 commit 535ab32
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 5 deletions.
86 changes: 83 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<VersionReq>,
#[serde(default)]
#[serde(deserialize_with = "deserialize_rust_version")]
pub rust_version: Option<Version>,
}

impl Package {
Expand Down Expand Up @@ -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<Option<Version>, D::Error>
where
D: Deserializer<'de>,
{
let mut buf = match Option::<String>::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<semver::Version>,
);

fn bare_version(str: &str) -> Version {
serde_json::from_str::<BareVersion>(&format!(r#""{}""#, str))
.unwrap()
.0
.unwrap()
}

fn bare_version_err(str: &str) -> String {
serde_json::from_str::<BareVersion>(&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"
);
}
}
2 changes: 1 addition & 1 deletion tests/all/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
7 changes: 7 additions & 0 deletions tests/all/bare-rust-version/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "bare-rust-version"
version = "0.1.0"
edition = "2021"
rust-version = "1.60"

[dependencies]
1 change: 1 addition & 0 deletions tests/all/bare-rust-version/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

2 changes: 1 addition & 1 deletion tests/test_samples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
);
}

Expand Down

0 comments on commit 535ab32

Please sign in to comment.