Skip to main content

js_semver/
error.rs

1use core::fmt;
2
3/// A structured semver parse error classification.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub(crate) enum SemverErrorKind {
6    /// A non-semver suffix or other unexpected character was found.
7    UnexpectedCharacter(char),
8    /// The input exceeded the maximum accepted length.
9    MaxLengthExceeded,
10    /// The input exceeded `MAX_SAFE_INTEGER`.
11    MaxSafeIntegerExceeded,
12    /// The entire input was empty.
13    Empty,
14    /// An empty segment was encountered.
15    EmptySegment,
16    /// A partial version ended with a dot.
17    TrailingDot,
18    /// A dot appeared in an unexpected position.
19    UnexpectedDot,
20    /// A numeric component had a leading zero.
21    LeadingZero,
22    /// A numeric component was invalid.
23    InvalidNumber,
24    /// A required version component was missing.
25    MissingVersionSegment,
26    /// An operator was not followed by a version.
27    MissingVersionAfterOperator(&'static str),
28}
29
30impl fmt::Display for SemverErrorKind {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            Self::UnexpectedCharacter(ch) => write!(f, "unexpected character: '{ch}'"),
34            Self::MaxLengthExceeded => f.write_str("maximum length of 256 characters exceeded"),
35            Self::MaxSafeIntegerExceeded => f.write_str("number exceeds MAX_SAFE_INTEGER"),
36            Self::Empty => f.write_str("empty"),
37            Self::EmptySegment => f.write_str("empty segment"),
38            Self::TrailingDot => f.write_str("trailing dot"),
39            Self::UnexpectedDot => f.write_str("unexpected dot"),
40            Self::LeadingZero => f.write_str("leading zero"),
41            Self::InvalidNumber => f.write_str("invalid number"),
42            Self::MissingVersionSegment => f.write_str("missing version segment"),
43            Self::MissingVersionAfterOperator(operator) => {
44                write!(f, "missing version after {operator}")
45            }
46        }
47    }
48}
49
50/// Error returned when a version or range string cannot be parsed.
51///
52/// # Examples
53///
54/// ```rust
55/// use js_semver::{SemverError, Version};
56///
57/// let err: SemverError = Version::parse("1.a.b").unwrap_err();
58/// eprintln!("{err}");
59/// ```
60///
61/// # Note
62///
63/// Do not depend on exact error message strings. The `Display` output is
64/// intended for humans and may change between releases.
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct SemverError {
67    kind: SemverErrorKind,
68}
69
70impl fmt::Display for SemverError {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        self.kind.fmt(f)
73    }
74}
75
76#[cfg(feature = "std")]
77impl std::error::Error for SemverError {}
78
79impl From<SemverErrorKind> for SemverError {
80    fn from(kind: SemverErrorKind) -> Self {
81        Self { kind }
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    #[cfg(not(feature = "std"))]
88    use alloc::string::ToString;
89
90    use super::{SemverError, SemverErrorKind};
91
92    #[test]
93    fn semver_error_kind_display_variants() {
94        let cases = [
95            (
96                SemverErrorKind::UnexpectedCharacter('x'),
97                "unexpected character: 'x'",
98            ),
99            (
100                SemverErrorKind::MaxLengthExceeded,
101                "maximum length of 256 characters exceeded",
102            ),
103            (
104                SemverErrorKind::MaxSafeIntegerExceeded,
105                "number exceeds MAX_SAFE_INTEGER",
106            ),
107            (SemverErrorKind::Empty, "empty"),
108            (SemverErrorKind::EmptySegment, "empty segment"),
109            (SemverErrorKind::TrailingDot, "trailing dot"),
110            (SemverErrorKind::UnexpectedDot, "unexpected dot"),
111            (SemverErrorKind::LeadingZero, "leading zero"),
112            (SemverErrorKind::InvalidNumber, "invalid number"),
113            (
114                SemverErrorKind::MissingVersionSegment,
115                "missing version segment",
116            ),
117            (
118                SemverErrorKind::MissingVersionAfterOperator(">="),
119                "missing version after >=",
120            ),
121        ];
122
123        for (kind, expected) in cases {
124            assert_eq!(kind.to_string(), expected);
125            let error: SemverError = kind.into();
126            assert_eq!(error.to_string(), expected);
127        }
128    }
129}