Skip to content

Commit f97c97b

Browse files
authored
feat!: optional strict parsing of matchspec and versionspec (#552)
Adds a mode to all `from_str` functions for `MatchSpec`, `NamelessMatchSpec`, `VersionSpec`, and `Constraint`. Strict mode disallows some ambigous syntax like: * `>=1.*` should just be `>=1` * `*.*` should just be `*` All parsing functions accept a `ParseStrictness` that can either be `Strict` or `Lenient`. `Lenient` mode should be used when parsing repodata etc, but `Strict` mode can be used to validate new user input.
1 parent e0b9840 commit f97c97b

File tree

17 files changed

+584
-342
lines changed

17 files changed

+584
-342
lines changed

crates/rattler-bin/src/commands/create.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use rattler::{
1111
package_cache::PackageCache,
1212
};
1313
use rattler_conda_types::{
14-
Channel, ChannelConfig, GenericVirtualPackage, MatchSpec, PackageRecord, Platform,
15-
PrefixRecord, RepoDataRecord, Version,
14+
Channel, ChannelConfig, GenericVirtualPackage, MatchSpec, PackageRecord, ParseStrictness,
15+
Platform, PrefixRecord, RepoDataRecord, Version,
1616
};
1717
use rattler_networking::{
1818
retry_policies::default_retry_policy, AuthenticationMiddleware, AuthenticationStorage,
@@ -80,7 +80,7 @@ pub async fn create(opt: Opt) -> anyhow::Result<()> {
8080
let specs = opt
8181
.specs
8282
.iter()
83-
.map(|spec| MatchSpec::from_str(spec))
83+
.map(|spec| MatchSpec::from_str(spec, ParseStrictness::Strict))
8484
.collect::<Result<Vec<_>, _>>()?;
8585

8686
// Find the default cache directory. Create it if it doesnt exist yet.

crates/rattler_conda_types/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod channel_data;
88
mod explicit_environment_spec;
99
mod match_spec;
1010
mod no_arch_type;
11+
mod parse_mode;
1112
mod platform;
1213
mod repo_data;
1314
mod repo_data_record;
@@ -34,6 +35,7 @@ pub use match_spec::parse::ParseMatchSpecError;
3435
pub use match_spec::{MatchSpec, NamelessMatchSpec};
3536
pub use no_arch_type::{NoArchKind, NoArchType};
3637
pub use package_name::{InvalidPackageNameError, PackageName};
38+
pub use parse_mode::ParseStrictness;
3739
pub use platform::{Arch, ParseArchError, ParsePlatformError, Platform};
3840
pub use prefix_record::PrefixRecord;
3941
pub use repo_data::patches::{PackageRecordPatch, PatchInstructions, RepoDataPatch};

crates/rattler_conda_types/src/match_spec/mod.rs

+38-30
Original file line numberDiff line numberDiff line change
@@ -68,38 +68,38 @@ use matcher::StringMatcher;
6868
/// # Examples:
6969
///
7070
/// ```rust
71-
/// use rattler_conda_types::{MatchSpec, VersionSpec, StringMatcher, PackageName, Channel};
71+
/// use rattler_conda_types::{MatchSpec, VersionSpec, StringMatcher, PackageName, Channel, ParseStrictness::*};
7272
/// use std::str::FromStr;
7373
/// use std::sync::Arc;
7474
///
75-
/// let spec = MatchSpec::from_str("foo 1.0 py27_0").unwrap();
75+
/// let spec = MatchSpec::from_str("foo 1.0 py27_0", Strict).unwrap();
7676
/// assert_eq!(spec.name, Some(PackageName::new_unchecked("foo")));
77-
/// assert_eq!(spec.version, Some(VersionSpec::from_str("1.0").unwrap()));
77+
/// assert_eq!(spec.version, Some(VersionSpec::from_str("1.0", Strict).unwrap()));
7878
/// assert_eq!(spec.build, Some(StringMatcher::from_str("py27_0").unwrap()));
7979
///
80-
/// let spec = MatchSpec::from_str("foo=1.0=py27_0").unwrap();
80+
/// let spec = MatchSpec::from_str("foo=1.0=py27_0", Strict).unwrap();
8181
/// assert_eq!(spec.name, Some(PackageName::new_unchecked("foo")));
82-
/// assert_eq!(spec.version, Some(VersionSpec::from_str("==1.0").unwrap()));
82+
/// assert_eq!(spec.version, Some(VersionSpec::from_str("==1.0", Strict).unwrap()));
8383
/// assert_eq!(spec.build, Some(StringMatcher::from_str("py27_0").unwrap()));
8484
///
85-
/// let spec = MatchSpec::from_str(r#"conda-forge::foo[version="1.0.*"]"#).unwrap();
85+
/// let spec = MatchSpec::from_str(r#"conda-forge::foo[version="1.0.*"]"#, Strict).unwrap();
8686
/// assert_eq!(spec.name, Some(PackageName::new_unchecked("foo")));
87-
/// assert_eq!(spec.version, Some(VersionSpec::from_str("1.0.*").unwrap()));
87+
/// assert_eq!(spec.version, Some(VersionSpec::from_str("1.0.*", Strict).unwrap()));
8888
/// assert_eq!(spec.channel, Some(Channel::from_str("conda-forge", &Default::default()).map(|channel| Arc::new(channel)).unwrap()));
8989
///
90-
/// let spec = MatchSpec::from_str("conda-forge/linux-64::foo>=1.0").unwrap();
90+
/// let spec = MatchSpec::from_str("conda-forge/linux-64::foo>=1.0", Strict).unwrap();
9191
/// assert_eq!(spec.name, Some(PackageName::new_unchecked("foo")));
92-
/// assert_eq!(spec.version, Some(VersionSpec::from_str(">=1.0").unwrap()));
92+
/// assert_eq!(spec.version, Some(VersionSpec::from_str(">=1.0", Strict).unwrap()));
9393
/// assert_eq!(spec.channel, Some(Channel::from_str("conda-forge", &Default::default()).map(|channel| Arc::new(channel)).unwrap()));
9494
/// assert_eq!(spec.subdir, Some("linux-64".to_string()));
9595
///
96-
/// let spec = MatchSpec::from_str("*/linux-64::foo>=1.0").unwrap();
96+
/// let spec = MatchSpec::from_str("*/linux-64::foo>=1.0", Strict).unwrap();
9797
/// assert_eq!(spec.name, Some(PackageName::new_unchecked("foo")));
98-
/// assert_eq!(spec.version, Some(VersionSpec::from_str(">=1.0").unwrap()));
98+
/// assert_eq!(spec.version, Some(VersionSpec::from_str(">=1.0", Strict).unwrap()));
9999
/// assert_eq!(spec.channel, Some(Channel::from_str("*", &Default::default()).map(|channel| Arc::new(channel)).unwrap()));
100100
/// assert_eq!(spec.subdir, Some("linux-64".to_string()));
101101
///
102-
/// let spec = MatchSpec::from_str(r#"foo[build="py2*"]"#).unwrap();
102+
/// let spec = MatchSpec::from_str(r#"foo[build="py2*"]"#, Strict).unwrap();
103103
/// assert_eq!(spec.name, Some(PackageName::new_unchecked("foo")));
104104
/// assert_eq!(spec.build, Some(StringMatcher::from_str("py2*").unwrap()));
105105
/// ```
@@ -404,32 +404,34 @@ mod tests {
404404

405405
use rattler_digest::{parse_digest_from_hex, Md5, Sha256};
406406

407-
use crate::{MatchSpec, NamelessMatchSpec, PackageName, PackageRecord, Version};
407+
use crate::{
408+
MatchSpec, NamelessMatchSpec, PackageName, PackageRecord, ParseStrictness::*, Version,
409+
};
408410
use insta::assert_snapshot;
409411
use std::hash::{Hash, Hasher};
410412

411413
#[test]
412414
fn test_matchspec_format_eq() {
413-
let spec = MatchSpec::from_str("conda-forge::mamba[version==1.0, sha256=aaac4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97, md5=dede6252c964db3f3e41c7d30d07f6bf]").unwrap();
415+
let spec = MatchSpec::from_str("conda-forge::mamba[version==1.0, sha256=aaac4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97, md5=dede6252c964db3f3e41c7d30d07f6bf]", Strict).unwrap();
414416
let spec_as_string = spec.to_string();
415-
let rebuild_spec = MatchSpec::from_str(&spec_as_string).unwrap();
417+
let rebuild_spec = MatchSpec::from_str(&spec_as_string, Strict).unwrap();
416418

417419
assert_eq!(spec, rebuild_spec);
418420
}
419421

420422
#[test]
421423
fn test_nameless_matchspec_format_eq() {
422-
let spec = NamelessMatchSpec::from_str("*[version==1.0, sha256=aaac4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97, md5=dede6252c964db3f3e41c7d30d07f6bf]").unwrap();
424+
let spec = NamelessMatchSpec::from_str("*[version==1.0, sha256=aaac4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97, md5=dede6252c964db3f3e41c7d30d07f6bf]", Strict).unwrap();
423425
let spec_as_string = spec.to_string();
424-
let rebuild_spec = NamelessMatchSpec::from_str(&spec_as_string).unwrap();
426+
let rebuild_spec = NamelessMatchSpec::from_str(&spec_as_string, Strict).unwrap();
425427

426428
assert_eq!(spec, rebuild_spec);
427429
}
428430

429431
#[test]
430432
fn test_hash_match() {
431-
let spec1 = MatchSpec::from_str("tensorflow 2.6.*").unwrap();
432-
let spec2 = MatchSpec::from_str("tensorflow 2.6.*").unwrap();
433+
let spec1 = MatchSpec::from_str("tensorflow 2.6.*", Strict).unwrap();
434+
let spec2 = MatchSpec::from_str("tensorflow 2.6.*", Strict).unwrap();
433435
assert_eq!(spec1, spec2);
434436

435437
let mut hasher = std::collections::hash_map::DefaultHasher::new();
@@ -445,8 +447,8 @@ mod tests {
445447

446448
#[test]
447449
fn test_hash_no_match() {
448-
let spec1 = MatchSpec::from_str("tensorflow 2.6.0.*").unwrap();
449-
let spec2 = MatchSpec::from_str("tensorflow 2.6.*").unwrap();
450+
let spec1 = MatchSpec::from_str("tensorflow 2.6.0.*", Strict).unwrap();
451+
let spec2 = MatchSpec::from_str("tensorflow 2.6.*", Strict).unwrap();
450452
assert_ne!(spec1, spec2);
451453

452454
let mut hasher = std::collections::hash_map::DefaultHasher::new();
@@ -474,24 +476,30 @@ mod tests {
474476
)
475477
};
476478

477-
let spec = MatchSpec::from_str("mamba[version==1.0, sha256=aaac4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97]").unwrap();
479+
let spec = MatchSpec::from_str("mamba[version==1.0, sha256=aaac4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97]", Strict).unwrap();
478480
assert!(!spec.matches(&record));
479481

480-
let spec = MatchSpec::from_str("mamba[version==1.0, sha256=f44c4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97]").unwrap();
482+
let spec = MatchSpec::from_str("mamba[version==1.0, sha256=f44c4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97]", Strict).unwrap();
481483
assert!(spec.matches(&record));
482484

483-
let spec = MatchSpec::from_str("mamba[version==1.0, md5=aaaa6252c964db3f3e41c7d30d07f6bf]")
484-
.unwrap();
485+
let spec = MatchSpec::from_str(
486+
"mamba[version==1.0, md5=aaaa6252c964db3f3e41c7d30d07f6bf]",
487+
Strict,
488+
)
489+
.unwrap();
485490
assert!(!spec.matches(&record));
486491

487-
let spec = MatchSpec::from_str("mamba[version==1.0, md5=dede6252c964db3f3e41c7d30d07f6bf]")
488-
.unwrap();
492+
let spec = MatchSpec::from_str(
493+
"mamba[version==1.0, md5=dede6252c964db3f3e41c7d30d07f6bf]",
494+
Strict,
495+
)
496+
.unwrap();
489497
assert!(spec.matches(&record));
490498

491-
let spec = MatchSpec::from_str("mamba[version==1.0, md5=dede6252c964db3f3e41c7d30d07f6bf, sha256=f44c4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97]").unwrap();
499+
let spec = MatchSpec::from_str("mamba[version==1.0, md5=dede6252c964db3f3e41c7d30d07f6bf, sha256=f44c4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97]", Strict).unwrap();
492500
assert!(spec.matches(&record));
493501

494-
let spec = MatchSpec::from_str("mamba[version==1.0, md5=dede6252c964db3f3e41c7d30d07f6bf, sha256=aaac4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97]").unwrap();
502+
let spec = MatchSpec::from_str("mamba[version==1.0, md5=dede6252c964db3f3e41c7d30d07f6bf, sha256=aaac4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97]", Strict).unwrap();
495503
assert!(!spec.matches(&record));
496504
}
497505

@@ -506,7 +514,7 @@ mod tests {
506514

507515
assert_snapshot!(specs
508516
.into_iter()
509-
.map(|s| MatchSpec::from_str(s).unwrap())
517+
.map(|s| MatchSpec::from_str(s, Strict).unwrap())
510518
.map(|s| s.to_string())
511519
.collect::<Vec<String>>()
512520
.join("\n"));

0 commit comments

Comments
 (0)