Skip to content

Commit

Permalink
Switch to SNAFU
Browse files Browse the repository at this point in the history
Better error messages for the user
  • Loading branch information
bruceadams committed Oct 18, 2019
1 parent 132a7de commit d6fcb95
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 39 deletions.
77 changes: 75 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "yj"
version = "1.0.2"
version = "1.1.0"
authors = ["Bruce Adams <[email protected]>"]

categories = ["command-line-utilities"]
Expand All @@ -16,9 +16,10 @@ repository = "https://github.com/bruceadams/yj"
travis-ci = { repository = "bruceadams/yj" }

[dependencies]
failure = "0.1.6"
exitfailure = "0.5.1"
serde_json = "1.0.41"
serde_yaml = "0.8.11"
snafu = "0.5.0"

[dependencies.structopt]
features = ["wrap_help"]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Simple command line tool to convert a YAML input file into a JSON output file.

```bash
$ yj --help
yj 1.0.1
yj 1.1.0
Bruce Adams <[email protected]>
Read YAML, write JSON

Expand Down
126 changes: 92 additions & 34 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,56 @@
use failure::{Error, ResultExt};
use exitfailure::ExitFailure;
use snafu::{ResultExt, Snafu};
use std::{
fs::File,
io::{stdin, stdout, Read, Write},
path::PathBuf,
};
use structopt::{clap::AppSettings::ColoredHelp, StructOpt};

struct Input {
handle: Box<dyn Read>,
name: String,
}

struct Output {
handle: Box<dyn Write>,
name: String,
}

#[derive(Debug, Snafu)]
enum Error {
#[snafu(display("Failed to parse JSON from {}: {}", name, source))]
ReadJSON {
name: String,
source: serde_json::error::Error,
},
#[snafu(display("Failed to parse YAML from {}: {}", name, source))]
ReadYAML {
name: String,
source: serde_yaml::Error,
},
#[snafu(display("Failed to format JSON from {}: {}", name, source))]
WriteJSON {
name: String,
source: serde_json::error::Error,
},
#[snafu(display("Failed to format YAML from {}: {}", name, source))]
WriteYAML {
name: String,
source: serde_yaml::Error,
},
#[snafu(display("Failed to open input file \"{}\": {}", filename.display(), source))]
OpenInput {
source: std::io::Error,
filename: PathBuf,
},
#[snafu(display("Failed to create output file \"{}\": {}", filename.display(), source))]
OpenOutput {
source: std::io::Error,
filename: PathBuf,
},
}

/// Read YAML, write JSON
#[derive(Debug, StructOpt)]
#[structopt(global_settings(&[ColoredHelp]))]
Expand Down Expand Up @@ -36,69 +81,82 @@ struct MyArgs {
input: Option<PathBuf>,
}

fn from_json(input: Box<dyn Read>, mut output: Box<dyn Write>, args: &MyArgs) -> Result<(), Error> {
fn from_json(input: Input, mut output: Output, args: &MyArgs) -> Result<(), Error> {
let data: serde_json::Value =
serde_json::from_reader(input).context("Failed to parse input JSON file")?;
serde_json::from_reader(input.handle).context(ReadJSON { name: input.name })?;

// Failure to format JSON output should never happen.
if args.yaml {
serde_yaml::to_writer(output.as_mut(), &data).context("Failed to format output as YAML")?;
serde_yaml::to_writer(output.handle.as_mut(), &data)
.context(WriteYAML { name: output.name })?;
// We'd like to write out a final newline. Ignore any failure to do so.
let _result = output.write(b"\n");
let _result = output.handle.write(b"\n");
} else if args.compact {
serde_json::to_writer(output, &data).context("Failed to format output as JSON")?;
serde_json::to_writer(output.handle, &data).context(WriteJSON { name: output.name })?;
} else {
serde_json::to_writer_pretty(output.as_mut(), &data)
.context("Failed to format output as JSON")?;
serde_json::to_writer_pretty(output.handle.as_mut(), &data)
.context(WriteJSON { name: output.name })?;
// We'd like to write out a final newline. Ignore any failure to do so.
let _result = output.write(b"\n");
let _result = output.handle.write(b"\n");
};
Ok(())
}

fn from_yaml(input: Box<dyn Read>, mut output: Box<dyn Write>, args: &MyArgs) -> Result<(), Error> {
fn from_yaml(input: Input, mut output: Output, args: &MyArgs) -> Result<(), Error> {
let data: serde_yaml::Value =
serde_yaml::from_reader(input).context("Failed to parse input YAML file")?;
serde_yaml::from_reader(input.handle).context(ReadYAML { name: input.name })?;

// Failure to format JSON output should never happen.
if args.yaml {
serde_yaml::to_writer(output.as_mut(), &data).context("Failed to format output as YAML")?;
serde_yaml::to_writer(output.handle.as_mut(), &data)
.context(WriteYAML { name: output.name })?;
// We'd like to write out a final newline. Ignore any failure to do so.
let _result = output.write(b"\n");
let _result = output.handle.write(b"\n");
} else if args.compact {
serde_json::to_writer(output, &data).context("Failed to format output as JSON")?;
serde_json::to_writer(output.handle, &data).context(WriteJSON { name: output.name })?;
} else {
serde_json::to_writer_pretty(output.as_mut(), &data)
.context("Failed to format output as JSON")?;
serde_json::to_writer_pretty(output.handle.as_mut(), &data)
.context(WriteJSON { name: output.name })?;
// We'd like to write out a final newline. Ignore any failure to do so.
let _result = output.write(b"\n");
let _result = output.handle.write(b"\n");
};
Ok(())
}

fn main() -> Result<(), Error> {
fn dispatch(input: Input, output: Output, args: &MyArgs) -> Result<(), Error> {
if args.json {
from_json(input, output, &args)
} else {
from_yaml(input, output, &args)
}
}

fn main() -> Result<(), ExitFailure> {
let args = MyArgs::from_args();

let input: Box<dyn Read> = match &args.input {
Some(filename) => Box::new(
File::open(&filename).context(format!("Failed to open input file {:?}", filename))?,
),
None => Box::new(stdin()),
let input: Input = match &args.input {
Some(filename) => Input {
handle: Box::new(File::open(&filename).context(OpenInput { filename })?),
name: filename.display().to_string(),
},
None => Input {
handle: Box::new(stdin()),
name: "<stdin>".to_string(),
},
};

let output: Box<dyn Write> = match &args.output {
Some(filename) => Box::new(
File::create(&filename)
.context(format!("Failed to create output file {:?}", filename))?,
),
None => Box::new(stdout()),
let output: Output = match &args.output {
Some(filename) => Output {
handle: Box::new(File::create(&filename).context(OpenOutput { filename })?),
name: filename.display().to_string(),
},
None => Output {
handle: Box::new(stdout()),
name: "<stdout>".to_string(),
},
};

if args.json {
from_json(input, output, &args)?;
} else {
from_yaml(input, output, &args)?;
}
dispatch(input, output, &args)?;

Ok(())
}
Expand Down

0 comments on commit d6fcb95

Please sign in to comment.