Skip to content

Commit 7f4f8af

Browse files
committed
Improve error handling
1 parent 5b48256 commit 7f4f8af

File tree

1 file changed

+48
-35
lines changed

1 file changed

+48
-35
lines changed

src/main.rs

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ use yaml_rust::yaml::Yaml;
1111
use regex::Regex;
1212
use lazy_static::lazy_static;
1313

14+
macro_rules! fatalerr {
15+
() => ({
16+
eprintln!();
17+
std::process::exit(1);
18+
});
19+
($($arg:tt)*) => ({
20+
eprintln!($($arg)*);
21+
std::process::exit(1);
22+
});
23+
}
24+
1425
struct Table<'a> {
1526
path: String,
1627
file: RefCell<Box<dyn Write>>,
@@ -25,9 +36,9 @@ impl<'a> Table<'a> {
2536
None => RefCell::new(Box::new(stdout())),
2637
Some(ref file) => RefCell::new(Box::new(
2738
match filemode {
28-
"truncate" => File::create(&Path::new(file)).unwrap(),
29-
"append" => OpenOptions::new().append(true).create(true).open(&Path::new(file)).unwrap(),
30-
mode => panic!("Invalid 'mode' setting in configuration file: {}", mode)
39+
"truncate" => File::create(&Path::new(file)).unwrap_or_else(|err| fatalerr!("Error: failed to create output file '{}': {}", file, err)),
40+
"append" => OpenOptions::new().append(true).create(true).open(&Path::new(file)).unwrap_or_else(|err| fatalerr!("Error: failed to open output file '{}': {}", file, err)),
41+
mode => fatalerr!("Error: invalid 'mode' setting in configuration file: {}", mode)
3142
}
3243
))
3344
},
@@ -36,7 +47,7 @@ impl<'a> Table<'a> {
3647
}
3748
}
3849
fn write(&self, text: &str) {
39-
self.file.borrow_mut().write_all(text.as_bytes()).expect("Write error encountered; exiting...");
50+
self.file.borrow_mut().write_all(text.as_bytes()).unwrap_or_else(|err| fatalerr!("Error: IO error encountered while writing table: {}", err));
4051
}
4152
fn clear_columns(&self) {
4253
for col in &self.columns {
@@ -114,7 +125,7 @@ fn gml_to_ewkb(cell: &RefCell<String>, coll: &[Geometry], bbox: Option<&BBox>, m
114125
// println!("{:?}", geom);
115126
let code = match geom.dims {
116127
2 => 32, // Indicate EWKB where the srid follows this byte
117-
3 => 32 | 128, // Add bit to indicate the presense of Z values
128+
3 => 32 | 128, // Add bit to indicate the presence of Z values
118129
_ => {
119130
eprintln!("GML number of dimensions {} not supported", geom.dims);
120131
32
@@ -169,20 +180,20 @@ fn gml_to_ewkb(cell: &RefCell<String>, coll: &[Geometry], bbox: Option<&BBox>, m
169180
fn add_table<'a>(rowpath: &str, outfile: Option<&str>, filemode: &str, skip: Option<&'a str>, colspec: &'a [Yaml]) -> Table<'a> {
170181
let mut table = Table::new(rowpath, outfile, filemode, skip);
171182
for col in colspec {
172-
let name = col["name"].as_str().expect("Column has no 'name' entry in configuration file");
173-
let colpath = col["path"].as_str().expect("Column has no 'path' entry in configuration file");
183+
let name = col["name"].as_str().unwrap_or_else(|| fatalerr!("Error: column has no 'name' entry in configuration file"));
184+
let colpath = col["path"].as_str().unwrap_or_else(|| fatalerr!("Error: column has no 'path' entry in configuration file"));
174185
let mut path = String::from(rowpath);
175186
path.push_str(colpath);
176187
let subtable: Option<Table> = match col["cols"].is_badvalue() {
177188
true => None,
178189
false => {
179-
let file = col["file"].as_str().expect("Subtable has no 'file' entry");
180-
Some(add_table(&path, Some(file), filemode, skip, col["cols"].as_vec().expect("Subtable 'cols' entry is not an array")))
190+
let file = col["file"].as_str().unwrap_or_else(|| fatalerr!("Error: subtable has no 'file' entry"));
191+
Some(add_table(&path, Some(file), filemode, skip, col["cols"].as_vec().unwrap_or_else(|| fatalerr!("Error: subtable 'cols' entry is not an array"))))
181192
}
182193
};
183194
let hide = col["hide"].as_bool().unwrap_or(false);
184-
let include: Option<Regex> = col["incl"].as_str().map(|str| Regex::new(str).expect("Invalid regex in 'incl' entry in configuration file"));
185-
let exclude: Option<Regex> = col["excl"].as_str().map(|str| Regex::new(str).expect("Invalid regex in 'excl' entry in configuration file"));
195+
let include: Option<Regex> = col["incl"].as_str().map(|str| Regex::new(str).unwrap_or_else(|err| fatalerr!("Error: invalid regex in 'incl' entry in configuration file: {}", err)));
196+
let exclude: Option<Regex> = col["excl"].as_str().map(|str| Regex::new(str).unwrap_or_else(|err| fatalerr!("Error: invalid regex in 'excl' entry in configuration file: {}", err)));
186197
let attr = col["attr"].as_str();
187198
let convert = col["conv"].as_str();
188199
let find = col["find"].as_str();
@@ -191,12 +202,17 @@ fn add_table<'a>(rowpath: &str, outfile: Option<&str>, filemode: &str, skip: Opt
191202
let bbox = col["bbox"].as_str().and_then(BBox::from);
192203
let multitype = col["mult"].as_bool().unwrap_or(false);
193204

194-
if convert.is_some() && !vec!("xml-to-text", "gml-to-ewkb").contains(&convert.unwrap()) {
195-
panic!("Option 'convert' contains invalid value {}", convert.unwrap());
205+
if let Some(val) = convert {
206+
if !vec!("xml-to-text", "gml-to-ewkb").contains(&val) {
207+
fatalerr!("Error: option 'convert' contains invalid value: {}", val);
208+
}
209+
if val == "gml-to-ewkb" {
210+
eprintln!("Warning: gml-to-ewkb conversion is experimental and in no way complete or standards compliant; use at your own risk");
211+
}
196212
}
197213
if include.is_some() || exclude.is_some() {
198214
if convert.is_some() {
199-
panic!("Filtering (incl/excl) and 'conv' cannot be used together on a single column");
215+
fatalerr!("Error: filtering (incl/excl) and 'conv' cannot be used together on a single column");
200216
}
201217
if find.is_some() {
202218
eprintln!("Notice: when using filtering (incl/excl) and find/replace on a single column, the filter is checked before replacements");
@@ -215,24 +231,22 @@ fn add_table<'a>(rowpath: &str, outfile: Option<&str>, filemode: &str, skip: Opt
215231
table
216232
}
217233

218-
fn main() -> std::io::Result<()> {
234+
fn main() {
219235
let args: Vec<_> = env::args().collect();
220236
let bufread: Box<dyn BufRead>;
221237
if args.len() == 2 {
222238
bufread = Box::new(BufReader::new(stdin()));
223239
}
224240
else if args.len() == 3 {
225-
bufread = Box::new(BufReader::new(File::open(&args[2])?));
226-
}
227-
else {
228-
eprintln!("usage: {} <configfile> [xmlfile]", args[0]);
229-
return Ok(());
241+
bufread = Box::new(BufReader::new(File::open(&args[2]).unwrap_or_else(|err| fatalerr!("Error: failed to open input file '{}': {}", args[2], err))));
230242
}
243+
else { fatalerr!("Usage: {} <configfile> [xmlfile]", args[0]); }
231244

232245
let config = {
233246
let mut config_str = String::new();
234-
File::open(&args[1]).unwrap().read_to_string(&mut config_str).unwrap();
235-
&YamlLoader::load_from_str(&config_str).unwrap_or_else(|err| { eprintln!("Syntax error in configuration file: {}", err); std::process::exit(0); })[0]
247+
let mut file = File::open(&args[1]).unwrap_or_else(|err| fatalerr!("Error: failed to open configuration file '{}': {}", args[1], err));
248+
file.read_to_string(&mut config_str).unwrap_or_else(|err| fatalerr!("Error: failed to read configuration file '{}': {}", args[1], err));
249+
&YamlLoader::load_from_str(&config_str).unwrap_or_else(|err| fatalerr!("Error: invalid syntax in configuration file: {}", err))[0]
236250
};
237251

238252
let mut reader;
@@ -246,12 +260,12 @@ fn main() -> std::io::Result<()> {
246260
let mut filtercount = 0;
247261
let mut skipcount = 0;
248262

249-
let rowpath = config["path"].as_str().expect("No valid 'path' entry in configuration file");
250-
let colspec = config["cols"].as_vec().expect("No valid 'cols' array in configuration file");
263+
let rowpath = config["path"].as_str().unwrap_or_else(|| fatalerr!("Error: no valid 'path' entry in configuration file"));
264+
let colspec = config["cols"].as_vec().unwrap_or_else(|| fatalerr!("Error: no valid 'cols' array in configuration file"));
251265
let outfile = config["file"].as_str();
252266
let filemode = match config["mode"].is_badvalue() {
253267
true => "truncate",
254-
false => config["mode"].as_str().expect("Invalid 'mode' entry in configuration file")
268+
false => config["mode"].as_str().unwrap_or_else(|| fatalerr!("Error: invalid 'mode' entry in configuration file"))
255269
};
256270
let skip = config["skip"].as_str();
257271
let maintable = add_table(rowpath, outfile, filemode, skip, colspec);
@@ -270,14 +284,14 @@ fn main() -> std::io::Result<()> {
270284
match reader.read_event(&mut buf) {
271285
Ok(Event::Start(ref e)) => {
272286
path.push('/');
273-
path.push_str(reader.decode(e.name()).unwrap());
287+
path.push_str(reader.decode(e.name()).unwrap_or_else(|err| fatalerr!("Error: failed to decode XML tag '{}': {}", String::from_utf8_lossy(e.name()), err)));
274288
if filtered || skipped { continue; }
275289
if path == table.skip {
276290
skipped = true;
277291
continue;
278292
}
279293
else if xmltotext {
280-
text.push_str(&format!("<{}>", &e.unescape_and_decode(&reader).unwrap()));
294+
text.push_str(&format!("<{}>", &e.unescape_and_decode(&reader).unwrap_or_else(|err| fatalerr!("Error: failed to decode XML tag '{}': {}", String::from_utf8_lossy(e.name()), err))));
281295
continue;
282296
}
283297
else if gmltoewkb {
@@ -307,7 +321,7 @@ fn main() -> std::io::Result<()> {
307321
let key = reader.decode(attr.key);
308322
match key {
309323
Ok("srsName") => {
310-
let mut value = String::from(reader.decode(&attr.value).unwrap());
324+
let mut value = String::from(reader.decode(&attr.value).unwrap_or_else(|err| fatalerr!("Error: failed to decode XML attribute '{}': {}", String::from_utf8_lossy(&attr.value), err)));
311325
if let Some(i) = value.rfind("::") {
312326
value = value.split_off(i+2);
313327
}
@@ -319,7 +333,7 @@ fn main() -> std::io::Result<()> {
319333
}
320334
},
321335
Ok("srsDimension") => {
322-
let value = reader.decode(&attr.value).unwrap();
336+
let value = reader.decode(&attr.value).unwrap_or_else(|err| fatalerr!("Error: failed to decode XML attribute '{}': {}", String::from_utf8_lossy(&attr.value), err));
323337
match value.parse::<u8>() {
324338
Ok(int) => {
325339
if let Some(geom) = gmlcoll.last_mut() { geom.dims = int };
@@ -397,14 +411,14 @@ fn main() -> std::io::Result<()> {
397411
Ok(Event::Text(ref e)) => {
398412
if filtered || skipped { continue; }
399413
if xmltotext {
400-
text.push_str(&e.unescape_and_decode(&reader).unwrap());
414+
text.push_str(&e.unescape_and_decode(&reader).unwrap_or_else(|err| fatalerr!("Error: failed to decode XML text node '{}': {}", String::from_utf8_lossy(e), err)));
401415
continue;
402416
}
403417
else if gmltoewkb {
404418
if gmlpos {
405-
let value = String::from(&e.unescape_and_decode(&reader).unwrap());
419+
let value = String::from(&e.unescape_and_decode(&reader).unwrap_or_else(|err| fatalerr!("Error: failed to decode XML gmlpos '{}': {}", String::from_utf8_lossy(e), err)));
406420
for pos in value.split(' ') {
407-
gmlcoll.last_mut().unwrap().rings.last_mut().unwrap().push(pos.parse().unwrap());
421+
gmlcoll.last_mut().unwrap().rings.last_mut().unwrap().push(pos.parse().unwrap_or_else(|err| fatalerr!("Error: failed to parse GML pos '{}' into float: {}", pos, err)));
408422
}
409423
}
410424
continue;
@@ -430,7 +444,7 @@ fn main() -> std::io::Result<()> {
430444
break;
431445
}
432446
}
433-
table.columns[i].value.borrow_mut().push_str(&e.unescape_and_decode(&reader).unwrap().replace("\\", "\\\\"));
447+
table.columns[i].value.borrow_mut().push_str(&e.unescape_and_decode(&reader).unwrap_or_else(|err| fatalerr!("Error: failed to decode XML text node '{}': {}", String::from_utf8_lossy(e), err)).replace("\\", "\\\\"));
434448
if let Some(re) = &table.columns[i].include {
435449
if !re.is_match(&table.columns[i].value.borrow()) {
436450
filtered = true;
@@ -514,7 +528,7 @@ fn main() -> std::io::Result<()> {
514528
}
515529
},
516530
Ok(Event::Eof) => break,
517-
Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
531+
Err(e) => fatalerr!("Error: failed to parse XML at position {}: {}", reader.buffer_position(), e),
518532
_ => ()
519533
}
520534
buf.clear();
@@ -525,5 +539,4 @@ fn main() -> std::io::Result<()> {
525539
match filtercount { 0 => "".to_owned(), n => format!(" ({} excluded)", n) },
526540
match skipcount { 0 => "".to_owned(), n => format!(" ({} skipped)", n) }
527541
);
528-
Ok(())
529542
}

0 commit comments

Comments
 (0)