Skip to content

Commit 6ffd5dd

Browse files
committed
models/krate: Extract custom error types
The `models` module should ideally not know anything about the HTTP API layer and the `cargo_err()` fn.
1 parent efc598b commit 6ffd5dd

File tree

2 files changed

+78
-40
lines changed

2 files changed

+78
-40
lines changed

src/controllers/krate/publish.rs

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

1818
use crate::controllers::cargo_prelude::*;
19-
use crate::models::krate::MAX_NAME_LENGTH;
19+
use crate::models::krate::{InvalidDependencyName, MAX_NAME_LENGTH};
2020
use crate::models::{
2121
insert_version_owner_action, Category, Crate, DependencyKind, Keyword, NewCrate, NewVersion,
2222
Rights, VersionAction,
@@ -238,7 +238,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
238238
}
239239

240240
for (key, values) in features.iter() {
241-
Crate::validate_feature_name(key)?;
241+
Crate::validate_feature_name(key).map_err(|error| cargo_err(&error))?;
242242

243243
let num_features = values.len();
244244
if num_features > max_features {
@@ -253,7 +253,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
253253
}
254254

255255
for value in values.iter() {
256-
Crate::validate_feature(value)?;
256+
Crate::validate_feature(value).map_err(|error| cargo_err(&error))?;
257257
}
258258
}
259259

@@ -596,7 +596,7 @@ pub fn validate_dependency(dep: &EncodableCrateDependency) -> AppResult<()> {
596596
}
597597

598598
for feature in &dep.features {
599-
Crate::validate_feature(feature)?;
599+
Crate::validate_feature(feature).map_err(|error| cargo_err(&error))?;
600600
}
601601

