loot_condition_interpreter/
error.rs1use 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 UnconsumedInput(String),
20 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 UnknownSize,
93 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}