metis_core/domain/
configuration.rs1use serde::{Deserialize, Serialize};
2use std::fmt;
3use crate::domain::documents::types::DocumentType;
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub struct FlightLevelConfig {
8 pub strategies_enabled: bool,
10 pub initiatives_enabled: bool,
12}
13
14impl FlightLevelConfig {
15 pub fn new(strategies_enabled: bool, initiatives_enabled: bool) -> Result<Self, ConfigurationError> {
17 if !initiatives_enabled && strategies_enabled {
19 return Err(ConfigurationError::InvalidConfiguration(
20 "Cannot enable strategies without initiatives - this would create a gap in the hierarchy".to_string()
21 ));
22 }
23
24 Ok(Self {
25 strategies_enabled,
26 initiatives_enabled,
27 })
28 }
29
30 pub fn full() -> Self {
32 Self {
33 strategies_enabled: true,
34 initiatives_enabled: true,
35 }
36 }
37
38 pub fn streamlined() -> Self {
40 Self {
41 strategies_enabled: false,
42 initiatives_enabled: true,
43 }
44 }
45
46 pub fn direct() -> Self {
48 Self {
49 strategies_enabled: false,
50 initiatives_enabled: false,
51 }
52 }
53
54 pub fn is_document_type_allowed(&self, doc_type: DocumentType) -> bool {
56 match doc_type {
57 DocumentType::Vision | DocumentType::Adr => true, DocumentType::Task => true, DocumentType::Strategy => self.strategies_enabled,
60 DocumentType::Initiative => self.initiatives_enabled,
61 }
62 }
63
64 pub fn get_parent_type(&self, doc_type: DocumentType) -> Option<DocumentType> {
66 match doc_type {
67 DocumentType::Vision | DocumentType::Adr => None, DocumentType::Strategy => Some(DocumentType::Vision),
69 DocumentType::Initiative => {
70 if self.strategies_enabled {
71 Some(DocumentType::Strategy)
72 } else {
73 Some(DocumentType::Vision)
74 }
75 }
76 DocumentType::Task => {
77 if self.initiatives_enabled {
78 Some(DocumentType::Initiative)
79 } else {
80 Some(DocumentType::Vision)
81 }
82 }
83 }
84 }
85
86 pub fn preset_name(&self) -> &'static str {
88 match (self.strategies_enabled, self.initiatives_enabled) {
89 (true, true) => "full",
90 (false, true) => "streamlined",
91 (false, false) => "direct",
92 (true, false) => "invalid", }
94 }
95
96 pub fn enabled_document_types(&self) -> Vec<DocumentType> {
98 let mut types = vec![DocumentType::Vision];
99
100 if self.strategies_enabled {
101 types.push(DocumentType::Strategy);
102 }
103
104 if self.initiatives_enabled {
105 types.push(DocumentType::Initiative);
106 }
107
108 types.push(DocumentType::Task);
109 types.push(DocumentType::Adr); types
112 }
113
114 pub fn hierarchy_display(&self) -> String {
116 let mut hierarchy = vec!["Vision"];
117
118 if self.strategies_enabled {
119 hierarchy.push("Strategy");
120 }
121
122 if self.initiatives_enabled {
123 hierarchy.push("Initiative");
124 }
125
126 hierarchy.push("Task");
127
128 hierarchy.join(" → ")
129 }
130}
131
132impl Default for FlightLevelConfig {
133 fn default() -> Self {
134 Self::full()
135 }
136}
137
138impl fmt::Display for FlightLevelConfig {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 write!(f, "{}", self.preset_name())
141 }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
146pub enum ConfigurationError {
147 InvalidConfiguration(String),
148 SerializationError(String),
149}
150
151impl fmt::Display for ConfigurationError {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 match self {
154 ConfigurationError::InvalidConfiguration(msg) => write!(f, "Invalid configuration: {}", msg),
155 ConfigurationError::SerializationError(msg) => write!(f, "Serialization error: {}", msg),
156 }
157 }
158}
159
160impl std::error::Error for ConfigurationError {}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_preset_configurations() {
168 let full = FlightLevelConfig::full();
169 assert!(full.strategies_enabled);
170 assert!(full.initiatives_enabled);
171 assert_eq!(full.preset_name(), "full");
172
173 let streamlined = FlightLevelConfig::streamlined();
174 assert!(!streamlined.strategies_enabled);
175 assert!(streamlined.initiatives_enabled);
176 assert_eq!(streamlined.preset_name(), "streamlined");
177
178 let direct = FlightLevelConfig::direct();
179 assert!(!direct.strategies_enabled);
180 assert!(!direct.initiatives_enabled);
181 assert_eq!(direct.preset_name(), "direct");
182 }
183
184 #[test]
185 fn test_configuration_validation() {
186 assert!(FlightLevelConfig::new(true, true).is_ok());
188 assert!(FlightLevelConfig::new(false, true).is_ok());
189 assert!(FlightLevelConfig::new(false, false).is_ok());
190
191 assert!(FlightLevelConfig::new(true, false).is_err());
193 }
194
195 #[test]
196 fn test_document_type_allowed() {
197 let full = FlightLevelConfig::full();
198 assert!(full.is_document_type_allowed(DocumentType::Vision));
199 assert!(full.is_document_type_allowed(DocumentType::Strategy));
200 assert!(full.is_document_type_allowed(DocumentType::Initiative));
201 assert!(full.is_document_type_allowed(DocumentType::Task));
202 assert!(full.is_document_type_allowed(DocumentType::Adr));
203
204 let streamlined = FlightLevelConfig::streamlined();
205 assert!(streamlined.is_document_type_allowed(DocumentType::Vision));
206 assert!(!streamlined.is_document_type_allowed(DocumentType::Strategy));
207 assert!(streamlined.is_document_type_allowed(DocumentType::Initiative));
208 assert!(streamlined.is_document_type_allowed(DocumentType::Task));
209 assert!(streamlined.is_document_type_allowed(DocumentType::Adr));
210
211 let direct = FlightLevelConfig::direct();
212 assert!(direct.is_document_type_allowed(DocumentType::Vision));
213 assert!(!direct.is_document_type_allowed(DocumentType::Strategy));
214 assert!(!direct.is_document_type_allowed(DocumentType::Initiative));
215 assert!(direct.is_document_type_allowed(DocumentType::Task));
216 assert!(direct.is_document_type_allowed(DocumentType::Adr));
217 }
218
219 #[test]
220 fn test_parent_type_resolution() {
221 let full = FlightLevelConfig::full();
222 assert_eq!(full.get_parent_type(DocumentType::Vision), None);
223 assert_eq!(full.get_parent_type(DocumentType::Strategy), Some(DocumentType::Vision));
224 assert_eq!(full.get_parent_type(DocumentType::Initiative), Some(DocumentType::Strategy));
225 assert_eq!(full.get_parent_type(DocumentType::Task), Some(DocumentType::Initiative));
226 assert_eq!(full.get_parent_type(DocumentType::Adr), None);
227
228 let streamlined = FlightLevelConfig::streamlined();
229 assert_eq!(streamlined.get_parent_type(DocumentType::Initiative), Some(DocumentType::Vision));
230 assert_eq!(streamlined.get_parent_type(DocumentType::Task), Some(DocumentType::Initiative));
231
232 let direct = FlightLevelConfig::direct();
233 assert_eq!(direct.get_parent_type(DocumentType::Task), Some(DocumentType::Vision));
234 }
235
236 #[test]
237 fn test_enabled_document_types() {
238 let full = FlightLevelConfig::full();
239 let full_types = full.enabled_document_types();
240 assert_eq!(full_types, vec![
241 DocumentType::Vision,
242 DocumentType::Strategy,
243 DocumentType::Initiative,
244 DocumentType::Task,
245 DocumentType::Adr
246 ]);
247
248 let streamlined = FlightLevelConfig::streamlined();
249 let streamlined_types = streamlined.enabled_document_types();
250 assert_eq!(streamlined_types, vec![
251 DocumentType::Vision,
252 DocumentType::Initiative,
253 DocumentType::Task,
254 DocumentType::Adr
255 ]);
256
257 let direct = FlightLevelConfig::direct();
258 let direct_types = direct.enabled_document_types();
259 assert_eq!(direct_types, vec![
260 DocumentType::Vision,
261 DocumentType::Task,
262 DocumentType::Adr
263 ]);
264 }
265
266 #[test]
267 fn test_hierarchy_display() {
268 assert_eq!(FlightLevelConfig::full().hierarchy_display(), "Vision → Strategy → Initiative → Task");
269 assert_eq!(FlightLevelConfig::streamlined().hierarchy_display(), "Vision → Initiative → Task");
270 assert_eq!(FlightLevelConfig::direct().hierarchy_display(), "Vision → Task");
271 }
272
273 #[test]
274 fn test_serialization() {
275 let config = FlightLevelConfig::streamlined();
276 let json = serde_json::to_string(&config).unwrap();
277 let deserialized: FlightLevelConfig = serde_json::from_str(&json).unwrap();
278 assert_eq!(config, deserialized);
279 }
280}