From 5e5891771efd0530b1e648c1acb6d673b032e432 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Fri, 20 Mar 2020 09:27:36 +0200 Subject: Change: use anyhow and thiserror for error handling --- Cargo.toml | 2 ++ legend.yaml | 5 ----- src/bin/roadmap2dot.rs | 3 ++- src/err.rs | 33 +++++++++++++++++++++++++++++++++ src/lib.rs | 3 +++ src/map.rs | 26 +++++++++++++------------- src/parser.rs | 12 ++++++------ 7 files changed, 59 insertions(+), 25 deletions(-) create mode 100644 src/err.rs diff --git a/Cargo.toml b/Cargo.toml index 771bd52..3a6827a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,5 @@ description = "model a project roadmap as a directed acyclic graph" serde_yaml = "0.8.9" structopt = "0.3" textwrap = "0.11.0" +thiserror = "1.0" +anyhow = "1.0" diff --git a/legend.yaml b/legend.yaml index 951d369..fc17ba8 100644 --- a/legend.yaml +++ b/legend.yaml @@ -1,8 +1,3 @@ -goal2: {} -goal3: {} -goal4: {} -goal5: {} - goal: label: | This is the end goal: diff --git a/src/bin/roadmap2dot.rs b/src/bin/roadmap2dot.rs index 244a300..b180da8 100644 --- a/src/bin/roadmap2dot.rs +++ b/src/bin/roadmap2dot.rs @@ -16,6 +16,7 @@ use std::fs::File; use std::io::Read; use std::path::PathBuf; use structopt::StructOpt; +use anyhow::Result; const LABEL_WIDTH: usize = 30; @@ -30,7 +31,7 @@ struct Opt { filename: PathBuf, } -fn main() -> Result<(), Box> { +fn main() -> Result<()> { let opt = Opt::from_args(); let mut text = String::new(); let mut f = File::open(opt.filename)?; diff --git a/src/err.rs b/src/err.rs new file mode 100644 index 0000000..6a224a1 --- /dev/null +++ b/src/err.rs @@ -0,0 +1,33 @@ +use serde_yaml; +use thiserror::Error; + +/// Errors that can be returned for roadmaps. +#[derive(Error, Debug)] +pub enum RoadmapError { + #[error("roadmap has no goals, must have exactly one")] + NoGoals, + + #[error("too many goals, must have exactly one: found {count:}: {}", .names.join(", "))] + ManyGoals { + count: usize, + names: Vec, + }, + + #[error("step {name:} depends on missing {missing:}")] + MissingDep { + name: String, + missing: String, + }, + + #[error("step is not a mapping")] + StepNotMapping, + + #[error("'depends' must be a list of step names")] + DependsNotNames, + + #[error("unknown status: {0}")] + UnknownStatus(String), + + #[error(transparent)] + SerdeError(#[from] serde_yaml::Error), +} diff --git a/src/lib.rs b/src/lib.rs index d3f672b..63497bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,9 @@ //! # } //! ``` +mod err; +pub use err::RoadmapError; + mod status; pub use status::Status; diff --git a/src/map.rs b/src/map.rs index c1294bf..671318e 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,12 +1,12 @@ -use std::error::Error; use textwrap::fill; pub use crate::from_yaml; +pub use crate::RoadmapError; pub use crate::Status; pub use crate::Step; /// Error in Roadmap, from parsing or otherwise. -pub type RoadmapResult = Result>; +pub type RoadmapResult = Result; /// Represent a full project roadmap. /// @@ -131,14 +131,14 @@ impl Roadmap { let goals = self.goals(); let n = goals.len(); match n { - 0 => return Err(format!("the roadmap doesn't have a goal").into()), + 0 => return Err(RoadmapError::NoGoals), 1 => (), _ => { - let mut names: Vec<&str> = goals.iter().map(|s| s.name()).collect(); - names.sort(); - let names = names.join(", "); - let msg = format!("only one goal allowed, found {}: {}", n, names); - return Err(msg.into()); + let names: Vec = goals.iter().map(|s| s.name().into()).collect(); + return Err(RoadmapError::ManyGoals { + count: n, + names: names, + }); } } @@ -146,11 +146,11 @@ impl Roadmap { for step in self.iter() { for depname in step.dependencies() { match self.get_step(depname) { - None => { - return Err( - format!("step {} depends on missing {}", step.name(), depname).into(), - ) - } + None => + return Err(RoadmapError::MissingDep { + name: step.name().into(), + missing: depname.into(), + }), Some(_) => (), } } diff --git a/src/parser.rs b/src/parser.rs index 7da2254..7602b94 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,6 +3,7 @@ use serde_yaml::Value; use std::collections::HashMap; pub use crate::Roadmap; +pub use crate::RoadmapError; pub use crate::RoadmapResult; pub use crate::Status; pub use crate::Step; @@ -37,7 +38,7 @@ fn step_from_value(name: &str, value: &Value) -> RoadmapResult { Ok(step) } - _ => Err("step is not a mapping".to_string().into()), + _ => Err(RoadmapError::StepNotMapping), } } @@ -46,19 +47,18 @@ fn parse_depends(map: &Value) -> RoadmapResult> { let key_name = "depends"; let key = Value::String(key_name.to_string()); let mut depends: Vec<&str> = vec![]; - let need_list_of_names = format!("'depends' must be a list of step names"); - + match map.get(&key) { None => (), Some(Value::Sequence(deps)) => { for depname in deps.iter() { match depname { Value::String(depname) => depends.push(depname), - _ => return Err(need_list_of_names.into()), + _ => return Err(RoadmapError::DependsNotNames), } } } - _ => return Err(need_list_of_names.into()), + _ => return Err(RoadmapError::DependsNotNames), } Ok(depends) @@ -74,7 +74,7 @@ fn parse_status<'a>(map: &'a Value) -> RoadmapResult { let text = parse_string("status", map); match Status::from_text(text) { Some(status) => Ok(status), - _ => Err(format!("unknown status: {:?}", text).into()), + _ => Err(RoadmapError::UnknownStatus(text.into())), } } -- cgit v1.2.1