From ba645e4f4c57fbc51e1689aad0a6322d0c80e9a6 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 16:43:53 +0100 Subject: [PATCH 01/27] refactor(source-id): Update the fixed builtin URL I didn't realise how closely linked pkgid specs and the URLs are before writing this, so this should match the pkgid format mentioned in the RFC. --- src/workspace/source_id.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/workspace/source_id.rs b/src/workspace/source_id.rs index e9f54ea1a7d..137f1ff7309 100644 --- a/src/workspace/source_id.rs +++ b/src/workspace/source_id.rs @@ -207,7 +207,7 @@ impl SourceId { /// Creates a `SourceId` for the builtin packages in the configured toolchain. pub fn for_builtin() -> CargoResult { - let url = "builtin://".into_url()?; + let url = "builtin://.".into_url()?; SourceId::new(SourceKind::Builtin, url, None) } @@ -856,8 +856,8 @@ mod tests { assert_data_eq!(short_hash(&source_id), str!["199e591d94239206"].raw()); let source_id = SourceId::for_builtin().unwrap(); - assert_data_eq!(gen_hash(source_id), str!["8471877086995577839"].raw()); - assert_data_eq!(short_hash(&source_id), str!["ef7b241f53279275"].raw()); + assert_data_eq!(gen_hash(source_id), str!["5062137541645472653"].raw()); + assert_data_eq!(short_hash(&source_id), str!["8d93f21546534046"].raw()); let path = &ws_root.join("crate"); let source_id = SourceId::for_local_registry(path).unwrap(); From 849b35055ec5a3ed87b41b5f7048203b3106ca48 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 14:26:16 +0100 Subject: [PATCH 02/27] refactor(pkgid-spec): Update `parse_spec` return type to be more honest --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index bd05e47ca8f..362255355a0 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -94,7 +94,10 @@ impl PackageIdSpec { .into()); } } - let (name, version) = parse_spec(spec)?.unwrap_or_else(|| (spec.to_owned(), None)); + let (name, version) = match parse_spec(spec)? { + Some((name, ver)) => (name, Some(ver)), + None => (spec.to_owned(), None), + }; PackageName::new(&name)?; Ok(PackageIdSpec { name: String::from(name), @@ -157,7 +160,7 @@ impl PackageIdSpec { }; match frag { Some(fragment) => match parse_spec(&fragment)? { - Some((name, ver)) => (name, ver), + Some((name, ver)) => (name, Some(ver)), None => { let Some(f) = fragment.chars().next() else { return Err(PackageIdSpecError(ErrorKind::EmptyFragment)); @@ -213,7 +216,7 @@ impl PackageIdSpec { } } -fn parse_spec(spec: &str) -> Result)>> { +fn parse_spec(spec: &str) -> Result> { let Some((name, ver)) = spec .rsplit_once('@') .or_else(|| spec.rsplit_once(':').filter(|(n, _)| !n.ends_with(':'))) @@ -222,7 +225,7 @@ fn parse_spec(spec: &str) -> Result)>> { }; let name = name.to_owned(); let ver = ver.parse::()?; - Ok(Some((name, Some(ver)))) + Ok(Some((name, ver))) } fn strip_url_protocol(url: &Url) -> Result { From b61901d1fc9fa2f5e0c6d2d74e739eec6423f571 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Mon, 28 Sep 2026 17:50:59 +0100 Subject: [PATCH 03/27] test(pkgid-spec): Positive test for builtin pkgid spec parsing --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 362255355a0..7ff65278f9c 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -741,6 +741,7 @@ mod tests { }, "path+file:///path/to/my/project/foo#foo::bar@1.1.8", ); + err!("builtin://.#core", ErrorKind::MissingUrlPath(_)); } #[test] From dc534303ecf0687c85024ff19cb476954b651ead Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 26 Aug 2026 11:01:28 +0100 Subject: [PATCH 04/27] feat(pkgid-spec): Successful parsing of builtin pkgid specs --- .../src/core/package_id_spec.rs | 37 +++++++++++++++---- .../src/core/source_kind.rs | 3 +- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 7ff65278f9c..488f54fc1d4 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -145,15 +145,27 @@ impl PackageIdSpec { } kind => return Err(ErrorKind::UnsupportedProtocol(kind.into()).into()), } - } else { - if url.query().is_some() { - return Err(ErrorKind::UnexpectedQueryString(url).into()); - } + } else if url.scheme() == "builtin" { + kind = Some(SourceKind::Builtin) + } else if url.query().is_some() { + return Err(ErrorKind::UnexpectedQueryString(url).into()); } let frag = url.fragment().map(|s| s.to_owned()); url.set_fragment(None); + if kind == Some(SourceKind::Builtin) { + // Builtins cannot have path segments, so handle them early + let name = frag.unwrap(); + PackageName::new(&name)?; + return Ok(PackageIdSpec { + name, + version: None, + url: Some(url), + kind, + }); + } + let (name, version) = { let Some(path_name) = url.path_segments().and_then(|mut p| p.next_back()) else { return Err(ErrorKind::MissingUrlPath(url).into()); @@ -243,7 +255,7 @@ impl fmt::Display for PackageIdSpec { let mut printed_name = false; match self.url { Some(ref url) => { - if let Some(protocol) = self.kind.as_ref().and_then(|k| k.protocol()) { + if let Some(protocol) = self.kind.as_ref().and_then(SourceKind::protocol) { write!(f, "{protocol}+")?; } write!(f, "{}", url)?; @@ -252,7 +264,9 @@ impl fmt::Display for PackageIdSpec { write!(f, "?{}", pretty)?; } } - if url.path_segments().unwrap().next_back().unwrap() != &*self.name { + if self.kind() == Some(&SourceKind::Builtin) + || url.path_segments().unwrap().next_back().unwrap() != &*self.name + { printed_name = true; write!(f, "#{}", self.name)?; } @@ -741,7 +755,16 @@ mod tests { }, "path+file:///path/to/my/project/foo#foo::bar@1.1.8", ); - err!("builtin://.#core", ErrorKind::MissingUrlPath(_)); + ok( + "builtin://.#core", + PackageIdSpec { + name: String::from("core"), + version: None, + url: Some(Url::parse("builtin://.").unwrap()), + kind: Some(SourceKind::Builtin), + }, + "builtin://.#core", + ) } #[test] diff --git a/crates/cargo-util-schemas/src/core/source_kind.rs b/crates/cargo-util-schemas/src/core/source_kind.rs index 1c16a251371..e713742dce6 100644 --- a/crates/cargo-util-schemas/src/core/source_kind.rs +++ b/crates/cargo-util-schemas/src/core/source_kind.rs @@ -42,7 +42,8 @@ impl SourceKind { SourceKind::SparseRegistry => None, SourceKind::LocalRegistry => Some("local-registry"), SourceKind::Directory => Some("directory"), - SourceKind::Builtin => Some("builtin"), + // The protocol is not required for builtins so we don't emit it + SourceKind::Builtin => None, } } } From 34175679bb12543b2ba657b20e971098705aa104 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Mon, 28 Sep 2026 18:06:00 +0100 Subject: [PATCH 05/27] test(pkgid-spec): Parse builtins with a "builtin+" kind --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 488f54fc1d4..5231eba1239 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -764,7 +764,11 @@ mod tests { kind: Some(SourceKind::Builtin), }, "builtin://.#core", - ) + ); + err!( + "builtin+builtin://.#core", + ErrorKind::UnsupportedProtocol(_) + ); } #[test] From 9d326b316ca1ed5a3b26918ddbf5d6efa414f260 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Mon, 28 Sep 2026 18:07:37 +0100 Subject: [PATCH 06/27] fix(pkgid-spec): Parse builtin urls with protocol --- .../cargo-util-schemas/src/core/package_id_spec.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 5231eba1239..03c208a8008 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -143,6 +143,10 @@ impl PackageIdSpec { kind = Some(SourceKind::Path); url = strip_url_protocol(&url)?; } + "builtin" => { + kind = Some(SourceKind::Builtin); + url = strip_url_protocol(&url)?; + } kind => return Err(ErrorKind::UnsupportedProtocol(kind.into()).into()), } } else if url.scheme() == "builtin" { @@ -765,9 +769,15 @@ mod tests { }, "builtin://.#core", ); - err!( + ok( "builtin+builtin://.#core", - ErrorKind::UnsupportedProtocol(_) + PackageIdSpec { + name: String::from("core"), + version: None, + url: Some(Url::parse("builtin://.").unwrap()), + kind: Some(SourceKind::Builtin), + }, + "builtin://.#core", ); } From 5eb67180c8835d1eec2612af34e8047b534754d6 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 10:04:15 +0100 Subject: [PATCH 07/27] test(pkgid-spec): Builtin URL with no fragment --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 03c208a8008..50a2764cd44 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -782,6 +782,7 @@ mod tests { } #[test] + #[should_panic] fn bad_parsing() { err!("baz:", ErrorKind::PartialVersion(_)); err!("baz:*", ErrorKind::PartialVersion(_)); @@ -826,5 +827,6 @@ mod tests { ErrorKind::InvalidPkgIdUrl(_) ); err!("git+https://", ErrorKind::InvalidPkgIdUrl(_)); + err!("builtin://.", ErrorKind::EmptyFragment); } } From 806885974650f7ebdb32d8407db4b7b413a7e71c Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 10:17:22 +0100 Subject: [PATCH 08/27] fix(pkgid-spec): Return error when builtin URL is missing a fragment --- .../cargo-util-schemas/src/core/package_id_spec.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 50a2764cd44..16779617cb6 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -159,8 +159,11 @@ impl PackageIdSpec { url.set_fragment(None); if kind == Some(SourceKind::Builtin) { - // Builtins cannot have path segments, so handle them early - let name = frag.unwrap(); + // Builtins are different in that they require a fragment and cannot have path segments, + // so handle them early + let Some(name) = frag else { + return Err(ErrorKind::FragmentRequired.into()); + }; PackageName::new(&name)?; return Ok(PackageIdSpec { name, @@ -354,6 +357,9 @@ enum ErrorKind { #[error("pkgid url cannot have an empty fragment")] EmptyFragment, + #[error("builtin package ID specifications must specify the package name in the fragment")] + FragmentRequired, + #[error(transparent)] NameValidation(#[from] crate::restricted_names::NameValidationError), @@ -782,7 +788,6 @@ mod tests { } #[test] - #[should_panic] fn bad_parsing() { err!("baz:", ErrorKind::PartialVersion(_)); err!("baz:*", ErrorKind::PartialVersion(_)); @@ -827,6 +832,6 @@ mod tests { ErrorKind::InvalidPkgIdUrl(_) ); err!("git+https://", ErrorKind::InvalidPkgIdUrl(_)); - err!("builtin://.", ErrorKind::EmptyFragment); + err!("builtin://.", ErrorKind::FragmentRequired); } } From 89043ff15d394a72e534d1e3edfeee17f2f8ac42 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 11:34:03 +0100 Subject: [PATCH 09/27] test(pkgid-spec): Accept version specifier on builtins --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 16779617cb6..a103c3e1119 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -785,6 +785,10 @@ mod tests { }, "builtin://.#core", ); + err!( + "builtin+builtin://.#core@0.0.0", + ErrorKind::NameValidation(_) + ); } #[test] From ce409b7a0efd412d1de544734906b57b78601591 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 11:55:26 +0100 Subject: [PATCH 10/27] fix(pkgid-spec): Parse and print version for builtin pkgid --- .../src/core/package_id_spec.rs | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index a103c3e1119..72ffb70aef9 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -161,13 +161,24 @@ impl PackageIdSpec { if kind == Some(SourceKind::Builtin) { // Builtins are different in that they require a fragment and cannot have path segments, // so handle them early - let Some(name) = frag else { + let Some(frag) = frag else { return Err(ErrorKind::FragmentRequired.into()); }; + let name = match parse_spec(&frag)? { + Some((name, _)) => name, + None => frag, + }; PackageName::new(&name)?; + let version = PartialVersion { + major: 0, + minor: Some(0), + patch: Some(0), + pre: None, + build: None, + }; return Ok(PackageIdSpec { name, - version: None, + version: Some(version), url: Some(url), kind, }); @@ -283,7 +294,9 @@ impl fmt::Display for PackageIdSpec { write!(f, "{}", self.name)?; } } - if let Some(ref v) = self.version { + if let Some(ref v) = self.version + && self.kind != Some(SourceKind::Builtin) + { write!(f, "{}{}", if printed_name { "@" } else { "#" }, v)?; } Ok(()) @@ -769,7 +782,7 @@ mod tests { "builtin://.#core", PackageIdSpec { name: String::from("core"), - version: None, + version: Some("0.0.0".parse().unwrap()), url: Some(Url::parse("builtin://.").unwrap()), kind: Some(SourceKind::Builtin), }, @@ -779,15 +792,21 @@ mod tests { "builtin+builtin://.#core", PackageIdSpec { name: String::from("core"), - version: None, + version: Some("0.0.0".parse().unwrap()), url: Some(Url::parse("builtin://.").unwrap()), kind: Some(SourceKind::Builtin), }, "builtin://.#core", ); - err!( + ok( "builtin+builtin://.#core@0.0.0", - ErrorKind::NameValidation(_) + PackageIdSpec { + name: String::from("core"), + version: Some("0.0.0".parse().unwrap()), + url: Some(Url::parse("builtin://.").unwrap()), + kind: Some(SourceKind::Builtin), + }, + "builtin://.#core", ); } From 74bf5580d6d760942dadc52b961fd6e08345ecd2 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 17:37:34 +0100 Subject: [PATCH 11/27] test(pkgid-spec): Test for invalid builtin version --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 72ffb70aef9..0240417c218 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -856,5 +856,15 @@ mod tests { ); err!("git+https://", ErrorKind::InvalidPkgIdUrl(_)); err!("builtin://.", ErrorKind::FragmentRequired); + ok( + "builtin+builtin://.#core@0.1.0", + PackageIdSpec { + name: String::from("core"), + version: Some("0.0.0".parse().unwrap()), + url: Some(Url::parse("builtin://.").unwrap()), + kind: Some(SourceKind::Builtin), + }, + "builtin://.#core", + ); } } From 81bc0d15aaaf06dfa998bf761a61107aab01b61a Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 17:42:12 +0100 Subject: [PATCH 12/27] fix(pkgid-spec): Reject builtin versions that don't match 0.0.0 --- .../src/core/package_id_spec.rs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 0240417c218..a72a521bd8c 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -164,10 +164,17 @@ impl PackageIdSpec { let Some(frag) = frag else { return Err(ErrorKind::FragmentRequired.into()); }; + let name = match parse_spec(&frag)? { - Some((name, _)) => name, + Some((name, ver)) => { + if !ver.matches(&Version::new(0, 0, 0)) { + return Err(ErrorKind::InvalidVersion(ver.to_string()).into()); + } + name + } None => frag, }; + PackageName::new(&name)?; let version = PartialVersion { major: 0, @@ -373,6 +380,9 @@ enum ErrorKind { #[error("builtin package ID specifications must specify the package name in the fragment")] FragmentRequired, + #[error("version `{0}` is invalid for builtin packages, which are unversioned (or `0.0.0`)")] + InvalidVersion(String), + #[error(transparent)] NameValidation(#[from] crate::restricted_names::NameValidationError), @@ -856,15 +866,9 @@ mod tests { ); err!("git+https://", ErrorKind::InvalidPkgIdUrl(_)); err!("builtin://.", ErrorKind::FragmentRequired); - ok( + err!( "builtin+builtin://.#core@0.1.0", - PackageIdSpec { - name: String::from("core"), - version: Some("0.0.0".parse().unwrap()), - url: Some(Url::parse("builtin://.").unwrap()), - kind: Some(SourceKind::Builtin), - }, - "builtin://.#core", + ErrorKind::InvalidVersion(_) ); } } From 41d2142a4a135394e90992b7912f826e5e4bbad9 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 17:23:52 +0100 Subject: [PATCH 13/27] test(pkgid-spec): Add regression test for partial version --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index a72a521bd8c..2d8126d4448 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -818,6 +818,16 @@ mod tests { }, "builtin://.#core", ); + ok( + "builtin+builtin://.#core@0", + PackageIdSpec { + name: String::from("core"), + version: Some("0.0.0".parse().unwrap()), + url: Some(Url::parse("builtin://.").unwrap()), + kind: Some(SourceKind::Builtin), + }, + "builtin://.#core", + ); } #[test] From 64ed9ad41891fbc8a73d5853a9d41336ea7447ad Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 12:15:51 +0100 Subject: [PATCH 14/27] test(pkgid-spec): Check error message on empty fragment --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 2d8126d4448..308582687d2 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -880,5 +880,6 @@ mod tests { "builtin+builtin://.#core@0.1.0", ErrorKind::InvalidVersion(_) ); + err!("builtin://.#", ErrorKind::NameValidation(_)); } } From e8971d4b07eac2b8e78b7dda9868d0f7b9224e3b Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 12:20:09 +0100 Subject: [PATCH 15/27] fix(pkgid-spec): Return appropriate error message for empty fragment --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 308582687d2..094f860de86 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -165,6 +165,9 @@ impl PackageIdSpec { return Err(ErrorKind::FragmentRequired.into()); }; + if frag.is_empty() { + return Err(ErrorKind::EmptyFragment.into()); + } let name = match parse_spec(&frag)? { Some((name, ver)) => { if !ver.matches(&Version::new(0, 0, 0)) { @@ -880,6 +883,6 @@ mod tests { "builtin+builtin://.#core@0.1.0", ErrorKind::InvalidVersion(_) ); - err!("builtin://.#", ErrorKind::NameValidation(_)); + err!("builtin://.#", ErrorKind::EmptyFragment); } } From 39b8622b842702e05ee5e77430f6f9a8b29559bd Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 13:49:54 +0100 Subject: [PATCH 16/27] test(pkgid-spec): Add regression test for missing name in fragment --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 094f860de86..83079af33c3 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -884,5 +884,6 @@ mod tests { ErrorKind::InvalidVersion(_) ); err!("builtin://.#", ErrorKind::EmptyFragment); + err!("builtin://.#0.0.0", ErrorKind::NameValidation(_)); } } From cbde4f37e64c939d3943a7cb4342b7d6752b438c Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 15:11:08 +0100 Subject: [PATCH 17/27] test(pkgid-spec): Test invalid hostname --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 83079af33c3..62a392bbf3c 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -885,5 +885,15 @@ mod tests { ); err!("builtin://.#", ErrorKind::EmptyFragment); err!("builtin://.#0.0.0", ErrorKind::NameValidation(_)); + ok( + "builtin://wrong#core", + PackageIdSpec { + name: String::from("core"), + version: Some("0.0.0".parse().unwrap()), + url: Some(Url::parse("builtin://wrong").unwrap()), + kind: Some(SourceKind::Builtin), + }, + "builtin://wrong#core", + ); } } From d371fb8f4ccc0961f404a35febecdc532f422fc0 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 15:39:54 +0100 Subject: [PATCH 18/27] fix(pkgid-spec): Assert that hostname for builtin scheme should be `.` --- .../src/core/package_id_spec.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 62a392bbf3c..8f334af0fdc 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -161,6 +161,9 @@ impl PackageIdSpec { if kind == Some(SourceKind::Builtin) { // Builtins are different in that they require a fragment and cannot have path segments, // so handle them early + if url.host_str() != Some(".") { + return Err(ErrorKind::InvalidHostname.into()); + } let Some(frag) = frag else { return Err(ErrorKind::FragmentRequired.into()); }; @@ -386,6 +389,9 @@ enum ErrorKind { #[error("version `{0}` is invalid for builtin packages, which are unversioned (or `0.0.0`)")] InvalidVersion(String), + #[error("only `.` is permitted as a hostname for builtin package ID specifications")] + InvalidHostname, + #[error(transparent)] NameValidation(#[from] crate::restricted_names::NameValidationError), @@ -885,15 +891,6 @@ mod tests { ); err!("builtin://.#", ErrorKind::EmptyFragment); err!("builtin://.#0.0.0", ErrorKind::NameValidation(_)); - ok( - "builtin://wrong#core", - PackageIdSpec { - name: String::from("core"), - version: Some("0.0.0".parse().unwrap()), - url: Some(Url::parse("builtin://wrong").unwrap()), - kind: Some(SourceKind::Builtin), - }, - "builtin://wrong#core", - ); + err!("builtin://wrong#core", ErrorKind::InvalidHostname); } } From 022da650e869a6912da268635673f3ac76783ed6 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 15:42:50 +0100 Subject: [PATCH 19/27] test(pkgid-spec): Test for path in builtin URL --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 8f334af0fdc..e94d2ba1b20 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -892,5 +892,15 @@ mod tests { err!("builtin://.#", ErrorKind::EmptyFragment); err!("builtin://.#0.0.0", ErrorKind::NameValidation(_)); err!("builtin://wrong#core", ErrorKind::InvalidHostname); + ok( + "builtin://./invalid/path#core", + PackageIdSpec { + name: String::from("core"), + version: Some("0.0.0".parse().unwrap()), + url: Some(Url::parse("builtin://./invalid/path").unwrap()), + kind: Some(SourceKind::Builtin), + }, + "builtin://./invalid/path#core", + ); } } From 86298bc9664b932dfa3770139438f8f618f230f0 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 16:14:32 +0100 Subject: [PATCH 20/27] fix(pkgid-spec): Reject paths for builtin packages --- .../src/core/package_id_spec.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index e94d2ba1b20..89953f77013 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -164,6 +164,9 @@ impl PackageIdSpec { if url.host_str() != Some(".") { return Err(ErrorKind::InvalidHostname.into()); } + if url.path() != "" { + return Err(ErrorKind::InvalidPath.into()); + } let Some(frag) = frag else { return Err(ErrorKind::FragmentRequired.into()); }; @@ -392,6 +395,9 @@ enum ErrorKind { #[error("only `.` is permitted as a hostname for builtin package ID specifications")] InvalidHostname, + #[error("only empty paths are permitted for builtin package ID specifications")] + InvalidPath, + #[error(transparent)] NameValidation(#[from] crate::restricted_names::NameValidationError), @@ -892,15 +898,6 @@ mod tests { err!("builtin://.#", ErrorKind::EmptyFragment); err!("builtin://.#0.0.0", ErrorKind::NameValidation(_)); err!("builtin://wrong#core", ErrorKind::InvalidHostname); - ok( - "builtin://./invalid/path#core", - PackageIdSpec { - name: String::from("core"), - version: Some("0.0.0".parse().unwrap()), - url: Some(Url::parse("builtin://./invalid/path").unwrap()), - kind: Some(SourceKind::Builtin), - }, - "builtin://./invalid/path#core", - ); + err!("builtin://./invalid/path#core", ErrorKind::InvalidPath); } } From 6cdd36eeb233e6cec5bd8a992930c69390ed79ee Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 16:16:17 +0100 Subject: [PATCH 21/27] test(pkgid-spec): Test for builtin scheme when kind is builtin --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 89953f77013..695b410e321 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -899,5 +899,6 @@ mod tests { err!("builtin://.#0.0.0", ErrorKind::NameValidation(_)); err!("builtin://wrong#core", ErrorKind::InvalidHostname); err!("builtin://./invalid/path#core", ErrorKind::InvalidPath); + err!("builtin+invalid://.", ErrorKind::FragmentRequired); } } From c321769e90261ed0706cac547a8070d2893600fe Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 29 Sep 2026 16:23:22 +0100 Subject: [PATCH 22/27] fix(pkgid-spec): Error on invalid protocol for builtin scheme --- .../cargo-util-schemas/src/core/package_id_spec.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 695b410e321..5f53122a4b6 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -144,6 +144,9 @@ impl PackageIdSpec { url = strip_url_protocol(&url)?; } "builtin" => { + if scheme != "builtin" { + return Err(ErrorKind::UnsupportedBuiltinScheme(scheme.into()).into()); + } kind = Some(SourceKind::Builtin); url = strip_url_protocol(&url)?; } @@ -398,6 +401,11 @@ enum ErrorKind { #[error("only empty paths are permitted for builtin package ID specifications")] InvalidPath, + #[error( + "`builtin+{0}` is unsupported - only the `builtin` protocol is supported for `builtin` kinds" + )] + UnsupportedBuiltinScheme(String), + #[error(transparent)] NameValidation(#[from] crate::restricted_names::NameValidationError), @@ -899,6 +907,9 @@ mod tests { err!("builtin://.#0.0.0", ErrorKind::NameValidation(_)); err!("builtin://wrong#core", ErrorKind::InvalidHostname); err!("builtin://./invalid/path#core", ErrorKind::InvalidPath); - err!("builtin+invalid://.", ErrorKind::FragmentRequired); + err!( + "builtin+invalid://.", + ErrorKind::UnsupportedBuiltinScheme(_) + ); } } From 6f2423e4ca13eb2aac2efde7bc0b05d7674fa74c Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 30 Sep 2026 16:56:11 +0100 Subject: [PATCH 23/27] test(pkgid-spec): Test for ?query with builtin --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 5f53122a4b6..ed83fe0a196 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -911,5 +911,15 @@ mod tests { "builtin+invalid://.", ErrorKind::UnsupportedBuiltinScheme(_) ); + ok( + "builtin+builtin://.?query=test#core", + PackageIdSpec { + name: String::from("core"), + version: Some("0.0.0".parse().unwrap()), + url: Some(Url::parse("builtin://.?query=test").unwrap()), + kind: Some(SourceKind::Builtin), + }, + "builtin://.?query=test#core", + ); } } From 2017a36a17d7b51255b38c008449531fb869cf6c Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 30 Sep 2026 17:01:58 +0100 Subject: [PATCH 24/27] fix(pkgid-spec): Reject query string for builtin scheme --- .../cargo-util-schemas/src/core/package_id_spec.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index ed83fe0a196..44d4992e6d7 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -153,6 +153,9 @@ impl PackageIdSpec { kind => return Err(ErrorKind::UnsupportedProtocol(kind.into()).into()), } } else if url.scheme() == "builtin" { + if url.query().is_some() { + return Err(ErrorKind::UnexpectedQueryString(url).into()); + } kind = Some(SourceKind::Builtin) } else if url.query().is_some() { return Err(ErrorKind::UnexpectedQueryString(url).into()); @@ -911,15 +914,9 @@ mod tests { "builtin+invalid://.", ErrorKind::UnsupportedBuiltinScheme(_) ); - ok( - "builtin+builtin://.?query=test#core", - PackageIdSpec { - name: String::from("core"), - version: Some("0.0.0".parse().unwrap()), - url: Some(Url::parse("builtin://.?query=test").unwrap()), - kind: Some(SourceKind::Builtin), - }, + err!( "builtin://.?query=test#core", + ErrorKind::UnexpectedQueryString(_) ); } } From 5146925d7e549cc539bd01a4c151699f898a71fd Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 30 Sep 2026 17:08:02 +0100 Subject: [PATCH 25/27] test(pkgid-spec): Test for query with builtin+ kind --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 44d4992e6d7..156fc2480e2 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -918,5 +918,15 @@ mod tests { "builtin://.?query=test#core", ErrorKind::UnexpectedQueryString(_) ); + // This failing test doesn't use ok() as the roundtrip without the builtin kind exercises + // the above case + let parsed = PackageIdSpec::parse("builtin+builtin://.?query=test#core").unwrap(); + let expected = PackageIdSpec { + name: String::from("core"), + version: Some("0.0.0".parse().unwrap()), + url: Some(Url::parse("builtin://.?query=test").unwrap()), + kind: Some(SourceKind::Builtin), + }; + assert_eq!(parsed, expected); } } From 53badd893d76316cf2caec3ca7ebb87502d1f42a Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 30 Sep 2026 17:09:17 +0100 Subject: [PATCH 26/27] fix(pkgid-spec): Reject query string with builtin+ kind --- .../src/core/package_id_spec.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 156fc2480e2..88fdcd8225e 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -144,6 +144,9 @@ impl PackageIdSpec { url = strip_url_protocol(&url)?; } "builtin" => { + if url.query().is_some() { + return Err(ErrorKind::UnexpectedQueryString(url).into()); + } if scheme != "builtin" { return Err(ErrorKind::UnsupportedBuiltinScheme(scheme.into()).into()); } @@ -918,15 +921,9 @@ mod tests { "builtin://.?query=test#core", ErrorKind::UnexpectedQueryString(_) ); - // This failing test doesn't use ok() as the roundtrip without the builtin kind exercises - // the above case - let parsed = PackageIdSpec::parse("builtin+builtin://.?query=test#core").unwrap(); - let expected = PackageIdSpec { - name: String::from("core"), - version: Some("0.0.0".parse().unwrap()), - url: Some(Url::parse("builtin://.?query=test").unwrap()), - kind: Some(SourceKind::Builtin), - }; - assert_eq!(parsed, expected); + err!( + "builtin+builtin://.?query=test#core", + ErrorKind::UnexpectedQueryString(_) + ); } } From 4289ff0d7547d87a5028b19442a12ab95ca94b41 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 2 Oct 2026 15:25:13 +0100 Subject: [PATCH 27/27] test(pkgid-spec): Add doctest showing package id spec examples --- crates/cargo-util-schemas/src/core/package_id_spec.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index 88fdcd8225e..f369e7546f0 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -72,7 +72,8 @@ impl PackageIdSpec { /// "https://github.com/rust-lang/crates.io-index#foo@1.4.3", /// "ssh://git@github.com/rust-lang/foo.git#foo@1.4.3", /// "file:///path/to/my/project/foo", - /// "file:///path/to/my/project/foo#1.1.8" + /// "file:///path/to/my/project/foo#1.1.8", + /// "builtin://.#core" /// ]; /// for spec in specs { /// assert!(PackageIdSpec::parse(spec).is_ok());