summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2020-03-20 09:27:36 +0200
committerLars Wirzenius <liw@liw.fi>2020-03-20 09:30:39 +0200
commit5e5891771efd0530b1e648c1acb6d673b032e432 (patch)
tree889930448ab8764dc8f37840da4a07dd750ac3e1
parentdcbcbd9457d4bd4d21bcf8dbcc9e416fff463d92 (diff)
downloadroadmap-5e5891771efd0530b1e648c1acb6d673b032e432.tar.gz
Change: use anyhow and thiserror for error handling
-rw-r--r--Cargo.toml2
-rw-r--r--legend.yaml5
-rw-r--r--src/bin/roadmap2dot.rs3
-rw-r--r--src/err.rs33
-rw-r--r--src/lib.rs3
-rw-r--r--src/map.rs26
-rw-r--r--src/parser.rs12
7 files changed, 59 insertions, 25 deletions
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<dyn std::error::Error>> {
+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<String>,
+ },
+
+ #[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<T> = Result<T, Box<dyn Error>>;
+pub type RoadmapResult<T> = Result<T, RoadmapError>;
/// 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<String> = 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<Step> {
Ok(step)
}
- _ => Err("step is not a mapping".to_string().into()),
+ _ => Err(RoadmapError::StepNotMapping),
}
}
@@ -46,19 +47,18 @@ fn parse_depends(map: &Value) -> RoadmapResult<Vec<&str>> {
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<Status> {
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())),
}
}