Skip to content

Commit a3267bf

Browse files
committed
refactor(util-schemas): error type for restricted_names
1 parent 90017c0 commit a3267bf

File tree

5 files changed

+100
-81
lines changed

5 files changed

+100
-81
lines changed

crates/cargo-util-schemas/src/manifest.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ use std::fmt::{self, Display, Write};
1010
use std::path::PathBuf;
1111
use std::str;
1212

13-
use anyhow::Result;
1413
use serde::de::{self, IntoDeserializer as _, Unexpected};
1514
use serde::ser;
1615
use serde::{Deserialize, Serialize};
@@ -21,6 +20,8 @@ use crate::core::PartialVersion;
2120
use crate::core::PartialVersionError;
2221
use crate::restricted_names;
2322

23+
pub use crate::restricted_names::NameValidationError;
24+
2425
/// This type is used to deserialize `Cargo.toml` files.
2526
#[derive(Debug, Deserialize, Serialize)]
2627
#[serde(rename_all = "kebab-case")]
@@ -1145,7 +1146,7 @@ macro_rules! str_newtype {
11451146
}
11461147

11471148
impl<'a> std::str::FromStr for $name<String> {
1148-
type Err = anyhow::Error;
1149+
type Err = restricted_names::NameValidationError;
11491150

11501151
fn from_str(value: &str) -> Result<Self, Self::Err> {
11511152
Self::new(value.to_owned())
@@ -1174,7 +1175,7 @@ str_newtype!(PackageName);
11741175

11751176
impl<T: AsRef<str>> PackageName<T> {
11761177
/// Validated package name
1177-
pub fn new(name: T) -> Result<Self> {
1178+
pub fn new(name: T) -> Result<Self, NameValidationError> {
11781179
restricted_names::validate_package_name(name.as_ref(), "package name", "")?;
11791180
Ok(Self(name))
11801181
}
@@ -1196,7 +1197,7 @@ str_newtype!(RegistryName);
11961197

11971198
impl<T: AsRef<str>> RegistryName<T> {
11981199
/// Validated registry name
1199-
pub fn new(name: T) -> Result<Self> {
1200+
pub fn new(name: T) -> Result<Self, NameValidationError> {
12001201
restricted_names::validate_package_name(name.as_ref(), "registry name", "")?;
12011202
Ok(Self(name))
12021203
}
@@ -1206,7 +1207,7 @@ str_newtype!(ProfileName);
12061207

12071208
impl<T: AsRef<str>> ProfileName<T> {
12081209
/// Validated profile name
1209-
pub fn new(name: T) -> Result<Self> {
1210+
pub fn new(name: T) -> Result<Self, NameValidationError> {
12101211
restricted_names::validate_profile_name(name.as_ref())?;
12111212
Ok(Self(name))
12121213
}
@@ -1216,7 +1217,7 @@ str_newtype!(FeatureName);
12161217

12171218
impl<T: AsRef<str>> FeatureName<T> {
12181219
/// Validated feature name
1219-
pub fn new(name: T) -> Result<Self> {
1220+
pub fn new(name: T) -> Result<Self, NameValidationError> {
12201221
restricted_names::validate_feature_name(name.as_ref())?;
12211222
Ok(Self(name))
12221223
}

crates/cargo-util-schemas/src/restricted_names.rs

Lines changed: 88 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,74 @@
11
//! Helpers for validating and checking names like package and crate names.
22
3-
use anyhow::bail;
4-
use anyhow::Result;
3+
type Result<T> = std::result::Result<T, NameValidationError>;
4+
5+
/// Error validating names in Cargo.
6+
#[non_exhaustive]
7+
#[derive(Debug, thiserror::Error)]
8+
pub enum NameValidationError {
9+
#[error("{0} cannot be empty")]
10+
Empty(&'static str),
11+
12+
#[error("invalid character `{ch}` in {what}: `{name}`, {reason}")]
13+
InvalidCharacter {
14+
ch: char,
15+
what: &'static str,
16+
name: String,
17+
reason: &'static str,
18+
},
19+
20+
#[error(
21+
"profile name `{name}` is reserved\n{help}\n\
22+
See https://doc.rust-lang.org/cargo/reference/profiles.html \
23+
for more on configuring profiles."
24+
)]
25+
ProfileNameReservedKeyword { name: String, help: &'static str },
26+
27+
#[error("feature named `{0}` is not allowed to start with `dep:`")]
28+
FeatureNameStartsWithDepColon(String),
29+
}
530

631
/// Check the base requirements for a package name.
732
///
833
/// This can be used for other things than package names, to enforce some
934
/// level of sanity. Note that package names have other restrictions
1035
/// elsewhere. `cargo new` has a few restrictions, such as checking for
1136
/// reserved names. crates.io has even more restrictions.
12-
pub fn validate_package_name(name: &str, what: &str, help: &str) -> Result<()> {
37+
pub fn validate_package_name(name: &str, what: &'static str, help: &str) -> Result<()> {
1338
if name.is_empty() {
14-
bail!("{what} cannot be empty");
39+
return Err(NameValidationError::Empty(what));
1540
}
1641

1742
let mut chars = name.chars();
1843
if let Some(ch) = chars.next() {
1944
if ch.is_digit(10) {
2045
// A specific error for a potentially common case.
21-
bail!(
22-
"the name `{}` cannot be used as a {}, \
23-
the name cannot start with a digit{}",
24-
name,
46+
return Err(NameValidationError::InvalidCharacter {
47+
ch,
2548
what,
26-
help
27-
);
49+
name: name.into(),
50+
reason: "the name cannot start with a digit",
51+
});
2852
}
2953
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_') {
30-
bail!(
31-
"invalid character `{}` in {}: `{}`, \
32-
the first character must be a Unicode XID start character \
33-
(most letters or `_`){}",
54+
return Err(NameValidationError::InvalidCharacter {
3455
ch,
3556
what,
36-
name,
37-
help
38-
);
57+
name: name.into(),
58+
reason: "the first character must be a Unicode XID start character \
59+
(most letters or `_`)",
60+
});
3961
}
4062
}
4163
for ch in chars {
4264
if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-') {
43-
bail!(
44-
"invalid character `{}` in {}: `{}`, \
45-
characters must be Unicode XID characters \
46-
(numbers, `-`, `_`, or most letters){}",
65+
return Err(NameValidationError::InvalidCharacter {
4766
ch,
4867
what,
49-
name,
50-
help
51-
);
68+
name: name.into(),
69+
reason: "characters must be Unicode XID characters \
70+
(numbers, `-`, `_`, or most letters)",
71+
});
5272
}
5373
}
5474
Ok(())
@@ -83,37 +103,28 @@ pub fn validate_profile_name(name: &str) -> Result<()> {
83103
.chars()
84104
.find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-')
85105
{
86-
bail!(
87-
"invalid character `{}` in profile name `{}`\n\
88-
Allowed characters are letters, numbers, underscore, and hyphen.",
106+
return Err(NameValidationError::InvalidCharacter {
89107
ch,
90-
name
91-
);
108+
what: "profile name",
109+
name: name.into(),
110+
reason: "allowed characters are letters, numbers, underscore, and hyphen",
111+
});
92112
}
93113

94-
const SEE_DOCS: &str = "See https://doc.rust-lang.org/cargo/reference/profiles.html \
95-
for more on configuring profiles.";
96-
97114
let lower_name = name.to_lowercase();
98115
if lower_name == "debug" {
99-
bail!(
100-
"profile name `{}` is reserved\n\
101-
To configure the default development profile, use the name `dev` \
102-
as in [profile.dev]\n\
103-
{}",
104-
name,
105-
SEE_DOCS
106-
);
116+
return Err(NameValidationError::ProfileNameReservedKeyword {
117+
name: name.into(),
118+
help: "To configure the default development profile, \
119+
use the name `dev` as in [profile.dev]",
120+
});
107121
}
108122
if lower_name == "build-override" {
109-
bail!(
110-
"profile name `{}` is reserved\n\
111-
To configure build dependency settings, use [profile.dev.build-override] \
112-
and [profile.release.build-override]\n\
113-
{}",
114-
name,
115-
SEE_DOCS
116-
);
123+
return Err(NameValidationError::ProfileNameReservedKeyword {
124+
name: name.into(),
125+
help: "To configure build dependency settings, use [profile.dev.build-override] \
126+
and [profile.release.build-override]",
127+
});
117128
}
118129

119130
// These are some arbitrary reservations. We have no plans to use
@@ -144,46 +155,55 @@ pub fn validate_profile_name(name: &str) -> Result<()> {
144155
| "uninstall"
145156
) || lower_name.starts_with("cargo")
146157
{
147-
bail!(
148-
"profile name `{}` is reserved\n\
149-
Please choose a different name.\n\
150-
{}",
151-
name,
152-
SEE_DOCS
153-
);
158+
return Err(NameValidationError::ProfileNameReservedKeyword {
159+
name: name.into(),
160+
help: "Please choose a different name.",
161+
});
154162
}
155163

156164
Ok(())
157165
}
158166

159167
pub fn validate_feature_name(name: &str) -> Result<()> {
168+
let what = "feature name";
160169
if name.is_empty() {
161-
bail!("feature name cannot be empty");
170+
return Err(NameValidationError::Empty(what));
162171
}
163172

164173
if name.starts_with("dep:") {
165-
bail!("feature named `{name}` is not allowed to start with `dep:`",);
174+
return Err(NameValidationError::FeatureNameStartsWithDepColon(
175+
name.into(),
176+
));
166177
}
167178
if name.contains('/') {
168-
bail!("feature named `{name}` is not allowed to contain slashes",);
179+
return Err(NameValidationError::InvalidCharacter {
180+
ch: '/',
181+
what,
182+
name: name.into(),
183+
reason: "feature name is not allowed to contain slashes",
184+
});
169185
}
170186
let mut chars = name.chars();
171187
if let Some(ch) = chars.next() {
172188
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_digit(10)) {
173-
bail!(
174-
"invalid character `{ch}` in feature `{name}`, \
175-
the first character must be a Unicode XID start character or digit \
176-
(most letters or `_` or `0` to `9`)",
177-
);
189+
return Err(NameValidationError::InvalidCharacter {
190+
ch,
191+
what,
192+
name: name.into(),
193+
reason: "the first character must be a Unicode XID start character or digit \
194+
(most letters or `_` or `0` to `9`)",
195+
});
178196
}
179197
}
180198
for ch in chars {
181199
if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-' || ch == '+' || ch == '.') {
182-
bail!(
183-
"invalid character `{ch}` in feature `{name}`, \
184-
characters must be Unicode XID characters, '-', `+`, or `.` \
185-
(numbers, `+`, `-`, `_`, `.`, or most letters)",
186-
);
200+
return Err(NameValidationError::InvalidCharacter {
201+
ch,
202+
what,
203+
name: name.into(),
204+
reason: "characters must be Unicode XID characters, '-', `+`, or `.` \
205+
(numbers, `+`, `-`, `_`, `.`, or most letters)",
206+
});
187207
}
188208
}
189209
Ok(())

tests/testsuite/features.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,7 +2063,7 @@ Caused by:
20632063
|
20642064
8 | \"+foo\" = []
20652065
| ^^^^^^
2066-
invalid character `+` in feature `+foo`, \
2066+
invalid character `+` in feature name: `+foo`, \
20672067
the first character must be a Unicode XID start character or digit \
20682068
(most letters or `_` or `0` to `9`)
20692069
",
@@ -2094,7 +2094,7 @@ Caused by:
20942094
|
20952095
8 | \"a&b\" = []
20962096
| ^^^^^
2097-
invalid character `&` in feature `a&b`, \
2097+
invalid character `&` in feature name: `a&b`, \
20982098
characters must be Unicode XID characters, '-', `+`, or `.` \
20992099
(numbers, `+`, `-`, `_`, `.`, or most letters)
21002100
",
@@ -2131,7 +2131,7 @@ Caused by:
21312131
|
21322132
7 | \"foo/bar\" = []
21332133
| ^^^^^^^^^
2134-
feature named `foo/bar` is not allowed to contain slashes
2134+
invalid character `/` in feature name: `foo/bar`, feature name is not allowed to contain slashes
21352135
",
21362136
)
21372137
.run();

tests/testsuite/new.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,7 @@ fn explicit_invalid_name_not_suggested() {
348348
.with_status(101)
349349
.with_stderr(
350350
"\
351-
[ERROR] the name `10-invalid` cannot be used as a package name, \
352-
the name cannot start with a digit\n\
351+
[ERROR] invalid character `1` in package name: `10-invalid`, the name cannot start with a digit
353352
If you need a binary with the name \"10-invalid\", use a valid package name, \
354353
and set the binary name to be different from the package. \
355354
This can be done by setting the binary filename to `src/bin/10-invalid.rs` \

tests/testsuite/profile_custom.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,7 @@ Caused by:
9090
|
9191
7 | [profile.'.release-lto']
9292
| ^^^^^^^^^^^^^^
93-
invalid character `.` in profile name `.release-lto`
94-
Allowed characters are letters, numbers, underscore, and hyphen.
93+
invalid character `.` in profile name: `.release-lto`, allowed characters are letters, numbers, underscore, and hyphen
9594
",
9695
)
9796
.run();

0 commit comments

Comments
 (0)