version_number/parsers/original/
error.rs

1use super::*;
2use crate::parsers::error::ExpectedError;
3use crate::parsers::NumericError;
4
5/// The top-level error type for an _orignal parser_.
6#[derive(Clone, Debug, thiserror::Error)]
7#[error(
8    "Unable to parse '{input}' to a version number: {reason}{}",
9    self.fmt()
10)]
11pub struct OriginalParserError {
12    input: String,
13    cursor: Option<usize>,
14    reason: ErrorReason,
15}
16
17impl OriginalParserError {
18    /// The reason why the given input could not be parsed to a [`Version`].
19    pub fn reason(&self) -> &ErrorReason {
20        &self.reason
21    }
22}
23
24impl OriginalParserError {
25    pub(crate) fn from_parser(parser: &Parser<'_>, reason: ErrorReason) -> Self {
26        Self {
27            input: String::from_utf8_lossy(parser.slice).to_string(),
28            cursor: None,
29            reason,
30        }
31    }
32
33    pub(crate) fn from_parser_with_cursor(
34        slice: &Parser<'_>,
35        cursor: usize,
36        reason: ErrorReason,
37    ) -> Self {
38        Self {
39            input: String::from_utf8_lossy(slice.slice).to_string(),
40            cursor: Some(cursor),
41            reason,
42        }
43    }
44
45    fn fmt(&self) -> String {
46        if let Some(c) = self.cursor {
47            Self::squiggle(&self.input, c).unwrap_or_default()
48        } else {
49            String::default()
50        }
51    }
52
53    fn squiggle(input: &str, cursor: usize) -> Option<String> {
54        let lead = "Unable to parse '".len();
55        let err_from = lead + cursor;
56        let err_end = input.len().checked_sub(cursor + 1)?; // this may fail to look as expected if the input contains multiple lines
57
58        let spaces = std::iter::repeat_with(|| " ").take(err_from);
59        let marker = std::iter::once_with(|| "^");
60        let squiggle = std::iter::repeat_with(|| "~").take(err_end);
61        let newline = std::iter::once_with(|| "\n");
62
63        Some(
64            newline
65                .clone()
66                .chain(spaces)
67                .chain(marker)
68                .chain(squiggle)
69                .chain(newline)
70                .collect(),
71        )
72    }
73}
74
75/// Reasons for why a given input cannot be parsed to a [`Version`].
76#[derive(Clone, Debug, thiserror::Error, Eq, PartialEq)]
77pub enum ErrorReason {
78    /// When this error variant is returned, the parser expected that no more
79    /// tokens should be present, but instead 1 or more additional tokens
80    /// were not parsed yet. The `extra_input` field contains the remaining
81    /// tokens.
82    ///
83    /// The error display implementation tries to print these remaining tokens
84    /// as a [`String`].
85    #[error("Expected end of input after parsing third version number component, but got: '{}'", String::from_utf8_lossy(.extra_input.as_slice()))]
86    ExpectedEndOfInput {
87        /// A `Vec` of unexpected tokens, which were still present while the parser
88        /// expected to have reached the end-of-input for the given input.
89        extra_input: Vec<u8>,
90    },
91
92    /// When this error variant is returned, the '.' token was expected, but
93    /// a different token was present, or the end-of-input reached.
94    ///
95    /// The `got` field shows the token read.
96    #[error(
97        "Expected the dot-separator '.', but got '{}'",
98        .got.map(|c| String::from(char::from(c))).unwrap_or_else(|| "EOI".to_string()),
99    )]
100    ExpectedSeparator {
101        /// Token read, or `None` if we unexpectedly got the end-of-input.
102        got: Option<u8>,
103    },
104
105    /// When this error variant is returned, a numeric token was expected, but
106    /// a different token was present, or the end-of-input reached.
107    #[error(
108        "Expected 0-9, but got '{}'",
109        .got.map(|c| String::from(char::from(c))).unwrap_or_else(|| "EOI".to_string()),
110    )]
111    ExpectedNumericToken {
112        /// Token read, or `None` if we unexpectedly got the end-of-input.
113        got: Option<u8>,
114    },
115
116    /// An error variant for faults when parsing and constructing a number.
117    #[error(transparent)]
118    NumberError(#[from] NumberError),
119}
120
121/// An error type for faults relating to parsing and constructing numbers.
122#[derive(Clone, Debug, thiserror::Error, Eq, PartialEq)]
123pub enum NumberError {
124    /// When this error variant is returned, the parser detected that the number started with a leading
125    /// zero, which is not allowed for number components.
126    #[error("Number may not start with a leading zero, unless the complete component is '0'")]
127    LeadingZero,
128
129    /// This error variant is returned if the number would overflow.
130    ///
131    /// Each number component consists of a 64 bits unsigned integer.
132    #[error("Overflow: Found number component which would be larger than the maximum supported number (max={})", u64::MAX)]
133    Overflow,
134}
135
136impl From<OriginalParserError> for ParserError {
137    fn from(value: OriginalParserError) -> Self {
138        match value.reason {
139            ErrorReason::NumberError(e) => match e {
140                NumberError::LeadingZero => ParserError::Numeric(NumericError::LeadingZero),
141                NumberError::Overflow => ParserError::Numeric(NumericError::Overflow),
142            },
143            ErrorReason::ExpectedEndOfInput { extra_input } => {
144                ParserError::Expected(ExpectedError::EndOfInput {
145                    at: value.cursor,
146                    got: char::from(extra_input[0]),
147                })
148            }
149            ErrorReason::ExpectedSeparator { got } => {
150                ParserError::Expected(ExpectedError::Separator {
151                    at: value.cursor,
152                    got: got.map(char::from),
153                })
154            }
155            ErrorReason::ExpectedNumericToken { got } => {
156                ParserError::Expected(ExpectedError::Numeric {
157                    at: value.cursor,
158                    got: got.map(char::from),
159                })
160            }
161        }
162    }
163}