Skip to content

Commit 3740cf0

Browse files
committed
Use the same crate name validation rule from Cargo
Signed-off-by: hi-rustin <[email protected]>
1 parent fe21803 commit 3740cf0

10 files changed

+83
-84
lines changed

src/controllers/krate/publish.rs

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use tokio::runtime::Handle;
1616
use url::Url;
1717

1818
use crate::controllers::cargo_prelude::*;
19-
use crate::models::krate::MAX_NAME_LENGTH;
2019
use crate::models::{
2120
insert_version_owner_action, Category, Crate, DependencyKind, Keyword, NewCrate, NewVersion,
2221
Rights, VersionAction,
@@ -53,14 +52,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
5352
let metadata: PublishMetadata = serde_json::from_slice(&json_bytes)
5453
.map_err(|e| cargo_err(&format_args!("invalid upload request: {e}")))?;
5554

56-
if !Crate::valid_name(&metadata.name) {
57-
return Err(cargo_err(&format_args!(
58-
"\"{}\" is an invalid crate name (crate names must start with a \
59-
letter, contain only letters, numbers, hyphens, or underscores and \
60-
have at most {MAX_NAME_LENGTH} characters)",
61-
metadata.name
62-
)));
63-
}
55+
Crate::valid_name(&metadata.name)?;
6456

6557
let version = match semver::Version::parse(&metadata.vers) {
6658
Ok(parsed) => parsed,
@@ -582,14 +574,7 @@ fn convert_dependency(
582574
}
583575

584576
pub fn validate_dependency(dep: &EncodableCrateDependency) -> AppResult<()> {
585-
if !Crate::valid_name(&dep.name) {
586-
return Err(cargo_err(&format_args!(
587-
"\"{}\" is an invalid dependency name (dependency names must \
588-
start with a letter, contain only letters, numbers, hyphens, \
589-
or underscores and have at most {MAX_NAME_LENGTH} characters)",
590-
dep.name
591-
)));
592-
}
577+
Crate::valid_name(&dep.name)?;
593578

594579
for feature in &dep.features {
595580
if !Crate::valid_feature(feature) {
@@ -622,14 +607,7 @@ pub fn validate_dependency(dep: &EncodableCrateDependency) -> AppResult<()> {
622607
}
623608

624609
if let Some(toml_name) = &dep.explicit_name_in_toml {
625-
if !Crate::valid_dependency_name(toml_name) {
626-
return Err(cargo_err(&format_args!(
627-
"\"{toml_name}\" is an invalid dependency name (dependency \
628-
names must start with a letter or underscore, contain only \
629-
letters, numbers, hyphens, or underscores and have at most \
630-
{MAX_NAME_LENGTH} characters)"
631-
)));
632-
}
610+
Crate::valid_dependency_name(toml_name)?;
633611
}
634612

635613
Ok(())

src/models/krate.rs

Lines changed: 72 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -192,32 +192,63 @@ impl Crate {
192192
})
193193
}
194194

