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..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());
@@ -94,7 +95,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),
@@ -140,24 +144,79 @@ impl PackageIdSpec {
kind = Some(SourceKind::Path);
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());
+ }
+ kind = Some(SourceKind::Builtin);
+ url = strip_url_protocol(&url)?;
+ }
kind => return Err(ErrorKind::UnsupportedProtocol(kind.into()).into()),
}
- } else {
+ } 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());
}
let frag = url.fragment().map(|s| s.to_owned());
url.set_fragment(None);
+ 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());
+ }
+ if url.path() != "" {
+ return Err(ErrorKind::InvalidPath.into());
+ }
+ let Some(frag) = frag else {
+ 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)) {
+ return Err(ErrorKind::InvalidVersion(ver.to_string()).into());
+ }
+ 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: Some(version),
+ 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());
};
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 +272,7 @@ impl PackageIdSpec {
}
}
-fn parse_spec(spec: &str) -> Result