mirror of
https://github.com/rust-lang/cargo
synced 2024-08-25 02:25:46 +00:00
Auto merge of #13186 - weihanglo:cargo-util-schemas-error-types, r=epage
refactor: custom error types for `cargo-util-schemas`
This commit is contained in:
commit
a9c749c33e
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -437,13 +437,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cargo-util-schemas"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde-untagged",
|
||||
"serde-value",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"unicode-xid",
|
||||
"url",
|
||||
|
|
|
@ -32,7 +32,7 @@ cargo-platform = { path = "crates/cargo-platform", version = "0.1.4" }
|
|||
cargo-test-macro = { path = "crates/cargo-test-macro" }
|
||||
cargo-test-support = { path = "crates/cargo-test-support" }
|
||||
cargo-util = { version = "0.2.6", path = "crates/cargo-util" }
|
||||
cargo-util-schemas = { version = "0.1.0", path = "crates/cargo-util-schemas" }
|
||||
cargo-util-schemas = { version = "0.2.0", path = "crates/cargo-util-schemas" }
|
||||
cargo_metadata = "0.18.1"
|
||||
clap = "4.4.10"
|
||||
color-print = "0.3.5"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "cargo-util-schemas"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
@ -9,11 +9,11 @@ repository.workspace = true
|
|||
description = "Deserialization schemas for Cargo"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
semver.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde-untagged.workspace = true
|
||||
serde-value.workspace = true
|
||||
thiserror.workspace = true
|
||||
toml.workspace = true
|
||||
unicode-xid.workspace = true
|
||||
url.workspace = true
|
||||
|
|
|
@ -3,6 +3,8 @@ mod partial_version;
|
|||
mod source_kind;
|
||||
|
||||
pub use package_id_spec::PackageIdSpec;
|
||||
pub use package_id_spec::PackageIdSpecError;
|
||||
pub use partial_version::PartialVersion;
|
||||
pub use partial_version::PartialVersionError;
|
||||
pub use source_kind::GitReference;
|
||||
pub use source_kind::SourceKind;
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
use std::fmt;
|
||||
|
||||
use anyhow::bail;
|
||||
use anyhow::Result;
|
||||
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:
|
||||
///
|
||||
|
@ -83,12 +85,11 @@ impl PackageIdSpec {
|
|||
if abs.exists() {
|
||||
let maybe_url = Url::from_file_path(abs)
|
||||
.map_or_else(|_| "a file:// URL".to_string(), |url| url.to_string());
|
||||
bail!(
|
||||
"package ID specification `{}` looks like a file path, \
|
||||
maybe try {}",
|
||||
spec,
|
||||
maybe_url
|
||||
);
|
||||
return Err(ErrorKind::MaybeFilePath {
|
||||
spec: spec.into(),
|
||||
maybe_url,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
let mut parts = spec.splitn(2, [':', '@']);
|
||||
|
@ -119,14 +120,14 @@ impl PackageIdSpec {
|
|||
}
|
||||
"registry" => {
|
||||
if url.query().is_some() {
|
||||
bail!("cannot have a query string in a pkgid: {url}")
|
||||
return Err(ErrorKind::UnexpectedQueryString(url).into());
|
||||
}
|
||||
kind = Some(SourceKind::Registry);
|
||||
url = strip_url_protocol(&url);
|
||||
}
|
||||
"sparse" => {
|
||||
if url.query().is_some() {
|
||||
bail!("cannot have a query string in a pkgid: {url}")
|
||||
return Err(ErrorKind::UnexpectedQueryString(url).into());
|
||||
}
|
||||
kind = Some(SourceKind::SparseRegistry);
|
||||
// Leave `sparse` as part of URL, see `SourceId::new`
|
||||
|
@ -134,19 +135,19 @@ impl PackageIdSpec {
|
|||
}
|
||||
"path" => {
|
||||
if url.query().is_some() {
|
||||
bail!("cannot have a query string in a pkgid: {url}")
|
||||
return Err(ErrorKind::UnexpectedQueryString(url).into());
|
||||
}
|
||||
if scheme != "file" {
|
||||
anyhow::bail!("`path+{scheme}` is unsupported; `path+file` and `file` schemes are supported");
|
||||
return Err(ErrorKind::UnsupportedPathPlusScheme(scheme.into()).into());
|
||||
}
|
||||
kind = Some(SourceKind::Path);
|
||||
url = strip_url_protocol(&url);
|
||||
}
|
||||
kind => anyhow::bail!("unsupported source protocol: {kind}"),
|
||||
kind => return Err(ErrorKind::UnsupportedProtocol(kind.into()).into()),
|
||||
}
|
||||
} else {
|
||||
if url.query().is_some() {
|
||||
bail!("cannot have a query string in a pkgid: {url}")
|
||||
return Err(ErrorKind::UnexpectedQueryString(url).into());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,16 +155,9 @@ impl PackageIdSpec {
|
|||
url.set_fragment(None);
|
||||
|
||||
let (name, version) = {
|
||||
let mut path = url
|
||||
.path_segments()
|
||||
.ok_or_else(|| anyhow::format_err!("pkgid urls must have a path: {}", url))?;
|
||||
let path_name = path.next_back().ok_or_else(|| {
|
||||
anyhow::format_err!(
|
||||
"pkgid urls must have at least one path \
|
||||
component: {}",
|
||||
url
|
||||
)
|
||||
})?;
|
||||
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)) => {
|
||||
|
@ -259,7 +253,7 @@ impl fmt::Display for PackageIdSpec {
|
|||
}
|
||||
|
||||
impl ser::Serialize for PackageIdSpec {
|
||||
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
|
||||
fn serialize<S>(&self, s: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: ser::Serializer,
|
||||
{
|
||||
|
@ -268,7 +262,7 @@ impl ser::Serialize for PackageIdSpec {
|
|||
}
|
||||
|
||||
impl<'de> de::Deserialize<'de> for PackageIdSpec {
|
||||
fn deserialize<D>(d: D) -> Result<PackageIdSpec, D::Error>
|
||||
fn deserialize<D>(d: D) -> std::result::Result<PackageIdSpec, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
|
@ -277,6 +271,49 @@ impl<'de> de::Deserialize<'de> for PackageIdSpec {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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::PackageIdSpec;
|
||||
|
|
|
@ -80,11 +80,11 @@ impl From<semver::Version> for PartialVersion {
|
|||
}
|
||||
|
||||
impl std::str::FromStr for PartialVersion {
|
||||
type Err = anyhow::Error;
|
||||
type Err = PartialVersionError;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
if is_req(value) {
|
||||
anyhow::bail!("unexpected version requirement, expected a version like \"1.32\"")
|
||||
return Err(ErrorKind::VersionReq.into());
|
||||
}
|
||||
match semver::Version::parse(value) {
|
||||
Ok(ver) => Ok(ver.into()),
|
||||
|
@ -92,15 +92,9 @@ impl std::str::FromStr for PartialVersion {
|
|||
// HACK: Leverage `VersionReq` for partial version parsing
|
||||
let mut version_req = match semver::VersionReq::parse(value) {
|
||||
Ok(req) => req,
|
||||
Err(_) if value.contains('-') => {
|
||||
anyhow::bail!(
|
||||
"unexpected prerelease field, expected a version like \"1.32\""
|
||||
)
|
||||
}
|
||||
Err(_) if value.contains('+') => {
|
||||
anyhow::bail!("unexpected build field, expected a version like \"1.32\"")
|
||||
}
|
||||
Err(_) => anyhow::bail!("expected a version like \"1.32\""),
|
||||
Err(_) if value.contains('-') => return Err(ErrorKind::Prerelease.into()),
|
||||
Err(_) if value.contains('+') => return Err(ErrorKind::BuildMetadata.into()),
|
||||
Err(_) => return Err(ErrorKind::Unexpected.into()),
|
||||
};
|
||||
assert_eq!(version_req.comparators.len(), 1, "guaranteed by is_req");
|
||||
let comp = version_req.comparators.pop().unwrap();
|
||||
|
@ -163,6 +157,28 @@ impl<'de> serde::Deserialize<'de> for PartialVersion {
|
|||
}
|
||||
}
|
||||
|
||||
/// Error parsing a [`PartialVersion`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct PartialVersionError(#[from] ErrorKind);
|
||||
|
||||
/// Non-public error kind for [`PartialVersionError`].
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum ErrorKind {
|
||||
#[error("unexpected version requirement, expected a version like \"1.32\"")]
|
||||
VersionReq,
|
||||
|
||||
#[error("unexpected prerelease field, expected a version like \"1.32\"")]
|
||||
Prerelease,
|
||||
|
||||
#[error("unexpected build field, expected a version like \"1.32\"")]
|
||||
BuildMetadata,
|
||||
|
||||
#[error("expected a version like \"1.32\"")]
|
||||
Unexpected,
|
||||
}
|
||||
|
||||
fn is_req(value: &str) -> bool {
|
||||
let Some(first) = value.chars().next() else {
|
||||
return false;
|
||||
|
|
|
@ -10,7 +10,6 @@ use std::fmt::{self, Display, Write};
|
|||
use std::path::PathBuf;
|
||||
use std::str;
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::de::{self, IntoDeserializer as _, Unexpected};
|
||||
use serde::ser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -18,8 +17,11 @@ use serde_untagged::UntaggedEnumVisitor;
|
|||
|
||||
use crate::core::PackageIdSpec;
|
||||
use crate::core::PartialVersion;
|
||||
use crate::core::PartialVersionError;
|
||||
use crate::restricted_names;
|
||||
|
||||
pub use crate::restricted_names::NameValidationError;
|
||||
|
||||
/// This type is used to deserialize `Cargo.toml` files.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
|
@ -1144,7 +1146,7 @@ macro_rules! str_newtype {
|
|||
}
|
||||
|
||||
impl<'a> std::str::FromStr for $name<String> {
|
||||
type Err = anyhow::Error;
|
||||
type Err = restricted_names::NameValidationError;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
Self::new(value.to_owned())
|
||||
|
@ -1173,8 +1175,8 @@ str_newtype!(PackageName);
|
|||
|
||||
impl<T: AsRef<str>> PackageName<T> {
|
||||
/// Validated package name
|
||||
pub fn new(name: T) -> Result<Self> {
|
||||
restricted_names::validate_package_name(name.as_ref(), "package name", "")?;
|
||||
pub fn new(name: T) -> Result<Self, NameValidationError> {
|
||||
restricted_names::validate_package_name(name.as_ref(), "package name")?;
|
||||
Ok(Self(name))
|
||||
}
|
||||
}
|
||||
|
@ -1195,8 +1197,8 @@ str_newtype!(RegistryName);
|
|||
|
||||
impl<T: AsRef<str>> RegistryName<T> {
|
||||
/// Validated registry name
|
||||
pub fn new(name: T) -> Result<Self> {
|
||||
restricted_names::validate_package_name(name.as_ref(), "registry name", "")?;
|
||||
pub fn new(name: T) -> Result<Self, NameValidationError> {
|
||||
restricted_names::validate_package_name(name.as_ref(), "registry name")?;
|
||||
Ok(Self(name))
|
||||
}
|
||||
}
|
||||
|
@ -1205,7 +1207,7 @@ str_newtype!(ProfileName);
|
|||
|
||||
impl<T: AsRef<str>> ProfileName<T> {
|
||||
/// Validated profile name
|
||||
pub fn new(name: T) -> Result<Self> {
|
||||
pub fn new(name: T) -> Result<Self, NameValidationError> {
|
||||
restricted_names::validate_profile_name(name.as_ref())?;
|
||||
Ok(Self(name))
|
||||
}
|
||||
|
@ -1215,7 +1217,7 @@ str_newtype!(FeatureName);
|
|||
|
||||
impl<T: AsRef<str>> FeatureName<T> {
|
||||
/// Validated feature name
|
||||
pub fn new(name: T) -> Result<Self> {
|
||||
pub fn new(name: T) -> Result<Self, NameValidationError> {
|
||||
restricted_names::validate_feature_name(name.as_ref())?;
|
||||
Ok(Self(name))
|
||||
}
|
||||
|
@ -1334,15 +1336,16 @@ impl std::ops::Deref for RustVersion {
|
|||
}
|
||||
|
||||
impl std::str::FromStr for RustVersion {
|
||||
type Err = anyhow::Error;
|
||||
type Err = RustVersionError;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
let partial = value.parse::<PartialVersion>()?;
|
||||
let partial = value.parse::<PartialVersion>();
|
||||
let partial = partial.map_err(RustVersionErrorKind::PartialVersion)?;
|
||||
if partial.pre.is_some() {
|
||||
anyhow::bail!("unexpected prerelease field, expected a version like \"1.32\"")
|
||||
return Err(RustVersionErrorKind::Prerelease.into());
|
||||
}
|
||||
if partial.build.is_some() {
|
||||
anyhow::bail!("unexpected prerelease field, expected a version like \"1.32\"")
|
||||
return Err(RustVersionErrorKind::BuildMetadata.into());
|
||||
}
|
||||
Ok(Self(partial))
|
||||
}
|
||||
|
@ -1366,6 +1369,25 @@ impl Display for RustVersion {
|
|||
}
|
||||
}
|
||||
|
||||
/// Error parsing a [`RustVersion`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct RustVersionError(#[from] RustVersionErrorKind);
|
||||
|
||||
/// Non-public error kind for [`RustVersionError`].
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum RustVersionErrorKind {
|
||||
#[error("unexpected prerelease field, expected a version like \"1.32\"")]
|
||||
Prerelease,
|
||||
|
||||
#[error("unexpected build field, expected a version like \"1.32\"")]
|
||||
BuildMetadata,
|
||||
|
||||
#[error(transparent)]
|
||||
PartialVersion(#[from] PartialVersionError),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct InvalidCargoFeatures {}
|
||||
|
||||
|
|
|
@ -1,7 +1,37 @@
|
|||
//! Helpers for validating and checking names like package and crate names.
|
||||
|
||||
use anyhow::bail;
|
||||
use anyhow::Result;
|
||||
type Result<T> = std::result::Result<T, NameValidationError>;
|
||||
|
||||
/// Error validating names in Cargo.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct NameValidationError(#[from] ErrorKind);
|
||||
|
||||
/// Non-public error kind for [`NameValidationError`].
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum ErrorKind {
|
||||
#[error("{0} cannot be empty")]
|
||||
Empty(&'static str),
|
||||
|
||||
#[error("invalid character `{ch}` in {what}: `{name}`, {reason}")]
|
||||
InvalidCharacter {
|
||||
ch: char,
|
||||
what: &'static str,
|
||||
name: String,
|
||||
reason: &'static str,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"profile name `{name}` is reserved\n{help}\n\
|
||||
See https://doc.rust-lang.org/cargo/reference/profiles.html \
|
||||
for more on configuring profiles."
|
||||
)]
|
||||
ProfileNameReservedKeyword { name: String, help: &'static str },
|
||||
|
||||
#[error("feature named `{0}` is not allowed to start with `dep:`")]
|
||||
FeatureNameStartsWithDepColon(String),
|
||||
}
|
||||
|
||||
/// Check the base requirements for a package name.
|
||||
///
|
||||
|
@ -9,53 +39,51 @@ use anyhow::Result;
|
|||
/// level of sanity. Note that package names have other restrictions
|
||||
/// elsewhere. `cargo new` has a few restrictions, such as checking for
|
||||
/// reserved names. crates.io has even more restrictions.
|
||||
pub fn validate_package_name(name: &str, what: &str, help: &str) -> Result<()> {
|
||||
pub(crate) fn validate_package_name(name: &str, what: &'static str) -> Result<()> {
|
||||
if name.is_empty() {
|
||||
bail!("{what} cannot be empty");
|
||||
return Err(ErrorKind::Empty(what).into());
|
||||
}
|
||||
|
||||
let mut chars = name.chars();
|
||||
if let Some(ch) = chars.next() {
|
||||
if ch.is_digit(10) {
|
||||
// A specific error for a potentially common case.
|
||||
bail!(
|
||||
"the name `{}` cannot be used as a {}, \
|
||||
the name cannot start with a digit{}",
|
||||
name,
|
||||
what,
|
||||
help
|
||||
);
|
||||
}
|
||||
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_') {
|
||||
bail!(
|
||||
"invalid character `{}` in {}: `{}`, \
|
||||
the first character must be a Unicode XID start character \
|
||||
(most letters or `_`){}",
|
||||
return Err(ErrorKind::InvalidCharacter {
|
||||
ch,
|
||||
what,
|
||||
name,
|
||||
help
|
||||
);
|
||||
name: name.into(),
|
||||
reason: "the name cannot start with a digit",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_') {
|
||||
return Err(ErrorKind::InvalidCharacter {
|
||||
ch,
|
||||
what,
|
||||
name: name.into(),
|
||||
reason: "the first character must be a Unicode XID start character \
|
||||
(most letters or `_`)",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
for ch in chars {
|
||||
if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-') {
|
||||
bail!(
|
||||
"invalid character `{}` in {}: `{}`, \
|
||||
characters must be Unicode XID characters \
|
||||
(numbers, `-`, `_`, or most letters){}",
|
||||
return Err(ErrorKind::InvalidCharacter {
|
||||
ch,
|
||||
what,
|
||||
name,
|
||||
help
|
||||
);
|
||||
name: name.into(),
|
||||
reason: "characters must be Unicode XID characters \
|
||||
(numbers, `-`, `_`, or most letters)",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure a package name is [valid][validate_package_name]
|
||||
pub fn sanitize_package_name(name: &str, placeholder: char) -> String {
|
||||
pub(crate) fn sanitize_package_name(name: &str, placeholder: char) -> String {
|
||||
let mut slug = String::new();
|
||||
let mut chars = name.chars();
|
||||
while let Some(ch) = chars.next() {
|
||||
|
@ -78,42 +106,36 @@ pub fn sanitize_package_name(name: &str, placeholder: char) -> String {
|
|||
}
|
||||
|
||||
/// Validate dir-names and profile names according to RFC 2678.
|
||||
pub fn validate_profile_name(name: &str) -> Result<()> {
|
||||
pub(crate) fn validate_profile_name(name: &str) -> Result<()> {
|
||||
if let Some(ch) = name
|
||||
.chars()
|
||||
.find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-')
|
||||
{
|
||||
bail!(
|
||||
"invalid character `{}` in profile name `{}`\n\
|
||||
Allowed characters are letters, numbers, underscore, and hyphen.",
|
||||
return Err(ErrorKind::InvalidCharacter {
|
||||
ch,
|
||||
name
|
||||
);
|
||||
what: "profile name",
|
||||
name: name.into(),
|
||||
reason: "allowed characters are letters, numbers, underscore, and hyphen",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
const SEE_DOCS: &str = "See https://doc.rust-lang.org/cargo/reference/profiles.html \
|
||||
for more on configuring profiles.";
|
||||
|
||||
let lower_name = name.to_lowercase();
|
||||
if lower_name == "debug" {
|
||||
bail!(
|
||||
"profile name `{}` is reserved\n\
|
||||
To configure the default development profile, use the name `dev` \
|
||||
as in [profile.dev]\n\
|
||||
{}",
|
||||
name,
|
||||
SEE_DOCS
|
||||
);
|
||||
return Err(ErrorKind::ProfileNameReservedKeyword {
|
||||
name: name.into(),
|
||||
help: "To configure the default development profile, \
|
||||
use the name `dev` as in [profile.dev]",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
if lower_name == "build-override" {
|
||||
bail!(
|
||||
"profile name `{}` is reserved\n\
|
||||
To configure build dependency settings, use [profile.dev.build-override] \
|
||||
and [profile.release.build-override]\n\
|
||||
{}",
|
||||
name,
|
||||
SEE_DOCS
|
||||
);
|
||||
return Err(ErrorKind::ProfileNameReservedKeyword {
|
||||
name: name.into(),
|
||||
help: "To configure build dependency settings, use [profile.dev.build-override] \
|
||||
and [profile.release.build-override]",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
// These are some arbitrary reservations. We have no plans to use
|
||||
|
@ -144,46 +166,57 @@ pub fn validate_profile_name(name: &str) -> Result<()> {
|
|||
| "uninstall"
|
||||
) || lower_name.starts_with("cargo")
|
||||
{
|
||||
bail!(
|
||||
"profile name `{}` is reserved\n\
|
||||
Please choose a different name.\n\
|
||||
{}",
|
||||
name,
|
||||
SEE_DOCS
|
||||
);
|
||||
return Err(ErrorKind::ProfileNameReservedKeyword {
|
||||
name: name.into(),
|
||||
help: "Please choose a different name.",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_feature_name(name: &str) -> Result<()> {
|
||||
pub(crate) fn validate_feature_name(name: &str) -> Result<()> {
|
||||
let what = "feature name";
|
||||
if name.is_empty() {
|
||||
bail!("feature name cannot be empty");
|
||||
return Err(ErrorKind::Empty(what).into());
|
||||
}
|
||||
|
||||
if name.starts_with("dep:") {
|
||||
bail!("feature named `{name}` is not allowed to start with `dep:`",);
|
||||
return Err(ErrorKind::FeatureNameStartsWithDepColon(name.into()).into());
|
||||
}
|
||||
if name.contains('/') {
|
||||
bail!("feature named `{name}` is not allowed to contain slashes",);
|
||||
return Err(ErrorKind::InvalidCharacter {
|
||||
ch: '/',
|
||||
what,
|
||||
name: name.into(),
|
||||
reason: "feature name is not allowed to contain slashes",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
let mut chars = name.chars();
|
||||
if let Some(ch) = chars.next() {
|
||||
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_digit(10)) {
|
||||
bail!(
|
||||
"invalid character `{ch}` in feature `{name}`, \
|
||||
the first character must be a Unicode XID start character or digit \
|
||||
(most letters or `_` or `0` to `9`)",
|
||||
);
|
||||
return Err(ErrorKind::InvalidCharacter {
|
||||
ch,
|
||||
what,
|
||||
name: name.into(),
|
||||
reason: "the first character must be a Unicode XID start character or digit \
|
||||
(most letters or `_` or `0` to `9`)",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
for ch in chars {
|
||||
if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-' || ch == '+' || ch == '.') {
|
||||
bail!(
|
||||
"invalid character `{ch}` in feature `{name}`, \
|
||||
characters must be Unicode XID characters, '-', `+`, or `.` \
|
||||
(numbers, `+`, `-`, `_`, `.`, or most letters)",
|
||||
);
|
||||
return Err(ErrorKind::InvalidCharacter {
|
||||
ch,
|
||||
what,
|
||||
name: name.into(),
|
||||
reason: "characters must be Unicode XID characters, '-', `+`, or `.` \
|
||||
(numbers, `+`, `-`, `_`, `.`, or most letters)",
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -72,7 +72,7 @@ impl Packages {
|
|||
let mut specs = packages
|
||||
.iter()
|
||||
.map(|p| PackageIdSpec::parse(p))
|
||||
.collect::<CargoResult<Vec<_>>>()?;
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
if !patterns.is_empty() {
|
||||
let matched_pkgs = ws
|
||||
.members()
|
||||
|
|
|
@ -186,7 +186,7 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()
|
|||
opts.invert
|
||||
.iter()
|
||||
.map(|p| PackageIdSpec::parse(p))
|
||||
.collect::<CargoResult<Vec<PackageIdSpec>>>()?
|
||||
.collect::<Result<Vec<PackageIdSpec>, _>>()?
|
||||
};
|
||||
let root_ids = ws_resolve.targeted_resolve.specs_to_ids(&root_specs)?;
|
||||
let root_indexes = graph.indexes_from_ids(&root_ids);
|
||||
|
@ -207,7 +207,7 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()
|
|||
let pkgs_to_prune = opts
|
||||
.pkgs_to_prune
|
||||
.iter()
|
||||
.map(|p| PackageIdSpec::parse(p))
|
||||
.map(|p| PackageIdSpec::parse(p).map_err(Into::into))
|
||||
.map(|r| {
|
||||
// Provide an error message if pkgid is not within the resolved
|
||||
// dependencies graph.
|
||||
|
|
|
@ -2063,7 +2063,7 @@ Caused by:
|
|||
|
|
||||
8 | \"+foo\" = []
|
||||
| ^^^^^^
|
||||
invalid character `+` in feature `+foo`, \
|
||||
invalid character `+` in feature name: `+foo`, \
|
||||
the first character must be a Unicode XID start character or digit \
|
||||
(most letters or `_` or `0` to `9`)
|
||||
",
|
||||
|
@ -2094,7 +2094,7 @@ Caused by:
|
|||
|
|
||||
8 | \"a&b\" = []
|
||||
| ^^^^^
|
||||
invalid character `&` in feature `a&b`, \
|
||||
invalid character `&` in feature name: `a&b`, \
|
||||
characters must be Unicode XID characters, '-', `+`, or `.` \
|
||||
(numbers, `+`, `-`, `_`, `.`, or most letters)
|
||||
",
|
||||
|
@ -2131,7 +2131,7 @@ Caused by:
|
|||
|
|
||||
7 | \"foo/bar\" = []
|
||||
| ^^^^^^^^^
|
||||
feature named `foo/bar` is not allowed to contain slashes
|
||||
invalid character `/` in feature name: `foo/bar`, feature name is not allowed to contain slashes
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
|
|
@ -348,8 +348,7 @@ fn explicit_invalid_name_not_suggested() {
|
|||
.with_status(101)
|
||||
.with_stderr(
|
||||
"\
|
||||
[ERROR] the name `10-invalid` cannot be used as a package name, \
|
||||
the name cannot start with a digit\n\
|
||||
[ERROR] invalid character `1` in package name: `10-invalid`, the name cannot start with a digit
|
||||
If you need a binary with the name \"10-invalid\", use a valid package name, \
|
||||
and set the binary name to be different from the package. \
|
||||
This can be done by setting the binary filename to `src/bin/10-invalid.rs` \
|
||||
|
|
|
@ -90,8 +90,7 @@ Caused by:
|
|||
|
|
||||
7 | [profile.'.release-lto']
|
||||
| ^^^^^^^^^^^^^^
|
||||
invalid character `.` in profile name `.release-lto`
|
||||
Allowed characters are letters, numbers, underscore, and hyphen.
|
||||
invalid character `.` in profile name: `.release-lto`, allowed characters are letters, numbers, underscore, and hyphen
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
|
Loading…
Reference in a new issue