Skip to content

Commit 594e2be

Browse files
committed
Auto merge of #13166 - epage:validate, r=weihanglo
refactor: Pull name validation into `util_schemas` ### What does this PR try to resolve? In preparation for #12801, this moves the last dependency on the rest of the `cargo` crate into the future `util_schemas` crate. It does this by refocusing the code from being validation functions to being newtypes. I did not try to thread the newtypes everywhere, that can be an exercise for the future. There are places I didn't put newtypes because it didn't seem worth it (e.g. places needing `StringOrVec`, `ProfileName` not being used in CLI because of the `check` psuedo-profile, etc) The main risk with this is when validation changes according to a nightly feature, like packages-as-namespaces. My thought is that the validation code would be updated for the nightly behavior and then the caller would check if it isn't nightly and fail in that case. This has a side effect of improving the error messages for manifest parsing because we will show more context ### How should we test and review this PR? #13164 should be reviewed first ### Additional information
2 parents 58745cb + cfa9421 commit 594e2be

20 files changed

+456
-297
lines changed

src/cargo/core/summary.rs

Lines changed: 3 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::core::{Dependency, PackageId, SourceId};
22
use crate::util::interning::InternedString;
33
use crate::util::CargoResult;
4+
use crate::util_schemas::manifest::FeatureName;
45
use crate::util_schemas::manifest::RustVersion;
56
use anyhow::bail;
67
use semver::Version;
@@ -49,7 +50,7 @@ impl Summary {
4950
)
5051
}
5152
}
52-
let feature_map = build_feature_map(pkg_id, features, &dependencies)?;
53+
let feature_map = build_feature_map(features, &dependencies)?;
5354
Ok(Summary {
5455
inner: Rc::new(Inner {
5556
package_id: pkg_id,
@@ -140,7 +141,6 @@ impl Hash for Summary {
140141
/// Checks features for errors, bailing out a CargoResult:Err if invalid,
141142
/// and creates FeatureValues for each feature.
142143
fn build_feature_map(
143-
pkg_id: PackageId,
144144
features: &BTreeMap<InternedString, Vec<InternedString>>,
145145
dependencies: &[Dependency],
146146
) -> CargoResult<FeatureMap> {
@@ -191,19 +191,7 @@ fn build_feature_map(
191191

192192
// Validate features are listed properly.
193193
for (feature, fvs) in &map {
194-
if feature.starts_with("dep:") {
195-
bail!(
196-
"feature named `{}` is not allowed to start with `dep:`",
197-
feature
198-
);
199-
}
200-
if feature.contains('/') {
201-
bail!(
202-
"feature named `{}` is not allowed to contain slashes",
203-
feature
204-
);
205-
}
206-
validate_feature_name(pkg_id, feature)?;
194+
FeatureName::new(feature)?;
207195
for fv in fvs {
208196
// Find data for the referenced dependency...
209197
let dep_data = {
@@ -429,68 +417,3 @@ impl fmt::Display for FeatureValue {
429417
}
430418

431419
pub type FeatureMap = BTreeMap<InternedString, Vec<FeatureValue>>;
432-
433-
fn validate_feature_name(pkg_id: PackageId, name: &str) -> CargoResult<()> {
434-
if name.is_empty() {
435-
bail!("feature name cannot be empty");
436-
}
437-
let mut chars = name.chars();
438-
if let Some(ch) = chars.next() {
439-
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_digit(10)) {
440-
bail!(
441-
"invalid character `{}` in feature `{}` in package {}, \
442-
the first character must be a Unicode XID start character or digit \
443-
(most letters or `_` or `0` to `9`)",
444-
ch,
445-
name,
446-
pkg_id
447-
);
448-
}
449-
}
450-
for ch in chars {
451-
if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-' || ch == '+' || ch == '.') {
452-
bail!(
453-
"invalid character `{}` in feature `{}` in package {}, \
454-
characters must be Unicode XID characters, '-', `+`, or `.` \
455-
(numbers, `+`, `-`, `_`, `.`, or most letters)",
456-
ch,
457-
name,
458-
pkg_id
459-
);
460-
}
461-
}
462-
Ok(())
463-
}
464-
465-
#[cfg(test)]
466-
mod tests {
467-
use super::*;
468-
use crate::sources::CRATES_IO_INDEX;
469-
use crate::util::into_url::IntoUrl;
470-
471-
use crate::core::SourceId;
472-
473-
#[test]
474-
fn valid_feature_names() {
475-
let loc = CRATES_IO_INDEX.into_url().unwrap();
476-
let source_id = SourceId::for_registry(&loc).unwrap();
477-
let pkg_id = PackageId::try_new("foo", "1.0.0", source_id).unwrap();
478-
479-
assert!(validate_feature_name(pkg_id, "c++17").is_ok());
480-
assert!(validate_feature_name(pkg_id, "128bit").is_ok());
481-
assert!(validate_feature_name(pkg_id, "_foo").is_ok());
482-
assert!(validate_feature_name(pkg_id, "feat-name").is_ok());
483-
assert!(validate_feature_name(pkg_id, "feat_name").is_ok());
484-
assert!(validate_feature_name(pkg_id, "foo.bar").is_ok());
485-
486-
assert!(validate_feature_name(pkg_id, "+foo").is_err());
487-
assert!(validate_feature_name(pkg_id, "-foo").is_err());
488-
assert!(validate_feature_name(pkg_id, ".foo").is_err());
489-
assert!(validate_feature_name(pkg_id, "foo:bar").is_err());
490-
assert!(validate_feature_name(pkg_id, "foo?").is_err());
491-
assert!(validate_feature_name(pkg_id, "?foo").is_err());
492-
assert!(validate_feature_name(pkg_id, "ⒶⒷⒸ").is_err());
493-
assert!(validate_feature_name(pkg_id, "a¼").is_err());
494-
assert!(validate_feature_name(pkg_id, "").is_err());
495-
}
496-
}

src/cargo/ops/cargo_add/crate_spec.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use anyhow::Context as _;
44

55
use super::Dependency;
66
use crate::util::toml_mut::dependency::RegistrySource;
7-
use crate::util::validate_package_name;
7+
use crate::util_schemas::manifest::PackageName;
88
use crate::CargoResult;
99

1010
/// User-specified crate
@@ -28,7 +28,7 @@ impl CrateSpec {
2828
.map(|(n, v)| (n, Some(v)))
2929
.unwrap_or((pkg_id, None));
3030

31-
validate_package_name(name, "dependency name", "")?;
31+
PackageName::new(name)?;
3232

3333
if let Some(version) = version {
3434
semver::VersionReq::parse(version)

src/cargo/ops/cargo_new.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::util::important_paths::find_root_manifest_for_wd;
44
use crate::util::toml_mut::is_sorted;
55
use crate::util::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo};
66
use crate::util::{restricted_names, Config};
7+
use crate::util_schemas::manifest::PackageName;
78
use anyhow::{anyhow, Context};
89
use cargo_util::paths::{self, write_atomic};
910
use serde::de;
@@ -180,7 +181,7 @@ fn check_name(
180181
};
181182
let bin_help = || {
182183
let mut help = String::from(name_help);
183-
if has_bin {
184+
if has_bin && !name.is_empty() {
184185
help.push_str(&format!(
185186
"\n\
186187
If you need a binary with the name \"{name}\", use a valid package \
@@ -197,7 +198,10 @@ fn check_name(
197198
}
198199
help
199200
};
200-
restricted_names::validate_package_name(name, "package name", &bin_help())?;
201+
PackageName::new(name).map_err(|err| {
202+
let help = bin_help();
203+
anyhow::anyhow!("{err}{help}")
204+
})?;
201205

202206
if restricted_names::is_keyword(name) {
203207
anyhow::bail!(

src/cargo/util/command_prelude.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use crate::util::{
1111
print_available_benches, print_available_binaries, print_available_examples,
1212
print_available_packages, print_available_tests,
1313
};
14+
use crate::util_schemas::manifest::ProfileName;
15+
use crate::util_schemas::manifest::RegistryName;
1416
use crate::util_schemas::manifest::StringOrVec;
1517
use crate::CargoResult;
1618
use anyhow::bail;
@@ -605,7 +607,7 @@ Run `{cmd}` to see possible targets."
605607
bail!("profile `doc` is reserved and not allowed to be explicitly specified")
606608
}
607609
(_, _, Some(name)) => {
608-
restricted_names::validate_profile_name(name)?;
610+
ProfileName::new(name)?;
609611
name
610612
}
611613
};
@@ -833,7 +835,7 @@ Run `{cmd}` to see possible targets."
833835
(None, None) => config.default_registry()?.map(RegistryOrIndex::Registry),
834836
(None, Some(i)) => Some(RegistryOrIndex::Index(i.into_url()?)),
835837
(Some(r), None) => {
836-
restricted_names::validate_package_name(r, "registry name", "")?;
838+
RegistryName::new(r)?;
837839
Some(RegistryOrIndex::Registry(r.to_string()))
838840
}
839841
(Some(_), Some(_)) => {
@@ -848,7 +850,7 @@ Run `{cmd}` to see possible targets."
848850
match self._value_of("registry").map(|s| s.to_string()) {
849851
None => config.default_registry(),
850852
Some(registry) => {
851-
restricted_names::validate_package_name(&registry, "registry name", "")?;
853+
RegistryName::new(&registry)?;
852854
Ok(Some(registry))
853855
}
854856
}

src/cargo/util/config/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ use crate::sources::CRATES_IO_REGISTRY;
7777
use crate::util::errors::CargoResult;
7878
use crate::util::network::http::configure_http_handle;
7979
use crate::util::network::http::http_handle;
80+
use crate::util::try_canonicalize;
8081
use crate::util::{internal, CanonicalUrl};
81-
use crate::util::{try_canonicalize, validate_package_name};
8282
use crate::util::{Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
83+
use crate::util_schemas::manifest::RegistryName;
8384
use anyhow::{anyhow, bail, format_err, Context as _};
8485
use cargo_credential::Secret;
8586
use cargo_util::paths;
@@ -1551,7 +1552,7 @@ impl Config {
15511552

15521553
/// Gets the index for a registry.
15531554
pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1554-
validate_package_name(registry, "registry name", "")?;
1555+
RegistryName::new(registry)?;
15551556
if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
15561557
self.resolve_registry_index(&index).with_context(|| {
15571558
format!(

src/cargo/util/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ pub(crate) use self::io::LimitErrorReader;
2121
pub use self::lockserver::{LockServer, LockServerClient, LockServerStarted};
2222
pub use self::progress::{Progress, ProgressStyle};
2323
pub use self::queue::Queue;
24-
pub use self::restricted_names::validate_package_name;
2524
pub use self::rustc::Rustc;
2625
pub use self::semver_ext::OptVersionReq;
2726
pub use self::vcs::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo};

src/cargo/util/restricted_names.rs

Lines changed: 0 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
//! Helpers for validating and checking names like package and crate names.
22
3-
use crate::util::CargoResult;
4-
use anyhow::bail;
53
use std::path::Path;
64

75
/// Returns `true` if the name contains non-ASCII characters.
@@ -36,80 +34,6 @@ pub fn is_conflicting_artifact_name(name: &str) -> bool {
3634
["deps", "examples", "build", "incremental"].contains(&name)
3735
}
3836

39-
/// Check the base requirements for a package name.
40-
///
41-
/// This can be used for other things than package names, to enforce some
42-
/// level of sanity. Note that package names have other restrictions
43-
/// elsewhere. `cargo new` has a few restrictions, such as checking for
44-
/// reserved names. crates.io has even more restrictions.
45-
pub fn validate_package_name(name: &str, what: &str, help: &str) -> CargoResult<()> {
46-
if name.is_empty() {
47-
bail!("{what} cannot be empty");
48-
}
49-
50-
let mut chars = name.chars();
51-
if let Some(ch) = chars.next() {
52-
if ch.is_digit(10) {
53-
// A specific error for a potentially common case.
54-
bail!(
55-
"the name `{}` cannot be used as a {}, \
56-
the name cannot start with a digit{}",
57-
name,
58-
what,
59-
help
60-
);
61-
}
62-
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_') {
63-
bail!(
64-
"invalid character `{}` in {}: `{}`, \
65-
the first character must be a Unicode XID start character \
66-
(most letters or `_`){}",
67-
ch,
68-
what,
69-
name,
70-
help
71-
);
72-
}
73-
}
74-
for ch in chars {
75-
if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-') {
76-
bail!(
77-
"invalid character `{}` in {}: `{}`, \
78-
characters must be Unicode XID characters \
79-
(numbers, `-`, `_`, or most letters){}",
80-
ch,
81-
what,
82-
name,
83-
help
84-
);
85-
}
86-
}
87-
Ok(())
88-
}
89-
90-
/// Ensure a package name is [valid][validate_package_name]
91-
pub fn sanitize_package_name(name: &str, placeholder: char) -> String {
92-
let mut slug = String::new();
93-
let mut chars = name.chars();
94-
while let Some(ch) = chars.next() {
95-
if (unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_') && !ch.is_digit(10) {
96-
slug.push(ch);
97-
break;
98-
}
99-
}
100-
while let Some(ch) = chars.next() {
101-
if unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-' {
102-
slug.push(ch);
103-
} else {
104-
slug.push(placeholder);
105-
}
106-
}
107-
if slug.is_empty() {
108-
slug.push_str("package");
109-
}
110-
slug
111-
}
112-
11337
/// Check the entire path for names reserved in Windows.
11438
pub fn is_windows_reserved_path(path: &Path) -> bool {
11539
path.iter()
@@ -124,82 +48,3 @@ pub fn is_windows_reserved_path(path: &Path) -> bool {
12448
pub fn is_glob_pattern<T: AsRef<str>>(name: T) -> bool {
12549
name.as_ref().contains(&['*', '?', '[', ']'][..])
12650
}
127-
128-
/// Validate dir-names and profile names according to RFC 2678.
129-
pub fn validate_profile_name(name: &str) -> CargoResult<()> {
130-
if let Some(ch) = name
131-
.chars()
132-
.find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-')
133-
{
134-
bail!(
135-
"invalid character `{}` in profile name `{}`\n\
136-
Allowed characters are letters, numbers, underscore, and hyphen.",
137-
ch,
138-
name
139-
);
140-
}
141-
142-
const SEE_DOCS: &str = "See https://doc.rust-lang.org/cargo/reference/profiles.html \
143-
for more on configuring profiles.";
144-
145-
let lower_name = name.to_lowercase();
146-
if lower_name == "debug" {
147-
bail!(
148-
"profile name `{}` is reserved\n\
149-
To configure the default development profile, use the name `dev` \
150-
as in [profile.dev]\n\
151-
{}",
152-
name,
153-
SEE_DOCS
154-
);
155-
}
156-
if lower_name == "build-override" {
157-
bail!(
158-
"profile name `{}` is reserved\n\
159-
To configure build dependency settings, use [profile.dev.build-override] \
160-
and [profile.release.build-override]\n\
161-
{}",
162-
name,
163-
SEE_DOCS
164-
);
165-
}
166-
167-
// These are some arbitrary reservations. We have no plans to use
168-
// these, but it seems safer to reserve a few just in case we want to
169-
// add more built-in profiles in the future. We can also uses special
170-
// syntax like cargo:foo if needed. But it is unlikely these will ever
171-
// be used.
172-
if matches!(
173-
lower_name.as_str(),
174-
"build"
175-
| "check"
176-
| "clean"
177-
| "config"
178-
| "fetch"
179-
| "fix"
180-
| "install"
181-
| "metadata"
182-
| "package"
183-
| "publish"
184-
| "report"
185-
| "root"
186-
| "run"
187-
| "rust"
188-
| "rustc"
189-
| "rustdoc"
190-
| "target"
191-
| "tmp"
192-
| "uninstall"
193-
) || lower_name.starts_with("cargo")
194-
{
195-
bail!(
196-
"profile name `{}` is reserved\n\
197-
Please choose a different name.\n\
198-
{}",
199-
name,
200-
SEE_DOCS
201-
);
202-
}
203-
204-
Ok(())
205-
}

0 commit comments

Comments
 (0)