Skip to content

Commit 5758d20

Browse files
authored
Add error handling to parse functions (#11)
Add error handling to parse functions, via the `Error` enum. This adds basic errors for parse and resolve, though provides little context on the actual error at this time.
1 parent 56ecc40 commit 5758d20

File tree

3 files changed

+237
-29
lines changed

3 files changed

+237
-29
lines changed

src/error.rs

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
//! Error management
2+
3+
use std::convert::From;
4+
use std::fmt;
5+
6+
/// Generic error for all weldr operations.
7+
#[derive(Debug)]
8+
pub enum Error {
9+
/// An error encountered while parsing some LDraw file content.
10+
Parse(ParseError),
11+
12+
/// An error encountered while resolving a sub-file reference.
13+
Resolve(ResolveError),
14+
}
15+
16+
#[derive(Debug)]
17+
pub struct ParseError {
18+
pub filename: String,
19+
pub parse_error: Option<Box<dyn std::error::Error>>,
20+
}
21+
22+
#[derive(Debug)]
23+
pub struct ResolveError {
24+
pub filename: String,
25+
pub resolve_error: Option<Box<dyn std::error::Error>>,
26+
}
27+
28+
impl ParseError {
29+
/// Create a [`ParseError`] that stems from an arbitrary error of an underlying parser.
30+
pub fn new(filename: &str, err: impl Into<Box<dyn std::error::Error>>) -> Self {
31+
ParseError {
32+
filename: filename.to_string(),
33+
parse_error: Some(err.into()),
34+
}
35+
}
36+
37+
/// Create a [`ParseError`] that stems from a [`nom`] parsing error, capturing the [`nom::error::ErrorKind`]
38+
/// from the underlying parser which failed.
39+
pub fn new_from_nom(filename: &str, err: &nom::Err<(&[u8], nom::error::ErrorKind)>) -> Self {
40+
ParseError {
41+
filename: filename.to_string(),
42+
parse_error: match err {
43+
nom::Err::Incomplete(_) => None,
44+
nom::Err::Error((_, e)) => {
45+
// Discard input slice due to lifetime constraint
46+
let e2: nom::Err<_> = nom::Err::Error(*e);
47+
Some(e2.into())
48+
}
49+
nom::Err::Failure((_, e)) => {
50+
// Discard input slice due to lifetime constraint
51+
let e2: nom::Err<_> = nom::Err::Failure(*e);
52+
Some(e2.into())
53+
}
54+
},
55+
}
56+
}
57+
}
58+
59+
impl ResolveError {
60+
/// Create a [`ResolveError`] that stems from an arbitrary error of an underlying resolution error.
61+
pub fn new(filename: &str, err: impl Into<Box<dyn std::error::Error>>) -> Self {
62+
ResolveError {
63+
filename: filename.to_string(),
64+
resolve_error: Some(err.into()),
65+
}
66+
}
67+
68+
/// Create a [`ResolveError`] without any underlying error.
69+
pub fn new_raw(filename: &str) -> Self {
70+
ResolveError {
71+
filename: filename.to_string(),
72+
resolve_error: None,
73+
}
74+
}
75+
}
76+
77+
impl fmt::Display for Error {
78+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79+
match self {
80+
Error::Parse(ParseError {
81+
filename,
82+
parse_error,
83+
}) => write!(f, "parse error in file '{}': {:?}", filename, parse_error),
84+
Error::Resolve(ResolveError {
85+
filename,
86+
resolve_error,
87+
}) => write!(
88+
f,
89+
"resolve error for filename '{}': {:?}",
90+
filename, resolve_error
91+
),
92+
}
93+
}
94+
}
95+
96+
impl std::error::Error for Error {
97+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
98+
None
99+
// match self {
100+
// Error::Parse(ParseError {
101+
// filename,
102+
// parse_error,
103+
// }) => parse_error,
104+
// Error::Resolve(ResolveError {
105+
// filename,
106+
// resolve_error,
107+
// }) => resolve_error,
108+
// }
109+
}
110+
}
111+
112+
impl From<ResolveError> for Error {
113+
fn from(e: ResolveError) -> Self {
114+
Error::Resolve(e)
115+
}
116+
}
117+
118+
impl From<ParseError> for Error {
119+
fn from(e: ParseError) -> Self {
120+
Error::Parse(e)
121+
}
122+
}
123+
124+
#[cfg(test)]
125+
mod tests {
126+
127+
use super::*;
128+
129+
fn get_error() -> Result<u32, Error> {
130+
let underlying = Error::Parse(ParseError {
131+
filename: "low_level.ldr".to_string(),
132+
parse_error: None,
133+
});
134+
Err(Error::Resolve(ResolveError::new(
135+
"test_file.ldr",
136+
underlying,
137+
)))
138+
}
139+
140+
#[test]
141+
fn test_error() {
142+
match get_error() {
143+
Err(e) => println!("Error: {}", e),
144+
_ => {}
145+
};
146+
}
147+
148+
#[test]
149+
fn test_new_from_nom() {
150+
let nom_error: nom::Err<(&[u8], nom::error::ErrorKind)> =
151+
nom::Err::Error((&b""[..], nom::error::ErrorKind::Alpha));
152+
let parse_error = ParseError::new_from_nom("file", &nom_error);
153+
assert_eq!(parse_error.filename, "file");
154+
assert!(parse_error.parse_error.is_some());
155+
}
156+
157+
#[test]
158+
fn test_source() {
159+
let resolve_error = ResolveError::new_raw("file");
160+
let error: Error = resolve_error.into();
161+
assert!(std::error::Error::source(&error).is_none());
162+
}
163+
164+
#[test]
165+
fn test_from() {
166+
let resolve_error = ResolveError::new_raw("file");
167+
let error: Error = resolve_error.into();
168+
println!("err: {}", error);
169+
match &error {
170+
Error::Resolve(resolve_error) => assert_eq!(resolve_error.filename, "file"),
171+
_ => panic!("Unexpected error type."),
172+
}
173+
174+
let parse_error = ParseError::new("file", error);
175+
let error: Error = parse_error.into();
176+
println!("err: {}", error);
177+
match &error {
178+
Error::Parse(parse_error) => assert_eq!(parse_error.filename, "file"),
179+
_ => panic!("Unexpected error type."),
180+
}
181+
}
182+
}

