Skip to content

Commit 33714c4

Browse files
committed
Auto merge of #14467 - epage:open-pkgid, r=Muscraft
fix(pkgid): Allow open namespaces in PackageIdSpec's ### What does this PR try to resolve? This is a part of #13576 This unblocks #14433. We have a test to ensure you can't publish a namespaced package and the error for that is getting masked in #14433 because the package name is getting parsed as a `PackageIdSpec` which wasn't supported until this PR. ### How should we test and review this PR? ### Additional information
2 parents 59ecb11 + f4c7ed1 commit 33714c4

File tree

2 files changed

+230
-36
lines changed

2 files changed

+230
-36
lines changed

crates/cargo-util-schemas/src/core/package_id_spec.rs

+140-36
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,8 @@ impl PackageIdSpec {
9494
.into());
9595
}
9696
}
97-
let mut parts = spec.splitn(2, [':', '@']);
98-
let name = parts.next().unwrap();
99-
let version = match parts.next() {
100-
Some(version) => Some(version.parse::<PartialVersion>()?),
101-
None => None,
102-
};
103-
PackageName::new(name)?;
97+
let (name, version) = parse_spec(spec)?.unwrap_or_else(|| (spec.to_owned(), None));
98+
PackageName::new(&name)?;
10499
Ok(PackageIdSpec {
105100
name: String::from(name),
106101
version,
@@ -161,11 +156,8 @@ impl PackageIdSpec {
161156
return Err(ErrorKind::MissingUrlPath(url).into());
162157
};
163158
match frag {
164-
Some(fragment) => match fragment.split_once([':', '@']) {
165-
Some((name, part)) => {
166-
let version = part.parse::<PartialVersion>()?;
167-
(String::from(name), Some(version))
168-
}
159+
Some(fragment) => match parse_spec(&fragment)? {
160+
Some((name, ver)) => (name, ver),
169161
None => {
170162
if fragment.chars().next().unwrap().is_alphabetic() {
171163
(String::from(fragment.as_str()), None)
@@ -217,6 +209,18 @@ impl PackageIdSpec {
217209
}
218210
}
219211

212+
fn parse_spec(spec: &str) -> Result<Option<(String, Option<PartialVersion>)>> {
213+
let Some((name, ver)) = spec
214+
.rsplit_once('@')
215+
.or_else(|| spec.rsplit_once(':').filter(|(n, _)| !n.ends_with(':')))
216+
else {
217+
return Ok(None);
218+
};
219+
let name = name.to_owned();
220+
let ver = ver.parse::<PartialVersion>()?;
221+
Ok(Some((name, Some(ver))))
222+
}
223+
220224
fn strip_url_protocol(url: &Url) -> Url {
221225
// Ridiculous hoop because `Url::set_scheme` errors when changing to http/https
222226
let raw = url.to_string();
@@ -323,18 +327,30 @@ mod tests {
323327
use crate::core::{GitReference, SourceKind};
324328
use url::Url;
325329

330+
#[track_caller]
331+
fn ok(spec: &str, expected: PackageIdSpec, expected_rendered: &str) {
332+
let parsed = PackageIdSpec::parse(spec).unwrap();
333+
assert_eq!(parsed, expected);
334+
let rendered = parsed.to_string();
335+
assert_eq!(rendered, expected_rendered);
336+
let reparsed = PackageIdSpec::parse(&rendered).unwrap();
337+
assert_eq!(reparsed, expected);
338+
}
339+
340+
macro_rules! err {
341+
($spec:expr, $expected:pat) => {
342+
let err = PackageIdSpec::parse($spec).unwrap_err();
343+
let kind = err.0;
344+
assert!(
345+
matches!(kind, $expected),
346+
"`{}` parse error mismatch, got {kind:?}",
347+
$spec
348+
);
349+
};
350+
}
351+
326352
#[test]
327353
fn good_parsing() {
328-
#[track_caller]
329-
fn ok(spec: &str, expected: PackageIdSpec, expected_rendered: &str) {
330-
let parsed = PackageIdSpec::parse(spec).unwrap();
331-
assert_eq!(parsed, expected);
332-
let rendered = parsed.to_string();
333-
assert_eq!(rendered, expected_rendered);
334-
let reparsed = PackageIdSpec::parse(&rendered).unwrap();
335-
assert_eq!(reparsed, expected);
336-
}
337-
338354
ok(
339355
"https://crates.io/foo",
340356
PackageIdSpec {
@@ -425,6 +441,16 @@ mod tests {
425441
},
426442
"foo",
427443
);
444+
ok(
445+
"foo::bar",
446+
PackageIdSpec {
447+
name: String::from("foo::bar"),
448+
version: None,
449+
url: None,
450+
kind: None,
451+
},
452+
"foo::bar",
453+
);
428454
ok(
429455
"foo:1.2.3",
430456
PackageIdSpec {
@@ -435,6 +461,16 @@ mod tests {
435461
},
436462
437463
);
464+
ok(
465+
"foo::bar:1.2.3",
466+
PackageIdSpec {
467+
name: String::from("foo::bar"),
468+
version: Some("1.2.3".parse().unwrap()),
469+
url: None,
470+
kind: None,
471+
},
472+
473+
);
438474
ok(
439475
440476
PackageIdSpec {
@@ -445,6 +481,16 @@ mod tests {
445481
},
446482
447483
);
484+
ok(
485+
486+
PackageIdSpec {
487+
name: String::from("foo::bar"),
488+
version: Some("1.2.3".parse().unwrap()),
489+
url: None,
490+
kind: None,
491+
},
492+
493+
);
448494
ok(
449495
450496
PackageIdSpec {
@@ -579,6 +625,16 @@ mod tests {
579625
},
580626
"file:///path/to/my/project/foo",
581627
);
628+
ok(
629+
"file:///path/to/my/project/foo::bar",
630+
PackageIdSpec {
631+
name: String::from("foo::bar"),
632+
version: None,
633+
url: Some(Url::parse("file:///path/to/my/project/foo::bar").unwrap()),
634+
kind: None,
635+
},
636+
"file:///path/to/my/project/foo::bar",
637+
);
582638
ok(
583639
"file:///path/to/my/project/foo#1.1.8",
584640
PackageIdSpec {
@@ -599,29 +655,77 @@ mod tests {
599655
},
600656
"path+file:///path/to/my/project/foo#1.1.8",
601657
);
658+
ok(
659+
"path+file:///path/to/my/project/foo#bar",
660+
PackageIdSpec {
661+
name: String::from("bar"),
662+
version: None,
663+
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
664+
kind: Some(SourceKind::Path),
665+
},
666+
"path+file:///path/to/my/project/foo#bar",
667+
);
668+
ok(
669+
"path+file:///path/to/my/project/foo#foo::bar",
670+
PackageIdSpec {
671+
name: String::from("foo::bar"),
672+
version: None,
673+
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
674+
kind: Some(SourceKind::Path),
675+
},
676+
"path+file:///path/to/my/project/foo#foo::bar",
677+
);
678+
ok(
679+
"path+file:///path/to/my/project/foo#bar:1.1.8",
680+
PackageIdSpec {
681+
name: String::from("bar"),
682+
version: Some("1.1.8".parse().unwrap()),
683+
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
684+
kind: Some(SourceKind::Path),
685+
},
686+
"path+file:///path/to/my/project/foo#[email protected]",
687+
);
688+
ok(
689+
"path+file:///path/to/my/project/foo#foo::bar:1.1.8",
690+
PackageIdSpec {
691+
name: String::from("foo::bar"),
692+
version: Some("1.1.8".parse().unwrap()),
693+
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
694+
kind: Some(SourceKind::Path),
695+
},
696+
"path+file:///path/to/my/project/foo#foo::[email protected]",
697+
);
698+
ok(
699+
"path+file:///path/to/my/project/foo#[email protected]",
700+
PackageIdSpec {
701+
name: String::from("bar"),
702+
version: Some("1.1.8".parse().unwrap()),
703+
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
704+
kind: Some(SourceKind::Path),
705+
},
706+
"path+file:///path/to/my/project/foo#[email protected]",
707+
);
708+
ok(
709+
"path+file:///path/to/my/project/foo#foo::[email protected]",
710+
PackageIdSpec {
711+
name: String::from("foo::bar"),
712+
version: Some("1.1.8".parse().unwrap()),
713+
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
714+
kind: Some(SourceKind::Path),
715+
},
716+
"path+file:///path/to/my/project/foo#foo::[email protected]",
717+
);
602718
}
603719

604720
#[test]
605721
fn bad_parsing() {
606-
macro_rules! err {
607-
($spec:expr, $expected:pat) => {
608-
let err = PackageIdSpec::parse($spec).unwrap_err();
609-
let kind = err.0;
610-
assert!(
611-
matches!(kind, $expected),
612-
"`{}` parse error mismatch, got {kind:?}",
613-
$spec
614-
);
615-
};
616-
}
617-
618722
err!("baz:", ErrorKind::PartialVersion(_));
619723
err!("baz:*", ErrorKind::PartialVersion(_));
620724
err!("baz@", ErrorKind::PartialVersion(_));
621725
err!("baz@*", ErrorKind::PartialVersion(_));
622726
err!("baz@^1.0", ErrorKind::PartialVersion(_));
623-
err!("https://baz:1.0", ErrorKind::PartialVersion(_));
624-
err!("https://#baz:1.0", ErrorKind::PartialVersion(_));
727+
err!("https://baz:1.0", ErrorKind::NameValidation(_));
728+
err!("https://#baz:1.0", ErrorKind::NameValidation(_));
625729
err!(
626730
"foobar+https://github.com/rust-lang/crates.io-index",
627731
ErrorKind::UnsupportedProtocol(_)

tests/testsuite/open_namespaces.rs

+90
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,96 @@ fn main() {}
327327
.run();
328328
}
329329

330+
#[cargo_test]
331+
fn generate_pkgid_with_namespace() {
332+
let p = project()
333+
.file(
334+
"Cargo.toml",
335+
r#"
336+
cargo-features = ["open-namespaces"]
337+
338+
[package]
339+
name = "foo::bar"
340+
version = "0.0.1"
341+
edition = "2015"
342+
"#,
343+
)
344+
.file("src/lib.rs", "")
345+
.build();
346+
347+
p.cargo("generate-lockfile")
348+
.masquerade_as_nightly_cargo(&["open-namespaces"])
349+
.run();
350+
p.cargo("pkgid")
351+
.masquerade_as_nightly_cargo(&["open-namespaces"])
352+
.with_stdout_data(str![[r#"
353+
path+[ROOTURL]/foo#foo::[email protected]
354+
355+
"#]])
356+
.with_stderr_data("")
357+
.run()
358+
}
359+
360+
#[cargo_test]
361+
fn update_spec_accepts_namespaced_name() {
362+
let p = project()
363+
.file(
364+
"Cargo.toml",
365+
r#"
366+
cargo-features = ["open-namespaces"]
367+
368+
[package]
369+
name = "foo::bar"
370+
version = "0.0.1"
371+
edition = "2015"
372+
"#,
373+
)
374+
.file("src/lib.rs", "")
375+
.build();
376+
377+
p.cargo("generate-lockfile")
378+
.masquerade_as_nightly_cargo(&["open-namespaces"])
379+
.run();
380+
p.cargo("update foo::bar")
381+
.masquerade_as_nightly_cargo(&["open-namespaces"])
382+
.with_stdout_data(str![""])
383+
.with_stderr_data(str![[r#"
384+
[LOCKING] 0 packages to latest compatible versions
385+
386+
"#]])
387+
.run()
388+
}
389+
390+
#[cargo_test]
391+
fn update_spec_accepts_namespaced_pkgid() {
392+
let p = project()
393+
.file(
394+
"Cargo.toml",
395+
r#"
396+
cargo-features = ["open-namespaces"]
397+
398+
[package]
399+
name = "foo::bar"
400+
version = "0.0.1"
401+
edition = "2015"
402+
"#,
403+
)
404+
.file("src/lib.rs", "")
405+
.build();
406+
407+
p.cargo("generate-lockfile")
408+
.masquerade_as_nightly_cargo(&["open-namespaces"])
409+
.run();
410+
p.cargo(&format!("update path+{}#foo::[email protected]", p.url()))
411+
.masquerade_as_nightly_cargo(&["open-namespaces"])
412+
.with_stdout_data(str![""])
413+
.with_stderr_data(str![[r#"
414+
[LOCKING] 0 packages to latest compatible versions
415+
416+
"#]])
417+
.run()
418+
}
419+
330420
#[cargo_test]
331421
#[cfg(unix)] // until we get proper packaging support
332422
fn publish_namespaced() {

0 commit comments

Comments
 (0)