1mod decode;
2
3use crate::*;
4use serde::{Deserialize, Serialize};
5use std::collections::BTreeMap;
6#[derive(Debug, Clone, PartialEq, Serialize)]
7#[serde(deny_unknown_fields)]
9pub struct Plan {
10 #[serde(deserialize_with = "read_format_version")]
12 pub format_version: u32,
13 pub assets: BTreeMap<AssetId, WorkspaceAsset>,
15 pub workspace: Workspace,
17 pub revision: u64,
19 pub projects: BTreeMap<ProjectId, Project>,
21 pub work_items: BTreeMap<WorkItemId, WorkItem>,
23 pub requirements: BTreeMap<RequirementId, Requirement>,
25 pub artifacts: BTreeMap<ArtifactId, Artifact>,
27 pub decisions: BTreeMap<DecisionId, Decision>,
29 pub risks: BTreeMap<RiskId, Risk>,
31 pub dependencies: Vec<Dependency>,
33 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
35 pub external_references: BTreeMap<ExternalReferenceId, ExternalReference>,
36 #[serde(default, skip_serializing_if = "Vec::is_empty")]
38 pub links: Vec<WorkLink>,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub calendars: Option<Calendars>,
42}
43
44impl Plan {
45 pub fn empty(name: impl Into<String>) -> Self {
47 Self {
48 format_version: 3,
49 assets: BTreeMap::new(),
50 workspace: Workspace {
51 id: WorkspaceId::new(),
52 name: name.into(),
53 },
54 revision: 0,
55 projects: BTreeMap::new(),
56 work_items: BTreeMap::new(),
57 requirements: BTreeMap::new(),
58 artifacts: BTreeMap::new(),
59 decisions: BTreeMap::new(),
60 risks: BTreeMap::new(),
61 dependencies: Vec::new(),
62 external_references: BTreeMap::new(),
63 links: Vec::new(),
64 calendars: None,
65 }
66 }
67
68 pub fn find_work_by_key(&self, key: &str) -> Option<&WorkItem> {
70 self.work_items.values().find(|work| work.key.0 == key)
71 }
72
73 pub fn find_work_by_key_mut(&mut self, key: &str) -> Option<&mut WorkItem> {
75 self.work_items.values_mut().find(|work| work.key.0 == key)
76 }
77
78 pub fn find_dependency(&self, id: DependencyId) -> Option<&Dependency> {
80 self.dependencies.iter().find(|edge| edge.id == id)
81 }
82
83 pub fn enforced_dependencies(&self) -> impl Iterator<Item = &Dependency> {
85 self.dependencies
86 .iter()
87 .filter(|edge| edge.waiver.is_none())
88 }
89
90 pub fn find_decision_by_key(&self, key: &str) -> Option<&Decision> {
92 self.decisions
93 .values()
94 .find(|decision| decision.key.0 == key)
95 }
96}
97
98fn read_format_version<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<u32, D::Error> {
99 let version = u32::deserialize(deserializer)?;
100 if version != 3 {
101 return Err(serde::de::Error::custom(format!(
102 "unsupported plan format {version}; expected 3. Preserve the original database and export with its original DPM release; convert that export before initializing a new store"
103 )));
104 }
105 Ok(version)
106}
107
108#[cfg(test)]
109mod tests;