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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//! Error management

use std::convert::From;
use std::fmt;

/// Generic error for all weldr operations.
#[derive(Debug)]
pub enum Error {
    /// An error encountered while parsing some LDraw file content.
    Parse(ParseError),

    /// An error encountered while resolving a sub-file reference.
    Resolve(ResolveError),
}

/// Error related to parsing the content of an LDraw file.
#[derive(Debug)]
pub struct ParseError {
    /// Filename of the sub-file reference, generally relative to some canonical catalog path(s).
    pub filename: String,

    /// Optional underlying error raised by the internal parser.
    pub parse_error: Option<Box<dyn std::error::Error>>,
}

/// Error related to resolving a sub-file reference of a source file.
#[derive(Debug)]
pub struct ResolveError {
    /// Filename of the sub-file reference, generally relative to some canonical catalog path(s).
    pub filename: String,

    /// Optional underlying error raised by the resolver implementation.
    pub resolve_error: Option<Box<dyn std::error::Error>>,
}

impl ParseError {
    /// Create a [`ParseError`] that stems from an arbitrary error of an underlying parser.
    pub fn new(filename: &str, err: impl Into<Box<dyn std::error::Error>>) -> Self {
        ParseError {
            filename: filename.to_string(),
            parse_error: Some(err.into()),
        }
    }

    /// Create a [`ParseError`] that stems from a [`nom`] parsing error, capturing the [`nom::error::ErrorKind`]
    /// from the underlying parser which failed.
    pub fn new_from_nom(filename: &str, err: &nom::Err<(&[u8], nom::error::ErrorKind)>) -> Self {
        ParseError {
            filename: filename.to_string(),
            parse_error: match err {
                nom::Err::Incomplete(_) => None,
                nom::Err::Error((_, e)) => {
                    // Discard input slice due to lifetime constraint
                    let e2: nom::Err<_> = nom::Err::Error(*e);
                    Some(e2.into())
                }
                nom::Err::Failure((_, e)) => {
                    // Discard input slice due to lifetime constraint
                    let e2: nom::Err<_> = nom::Err::Failure(*e);
                    Some(e2.into())
                }
            },
        }
    }
}

impl ResolveError {
    /// Create a [`ResolveError`] that stems from an arbitrary error of an underlying resolution error.
    pub fn new(filename: &str, err: impl Into<Box<dyn std::error::Error>>) -> Self {
        ResolveError {
            filename: filename.to_string(),
            resolve_error: Some(err.into()),
        }
    }

    /// Create a [`ResolveError`] without any underlying error.
    pub fn new_raw(filename: &str) -> Self {
        ResolveError {
            filename: filename.to_string(),
            resolve_error: None,
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::Parse(ParseError {
                filename,
                parse_error,
            }) => write!(f, "parse error in file '{}': {:?}", filename, parse_error),
            Error::Resolve(ResolveError {
                filename,
                resolve_error,
            }) => write!(
                f,
                "resolve error for filename '{}': {:?}",
                filename, resolve_error
            ),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        None
        // match self {
        //     Error::Parse(ParseError {
        //         filename,
        //         parse_error,
        //     }) => parse_error,
        //     Error::Resolve(ResolveError {
        //         filename,
        //         resolve_error,
        //     }) => resolve_error,
        // }
    }
}

impl From<ResolveError> for Error {
    fn from(e: ResolveError) -> Self {
        Error::Resolve(e)
    }
}

impl From<ParseError> for Error {
    fn from(e: ParseError) -> Self {
        Error::Parse(e)
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    fn get_error() -> Result<u32, Error> {
        let underlying = Error::Parse(ParseError {
            filename: "low_level.ldr".to_string(),
            parse_error: None,
        });
        Err(Error::Resolve(ResolveError::new(
            "test_file.ldr",
            underlying,
        )))
    }

    #[test]
    fn test_error() {
        match get_error() {
            Err(e) => eprintln!("Error: {}", e),
            _ => {}
        };
    }

    #[test]
    fn test_new_from_nom() {
        let nom_error: nom::Err<(&[u8], nom::error::ErrorKind)> =
            nom::Err::Error((&b""[..], nom::error::ErrorKind::Alpha));
        let parse_error = ParseError::new_from_nom("file", &nom_error);
        assert_eq!(parse_error.filename, "file");
        assert!(parse_error.parse_error.is_some());
    }

    #[test]
    fn test_source() {
        let resolve_error = ResolveError::new_raw("file");
        let error: Error = resolve_error.into();
        assert!(std::error::Error::source(&error).is_none());
    }

    #[test]
    fn test_from() {
        let resolve_error = ResolveError::new_raw("file");
        let error: Error = resolve_error.into();
        eprintln!("err: {}", error);
        match &error {
            Error::Resolve(resolve_error) => assert_eq!(resolve_error.filename, "file"),
            _ => panic!("Unexpected error type."),
        }

        let parse_error = ParseError::new("file", error);
        let error: Error = parse_error.into();
        eprintln!("err: {}", error);
        match &error {
            Error::Parse(parse_error) => assert_eq!(parse_error.filename, "file"),
            _ => panic!("Unexpected error type."),
        }
    }
}