1use crate::Plan;
2use thiserror::Error;
3
4mod conditions;
5mod dependency;
6mod entities;
7mod events;
8mod graph;
9mod instructions;
10mod provisional;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
14pub enum EstimateError {
15 #[error("estimate values must be finite")]
17 NonFinite,
18 #[error("optimistic duration must be non-negative")]
20 Negative,
21 #[error("estimate must satisfy optimistic <= likely <= pessimistic")]
23 Unordered,
24}
25
26#[derive(Debug, Error)]
28pub enum ValidationError {
29 #[error("invalid {entity} {id}: {reason}")]
31 Invalid {
32 entity: &'static str,
34 id: String,
36 reason: String,
38 },
39 #[error("invalid estimate for work {work}: {source}")]
41 Estimate {
42 work: crate::WorkItemId,
44 #[source]
46 source: EstimateError,
47 },
48}
49
50pub(crate) fn invalid(
51 entity: &'static str,
52 id: impl std::fmt::Display,
53 reason: impl Into<String>,
54) -> ValidationError {
55 ValidationError::Invalid {
56 entity,
57 id: id.to_string(),
58 reason: reason.into(),
59 }
60}
61
62impl Plan {
63 pub fn validate(&self) -> Result<(), ValidationError> {
67 if self.format_version != 3 {
68 return Err(invalid(
69 "plan format",
70 self.workspace.id,
71 format!("unsupported version {}; expected 3", self.format_version),
72 ));
73 }
74 for work in self.work_items.values() {
75 if !work.order.is_valid() {
76 return Err(invalid(
77 "work order",
78 work.id,
79 "order requires 1..128 digits and a nonzero final digit",
80 ));
81 }
82 }
83 crate::asset::validate(self)?;
84 entities::validate(self)?;
85 crate::tracking::validate(self)?;
86 graph::validate(self)?;
87 conditions::validate(self)?;
88 dependency::validate(self)?;
89 crate::calendar::validate::validate(self)?;
90 provisional::validate(self)
91 }
92}
93
94#[cfg(test)]
95mod tests;