refactor(util-schemas): make error enum private

This commit is contained in:
Weihang Lo 2023-12-19 18:58:46 -05:00
parent a0201cd465
commit f9e726b056
No known key found for this signature in database
GPG key ID: D7DBF189825E82E7
4 changed files with 90 additions and 48 deletions

View file

@ -6,8 +6,10 @@ use url::Url;
use crate::core::GitReference; use crate::core::GitReference;
use crate::core::PartialVersion; use crate::core::PartialVersion;
use crate::core::PartialVersionError;
use crate::core::SourceKind; use crate::core::SourceKind;
use crate::manifest::PackageName; use crate::manifest::PackageName;
use crate::restricted_names::NameValidationError;
type Result<T> = std::result::Result<T, PackageIdSpecError>; type Result<T> = std::result::Result<T, PackageIdSpecError>;
@ -83,10 +85,11 @@ impl PackageIdSpec {
if abs.exists() { if abs.exists() {
let maybe_url = Url::from_file_path(abs) let maybe_url = Url::from_file_path(abs)
.map_or_else(|_| "a file:// URL".to_string(), |url| url.to_string()); .map_or_else(|_| "a file:// URL".to_string(), |url| url.to_string());
return Err(PackageIdSpecError::MaybeFilePath { return Err(ErrorKind::MaybeFilePath {
spec: spec.into(), spec: spec.into(),
maybe_url, maybe_url,
}); }
.into());
} }
} }
let mut parts = spec.splitn(2, [':', '@']); let mut parts = spec.splitn(2, [':', '@']);
@ -117,14 +120,14 @@ impl PackageIdSpec {
} }
"registry" => { "registry" => {
if url.query().is_some() { if url.query().is_some() {
return Err(PackageIdSpecError::UnexpectedQueryString(url)); return Err(ErrorKind::UnexpectedQueryString(url).into());
} }
kind = Some(SourceKind::Registry); kind = Some(SourceKind::Registry);
url = strip_url_protocol(&url); url = strip_url_protocol(&url);
} }
"sparse" => { "sparse" => {
if url.query().is_some() { if url.query().is_some() {
return Err(PackageIdSpecError::UnexpectedQueryString(url)); return Err(ErrorKind::UnexpectedQueryString(url).into());
} }
kind = Some(SourceKind::SparseRegistry); kind = Some(SourceKind::SparseRegistry);
// Leave `sparse` as part of URL, see `SourceId::new` // Leave `sparse` as part of URL, see `SourceId::new`
@ -132,19 +135,19 @@ impl PackageIdSpec {
} }
"path" => { "path" => {
if url.query().is_some() { if url.query().is_some() {
return Err(PackageIdSpecError::UnexpectedQueryString(url)); return Err(ErrorKind::UnexpectedQueryString(url).into());
} }
if scheme != "file" { if scheme != "file" {
return Err(PackageIdSpecError::UnsupportedPathPlusScheme(scheme.into())); return Err(ErrorKind::UnsupportedPathPlusScheme(scheme.into()).into());
} }
kind = Some(SourceKind::Path); kind = Some(SourceKind::Path);
url = strip_url_protocol(&url); url = strip_url_protocol(&url);
} }
kind => return Err(PackageIdSpecError::UnsupportedProtocol(kind.into())), kind => return Err(ErrorKind::UnsupportedProtocol(kind.into()).into()),
} }
} else { } else {
if url.query().is_some() { if url.query().is_some() {
return Err(PackageIdSpecError::UnexpectedQueryString(url)); return Err(ErrorKind::UnexpectedQueryString(url).into());
} }
} }
@ -153,7 +156,7 @@ impl PackageIdSpec {
let (name, version) = { let (name, version) = {
let Some(path_name) = url.path_segments().and_then(|mut p| p.next_back()) else { let Some(path_name) = url.path_segments().and_then(|mut p| p.next_back()) else {
return Err(PackageIdSpecError::MissingUrlPath(url)); return Err(ErrorKind::MissingUrlPath(url).into());
}; };
match frag { match frag {
Some(fragment) => match fragment.split_once([':', '@']) { Some(fragment) => match fragment.split_once([':', '@']) {
@ -269,9 +272,26 @@ impl<'de> de::Deserialize<'de> for PackageIdSpec {
} }
/// Error parsing a [`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] #[non_exhaustive]
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum PackageIdSpecError { enum ErrorKind {
#[error("unsupported source protocol: {0}")] #[error("unsupported source protocol: {0}")]
UnsupportedProtocol(String), UnsupportedProtocol(String),

View file

@ -84,7 +84,7 @@ impl std::str::FromStr for PartialVersion {
fn from_str(value: &str) -> Result<Self, Self::Err> { fn from_str(value: &str) -> Result<Self, Self::Err> {
if is_req(value) { if is_req(value) {
return Err(PartialVersionError::VersionReq); return Err(ErrorKind::VersionReq.into());
} }
match semver::Version::parse(value) { match semver::Version::parse(value) {
Ok(ver) => Ok(ver.into()), Ok(ver) => Ok(ver.into()),
@ -92,11 +92,9 @@ impl std::str::FromStr for PartialVersion {
// HACK: Leverage `VersionReq` for partial version parsing // HACK: Leverage `VersionReq` for partial version parsing
let mut version_req = match semver::VersionReq::parse(value) { let mut version_req = match semver::VersionReq::parse(value) {
Ok(req) => req, Ok(req) => req,
Err(_) if value.contains('-') => return Err(PartialVersionError::Prerelease), Err(_) if value.contains('-') => return Err(ErrorKind::Prerelease.into()),
Err(_) if value.contains('+') => { Err(_) if value.contains('+') => return Err(ErrorKind::BuildMetadata.into()),
return Err(PartialVersionError::BuildMetadata) Err(_) => return Err(ErrorKind::Unexpected.into()),
}
Err(_) => return Err(PartialVersionError::Unexpected),
}; };
assert_eq!(version_req.comparators.len(), 1, "guaranteed by is_req"); assert_eq!(version_req.comparators.len(), 1, "guaranteed by is_req");
let comp = version_req.comparators.pop().unwrap(); let comp = version_req.comparators.pop().unwrap();
@ -160,9 +158,14 @@ impl<'de> serde::Deserialize<'de> for PartialVersion {
} }
/// Error parsing a [`PartialVersion`]. /// Error parsing a [`PartialVersion`].
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct PartialVersionError(#[from] ErrorKind);
/// Non-public error kind for [`PartialVersionError`].
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum PartialVersionError { enum ErrorKind {
#[error("unexpected version requirement, expected a version like \"1.32\"")] #[error("unexpected version requirement, expected a version like \"1.32\"")]
VersionReq, VersionReq,

View file

@ -1339,12 +1339,13 @@ impl std::str::FromStr for RustVersion {
type Err = RustVersionError; type Err = RustVersionError;
fn from_str(value: &str) -> Result<Self, Self::Err> { 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() { if partial.pre.is_some() {
return Err(RustVersionError::Prerelease); return Err(RustVersionErrorKind::Prerelease.into());
} }
if partial.build.is_some() { if partial.build.is_some() {
return Err(RustVersionError::BuildMetadata); return Err(RustVersionErrorKind::BuildMetadata.into());
} }
Ok(Self(partial)) Ok(Self(partial))
} }
@ -1368,10 +1369,15 @@ impl Display for RustVersion {
} }
} }
/// Error parsing a [`PartialVersion`]. /// Error parsing a [`RustVersion`].
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct RustVersionError(#[from] RustVersionErrorKind);
/// Non-public error kind for [`RustVersionError`].
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum RustVersionError { enum RustVersionErrorKind {
#[error("unexpected prerelease field, expected a version like \"1.32\"")] #[error("unexpected prerelease field, expected a version like \"1.32\"")]
Prerelease, Prerelease,

View file

@ -3,9 +3,14 @@
type Result<T> = std::result::Result<T, NameValidationError>; type Result<T> = std::result::Result<T, NameValidationError>;
/// Error validating names in Cargo. /// Error validating names in Cargo.
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct NameValidationError(#[from] ErrorKind);
/// Non-public error kind for [`NameValidationError`].
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum NameValidationError { enum ErrorKind {
#[error("{0} cannot be empty")] #[error("{0} cannot be empty")]
Empty(&'static str), Empty(&'static str),
@ -36,39 +41,42 @@ pub enum NameValidationError {
/// reserved names. crates.io has even more restrictions. /// reserved names. crates.io has even more restrictions.
pub(crate) fn validate_package_name(name: &str, what: &'static str) -> Result<()> { pub(crate) fn validate_package_name(name: &str, what: &'static str) -> Result<()> {
if name.is_empty() { if name.is_empty() {
return Err(NameValidationError::Empty(what)); return Err(ErrorKind::Empty(what).into());
} }
let mut chars = name.chars(); let mut chars = name.chars();
if let Some(ch) = chars.next() { if let Some(ch) = chars.next() {
if ch.is_digit(10) { if ch.is_digit(10) {
// A specific error for a potentially common case. // A specific error for a potentially common case.
return Err(NameValidationError::InvalidCharacter { return Err(ErrorKind::InvalidCharacter {
ch, ch,
what, what,
name: name.into(), name: name.into(),
reason: "the name cannot start with a digit", reason: "the name cannot start with a digit",
}); }
.into());
} }
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_') { if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_') {
return Err(NameValidationError::InvalidCharacter { return Err(ErrorKind::InvalidCharacter {
ch, ch,
what, what,
name: name.into(), name: name.into(),
reason: "the first character must be a Unicode XID start character \ reason: "the first character must be a Unicode XID start character \
(most letters or `_`)", (most letters or `_`)",
}); }
.into());
} }
} }
for ch in chars { for ch in chars {
if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-') { if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-') {
return Err(NameValidationError::InvalidCharacter { return Err(ErrorKind::InvalidCharacter {
ch, ch,
what, what,
name: name.into(), name: name.into(),
reason: "characters must be Unicode XID characters \ reason: "characters must be Unicode XID characters \
(numbers, `-`, `_`, or most letters)", (numbers, `-`, `_`, or most letters)",
}); }
.into());
} }
} }
Ok(()) Ok(())
@ -103,28 +111,31 @@ pub(crate) fn validate_profile_name(name: &str) -> Result<()> {
.chars() .chars()
.find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-') .find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-')
{ {
return Err(NameValidationError::InvalidCharacter { return Err(ErrorKind::InvalidCharacter {
ch, ch,
what: "profile name", what: "profile name",
name: name.into(), name: name.into(),
reason: "allowed characters are letters, numbers, underscore, and hyphen", reason: "allowed characters are letters, numbers, underscore, and hyphen",
}); }
.into());
} }
let lower_name = name.to_lowercase(); let lower_name = name.to_lowercase();
if lower_name == "debug" { if lower_name == "debug" {
return Err(NameValidationError::ProfileNameReservedKeyword { return Err(ErrorKind::ProfileNameReservedKeyword {
name: name.into(), name: name.into(),
help: "To configure the default development profile, \ help: "To configure the default development profile, \
use the name `dev` as in [profile.dev]", use the name `dev` as in [profile.dev]",
}); }
.into());
} }
if lower_name == "build-override" { if lower_name == "build-override" {
return Err(NameValidationError::ProfileNameReservedKeyword { return Err(ErrorKind::ProfileNameReservedKeyword {
name: name.into(), name: name.into(),
help: "To configure build dependency settings, use [profile.dev.build-override] \ help: "To configure build dependency settings, use [profile.dev.build-override] \
and [profile.release.build-override]", and [profile.release.build-override]",
}); }
.into());
} }
// These are some arbitrary reservations. We have no plans to use // These are some arbitrary reservations. We have no plans to use
@ -155,10 +166,11 @@ pub(crate) fn validate_profile_name(name: &str) -> Result<()> {
| "uninstall" | "uninstall"
) || lower_name.starts_with("cargo") ) || lower_name.starts_with("cargo")
{ {
return Err(NameValidationError::ProfileNameReservedKeyword { return Err(ErrorKind::ProfileNameReservedKeyword {
name: name.into(), name: name.into(),
help: "Please choose a different name.", help: "Please choose a different name.",
}); }
.into());
} }
Ok(()) Ok(())
@ -167,43 +179,44 @@ pub(crate) fn validate_profile_name(name: &str) -> Result<()> {
pub(crate) fn validate_feature_name(name: &str) -> Result<()> { pub(crate) fn validate_feature_name(name: &str) -> Result<()> {
let what = "feature name"; let what = "feature name";
if name.is_empty() { if name.is_empty() {
return Err(NameValidationError::Empty(what)); return Err(ErrorKind::Empty(what).into());
} }
if name.starts_with("dep:") { if name.starts_with("dep:") {
return Err(NameValidationError::FeatureNameStartsWithDepColon( return Err(ErrorKind::FeatureNameStartsWithDepColon(name.into()).into());
name.into(),
));
} }
if name.contains('/') { if name.contains('/') {
return Err(NameValidationError::InvalidCharacter { return Err(ErrorKind::InvalidCharacter {
ch: '/', ch: '/',
what, what,
name: name.into(), name: name.into(),
reason: "feature name is not allowed to contain slashes", reason: "feature name is not allowed to contain slashes",
}); }
.into());
} }
let mut chars = name.chars(); let mut chars = name.chars();
if let Some(ch) = chars.next() { if let Some(ch) = chars.next() {
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_digit(10)) { if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' || ch.is_digit(10)) {
return Err(NameValidationError::InvalidCharacter { return Err(ErrorKind::InvalidCharacter {
ch, ch,
what, what,
name: name.into(), name: name.into(),
reason: "the first character must be a Unicode XID start character or digit \ reason: "the first character must be a Unicode XID start character or digit \
(most letters or `_` or `0` to `9`)", (most letters or `_` or `0` to `9`)",
}); }
.into());
} }
} }
for ch in chars { for ch in chars {
if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-' || ch == '+' || ch == '.') { if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-' || ch == '+' || ch == '.') {
return Err(NameValidationError::InvalidCharacter { return Err(ErrorKind::InvalidCharacter {
ch, ch,
what, what,
name: name.into(), name: name.into(),
reason: "characters must be Unicode XID characters, '-', `+`, or `.` \ reason: "characters must be Unicode XID characters, '-', `+`, or `.` \
(numbers, `+`, `-`, `_`, `.`, or most letters)", (numbers, `+`, `-`, `_`, `.`, or most letters)",
}); }
.into());
} }
} }
Ok(()) Ok(())