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