602602
if let Some(registry) = &dep.registry {
@@ -623,7 +623,7 @@ pub fn validate_dependency(dep: &EncodableCrateDependency) -> AppResult<()> {
623623

624624
if let Some(toml_name) = &dep.explicit_name_in_toml {
625625
if !Crate::valid_dependency_name(toml_name) {
626-
return Err(cargo_err(&Crate::invalid_dependency_name_msg(toml_name)));
626+
return Err(cargo_err(&InvalidDependencyName(toml_name.into())));
627627
}
628628
}
629629

src/models/krate.rs

Lines changed: 73 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -220,32 +220,18 @@ impl Crate {
220220
.unwrap_or(false)
221221
}
222222

223-
pub fn invalid_dependency_name_msg(dep: &str) -> String {
224-
format!(
225-
"\"{dep}\" is an invalid dependency name (dependency \
226-
names must start with a letter or underscore, contain only \
227-
letters, numbers, hyphens, or underscores and have at most \
228-
{MAX_NAME_LENGTH} characters)"
229-
)
230-
}
231-
232223
/// Validates the THIS parts of `features = ["THIS", "and/THIS", "dep:THIS", "dep?/THIS"]`.
233224
/// 1. The name must be non-empty.
234225
/// 2. The first character must be a Unicode XID start character, `_`, or a digit.
235226
/// 3. The remaining characters must be Unicode XID characters, `_`, `+`, `-`, or `.`.
236-
pub fn validate_feature_name(name: &str) -> AppResult<()> {
227+
pub fn validate_feature_name(name: &str) -> Result<(), InvalidFeature> {
237228
if name.is_empty() {
238-
return Err(cargo_err("feature cannot be an empty"));
229+
return Err(InvalidFeature::Empty);
239230
}
240231
let mut chars = name.chars();
241232
if let Some(ch) = chars.next() {
242233
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_ascii_digit()) {
243-
return Err(cargo_err(&format!(
244-
"invalid character `{}` in feature `{}`, \
245-
the first character must be a Unicode XID start character or digit \
246-
(most letters or `_` or `0` to `9`)",
247-
ch, name,
248-
)));
234+
return Err(InvalidFeature::Start(ch, name.into()));
249235
}
250236
}
251237
for ch in chars {
@@ -254,29 +240,26 @@ impl Crate {
254240
|| ch == '-'
255241
|| ch == '.')
256242
{
257-
return Err(cargo_err(&format!(
258-
"invalid character `{}` in feature `{}`, \
259-
characters must be Unicode XID characters, `+`, `-`, or `.` \
260-
(numbers, `+`, `-`, `_`, `.`, or most letters)",
261-
ch, name,
262-
)));
243+
return Err(InvalidFeature::Char(ch, name.into()));
263244
}
264245
}
265246

266247
Ok(())
267248
}
268249

269250
/// Validates a whole feature string, `features = ["THIS", "and/THIS", "dep:THIS", "dep?/THIS"]`.
270-
pub fn validate_feature(name: &str) -> AppResult<()> {
251+
pub fn validate_feature(name: &str) -> Result<(), InvalidFeature> {
271252
if let Some((dep, dep_feat)) = name.split_once('/') {
272253
let dep = dep.strip_suffix('?').unwrap_or(dep);
273254
if !Crate::valid_dependency_name(dep) {
274-
return Err(cargo_err(&Crate::invalid_dependency_name_msg(dep)));
255+
let err = InvalidDependencyName(dep.into());
256+
return Err(InvalidFeature::DependencyName(err));
275257
}
276258
Crate::validate_feature_name(dep_feat)
277259
} else if let Some((_, dep)) = name.split_once("dep:") {
278260
if !Crate::valid_dependency_name(dep) {
279-
return Err(cargo_err(&Crate::invalid_dependency_name_msg(dep)));
261+
let err = InvalidDependencyName(dep.into());
262+
return Err(InvalidFeature::DependencyName(err));
280263
}
281264
return Ok(());
282265
} else {
@@ -528,6 +511,34 @@ impl CrateVersions for [Crate] {
528511
}
529512
}
530513

514+
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
515+
pub enum InvalidFeature {
516+
#[error("feature cannot be an empty")]
517+
Empty,
518+
#[error(
519+
"invalid character `{0}` in feature `{1}`, the first character must be \
520+
a Unicode XID start character or digit (most letters or `_` or `0` to \
521+
`9`)"
522+
)]
523+
Start(char, String),
524+
#[error(
525+
"invalid character `{0}` in feature `{1}`, characters must be Unicode \
526+
XID characters, `+`, `-`, or `.` (numbers, `+`, `-`, `_`, `.`, or most \
527+
letters)"
528+
)]
529+
Char(char, String),
530+
#[error(transparent)]
531+
DependencyName(#[from] InvalidDependencyName),
532+
}
533+
534+
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
535+
#[error(
536+
"\"{0}\" is an invalid dependency name (dependency names must start with a \
537+
letter or underscore, contain only letters, numbers, hyphens, or \
538+
underscores and have at most {MAX_NAME_LENGTH} characters)"
539+
)]
540+
pub struct InvalidDependencyName(pub String);
541+
531542
#[cfg(test)]
532543
mod tests {
533544
use crate::models::Crate;
@@ -562,27 +573,54 @@ mod tests {
562573

563574
#[test]
564575
fn valid_feature_names() {
576+
use super::InvalidDependencyName;
577+
use super::InvalidFeature::*;
578+
565579
assert_ok!(Crate::validate_feature("foo"));
566580
assert_ok!(Crate::validate_feature("1foo"));
567581
assert_ok!(Crate::validate_feature("_foo"));
568582
assert_ok!(Crate::validate_feature("_foo-_+.1"));
569583
assert_ok!(Crate::validate_feature("_foo-_+.1"));
570-
assert_err!(Crate::validate_feature(""));
571-
assert_err!(Crate::validate_feature("/"));
572-
assert_err!(Crate::validate_feature("%/%"));
584+
assert_err_eq!(Crate::validate_feature(""), Empty);
585+
assert_err_eq!(
586+
Crate::validate_feature("/"),
587+
InvalidDependencyName("".into()).into()
588+
);
589+
assert_err_eq!(
590+
Crate::validate_feature("%/%"),
591+
InvalidDependencyName("%".into()).into()
592+
);
573593
assert_ok!(Crate::validate_feature("a/a"));
574594
assert_ok!(Crate::validate_feature("32-column-tables"));
575595
assert_ok!(Crate::validate_feature("c++20"));
576596
assert_ok!(Crate::validate_feature("krate/c++20"));
577-
assert_err!(Crate::validate_feature("c++20/wow"));
597+
assert_err_eq!(
598+
Crate::validate_feature("c++20/wow"),
599+
InvalidDependencyName("c++20".into()).into()
600+
);
578601
assert_ok!(Crate::validate_feature("foo?/bar"));
579602
assert_ok!(Crate::validate_feature("dep:foo"));
580-
assert_err!(Crate::validate_feature("dep:foo?/bar"));
581-
assert_err!(Crate::validate_feature("foo/?bar"));
582-
assert_err!(Crate::validate_feature("foo?bar"));
603+
assert_err_eq!(
604+
Crate::validate_feature("dep:foo?/bar"),
605+
InvalidDependencyName("dep:foo".into()).into()
606+
);
607+
assert_err_eq!(
608+
Crate::validate_feature("foo/?bar"),
609+
Start('?', "?bar".into())
610+
);
611+
assert_err_eq!(
612+
Crate::validate_feature("foo?bar"),
613+
Char('?', "foo?bar".into())
614+
);
583615
assert_ok!(Crate::validate_feature("bar.web"));
584616
assert_ok!(Crate::validate_feature("foo/bar.web"));
585-
assert_err!(Crate::validate_feature("dep:0foo"));
586-
assert_err!(Crate::validate_feature("0foo?/bar.web"));
617+
assert_err_eq!(
618+
Crate::validate_feature("dep:0foo"),
619+
InvalidDependencyName("0foo".into()).into()
620+
);
621+
assert_err_eq!(
622+
Crate::validate_feature("0foo?/bar.web"),
623+
InvalidDependencyName("0foo".into()).into()
624+
);
587625
}
588626
}

0 commit comments

Comments
 (0)