src/lib.rs

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ pub use math::Vec3;
109109
#[cfg(feature = "cgmath")]
110110
pub type Vec3 = cgmath::Vector3<f32>;
111111

112+
pub mod error;
113+
114+
pub use error::{Error, ParseError, ResolveError};
115+
112116
// LDraw File Format Specification
113117
// https://www.ldraw.org/article/218.html
114118

@@ -603,15 +607,15 @@ named!(
603607
/// Vec3{ x: 1.0, y: 1.0, z: 1.0 }
604608
/// ]
605609
/// });
606-
/// assert_eq!(parse_raw(b"0 this is a comment\n2 16 0 0 0 1 1 1"), vec![cmd0, cmd1]);
610+
/// assert_eq!(parse_raw(b"0 this is a comment\n2 16 0 0 0 1 1 1").unwrap(), vec![cmd0, cmd1]);
607611
/// }
608612
/// ```
609-
pub fn parse_raw(ldr_content: &[u8]) -> Vec<CommandType> {
613+
pub fn parse_raw(ldr_content: &[u8]) -> Result<Vec<CommandType>, Error> {
610614
// "An LDraw file consists of one command per line."
611-
match many0(read_line)(ldr_content) {
612-
Ok((_, cmds)) => cmds,
613-
Err(_) => panic!("TODO - Error handling"),
614-
}
615+
many0(read_line)(ldr_content).map_or_else(
616+
|e| Err(Error::Parse(ParseError::new_from_nom("", &e))),
617+
|(_, cmds)| Ok(cmds),
618+
)
615619
}
616620

617621
struct QueuedFileRef {
@@ -710,17 +714,20 @@ fn resolve_file_refs(
710714
}
711715
}
712716

713-
fn load_and_parse_single_file(filename: &str, resolver: &dyn FileRefResolver) -> SourceFile {
717+
fn load_and_parse_single_file(
718+
filename: &str,
719+
resolver: &dyn FileRefResolver,
720+
) -> Result<SourceFile, Error> {
714721
//match resolver.resolve(filename) {}
715-
let raw_content = resolver.resolve(filename);
722+
let raw_content = resolver.resolve(filename)?;
716723
let mut source_file = SourceFile {
717724
filename: filename.to_string(),
718725
raw_content,
719726
cmds: Vec::new(),
720727
};
721-
let cmds = parse_raw(&source_file.raw_content[..]);
728+
let cmds = parse_raw(&source_file.raw_content[..])?;
722729
source_file.cmds = cmds;
723-
source_file
730+
Ok(source_file)
724731
}
725732

726733
/// Parse a single file and its sub-file references recursively.
@@ -731,35 +738,36 @@ fn load_and_parse_single_file(filename: &str, resolver: &dyn FileRefResolver) ->
731738
/// up populating the given `source_map`, which can be pre-populated manually or from a
732739
/// previous call with already loaded and parsed files.
733740
/// ```rust
734-
/// use weldr::{ FileRefResolver, parse };
741+
/// use weldr::{ FileRefResolver, parse, ResolveError };
735742
/// use std::collections::HashMap;
736743
///
737744
/// struct MyCustomResolver {};
738745
///
739746
/// impl FileRefResolver for MyCustomResolver {
740-
/// fn resolve(&self, filename: &str) -> Vec<u8> {
741-
/// vec![] // replace with custom impl
747+
/// fn resolve(&self, filename: &str) -> Result<Vec<u8>, ResolveError> {
748+
/// Ok(vec![]) // replace with custom impl
742749
/// }
743750
/// }
744751
///
745-
/// fn main() {
752+
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
746753
/// let resolver = MyCustomResolver{};
747754
/// let mut source_map = HashMap::new();
748-
/// let root_file = parse("root.ldr", &resolver, &mut source_map);
755+
/// let root_file = parse("root.ldr", &resolver, &mut source_map)?;
749756
/// assert_eq!(root_file.borrow().filename, "root.ldr");
757+
/// Ok(())
750758
/// }
751759
/// ```
752760
pub fn parse(
753761
filename: &str,
754762
resolver: &dyn FileRefResolver,
755763
source_map: &mut HashMap<String, Rc<RefCell<SourceFile>>>,
756-
) -> Rc<RefCell<SourceFile>> {
764+
) -> Result<Rc<RefCell<SourceFile>>, Error> {
757765
if let Some(existing_file) = source_map.get(filename) {
758-
return Rc::clone(existing_file);
766+
return Ok(Rc::clone(existing_file));
759767
}
760768
let mut queue = ResolveQueue::new();
761769
println!("Loading root file: {}", filename);
762-
let root_file = load_and_parse_single_file(filename, resolver);
770+
let root_file = load_and_parse_single_file(filename, resolver)?;
763771
let root_file = Rc::new(RefCell::new(root_file));
764772
{
765773
println!(
@@ -777,7 +785,7 @@ pub fn parse(
777785
println!("Already parsed; reusing sub-file: {}", filename);
778786
} else {
779787
println!("Not yet parsed; parsing sub-file: {}", filename);
780-
let source_file = load_and_parse_single_file(&filename[..], resolver);
788+
let source_file = load_and_parse_single_file(&filename[..], resolver)?;
781789
let subfile = Rc::new(RefCell::new(source_file));
782790
println!(
783791
"Post-loading resolving subfile refs of sub-file: {}",
@@ -796,7 +804,7 @@ pub fn parse(
796804
resolve_file_refs(queued_file.0.referer, &mut queue, source_map);
797805
}
798806
}
799-
root_file
807+
Ok(root_file)
800808
}
801809

802810
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) META command:
@@ -1029,7 +1037,7 @@ pub trait FileRefResolver {
10291037
/// Unix style `\n` or Windows style `\r\n`.
10301038
///
10311039
/// See [`parse`] for usage.
1032-
fn resolve(&self, filename: &str) -> Vec<u8>;
1040+
fn resolve(&self, filename: &str) -> Result<Vec<u8>, ResolveError>;
10331041
}
10341042

10351043
#[cfg(test)]
@@ -1980,7 +1988,7 @@ mod tests {
19801988
],
19811989
});
19821990
assert_eq!(
1983-
parse_raw(b"0 this is a comment\n2 16 0 0 0 1 1 1"),
1991+
parse_raw(b"0 this is a comment\n2 16 0 0 0 1 1 1").unwrap(),
19841992
vec![cmd0, cmd1]
19851993
);
19861994

@@ -2012,7 +2020,8 @@ mod tests {
20122020
file: SubFileRef::UnresolvedRef("aa/aaaaddd".to_string()),
20132021
});
20142022
assert_eq!(
2015-
parse_raw(b"\n0 this doesn't matter\n\n1 16 0 0 0 1 0 0 0 1 0 0 0 1 aa/aaaaddd"),
2023+
parse_raw(b"\n0 this doesn't matter\n\n1 16 0 0 0 1 0 0 0 1 0 0 0 1 aa/aaaaddd")
2024+
.unwrap(),
20162025
vec![cmd0, cmd1]
20172026
);
20182027

@@ -2046,7 +2055,8 @@ mod tests {
20462055
assert_eq!(
20472056
parse_raw(
20482057
b"\r\n0 this doesn't \"matter\"\r\n\r\n1 16 0 0 0 1 0 0 0 1 0 0 0 1 aa/aaaaddd\n"
2049-
),
2058+
)
2059+
.unwrap(),
20502060
vec![cmd0, cmd1]
20512061
);
20522062

@@ -2101,7 +2111,8 @@ mod tests {
21012111
assert_eq!(
21022112
parse_raw(
21032113
b"1 16 0 0 0 1 0 0 0 1 0 0 0 1 aa/aaaaddd\n1 16 0 0 0 1 0 0 0 1 0 0 0 1 aa/aaaaddd"
2104-
),
2114+
)
2115+
.unwrap(),
21052116
vec![cmd0, cmd1]
21062117
);
21072118
}

0 commit comments

Comments
 (0)