use std::result::Result as StdResult;
use nom::error::Error as NomError;
use thiserror::Error as ThisError;
pub type Result<T> = StdResult<T, Error>;
#[derive(Debug, ThisError)]
pub enum Error {
#[error("failed to parse `{input}`: {reason}")]
Parse {
input: String,
reason: String,
#[source]
source: Option<Box<NomError<String>>>,
},
#[error("invalid week number: {week}, expected 1-53")]
InvalidWeek {
week: u8,
},
#[error("invalid year/week combination: {year}.{week}")]
InvalidYearWeek {
year: i32,
week: u8,
},
#[error("invalid year: {year}, should be ISO week-numbering year minus 2000")]
InvalidYear {
year: i32,
},
#[error("invalid version format: {version}")]
InvalidVersion {
version: String,
},
#[error(
"expected version to match format YY.WW.PATCH (where YY is ISO year minus 2000), got: \
{version}"
)]
FormatError {
version: String,
},
#[error("invalid operator: {operator}")]
InvalidOperator {
operator: String,
},
#[error("invalid version requirement: {input}")]
InvalidRequirement {
input: String,
},
#[error("empty version requirement")]
EmptyRequirement,
#[error("unexpected character in requirement at position {position}: {found}")]
UnexpectedRequirementChar {
position: usize,
found: char,
},
#[error("{0}")]
Message(String),
}
impl Error {
pub(crate) fn parse<S>(input: S, reason: S) -> Self
where
S: Into<String>,
{
Error::Parse {
input: input.into(),
reason: reason.into(),
source: None,
}
}
pub(crate) fn from_nom<S>(input: S, source: NomError<&str>) -> Self
where
S: Into<String>,
{
Error::Parse {
input: input.into(),
reason: format!("parser error: {:?}", source.code),
source: Some(Box::new(NomError::new(
source.input.to_string(),
source.code,
))),
}
}
pub(crate) fn format<S>(version: S) -> Self
where
S: Into<String>,
{
Error::FormatError {
version: version.into(),
}
}
pub(crate) fn invalid_version<S>(version: S) -> Self
where
S: Into<String>,
{
Error::InvalidVersion {
version: version.into(),
}
}
pub(crate) fn invalid_requirement<S>(input: S) -> Self
where
S: Into<String>,
{
Error::InvalidRequirement {
input: input.into(),
}
}
}
impl From<NomError<&str>> for Error {
fn from(e: NomError<&str>) -> Self {
Error::from_nom(e.input, e)
}
}
#[cfg(test)]
mod tests {
use nom::error::ErrorKind;
use super::*;
#[test]
fn test_parse_error_display() {
let err = Error::parse("25.abc.0", "expected digit");
assert_eq!(
format!("{err}"),
"failed to parse `25.abc.0`: expected digit"
);
}
#[test]
fn test_nom_error_conversion() {
let nom_err = NomError::new("invalid", ErrorKind::Digit);
let err = Error::from_nom("25.invalid.0", nom_err);
assert_eq!(
format!("{err}"),
"failed to parse `25.invalid.0`: parser error: Digit"
);
}
#[test]
fn test_invalid_week_display() {
let err = Error::InvalidWeek {
week: 54,
};
assert_eq!(format!("{err}"), "invalid week number: 54, expected 1-53");
}
#[test]
fn test_invalid_year_week_display() {
let err = Error::InvalidYearWeek {
year: 25,
week: 54,
};
assert_eq!(format!("{err}"), "invalid year/week combination: 25.54");
}
#[test]
fn test_invalid_requirement_display() {
let err = Error::invalid_requirement("><25.10.0");
assert_eq!(format!("{err}"), "invalid version requirement: ><25.10.0");
}
#[test]
fn test_format_error_display() {
let err = Error::format("25-10-0");
assert_eq!(
format!("{err}"),
"expected version to match format YY.WW.PATCH (where YY is ISO year minus 2000), got: \
25-10-0"
);
}
#[test]
fn test_unexpected_char_display() {
let err = Error::UnexpectedRequirementChar {
position: 2,
found: '@',
};
assert_eq!(
format!("{err}"),
"unexpected character in requirement at position 2: @"
);
}
#[test]
fn test_empty_requirement_display() {
let err = Error::EmptyRequirement;
assert_eq!(format!("{err}"), "empty version requirement");
}
}