195-
pub fn valid_name(name: &str) -> bool {
196-
let under_max_length = name.chars().take(MAX_NAME_LENGTH + 1).count() <= MAX_NAME_LENGTH;
197-
Crate::valid_ident(name) && under_max_length
195+
pub fn valid_name(name: &str) -> AppResult<()> {
196+
if name.chars().count() > MAX_NAME_LENGTH {
197+
return Err(cargo_err(&format!(
198+
"the name `{}` is too long (max {} characters)",
199+
name, MAX_NAME_LENGTH
200+
)));
201+
}
202+
Crate::valid_ident(name, "crate name")
198203
}
199204

200-
fn valid_ident(name: &str) -> bool {
201-
Self::valid_feature_prefix(name)
202-
&& name
203-
.chars()
204-
.next()
205-
.map(char::is_alphabetic)
206-
.unwrap_or(false)
205+
pub fn valid_dependency_name(name: &str) -> AppResult<()> {
206+
if name.chars().count() > MAX_NAME_LENGTH {
207+
return Err(cargo_err(&format!(
208+
"the name `{}` is too long (max {} characters)",
209+
name, MAX_NAME_LENGTH
210+
)));
211+
}
212+
Crate::valid_ident(name, "dependency name")
207213
}
208214

209-
pub fn valid_dependency_name(name: &str) -> bool {
210-
let under_max_length = name.chars().take(MAX_NAME_LENGTH + 1).count() <= MAX_NAME_LENGTH;
211-
Crate::valid_dependency_ident(name) && under_max_length
212-
}
215+
// Checks that the name is a valid identifier.
216+
// 1. The name must be non-empty.
217+
// 2. The first character must be an ASCII character or `_`.
218+
// 3. The remaining characters must be ASCII alphanumerics or `-` or `_`.
219+
fn valid_ident(name: &str, what: &str) -> AppResult<()> {
220+
if name.is_empty() {
221+
return Err(cargo_err(&format!("the {} cannot be an empty", what)));
222+
}
223+
let mut chars = name.chars();
224+
if let Some(ch) = chars.next() {
225+
if ch.is_ascii_digit() {
226+
return Err(cargo_err(&format!(
227+
"the name `{}` cannot be used as a {}, \
228+
the name cannot start with a digit",
229+
name, what,
230+
)));
231+
}
232+
if !(ch.is_ascii_alphabetic() || ch == '_') {
233+
return Err(cargo_err(&format!(
234+
"invalid character `{}` in {}: `{}`, \
235+
the first character must be an ASCII character, or `_`",
236+
ch, what, name
237+
)));
238+
}
239+
}
213240

214-
fn valid_dependency_ident(name: &str) -> bool {
215-
Self::valid_feature_prefix(name)
216-
&& name
217-
.chars()
218-
.next()
219-
.map(|n| n.is_alphabetic() || n == '_')
220-
.unwrap_or(false)
241+
for ch in chars {
242+
if !(ch.is_ascii_alphanumeric() || ch == '-' || ch == '_') {
243+
return Err(cargo_err(&format!(
244+
"invalid character `{}` in {}: `{}`, \
245+
characters must be an ASCII alphanumeric characters, `-`, or `_`",
246+
ch, what, name
247+
)));
248+
}
249+
}
250+
251+
Ok(())
221252
}
222253

223254
/// Validates the THIS parts of `features = ["THIS", "and/THIS", "dep:THIS", "dep?/THIS"]`.
@@ -257,21 +288,13 @@ impl Crate {
257288
Ok(())
258289
}
259290

260-
/// Validates the prefix in front of the slash: `features = ["THIS/feature"]`.
261-
/// Normally this corresponds to the crate name of a dependency.
262-
fn valid_feature_prefix(name: &str) -> bool {
263-
!name.is_empty()
264-
&& name
265-
.chars()
266-
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
267-
}
268-
269291
/// Validates a whole feature string, `features = ["THIS", "ALL/THIS"]`.
270292
pub fn valid_feature(name: &str) -> bool {
271293
match name.split_once('/') {
272294
Some((dep, dep_feat)) => {
273295
let dep = dep.strip_suffix('?').unwrap_or(dep);
274-
Crate::valid_feature_prefix(dep) && Crate::valid_feature_name(dep_feat).is_ok()
296+
Crate::valid_dependency_name(dep).is_ok()
297+
&& Crate::valid_feature_name(dep_feat).is_ok()
275298
}
276299
None => Crate::valid_feature_name(name.strip_prefix("dep:").unwrap_or(name)).is_ok(),
277300
}
@@ -518,30 +541,28 @@ mod tests {
518541

519542
#[test]
520543
fn valid_name() {
521-
assert!(Crate::valid_name("foo"));
522-
assert!(!Crate::valid_name("京"));
523-
assert!(!Crate::valid_name(""));
524-
assert!(!Crate::valid_name("💝"));
525-
assert!(Crate::valid_name("foo_underscore"));
526-
assert!(Crate::valid_name("foo-dash"));
527-
assert!(!Crate::valid_name("foo+plus"));
528-
// Starting with an underscore is an invalid crate name.
529-
assert!(!Crate::valid_name("_foo"));
530-
assert!(!Crate::valid_name("-foo"));
544+
assert!(Crate::valid_name("foo").is_ok());
545+
assert!(Crate::valid_name("京").is_err());
546+
assert!(Crate::valid_name("").is_err());
547+
assert!(Crate::valid_name("💝").is_err());
548+
assert!(Crate::valid_name("foo_underscore").is_ok());
549+
assert!(Crate::valid_name("foo-dash").is_ok());
550+
assert!(Crate::valid_name("foo+plus").is_err());
551+
assert!(Crate::valid_name("_foo").is_ok());
552+
assert!(Crate::valid_name("-foo").is_err());
531553
}
532554

533555
#[test]
534556
fn valid_dependency_name() {
535-
assert!(Crate::valid_dependency_name("foo"));
536-
assert!(!Crate::valid_dependency_name("京"));
537-
assert!(!Crate::valid_dependency_name(""));
538-
assert!(!Crate::valid_dependency_name("💝"));
539-
assert!(Crate::valid_dependency_name("foo_underscore"));
540-
assert!(Crate::valid_dependency_name("foo-dash"));
541-
assert!(!Crate::valid_dependency_name("foo+plus"));
542-
// Starting with an underscore is a valid dependency name.
543-
assert!(Crate::valid_dependency_name("_foo"));
544-
assert!(!Crate::valid_dependency_name("-foo"));
557+
assert!(Crate::valid_dependency_name("foo").is_ok());
558+
assert!(Crate::valid_dependency_name("京").is_err());
559+
assert!(Crate::valid_dependency_name("").is_err());
560+
assert!(Crate::valid_dependency_name("💝").is_err());
561+
assert!(Crate::valid_dependency_name("foo_underscore").is_ok());
562+
assert!(Crate::valid_dependency_name("foo-dash").is_ok());
563+
assert!(Crate::valid_dependency_name("foo+plus").is_err());
564+
assert!(Crate::valid_dependency_name("_foo").is_ok());
565+
assert!(Crate::valid_dependency_name("-foo").is_err());
545566
}
546567

547568
#[test]

src/models/token/scopes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ impl CrateScope {
108108
}
109109

110110
let name_without_wildcard = pattern.strip_suffix('*').unwrap_or(pattern);
111-
Crate::valid_name(name_without_wildcard)
111+
Crate::valid_name(name_without_wildcard).is_ok()
112112
}
113113

114114
pub fn matches(&self, crate_name: &str) -> bool {

src/tests/krate/publish/snapshots/all__krate__publish__dependencies__invalid_dependency_name.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ expression: response.into_json()
55
{
66
"errors": [
77
{
8-
"detail": "\"🦀\" is an invalid dependency name (dependency names must start with a letter, contain only letters, numbers, hyphens, or underscores and have at most 64 characters)"
8+
"detail": "invalid character `🦀` in crate name: `🦀`, the first character must be an ASCII character, or `_`"
99
}
1010
]
1111
}

src/tests/krate/publish/snapshots/all__krate__publish__dependencies__invalid_dependency_rename.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ expression: response.into_json()
55
{
66
"errors": [
77
{
8-
"detail": "\"💩\" is an invalid dependency name (dependency names must start with a letter or underscore, contain only letters, numbers, hyphens, or underscores and have at most 64 characters)"
8+
"detail": "invalid character `💩` in dependency name: `💩`, the first character must be an ASCII character, or `_`"
99
}
1010
]
1111
}

src/tests/krate/publish/snapshots/all__krate__publish__validation__invalid_names-2.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ expression: response.into_json()
55
{
66
"errors": [
77
{
8-
"detail": "\"foo bar\" is an invalid crate name (crate names must start with a letter, contain only letters, numbers, hyphens, or underscores and have at most 64 characters)"
8+
"detail": "invalid character ` ` in crate name: `foo bar`, characters must be an ASCII alphanumeric characters, `-`, or `_`"
99
}
1010
]
1111
}

src/tests/krate/publish/snapshots/all__krate__publish__validation__invalid_names-3.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ expression: response.into_json()
55
{
66
"errors": [
77
{
8-
"detail": "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\" is an invalid crate name (crate names must start with a letter, contain only letters, numbers, hyphens, or underscores and have at most 64 characters)"
8+
"detail": "the name `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa` is too long (max 64 characters)"
99
}
1010
]
1111
}

src/tests/krate/publish/snapshots/all__krate__publish__validation__invalid_names-4.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ expression: response.into_json()
55
{
66
"errors": [
77
{
8-
"detail": "\"snow☃\" is an invalid crate name (crate names must start with a letter, contain only letters, numbers, hyphens, or underscores and have at most 64 characters)"
8+
"detail": "invalid character `☃` in crate name: `snow☃`, characters must be an ASCII alphanumeric characters, `-`, or `_`"
99
}
1010
]
1111
}

src/tests/krate/publish/snapshots/all__krate__publish__validation__invalid_names-5.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ expression: response.into_json()
55
{
66
"errors": [
77
{
8-
"detail": "\"áccênts\" is an invalid crate name (crate names must start with a letter, contain only letters, numbers, hyphens, or underscores and have at most 64 characters)"
8+
"detail": "invalid character `á` in crate name: `áccênts`, the first character must be an ASCII character, or `_`"
99
}
1010
]
1111
}

src/tests/krate/publish/snapshots/all__krate__publish__validation__invalid_names.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ expression: response.into_json()
55
{
66
"errors": [
77
{
8-
"detail": "\"\" is an invalid crate name (crate names must start with a letter, contain only letters, numbers, hyphens, or underscores and have at most 64 characters)"
8+
"detail": "the crate name cannot be an empty"
99
}
1010
]
1111
}

0 commit comments

Comments
 (0)