mirror of https://github.com/rust-lang/cargo
656 lines
22 KiB
Rust
656 lines
22 KiB
Rust
use std::fmt;
|
|
|
|
use semver::Version;
|
|
use serde::{de, ser};
|
|
use url::Url;
|
|
|
|
use crate::core::GitReference;
|
|
use crate::core::PartialVersion;
|
|
use crate::core::PartialVersionError;
|
|
use crate::core::SourceKind;
|
|
use crate::manifest::PackageName;
|
|
use crate::restricted_names::NameValidationError;
|
|
|
|
type Result<T> = std::result::Result<T, PackageIdSpecError>;
|
|
|
|
/// Some or all of the data required to identify a package:
|
|
///
|
|
/// 1. the package name (a `String`, required)
|
|
/// 2. the package version (a `Version`, optional)
|
|
/// 3. the package source (a `Url`, optional)
|
|
///
|
|
/// If any of the optional fields are omitted, then the package ID may be ambiguous, there may be
|
|
/// more than one package/version/url combo that will match. However, often just the name is
|
|
/// sufficient to uniquely define a package ID.
|
|
#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
|
|
pub struct PackageIdSpec {
|
|
name: String,
|
|
version: Option<PartialVersion>,
|
|
url: Option<Url>,
|
|
kind: Option<SourceKind>,
|
|
}
|
|
|
|
impl PackageIdSpec {
|
|
pub fn new(name: String) -> Self {
|
|
Self {
|
|
name,
|
|
version: None,
|
|
url: None,
|
|
kind: None,
|
|
}
|
|
}
|
|
|
|
pub fn with_version(mut self, version: PartialVersion) -> Self {
|
|
self.version = Some(version);
|
|
self
|
|
}
|
|
|
|
pub fn with_url(mut self, url: Url) -> Self {
|
|
self.url = Some(url);
|
|
self
|
|
}
|
|
|
|
pub fn with_kind(mut self, kind: SourceKind) -> Self {
|
|
self.kind = Some(kind);
|
|
self
|
|
}
|
|
|
|
/// Parses a spec string and returns a `PackageIdSpec` if the string was valid.
|
|
///
|
|
/// # Examples
|
|
/// Some examples of valid strings
|
|
///
|
|
/// ```
|
|
/// use cargo_util_schemas::core::PackageIdSpec;
|
|
///
|
|
/// let specs = vec![
|
|
/// "foo",
|
|
/// "foo@1.4",
|
|
/// "foo@1.4.3",
|
|
/// "foo:1.2.3",
|
|
/// "https://github.com/rust-lang/crates.io-index#foo",
|
|
/// "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"
|
|
/// ];
|
|
/// for spec in specs {
|
|
/// assert!(PackageIdSpec::parse(spec).is_ok());
|
|
/// }
|
|
pub fn parse(spec: &str) -> Result<PackageIdSpec> {
|
|
if spec.contains("://") {
|
|
if let Ok(url) = Url::parse(spec) {
|
|
return PackageIdSpec::from_url(url);
|
|
}
|
|
} else if spec.contains('/') || spec.contains('\\') {
|
|
let abs = std::env::current_dir().unwrap_or_default().join(spec);
|
|
if abs.exists() {
|
|
let maybe_url = Url::from_file_path(abs)
|
|
.map_or_else(|_| "a file:// URL".to_string(), |url| url.to_string());
|
|
return Err(ErrorKind::MaybeFilePath {
|
|
spec: spec.into(),
|
|
maybe_url,
|
|
}
|
|
.into());
|
|
}
|
|
}
|
|
let mut parts = spec.splitn(2, [':', '@']);
|
|
let name = parts.next().unwrap();
|
|
let version = match parts.next() {
|
|
Some(version) => Some(version.parse::<PartialVersion>()?),
|
|
None => None,
|
|
};
|
|
PackageName::new(name)?;
|
|
Ok(PackageIdSpec {
|
|
name: String::from(name),
|
|
version,
|
|
url: None,
|
|
kind: None,
|
|
})
|
|
}
|
|
|
|
/// Tries to convert a valid `Url` to a `PackageIdSpec`.
|
|
fn from_url(mut url: Url) -> Result<PackageIdSpec> {
|
|
let mut kind = None;
|
|
if let Some((kind_str, scheme)) = url.scheme().split_once('+') {
|
|
match kind_str {
|
|
"git" => {
|
|
let git_ref = GitReference::from_query(url.query_pairs());
|
|
url.set_query(None);
|
|
kind = Some(SourceKind::Git(git_ref));
|
|
url = strip_url_protocol(&url);
|
|
}
|
|
"registry" => {
|
|
if url.query().is_some() {
|
|
return Err(ErrorKind::UnexpectedQueryString(url).into());
|
|
}
|
|
kind = Some(SourceKind::Registry);
|
|
url = strip_url_protocol(&url);
|
|
}
|
|
"sparse" => {
|
|
if url.query().is_some() {
|
|
return Err(ErrorKind::UnexpectedQueryString(url).into());
|
|
}
|
|
kind = Some(SourceKind::SparseRegistry);
|
|
// Leave `sparse` as part of URL, see `SourceId::new`
|
|
// url = strip_url_protocol(&url);
|
|
}
|
|
"path" => {
|
|
if url.query().is_some() {
|
|
return Err(ErrorKind::UnexpectedQueryString(url).into());
|
|
}
|
|
if scheme != "file" {
|
|
return Err(ErrorKind::UnsupportedPathPlusScheme(scheme.into()).into());
|
|
}
|
|
kind = Some(SourceKind::Path);
|
|
url = strip_url_protocol(&url);
|
|
}
|
|
kind => return Err(ErrorKind::UnsupportedProtocol(kind.into()).into()),
|
|
}
|
|
} 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);
|
|
|
|
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 fragment.split_once([':', '@']) {
|
|
Some((name, part)) => {
|
|
let version = part.parse::<PartialVersion>()?;
|
|
(String::from(name), Some(version))
|
|
}
|
|
None => {
|
|
if fragment.chars().next().unwrap().is_alphabetic() {
|
|
(String::from(fragment.as_str()), None)
|
|
} else {
|
|
let version = fragment.parse::<PartialVersion>()?;
|
|
(String::from(path_name), Some(version))
|
|
}
|
|
}
|
|
},
|
|
None => (String::from(path_name), None),
|
|
}
|
|
};
|
|
PackageName::new(&name)?;
|
|
Ok(PackageIdSpec {
|
|
name,
|
|
version,
|
|
url: Some(url),
|
|
kind,
|
|
})
|
|
}
|
|
|
|
pub fn name(&self) -> &str {
|
|
self.name.as_str()
|
|
}
|
|
|
|
/// Full `semver::Version`, if present
|
|
pub fn version(&self) -> Option<Version> {
|
|
self.version.as_ref().and_then(|v| v.to_version())
|
|
}
|
|
|
|
pub fn partial_version(&self) -> Option<&PartialVersion> {
|
|
self.version.as_ref()
|
|
}
|
|
|
|
pub fn url(&self) -> Option<&Url> {
|
|
self.url.as_ref()
|
|
}
|
|
|
|
pub fn set_url(&mut self, url: Url) {
|
|
self.url = Some(url);
|
|
}
|
|
|
|
pub fn kind(&self) -> Option<&SourceKind> {
|
|
self.kind.as_ref()
|
|
}
|
|
|
|
pub fn set_kind(&mut self, kind: SourceKind) {
|
|
self.kind = Some(kind);
|
|
}
|
|
}
|
|
|
|
fn strip_url_protocol(url: &Url) -> Url {
|
|
// Ridiculous hoop because `Url::set_scheme` errors when changing to http/https
|
|
let raw = url.to_string();
|
|
raw.split_once('+').unwrap().1.parse().unwrap()
|
|
}
|
|
|
|
impl fmt::Display for PackageIdSpec {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let mut printed_name = false;
|
|
match self.url {
|
|
Some(ref url) => {
|
|
if let Some(protocol) = self.kind.as_ref().and_then(|k| k.protocol()) {
|
|
write!(f, "{protocol}+")?;
|
|
}
|
|
write!(f, "{}", url)?;
|
|
if let Some(SourceKind::Git(git_ref)) = self.kind.as_ref() {
|
|
if let Some(pretty) = git_ref.pretty_ref(true) {
|
|
write!(f, "?{}", pretty)?;
|
|
}
|
|
}
|
|
if url.path_segments().unwrap().next_back().unwrap() != &*self.name {
|
|
printed_name = true;
|
|
write!(f, "#{}", self.name)?;
|
|
}
|
|
}
|
|
None => {
|
|
printed_name = true;
|
|
write!(f, "{}", self.name)?;
|
|
}
|
|
}
|
|
if let Some(ref v) = self.version {
|
|
write!(f, "{}{}", if printed_name { "@" } else { "#" }, v)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl ser::Serialize for PackageIdSpec {
|
|
fn serialize<S>(&self, s: S) -> std::result::Result<S::Ok, S::Error>
|
|
where
|
|
S: ser::Serializer,
|
|
{
|
|
self.to_string().serialize(s)
|
|
}
|
|
}
|
|
|
|
impl<'de> de::Deserialize<'de> for PackageIdSpec {
|
|
fn deserialize<D>(d: D) -> std::result::Result<PackageIdSpec, D::Error>
|
|
where
|
|
D: de::Deserializer<'de>,
|
|
{
|
|
let string = String::deserialize(d)?;
|
|
PackageIdSpec::parse(&string).map_err(de::Error::custom)
|
|
}
|
|
}
|
|
|
|
/// Error parsing a [`PackageIdSpec`].
|
|
#[derive(Debug, thiserror::Error)]
|
|
#[error(transparent)]
|
|
pub struct PackageIdSpecError(#[from] ErrorKind);
|
|
|
|
impl From<PartialVersionError> for PackageIdSpecError {
|
|
fn from(value: PartialVersionError) -> Self {
|
|
ErrorKind::PartialVersion(value).into()
|
|
}
|
|
}
|
|
|
|
impl From<NameValidationError> for PackageIdSpecError {
|
|
fn from(value: NameValidationError) -> Self {
|
|
ErrorKind::NameValidation(value).into()
|
|
}
|
|
}
|
|
|
|
/// Non-public error kind for [`PackageIdSpecError`].
|
|
#[non_exhaustive]
|
|
#[derive(Debug, thiserror::Error)]
|
|
enum ErrorKind {
|
|
#[error("unsupported source protocol: {0}")]
|
|
UnsupportedProtocol(String),
|
|
|
|
#[error("`path+{0}` is unsupported; `path+file` and `file` schemes are supported")]
|
|
UnsupportedPathPlusScheme(String),
|
|
|
|
#[error("cannot have a query string in a pkgid: {0}")]
|
|
UnexpectedQueryString(Url),
|
|
|
|
#[error("pkgid urls must have at least one path component: {0}")]
|
|
MissingUrlPath(Url),
|
|
|
|
#[error("package ID specification `{spec}` looks like a file path, maybe try {maybe_url}")]
|
|
MaybeFilePath { spec: String, maybe_url: String },
|
|
|
|
#[error(transparent)]
|
|
NameValidation(#[from] crate::restricted_names::NameValidationError),
|
|
|
|
#[error(transparent)]
|
|
PartialVersion(#[from] crate::core::PartialVersionError),
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::ErrorKind;
|
|
use super::PackageIdSpec;
|
|
use crate::core::{GitReference, SourceKind};
|
|
use url::Url;
|
|
|
|
#[test]
|
|
fn good_parsing() {
|
|
#[track_caller]
|
|
fn ok(spec: &str, expected: PackageIdSpec, expected_rendered: &str) {
|
|
let parsed = PackageIdSpec::parse(spec).unwrap();
|
|
assert_eq!(parsed, expected);
|
|
let rendered = parsed.to_string();
|
|
assert_eq!(rendered, expected_rendered);
|
|
let reparsed = PackageIdSpec::parse(&rendered).unwrap();
|
|
assert_eq!(reparsed, expected);
|
|
}
|
|
|
|
ok(
|
|
"https://crates.io/foo",
|
|
PackageIdSpec {
|
|
name: String::from("foo"),
|
|
version: None,
|
|
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
|
kind: None,
|
|
},
|
|
"https://crates.io/foo",
|
|
);
|
|
ok(
|
|
"https://crates.io/foo#1.2.3",
|
|
PackageIdSpec {
|
|
name: String::from("foo"),
|
|
version: Some("1.2.3".parse().unwrap()),
|
|
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
|
kind: None,
|
|
},
|
|
"https://crates.io/foo#1.2.3",
|
|
);
|
|
ok(
|
|
"https://crates.io/foo#1.2",
|
|
PackageIdSpec {
|
|
name: String::from("foo"),
|
|
version: Some("1.2".parse().unwrap()),
|
|
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
|
kind: None,
|
|
},
|
|
"https://crates.io/foo#1.2",
|
|
);
|
|
ok(
|
|
"https://crates.io/foo#bar:1.2.3",
|
|
PackageIdSpec {
|
|
name: String::from("bar"),
|
|
version: Some("1.2.3".parse().unwrap()),
|
|
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
|
kind: None,
|
|
},
|
|
"https://crates.io/foo#bar@1.2.3",
|
|
);
|
|
ok(
|
|
"https://crates.io/foo#bar@1.2.3",
|
|
PackageIdSpec {
|
|
name: String::from("bar"),
|
|
version: Some("1.2.3".parse().unwrap()),
|
|
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
|
kind: None,
|
|
},
|
|
"https://crates.io/foo#bar@1.2.3",
|
|
);
|
|
ok(
|
|
"https://crates.io/foo#bar@1.2",
|
|
PackageIdSpec {
|
|
name: String::from("bar"),
|
|
version: Some("1.2".parse().unwrap()),
|
|
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
|
kind: None,
|
|
},
|
|
"https://crates.io/foo#bar@1.2",
|
|
);
|
|
ok(
|
|
"registry+https://crates.io/foo#bar@1.2",
|
|
PackageIdSpec {
|
|
name: String::from("bar"),
|
|
version: Some("1.2".parse().unwrap()),
|
|
url: Some(Url::parse("https://crates.io/foo").unwrap()),
|
|
kind: Some(SourceKind::Registry),
|
|
},
|
|
"registry+https://crates.io/foo#bar@1.2",
|
|
);
|
|
ok(
|
|
"sparse+https://crates.io/foo#bar@1.2",
|
|
PackageIdSpec {
|
|
name: String::from("bar"),
|
|
version: Some("1.2".parse().unwrap()),
|
|
url: Some(Url::parse("sparse+https://crates.io/foo").unwrap()),
|
|
kind: Some(SourceKind::SparseRegistry),
|
|
},
|
|
"sparse+https://crates.io/foo#bar@1.2",
|
|
);
|
|
ok(
|
|
"foo",
|
|
PackageIdSpec {
|
|
name: String::from("foo"),
|
|
version: None,
|
|
url: None,
|
|
kind: None,
|
|
},
|
|
"foo",
|
|
);
|
|
ok(
|
|
"foo:1.2.3",
|
|
PackageIdSpec {
|
|
name: String::from("foo"),
|
|
version: Some("1.2.3".parse().unwrap()),
|
|
url: None,
|
|
kind: None,
|
|
},
|
|
"foo@1.2.3",
|
|
);
|
|
ok(
|
|
"foo@1.2.3",
|
|
PackageIdSpec {
|
|
name: String::from("foo"),
|
|
version: Some("1.2.3".parse().unwrap()),
|
|
url: None,
|
|
kind: None,
|
|
},
|
|
"foo@1.2.3",
|
|
);
|
|
ok(
|
|
"foo@1.2",
|
|
PackageIdSpec {
|
|
name: String::from("foo"),
|
|
version: Some("1.2".parse().unwrap()),
|
|
url: None,
|
|
kind: None,
|
|
},
|
|
"foo@1.2",
|
|
);
|
|
|
|
// pkgid-spec.md
|
|
ok(
|
|
"regex",
|
|
PackageIdSpec {
|
|
name: String::from("regex"),
|
|
version: None,
|
|
url: None,
|
|
kind: None,
|
|
},
|
|
"regex",
|
|
);
|
|
ok(
|
|
"regex@1.4",
|
|
PackageIdSpec {
|
|
name: String::from("regex"),
|
|
version: Some("1.4".parse().unwrap()),
|
|
url: None,
|
|
kind: None,
|
|
},
|
|
"regex@1.4",
|
|
);
|
|
ok(
|
|
"regex@1.4.3",
|
|
PackageIdSpec {
|
|
name: String::from("regex"),
|
|
version: Some("1.4.3".parse().unwrap()),
|
|
url: None,
|
|
kind: None,
|
|
},
|
|
"regex@1.4.3",
|
|
);
|
|
ok(
|
|
"https://github.com/rust-lang/crates.io-index#regex",
|
|
PackageIdSpec {
|
|
name: String::from("regex"),
|
|
version: None,
|
|
url: Some(Url::parse("https://github.com/rust-lang/crates.io-index").unwrap()),
|
|
kind: None,
|
|
},
|
|
"https://github.com/rust-lang/crates.io-index#regex",
|
|
);
|
|
ok(
|
|
"https://github.com/rust-lang/crates.io-index#regex@1.4.3",
|
|
PackageIdSpec {
|
|
name: String::from("regex"),
|
|
version: Some("1.4.3".parse().unwrap()),
|
|
url: Some(Url::parse("https://github.com/rust-lang/crates.io-index").unwrap()),
|
|
kind: None,
|
|
},
|
|
"https://github.com/rust-lang/crates.io-index#regex@1.4.3",
|
|
);
|
|
ok(
|
|
"sparse+https://github.com/rust-lang/crates.io-index#regex@1.4.3",
|
|
PackageIdSpec {
|
|
name: String::from("regex"),
|
|
version: Some("1.4.3".parse().unwrap()),
|
|
url: Some(
|
|
Url::parse("sparse+https://github.com/rust-lang/crates.io-index").unwrap(),
|
|
),
|
|
kind: Some(SourceKind::SparseRegistry),
|
|
},
|
|
"sparse+https://github.com/rust-lang/crates.io-index#regex@1.4.3",
|
|
);
|
|
ok(
|
|
"https://github.com/rust-lang/cargo#0.52.0",
|
|
PackageIdSpec {
|
|
name: String::from("cargo"),
|
|
version: Some("0.52.0".parse().unwrap()),
|
|
url: Some(Url::parse("https://github.com/rust-lang/cargo").unwrap()),
|
|
kind: None,
|
|
},
|
|
"https://github.com/rust-lang/cargo#0.52.0",
|
|
);
|
|
ok(
|
|
"https://github.com/rust-lang/cargo#cargo-platform@0.1.2",
|
|
PackageIdSpec {
|
|
name: String::from("cargo-platform"),
|
|
version: Some("0.1.2".parse().unwrap()),
|
|
url: Some(Url::parse("https://github.com/rust-lang/cargo").unwrap()),
|
|
kind: None,
|
|
},
|
|
"https://github.com/rust-lang/cargo#cargo-platform@0.1.2",
|
|
);
|
|
ok(
|
|
"ssh://git@github.com/rust-lang/regex.git#regex@1.4.3",
|
|
PackageIdSpec {
|
|
name: String::from("regex"),
|
|
version: Some("1.4.3".parse().unwrap()),
|
|
url: Some(Url::parse("ssh://git@github.com/rust-lang/regex.git").unwrap()),
|
|
kind: None,
|
|
},
|
|
"ssh://git@github.com/rust-lang/regex.git#regex@1.4.3",
|
|
);
|
|
ok(
|
|
"git+ssh://git@github.com/rust-lang/regex.git#regex@1.4.3",
|
|
PackageIdSpec {
|
|
name: String::from("regex"),
|
|
version: Some("1.4.3".parse().unwrap()),
|
|
url: Some(Url::parse("ssh://git@github.com/rust-lang/regex.git").unwrap()),
|
|
kind: Some(SourceKind::Git(GitReference::DefaultBranch)),
|
|
},
|
|
"git+ssh://git@github.com/rust-lang/regex.git#regex@1.4.3",
|
|
);
|
|
ok(
|
|
"git+ssh://git@github.com/rust-lang/regex.git?branch=dev#regex@1.4.3",
|
|
PackageIdSpec {
|
|
name: String::from("regex"),
|
|
version: Some("1.4.3".parse().unwrap()),
|
|
url: Some(Url::parse("ssh://git@github.com/rust-lang/regex.git").unwrap()),
|
|
kind: Some(SourceKind::Git(GitReference::Branch("dev".to_owned()))),
|
|
},
|
|
"git+ssh://git@github.com/rust-lang/regex.git?branch=dev#regex@1.4.3",
|
|
);
|
|
ok(
|
|
"file:///path/to/my/project/foo",
|
|
PackageIdSpec {
|
|
name: String::from("foo"),
|
|
version: None,
|
|
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
|
|
kind: None,
|
|
},
|
|
"file:///path/to/my/project/foo",
|
|
);
|
|
ok(
|
|
"file:///path/to/my/project/foo#1.1.8",
|
|
PackageIdSpec {
|
|
name: String::from("foo"),
|
|
version: Some("1.1.8".parse().unwrap()),
|
|
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
|
|
kind: None,
|
|
},
|
|
"file:///path/to/my/project/foo#1.1.8",
|
|
);
|
|
ok(
|
|
"path+file:///path/to/my/project/foo#1.1.8",
|
|
PackageIdSpec {
|
|
name: String::from("foo"),
|
|
version: Some("1.1.8".parse().unwrap()),
|
|
url: Some(Url::parse("file:///path/to/my/project/foo").unwrap()),
|
|
kind: Some(SourceKind::Path),
|
|
},
|
|
"path+file:///path/to/my/project/foo#1.1.8",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn bad_parsing() {
|
|
macro_rules! err {
|
|
($spec:expr, $expected:pat) => {
|
|
let err = PackageIdSpec::parse($spec).unwrap_err();
|
|
let kind = err.0;
|
|
assert!(
|
|
matches!(kind, $expected),
|
|
"`{}` parse error mismatch, got {kind:?}",
|
|
$spec
|
|
);
|
|
};
|
|
}
|
|
|
|
err!("baz:", ErrorKind::PartialVersion(_));
|
|
err!("baz:*", ErrorKind::PartialVersion(_));
|
|
err!("baz@", ErrorKind::PartialVersion(_));
|
|
err!("baz@*", ErrorKind::PartialVersion(_));
|
|
err!("baz@^1.0", ErrorKind::PartialVersion(_));
|
|
err!("https://baz:1.0", ErrorKind::PartialVersion(_));
|
|
err!("https://#baz:1.0", ErrorKind::PartialVersion(_));
|
|
err!(
|
|
"foobar+https://github.com/rust-lang/crates.io-index",
|
|
ErrorKind::UnsupportedProtocol(_)
|
|
);
|
|
err!(
|
|
"path+https://github.com/rust-lang/crates.io-index",
|
|
ErrorKind::UnsupportedPathPlusScheme(_)
|
|
);
|
|
|
|
// Only `git+` can use `?`
|
|
err!(
|
|
"file:///path/to/my/project/foo?branch=dev",
|
|
ErrorKind::UnexpectedQueryString(_)
|
|
);
|
|
err!(
|
|
"path+file:///path/to/my/project/foo?branch=dev",
|
|
ErrorKind::UnexpectedQueryString(_)
|
|
);
|
|
err!(
|
|
"registry+https://github.com/rust-lang/cargo?branch=dev#0.52.0",
|
|
ErrorKind::UnexpectedQueryString(_)
|
|
);
|
|
err!(
|
|
"sparse+https://github.com/rust-lang/cargo?branch=dev#0.52.0",
|
|
ErrorKind::UnexpectedQueryString(_)
|
|
);
|
|
err!("@1.2.3", ErrorKind::NameValidation(_));
|
|
err!("registry+https://github.com", ErrorKind::NameValidation(_));
|
|
err!("https://crates.io/1foo#1.2.3", ErrorKind::NameValidation(_));
|
|
}
|
|
}
|