loot_condition_interpreter/
error.rs

1use std::error;
2use std::fmt;
3use std::io;
4use std::num::NonZeroUsize;
5use std::num::ParseIntError;
6use std::path::Path;
7use std::path::PathBuf;
8use std::slice::EscapeAscii;
9
10use nom::error::ErrorKind;
11use nom::Err;
12
13#[expect(clippy::error_impl_error)]
14#[derive(Debug)]
15#[non_exhaustive]
16pub enum Error {
17    ParsingIncomplete(MoreDataNeeded),
18    // The string is the input that was not parsed.
19    UnconsumedInput(String),
20    /// The string is the input at which the error was encountered.
21    ParsingError(String, ParsingErrorKind),
22    PeParsingError(PathBuf, Box<dyn error::Error + Send + Sync + 'static>),
23    IoError(PathBuf, io::Error),
24}
25
26fn escape<I: fmt::Display>(input: I) -> String {
27    input.to_string().replace('"', "\\\"")
28}
29
30impl<I: fmt::Debug + fmt::Display> From<Err<ParsingError<I>>> for Error {
31    fn from(error: Err<ParsingError<I>>) -> Self {
32        match error {
33            Err::Incomplete(nom::Needed::Unknown) => {
34                Error::ParsingIncomplete(MoreDataNeeded::UnknownSize)
35            }
36            Err::Incomplete(nom::Needed::Size(size)) => {
37                Error::ParsingIncomplete(MoreDataNeeded::Size(size))
38            }
39            Err::Error(e) | Err::Failure(e) => Error::ParsingError(escape(e.input), e.kind),
40        }
41    }
42}
43
44impl fmt::Display for Error {
45    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46        match self {
47            Error::ParsingIncomplete(MoreDataNeeded::UnknownSize) => write!(
48                f,
49                "An unknown number of bytes of additional input was expected by the parser"
50            ),
51            Error::ParsingIncomplete(MoreDataNeeded::Size(size)) => write!(
52                f,
53                "{size} bytes of additional input was expected by the parser"
54            ),
55            Error::UnconsumedInput(i) => {
56                write!(f, "The parser did not consume the following input: \"{i}\"")
57            }
58            Error::ParsingError(i, e) => write!(
59                f,
60                "An error was encountered while parsing the expression \"{i}\": {e}"
61            ),
62            Error::PeParsingError(p, e) => write!(
63                f,
64                "An error was encountered while reading the version fields of \"{}\": {}",
65                escape_ascii(p),
66                e
67            ),
68            Error::IoError(p, e) => write!(
69                f,
70                "An error was encountered while accessing the path \"{}\": {}",
71                escape_ascii(p),
72                e
73            ),
74        }
75    }
76}
77
78impl error::Error for Error {
79    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
80        match self {
81            Error::ParsingError(_, e) => Some(e),
82            Error::PeParsingError(_, e) => Some(e.as_ref()),
83            Error::IoError(_, e) => Some(e),
84            _ => None,
85        }
86    }
87}
88
89#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
90pub enum MoreDataNeeded {
91    /// It's not known how much more data are needed
92    UnknownSize,
93    /// Contains the number of bytes of data that are needed
94    Size(NonZeroUsize),
95}
96
97#[derive(Clone, Debug, Eq, PartialEq)]
98pub struct ParsingError<I: fmt::Debug + fmt::Display> {
99    input: I,
100    kind: ParsingErrorKind,
101}
102
103impl<I: fmt::Debug + fmt::Display> From<(I, ErrorKind)> for ParsingError<I> {
104    fn from((input, kind): (I, ErrorKind)) -> Self {
105        use nom::error::ParseError;
106        ParsingError::from_error_kind(input, kind)
107    }
108}
109
110impl<I: fmt::Debug + fmt::Display> From<nom::error::Error<I>> for ParsingError<I> {
111    fn from(error: nom::error::Error<I>) -> Self {
112        use nom::error::ParseError;
113        ParsingError::from_error_kind(error.input, error.code)
114    }
115}
116
117impl<I: fmt::Debug + fmt::Display> fmt::Display for ParsingError<I> {
118    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119        write!(
120            f,
121            "An error was encountered while parsing the expression \"{}\": {}",
122            self.input, self.kind
123        )
124    }
125}
126
127impl<I: fmt::Debug + fmt::Display> error::Error for ParsingError<I> {
128    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
129        self.kind.source()
130    }
131}
132
133impl<I: fmt::Debug + fmt::Display> nom::error::ParseError<I> for ParsingError<I> {
134    fn from_error_kind(input: I, kind: ErrorKind) -> Self {
135        ParsingError {
136            input,
137            kind: ParsingErrorKind::GenericParserError(kind.description().to_owned()),
138        }
139    }
140
141    fn append(_: I, _: ErrorKind, other: Self) -> Self {
142        other
143    }
144}
145
146#[derive(Clone, Debug, Eq, PartialEq)]
147pub enum ParsingErrorKind {
148    InvalidRegexSyntax(String),
149    InvalidRegexUnknown,
150    InvalidCrc(ParseIntError),
151    PathEndsInADirectorySeparator(PathBuf),
152    PathIsNotInGameDirectory(PathBuf),
153    GenericParserError(String),
154}
155
156impl ParsingErrorKind {
157    pub fn at<I: fmt::Debug + fmt::Display>(self, input: I) -> ParsingError<I> {
158        ParsingError { input, kind: self }
159    }
160}
161
162impl From<regex::Error> for ParsingErrorKind {
163    fn from(error: regex::Error) -> Self {
164        match error {
165            regex::Error::Syntax(s) => ParsingErrorKind::InvalidRegexSyntax(s),
166            _ => ParsingErrorKind::InvalidRegexUnknown,
167        }
168    }
169}
170
171impl From<ParseIntError> for ParsingErrorKind {
172    fn from(error: ParseIntError) -> Self {
173        ParsingErrorKind::InvalidCrc(error)
174    }
175}
176
177impl error::Error for ParsingErrorKind {
178    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
179        match self {
180            ParsingErrorKind::InvalidCrc(e) => Some(e),
181            _ => None,
182        }
183    }
184}
185
186impl fmt::Display for ParsingErrorKind {
187    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
188        match self {
189            ParsingErrorKind::InvalidRegexSyntax(s) => write!(f, "{s}"),
190            ParsingErrorKind::InvalidRegexUnknown => write!(f, "Unknown regex parsing error"),
191            ParsingErrorKind::InvalidCrc(e) => e.fmt(f),
192            ParsingErrorKind::PathEndsInADirectorySeparator(p) => {
193                write!(f, "\"{}\" ends in a directory separator", escape_ascii(p))
194            }
195            ParsingErrorKind::PathIsNotInGameDirectory(p) => {
196                write!(f, "\"{}\" is not in the game directory", escape_ascii(p))
197            }
198            ParsingErrorKind::GenericParserError(e) => write!(f, "Error in parser: {e}"),
199        }
200    }
201}
202
203fn escape_ascii(path: &Path) -> EscapeAscii<'_> {
204    path.as_os_str().as_encoded_bytes().escape_ascii()
205}