Skip to main content

mshio/
error.rs

1use std::borrow::{Borrow, Cow};
2use std::error::Error;
3use std::fmt;
4use std::fmt::{Debug, Display};
5
6use nom::error::{ErrorKind, ParseError};
7use nom::{HexDisplay, IResult};
8
9pub(crate) fn make_error<I>(input: I, kind: MshParserErrorKind) -> nom::Err<MshParserError<I>> {
10    MshParserError::from_error_kind(input, kind.clone()).into_nom_error()
11}
12
13/// Returns a combinator that always returns an error of the specified kind
14pub(crate) fn always_error<I, O>(
15    kind: MshParserErrorKind,
16) -> impl Fn(I) -> IResult<I, O, MshParserError<I>> {
17    move |i: I| Err(make_error(i, kind.clone()))
18}
19
20/// Returns a combinator that appends an if the callable returns an error
21pub(crate) fn error<I: Clone, F, O>(
22    kind: MshParserErrorKind,
23    f: F,
24) -> impl Fn(I) -> IResult<I, O, MshParserError<I>>
25where
26    F: Fn(I) -> IResult<I, O, MshParserError<I>>,
27{
28    move |i: I| f(i.clone()).with_error(i, kind.clone())
29}
30
31/// Returns a combinator that appends a context message if the callable returns an error
32pub(crate) fn context<I: Clone, F, O>(
33    ctx: &'static str,
34    f: F,
35) -> impl Fn(I) -> IResult<I, O, MshParserError<I>>
36where
37    F: Fn(I) -> IResult<I, O, MshParserError<I>>,
38{
39    move |i: I| f(i.clone()).with_context(i, ctx)
40}
41
42/// Returns a combinator that appends a context message obtained from the callable if the callable returns an error
43pub(crate) fn context_from<I: Clone, C, F, O, S: Clone + Into<Cow<'static, str>>>(
44    ctx: C,
45    f: F,
46) -> impl Fn(I) -> IResult<I, O, MshParserError<I>>
47where
48    C: Fn() -> S,
49    F: Fn(I) -> IResult<I, O, MshParserError<I>>,
50{
51    move |i: I| f(i.clone()).with_context(i, ctx())
52}
53
54/// Enum for the categories of value types that MSH files contain
55#[derive(Copy, Clone, Debug, Eq, PartialEq, thiserror::Error)]
56pub enum ValueType {
57    /// The unsigned integer or size_t type
58    #[error("unsigned integer")]
59    UnsignedInt,
60    /// The integer or int type
61    #[error("integer")]
62    Int,
63    /// The floating point or double type
64    #[error("floating point")]
65    Float,
66}
67
68/// Enum of all error kinds that may be part of a [`MshParserError`](struct.MshParserError.html) backtrace
69#[rustfmt::skip]
70#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
71pub enum MshParserErrorKind {
72    /// Error indicating that the MSH file header specifies an unsupported file format revision (only 4.1 is supported)
73    #[error("MSH file of unsupported format version loaded. Only the MSH file format specification of revision 4.1 is supported.")]
74    UnsupportedMshVersion,
75    /// Error indicating that the MSH file header specifies a size for a value type which is not supported by this crate
76    #[error("There is no parser available to parse binary {0} values with a size of {1}.")]
77    UnsupportedTypeSize(ValueType, usize),
78    /// Error indicating that the MSH file header does not conform to the file format specification
79    #[error("The MSH file header is not valid.")]
80    InvalidFileHeader,
81    /// Error indicating that the MSH body contains tokens that do not form a valid section
82    #[error("Unexpected tokens found after file header. Expected a section according to the MSH file format specification.")]
83    InvalidSectionHeader,
84    /// Error indicating that an element entity contains an [`ElementType`](../mshfile/enum.ElementType.html) that is not supported by this crate
85    #[error("An unknown element type was encountered in the MSH file.")]
86    UnknownElement,
87    /// Error indicating that a section contains too many entities (e.g. nodes, elements, groups), i.e. they do not fit into a `Vec` because `usize::MAX` is too small
88    #[error("There are too many entities to parse them into contiguous memory on the current system (usize type too small).")]
89    TooManyEntities,
90    /// Error indicating that a value was encountered in the MSH file that is out of range of the target value type
91    #[error("A {0} value could not be parsed because it was out of range of the target data type.")]
92    ValueOutOfRange(ValueType),
93    /// Error indicating that an entity tag with an invalid value was encountered, e.g. the Gmsh internally reserved value of 0 or a `max_tag` that is smaller than a `min_tag`
94    #[error("An invalid entity tag value was encountered, e.g. the internally reserved value of 0, or a max_tag that is smaller than a min_tag")]
95    InvalidTag,
96    /// Error indicating that an invalid parameter value was encountered
97    #[error("An invalid parameter value was encountered.")]
98    InvalidParameter,
99    /// Error indicating that a single element definition could not be parsed, e.g. it did not provide the correct number of node indices corresponding to the [`ElementType`](../mshfile/enum.ElementType.html) of the element block
100    #[error("An invalid element definition was encountered.")]
101    InvalidElementDefinition,
102    /// Error indicating that a single node definition could not be parsed, e.g. it does not contain three parsable floating point values for its coordinates
103    #[error("An invalid node definition was encountered.")]
104    InvalidNodeDefinition,
105    /// Error indicating that the MSH file contains a MSH format feature that is not yet supported by this crate
106    #[error("An unimplemented feature was detected.")]
107    Unimplemented,
108    /// Additional context information for pretty printing the backtrace for a user
109    #[error("{0}")]
110    Context(Cow<'static,str>),
111    /// Internal nom parser error, such as an error when parsing a single digit
112    #[error("{0:?}")]
113    NomError(ErrorKind),
114}
115
116impl MshParserErrorKind {
117    pub(crate) fn into_error<I>(self, input: I) -> MshParserError<I> {
118        MshParserError::from_error_kind(input, self)
119    }
120
121    /// Returns whether the variant is an internal nom error
122    pub fn is_nom_error(&self) -> bool {
123        match self {
124            MshParserErrorKind::NomError(_) => true,
125            _ => false,
126        }
127    }
128
129    /// Returns a reference to the context message of this error contains one
130    pub fn context(&self) -> Option<&str> {
131        match self {
132            MshParserErrorKind::Context(ctx) => Some(ctx.borrow()),
133            _ => None,
134        }
135    }
136}
137
138impl From<ErrorKind> for MshParserErrorKind {
139    fn from(ek: ErrorKind) -> Self {
140        MshParserErrorKind::NomError(ek)
141    }
142}
143
144/// Error type returned by the crate when parsing fails
145pub struct MshParserError<I> {
146    /// Error backtrace that contains per level a reference into the input where the error ocurred and the corresponding error kind
147    pub backtrace: Vec<(I, MshParserErrorKind)>,
148}
149
150impl<I> MshParserError<I> {
151    /// Creates a new empty error
152    fn new() -> Self {
153        Self {
154            backtrace: Vec::new(),
155        }
156    }
157
158    /// Construct a new error with the given input and error kind
159    pub(crate) fn from_error_kind(input: I, kind: MshParserErrorKind) -> Self {
160        Self {
161            backtrace: vec![(input, kind)],
162        }
163    }
164
165    /// Wraps the error into a (recoverable) nom::Err::Error
166    pub(crate) fn into_nom_error(self) -> nom::Err<Self> {
167        nom::Err::Error(self)
168    }
169
170    /// Wraps the error into a (unrecoverable) nom::Err::Failure
171    pub(crate) fn into_nom_failure(self) -> nom::Err<Self> {
172        nom::Err::Failure(self)
173    }
174
175    /// Append an error to the backtrace with the given input and error kind
176    pub(crate) fn with_append(mut self, input: I, kind: MshParserErrorKind) -> Self {
177        self.backtrace.push((input, kind));
178        self
179    }
180
181    /// Append a context message to the backtrace
182    pub(crate) fn with_context<S: Into<Cow<'static, str>>>(self, input: I, ctx: S) -> Self {
183        self.with_append(input, MshParserErrorKind::Context(ctx.into()))
184    }
185
186    /// Iterator that skips all errors in the beginning of the backtrace that are not actual MSH format errors (i.e. internal nom parser errors)
187    pub fn begin_msh_errors(&self) -> impl Iterator<Item = &(I, MshParserErrorKind)> {
188        self.backtrace.iter().skip_while(|(_, e)| e.is_nom_error())
189    }
190
191    /// Iterator over all errors in the backtrace that are actual MSH format errors (i.e. filters out all internal nom parser errors)
192    pub fn filter_msh_errors(&self) -> impl Iterator<Item = &(I, MshParserErrorKind)> {
193        self.backtrace.iter().filter(|(_, e)| !e.is_nom_error())
194    }
195
196    /// Returns the kind of the first error in the backtrace that is an actual MSH format error kind (i.e. skips internal nom parser errors)
197    pub fn first_msh_error(&self) -> Option<MshParserErrorKind> {
198        self.begin_msh_errors().next().map(|(_, ek)| ek).cloned()
199    }
200}
201
202impl<I: Clone> MshParserError<I> {
203    /// Returns a backtrace containing only the errors that are actual MSH format errors (i.e. without internal nom parser errors)
204    pub fn filtered_backtrace(&self) -> Vec<(I, MshParserErrorKind)> {
205        self.filter_msh_errors().cloned().collect()
206    }
207}
208
209impl<I: Debug> Debug for MshParserError<I> {
210    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
211        write!(f, "MshParserError({:?})", self.backtrace)
212    }
213}
214
215impl<I: Debug + HexDisplay + ?Sized> Display for MshParserError<&I> {
216    // TODO: Move this to a "report" method of the error.
217    // TODO: Instead, make Display implementation more simple.
218    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
219        // Remove all internal nom errors
220        let backtrace = self.filtered_backtrace();
221        if backtrace.len() > 1 {
222            write!(f, "During parsing...\n")?;
223            for (_, ek) in backtrace[1..].iter().rev() {
224                if let Some(c) = ek.context() {
225                    write!(f, "\tin {},\n", c)?;
226                } else {
227                    write!(f, "\tin {},\n", ek)?;
228                }
229            }
230            write!(f, "an error occurred: ")?;
231            write!(f, "{}\n", backtrace[0].1)?;
232            write!(
233                f,
234                "Hex dump of the file at the error location:\n{}",
235                // TODO: Limit to a reasonable number of bytes
236                backtrace[0].0.to_hex(16)
237            )?;
238            Ok(())
239        } else if backtrace.len() == 1 {
240            write!(f, "An error occurred during: ")?;
241            write!(f, "{}", backtrace[0].1)?;
242            Ok(())
243        } else {
244            write!(f, "Unknown error occurred\n")
245        }
246    }
247}
248
249impl<I> ParseError<I> for MshParserError<I> {
250    fn from_error_kind(input: I, kind: ErrorKind) -> Self {
251        Self {
252            backtrace: vec![(input, MshParserErrorKind::NomError(kind))],
253        }
254    }
255
256    fn append(input: I, kind: ErrorKind, mut other: Self) -> Self {
257        other
258            .backtrace
259            .push((input, MshParserErrorKind::NomError(kind)));
260        other
261    }
262
263    fn add_context(input: I, ctx: &'static str, mut other: Self) -> Self {
264        other
265            .backtrace
266            .push((input, MshParserErrorKind::Context(Cow::Borrowed(ctx))));
267        other
268    }
269}
270
271impl<I: Debug + HexDisplay + ?Sized> Error for MshParserError<&I> {}
272
273/// Convert a nom::Err to MshParserError
274impl<I: Debug, E: Into<MshParserError<I>>> From<nom::Err<E>> for MshParserError<I> {
275    fn from(error: nom::Err<E>) -> Self {
276        match error {
277            nom::Err::Error(ve) | nom::Err::Failure(ve) => ve.into(),
278            _ => Self::new(),
279        }
280    }
281}
282
283pub(crate) trait MapMshError<I> {
284    /// Maps the MshParserError if self contains an error
285    fn map_msh_err<F>(self, f: F) -> Self
286    where
287        F: FnOnce(MshParserError<I>) -> MshParserError<I>;
288
289    /// Appends the specified error if self already contains an error
290    fn with_error(self, input: I, kind: MshParserErrorKind) -> Self
291    where
292        Self: Sized,
293    {
294        self.map_msh_err(|e| e.with_append(input, kind))
295    }
296
297    /// Appends the given context if self already contains an error
298    fn with_context<S: Into<Cow<'static, str>>>(self, input: I, ctx: S) -> Self
299    where
300        Self: Sized,
301    {
302        self.map_msh_err(|e| e.with_context(input, ctx))
303    }
304
305    /// Obtains a context from the given callable if self already contains an error
306    fn with_context_from<S: Into<Cow<'static, str>>, C: Fn() -> S>(self, input: I, ctx: C) -> Self
307    where
308        Self: Sized,
309    {
310        self.map_msh_err(|e| e.with_context(input, ctx()))
311    }
312}
313
314/// Implementation that allows to map a MshParserError inside of an nom::Err, if it contains one
315impl<I> MapMshError<I> for nom::Err<MshParserError<I>> {
316    fn map_msh_err<F>(self, f: F) -> Self
317    where
318        F: FnOnce(MshParserError<I>) -> MshParserError<I>,
319    {
320        self.map(f)
321    }
322}
323
324/// Implementation that allows to map a MshParserError inside of an IResult, if it contains one
325impl<I, O> MapMshError<I> for IResult<I, O, MshParserError<I>> {
326    fn map_msh_err<F>(self, f: F) -> Self
327    where
328        F: FnOnce(MshParserError<I>) -> MshParserError<I>,
329    {
330        self.map_err(|err| err.map(f))
331    }
332}