esplugin/
error.rs

1/*
2 * This file is part of esplugin
3 *
4 * Copyright (C) 2017 Oliver Hamlet
5 *
6 * esplugin is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * esplugin is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with esplugin. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20use std::error;
21use std::fmt;
22use std::io;
23use std::num::NonZeroUsize;
24use std::path::Path;
25use std::path::PathBuf;
26use std::slice::EscapeAscii;
27
28use nom::Err;
29
30#[expect(clippy::error_impl_error)]
31#[derive(Debug)]
32pub enum Error {
33    IoError(io::Error),
34    NoFilename(PathBuf),
35    ParsingIncomplete(MoreDataNeeded),
36    ParsingError(Box<[u8]>, ParsingErrorKind),
37    DecodeError(Box<[u8]>),
38    UnresolvedRecordIds(PathBuf),
39    PluginMetadataNotFound(String),
40}
41
42impl From<Err<nom::error::Error<&[u8]>>> for Error {
43    fn from(error: Err<nom::error::Error<&[u8]>>) -> Self {
44        match error {
45            Err::Incomplete(nom::Needed::Unknown) => {
46                Error::ParsingIncomplete(MoreDataNeeded::UnknownSize)
47            }
48            Err::Incomplete(nom::Needed::Size(size)) => {
49                Error::ParsingIncomplete(MoreDataNeeded::Size(size))
50            }
51            Err::Error(err) | Err::Failure(err) => Error::ParsingError(
52                err.input.into(),
53                ParsingErrorKind::GenericParserError(err.code.description().to_owned()),
54            ),
55        }
56    }
57}
58
59impl From<io::Error> for Error {
60    fn from(error: io::Error) -> Self {
61        Error::IoError(error)
62    }
63}
64
65impl fmt::Display for Error {
66    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67        match self {
68            Error::IoError(x) => x.fmt(f),
69            Error::NoFilename(path) => {
70                write!(
71                    f,
72                    "The plugin path \"{}\" has no filename part",
73                    escape_ascii(path)
74                )
75            }
76            Error::ParsingIncomplete(MoreDataNeeded::UnknownSize) => write!(
77                f,
78                "An unknown number of bytes of additional input was expected by the plugin parser"
79            ),
80            Error::ParsingIncomplete(MoreDataNeeded::Size(size)) => write!(
81                f,
82                "{size} bytes of additional input was expected by the plugin parser"
83            ),
84            Error::ParsingError(input, kind) => write!(
85                f,
86                "An error was encountered while parsing the plugin content \"{}\": {kind}",
87                input.escape_ascii()
88            ),
89            Error::DecodeError(bytes) => write!(
90                f,
91                "Plugin string content could not be decoded from Windows-1252, content is \"{}\"",
92                bytes.escape_ascii()
93            ),
94            Error::UnresolvedRecordIds(path) => {
95                write!(
96                    f,
97                    "Record IDs are unresolved for plugin at \"{}\"",
98                    escape_ascii(path)
99                )
100            }
101            Error::PluginMetadataNotFound(plugin) => {
102                write!(f, "Plugin metadata for \"{plugin}\" not found")
103            }
104        }
105    }
106}
107
108impl error::Error for Error {
109    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
110        match self {
111            Error::IoError(x) => Some(x),
112            _ => None,
113        }
114    }
115}
116
117fn escape_ascii(path: &Path) -> EscapeAscii {
118    path.as_os_str().as_encoded_bytes().escape_ascii()
119}
120
121#[derive(Debug)]
122pub enum ParsingErrorKind {
123    /// The `Vec<u8>` field is the expected record type.
124    UnexpectedRecordType(Vec<u8>),
125    /// The usize field is the expected minimum data length.
126    SubrecordDataTooShort(usize),
127    /// The String field is the name of the parser that errored.
128    GenericParserError(String),
129}
130
131impl fmt::Display for ParsingErrorKind {
132    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133        match self {
134            ParsingErrorKind::UnexpectedRecordType(v) => {
135                write!(f, "Expected record type \"{}\"", v.escape_ascii())
136            }
137            ParsingErrorKind::SubrecordDataTooShort(s) => write!(
138                f,
139                "Subrecord data field too short, expected at least {s} bytes",
140            ),
141            ParsingErrorKind::GenericParserError(e) => write!(f, "Error in parser: {e}"),
142        }
143    }
144}
145
146#[derive(Debug, PartialEq, Eq, Clone, Copy)]
147pub enum MoreDataNeeded {
148    /// It's not known how much more data are needed
149    UnknownSize,
150    /// Contains the number of bytes of data that are needed
151    Size(NonZeroUsize),
152}