diff options
Diffstat (limited to 'src/map.rs')
-rw-r--r-- | src/map.rs | 46 |
1 files changed, 40 insertions, 6 deletions
@@ -4,6 +4,11 @@ pub use crate::from_yaml; pub use crate::Status; pub use crate::Step; + +/// Error in Roadmap, from parsing or otherwise. +pub type RoadmapError<T> = Result<T, String>; + + /// Represent a full project roadmap. /// /// This stores all the steps needed to reach the end goal. See the @@ -116,10 +121,41 @@ impl Roadmap { self.steps.iter().all(|other| !other.depends_on(step.name())) } + + // Validate that the parsed, constructed roadmap is valid. + pub fn validate(&self) -> RoadmapError<()> { + // Is there exactly one goal? + match self.count_goals() { + 0 => return Err(format!("the roadmap doesn't have a goal")), + 1 => (), + _ => return Err(format!("must have exactly one goal for roadmap")), + } + + // Does every dependency exist? + 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 + )) + } + Some(_) => (), + } + } + } + + Ok(()) + } + /// Get a Graphviz dot language representation of a roadmap. This /// is the textual representation, and the caller needs to use the /// Graphviz dot(1) tool to create an image from it. pub fn format_as_dot(&self, label_width: usize) -> Result<String, Box<dyn std::error::Error>> { + self.validate()?; + let labels = self.steps.iter().map(|step| { format!( "{} [label=\"{}\" style=filled fillcolor=\"{}\" shape=\"{}\"];\n", @@ -237,12 +273,10 @@ blocked: #[test] fn empty_dot() { let roadmap = Roadmap::new(); - assert_eq!( - roadmap.format_as_dot(999).unwrap(), - "digraph \"roadmap\" { -} -" - ); + match roadmap.format_as_dot(999) { + Err(_) => (), + _ => panic!("expected error for empty roadmap"), + } } #[test] |