Skip to content

Commit 78ea6be

Browse files
qticascramm
authored andcommitted
Split CrateID CrateNameAndVersionReq (bazelbuild#2525)
Currently we use the same CrateId type for two contexts: A known crate name + version, and in an annotation to specify a version requirement that an annotation applies to. These are different use-cases, and neither should use String to represent a the version. Also, update CrateId to use a `semver::Version` (which is required, not optional).
1 parent 758f855 commit 78ea6be

File tree

10 files changed

+537
-154
lines changed

10 files changed

+537
-154
lines changed

crate_universe/src/config.rs

Lines changed: 233 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
//! A module for configuration information
22
3+
use std::cmp::Ordering;
34
use std::collections::{BTreeMap, BTreeSet};
45
use std::convert::AsRef;
6+
use std::fmt::Formatter;
57
use std::iter::Sum;
68
use std::ops::Add;
79
use std::path::Path;
10+
use std::str::FromStr;
811
use std::{fmt, fs};
912

10-
use anyhow::Result;
13+
use anyhow::{Context, Result};
1114
use cargo_lock::package::GitReference;
1215
use cargo_metadata::Package;
1316
use semver::VersionReq;
1417
use serde::de::value::SeqAccessDeserializer;
15-
use serde::de::{Deserializer, SeqAccess, Visitor};
18+
use serde::de::{Deserializer, SeqAccess, Unexpected, Visitor};
1619
use serde::{Deserialize, Serialize, Serializer};
1720

1821
use crate::select::{Select, Selectable};
@@ -516,55 +519,21 @@ pub struct CrateId {
516519
pub name: String,
517520

518521
/// The crate's semantic version
519-
pub version: String,
522+
pub version: semver::Version,
520523
}
521524

522525
impl CrateId {
523526
/// Construct a new [CrateId]
524-
pub fn new(name: String, version: String) -> Self {
527+
pub fn new(name: String, version: semver::Version) -> Self {
525528
Self { name, version }
526529
}
527-
528-
/// Compares a [CrateId] against a [cargo_metadata::Package].
529-
pub fn matches(&self, package: &Package) -> bool {
530-
// If the package name does not match, it's obviously
531-
// not the right package
532-
if self.name != "*" && self.name != package.name {
533-
return false;
534-
}
535-
536-
// First see if the package version matches exactly
537-
if package.version.to_string() == self.version {
538-
return true;
539-
}
540-
541-
// If the version provided is the wildcard "*", it matches. Do not
542-
// delegate to the semver crate in this case because semver does not
543-
// consider "*" to match prerelease packages. That's expected behavior
544-
// in the context of declaring package dependencies, but not in the
545-
// context of declaring which versions of preselected packages an
546-
// annotation applies to.
547-
if self.version == "*" {
548-
return true;
549-
}
550-
551-
// Next, check to see if the version provided is a semver req and
552-
// check if the package matches the condition
553-
if let Ok(semver) = VersionReq::parse(&self.version) {
554-
if semver.matches(&package.version) {
555-
return true;
556-
}
557-
}
558-
559-
false
560-
}
561530
}
562531

563532
impl From<&Package> for CrateId {
564533
fn from(package: &Package) -> Self {
565534
Self {
566535
name: package.name.clone(),
567-
version: package.version.to_string(),
536+
version: package.version.clone(),
568537
}
569538
}
570539
}
@@ -590,16 +559,20 @@ impl<'de> Visitor<'de> for CrateIdVisitor {
590559
where
591560
E: serde::de::Error,
592561
{
593-
v.rsplit_once(' ')
594-
.map(|(name, version)| CrateId {
595-
name: name.to_string(),
596-
version: version.to_string(),
597-
})
598-
.ok_or_else(|| {
599-
E::custom(format!(
600-
"Expected string value of `{{name}} {{version}}`. Got '{v}'"
601-
))
602-
})
562+
let (name, version_str) = v.rsplit_once(' ').ok_or_else(|| {
563+
E::custom(format!(
564+
"Expected string value of `{{name}} {{version}}`. Got '{v}'"
565+
))
566+
})?;
567+
let version = semver::Version::parse(version_str).map_err(|err| {
568+
E::custom(format!(
569+
"Couldn't parse {version_str} as a semver::Version: {err}"
570+
))
571+
})?;
572+
Ok(CrateId {
573+
name: name.to_string(),
574+
version,
575+
})
603576
}
604577
}
605578

@@ -688,7 +661,7 @@ pub struct Config {
688661

689662
/// Additional settings to apply to generated crates
690663
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
691-
pub annotations: BTreeMap<CrateId, CrateAnnotations>,
664+
pub annotations: BTreeMap<CrateNameAndVersionReq, CrateAnnotations>,
692665

693666
/// Settings used to determine various render info
694667
pub rendering: RenderConfig,
@@ -708,6 +681,178 @@ impl Config {
708681
}
709682
}
710683

684+
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
685+
pub struct CrateNameAndVersionReq {
686+
/// The name of the crate
687+
pub name: String,
688+
689+
version_req_string: VersionReqString,
690+
}
691+
692+
impl Serialize for CrateNameAndVersionReq {
693+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
694+
where
695+
S: Serializer,
696+
{
697+
serializer.serialize_str(&format!(
698+
"{} {}",
699+
self.name, self.version_req_string.original
700+
))
701+
}
702+
}
703+
704+
struct CrateNameAndVersionReqVisitor;
705+
impl<'de> Visitor<'de> for CrateNameAndVersionReqVisitor {
706+
type Value = CrateNameAndVersionReq;
707+
708+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
709+
formatter.write_str("Expected string value of `{name} {version}`.")
710+
}
711+
712+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
713+
where
714+
E: serde::de::Error,
715+
{
716+
let (name, version) = v.rsplit_once(' ').ok_or_else(|| {
717+
E::custom(format!(
718+
"Expected string value of `{{name}} {{version}}`. Got '{v}'"
719+
))
720+
})?;
721+
version
722+
.parse()
723+
.map(|version| CrateNameAndVersionReq {
724+
name: name.to_string(),
725+
version_req_string: version,
726+
})
727+
.map_err(|err| E::custom(err.to_string()))
728+
}
729+
}
730+
731+
impl<'de> Deserialize<'de> for CrateNameAndVersionReq {
732+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
733+
where
734+
D: serde::Deserializer<'de>,
735+
{
736+
deserializer.deserialize_str(CrateNameAndVersionReqVisitor)
737+
}
738+
}
739+
740+
/// A version requirement (i.e. a semver::VersionReq) which preserves the original string it was parsed from.
741+
/// This means that you can report back to the user whether they wrote `1` or `1.0.0` or `^1.0.0` or `>=1,<2`,
742+
/// and support exact round-trip serialization and deserialization.
743+
#[derive(Clone, Debug)]
744+
pub struct VersionReqString {
745+
original: String,
746+
747+
parsed: VersionReq,
748+
}
749+
750+
impl FromStr for VersionReqString {
751+
type Err = anyhow::Error;
752+
753+
fn from_str(original: &str) -> Result<Self, Self::Err> {
754+
let parsed = VersionReq::parse(original)
755+
.context("VersionReqString must be a valid semver requirement")?;
756+
Ok(VersionReqString {
757+
original: original.to_owned(),
758+
parsed,
759+
})
760+
}
761+
}
762+
763+
impl PartialEq for VersionReqString {
764+
fn eq(&self, other: &Self) -> bool {
765+
self.original == other.original
766+
}
767+
}
768+
769+
impl Eq for VersionReqString {}
770+
771+
impl PartialOrd for VersionReqString {
772+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
773+
Some(self.cmp(other))
774+
}
775+
}
776+
777+
impl Ord for VersionReqString {
778+
fn cmp(&self, other: &Self) -> Ordering {
779+
Ord::cmp(&self.original, &other.original)
780+
}
781+
}
782+
783+
impl Serialize for VersionReqString {
784+
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
785+
where
786+
S: Serializer,
787+
{
788+
serializer.serialize_str(&self.original)
789+
}
790+
}
791+
792+
impl<'de> Deserialize<'de> for VersionReqString {
793+
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
794+
where
795+
D: Deserializer<'de>,
796+
{
797+
struct StringVisitor;
798+
799+
impl<'de> Visitor<'de> for StringVisitor {
800+
type Value = String;
801+
802+
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
803+
formatter.write_str("string of a semver requirement")
804+
}
805+
}
806+
807+
let original = deserializer.deserialize_str(StringVisitor)?;
808+
let parsed = VersionReq::parse(&original).map_err(|_| {
809+
serde::de::Error::invalid_value(
810+
Unexpected::Str(&original),
811+
&"a valid semver requirement",
812+
)
813+
})?;
814+
Ok(VersionReqString { original, parsed })
815+
}
816+
}
817+
818+
impl CrateNameAndVersionReq {
819+
#[cfg(test)]
820+
pub fn new(name: String, version_req_string: VersionReqString) -> CrateNameAndVersionReq {
821+
CrateNameAndVersionReq {
822+
name,
823+
version_req_string,
824+
}
825+
}
826+
827+
/// Compares a [CrateNameAndVersionReq] against a [cargo_metadata::Package].
828+
pub fn matches(&self, package: &Package) -> bool {
829+
// If the package name does not match, it's obviously
830+
// not the right package
831+
if self.name != "*" && self.name != package.name {
832+
return false;
833+
}
834+
835+
// First see if the package version matches exactly
836+
if package.version.to_string() == self.version_req_string.original {
837+
return true;
838+
}
839+
840+
// If the version provided is the wildcard "*", it matches. Do not
841+
// delegate to the semver crate in this case because semver does not
842+
// consider "*" to match prerelease packages. That's expected behavior
843+
// in the context of declaring package dependencies, but not in the
844+
// context of declaring which versions of preselected packages an
845+
// annotation applies to.
846+
if self.version_req_string.original == "*" {
847+
return true;
848+
}
849+
850+
// Next, check to see if the version provided is a semver req and
851+
// check if the package matches the condition
852+
self.version_req_string.parsed.matches(&package.version)
853+
}
854+
}
855+
711856
#[cfg(test)]
712857
mod test {
713858
use super::*;
@@ -717,21 +862,17 @@ mod test {
717862
#[test]
718863
fn test_crate_id_serde() {
719864
let id: CrateId = serde_json::from_str("\"crate 0.1.0\"").unwrap();
720-
assert_eq!(id, CrateId::new("crate".to_owned(), "0.1.0".to_owned()));
865+
assert_eq!(
866+
id,
867+
CrateId::new("crate".to_owned(), semver::Version::new(0, 1, 0))
868+
);
721869
assert_eq!(serde_json::to_string(&id).unwrap(), "\"crate 0.1.0\"");
722870
}
723871

724-
#[test]
725-
fn test_crate_id_serde_semver() {
726-
let semver_id: CrateId = serde_json::from_str("\"crate *\"").unwrap();
727-
assert_eq!(semver_id, CrateId::new("crate".to_owned(), "*".to_owned()));
728-
assert_eq!(serde_json::to_string(&semver_id).unwrap(), "\"crate *\"");
729-
}
730-
731872
#[test]
732873
fn test_crate_id_matches() {
733874
let mut package = mock_cargo_metadata_package();
734-
let id = CrateId::new("mock-pkg".to_owned(), "0.1.0".to_owned());
875+
let id = CrateNameAndVersionReq::new("mock-pkg".to_owned(), "0.1.0".parse().unwrap());
735876

736877
package.version = cargo_metadata::semver::Version::new(0, 1, 0);
737878
assert!(id.matches(&package));
@@ -741,19 +882,43 @@ mod test {
741882
}
742883

743884
#[test]
744-
fn test_crate_id_semver_matches() {
885+
fn test_crate_name_and_version_req_serde() {
886+
let id: CrateNameAndVersionReq = serde_json::from_str("\"crate 0.1.0\"").unwrap();
887+
assert_eq!(
888+
id,
889+
CrateNameAndVersionReq::new(
890+
"crate".to_owned(),
891+
VersionReqString::from_str("0.1.0").unwrap()
892+
)
893+
);
894+
assert_eq!(serde_json::to_string(&id).unwrap(), "\"crate 0.1.0\"");
895+
}
896+
897+
#[test]
898+
fn test_crate_name_and_version_req_serde_semver() {
899+
let id: CrateNameAndVersionReq = serde_json::from_str("\"crate *\"").unwrap();
900+
assert_eq!(
901+
id,
902+
CrateNameAndVersionReq::new(
903+
"crate".to_owned(),
904+
VersionReqString::from_str("*").unwrap()
905+
)
906+
);
907+
assert_eq!(serde_json::to_string(&id).unwrap(), "\"crate *\"");
908+
}
909+
910+
#[test]
911+
fn test_crate_name_and_version_req_semver_matches() {
745912
let mut package = mock_cargo_metadata_package();
746913
package.version = cargo_metadata::semver::Version::new(1, 0, 0);
747-
let mut id = CrateId::new("mock-pkg".to_owned(), "0.1.0".to_owned());
748-
749-
id.version = "*".to_owned();
914+
let id = CrateNameAndVersionReq::new("mock-pkg".to_owned(), "*".parse().unwrap());
750915
assert!(id.matches(&package));
751916

752917
let mut prerelease = mock_cargo_metadata_package();
753918
prerelease.version = cargo_metadata::semver::Version::parse("1.0.0-pre.0").unwrap();
754919
assert!(id.matches(&prerelease));
755920

756-
id.version = "<1".to_owned();
921+
let id = CrateNameAndVersionReq::new("mock-pkg".to_owned(), "<1".parse().unwrap());
757922
assert!(!id.matches(&package));
758923
}
759924

@@ -770,7 +935,10 @@ mod test {
770935
// Annotations
771936
let annotation = config
772937
.annotations
773-
.get(&CrateId::new("rand".to_owned(), "0.8.5".to_owned()))
938+
.get(&CrateNameAndVersionReq::new(
939+
"rand".to_owned(),
940+
"0.8.5".parse().unwrap(),
941+
))
774942
.unwrap();
775943
assert_eq!(
776944
annotation.crate_features,

0 commit comments

Comments
 (0)