1use crate::error::SpecError;
4use crate::models::{Spec, SpecInheritance};
5use std::collections::{HashMap, HashSet};
6
7pub struct SpecInheritanceResolver;
9
10impl SpecInheritanceResolver {
11 pub fn resolve(specs: &[Spec]) -> Result<Vec<Spec>, SpecError> {
23 Self::validate_chain(specs)?;
25
26 let mut sorted_specs = specs.to_vec();
28 sorted_specs.sort_by_key(|spec| {
29 spec.inheritance
30 .as_ref()
31 .map(|inh| inh.precedence_level)
32 .unwrap_or(0)
33 });
34
35 Ok(sorted_specs)
36 }
37
38 pub fn merge(parent: &Spec, child: &Spec) -> Result<Spec, SpecError> {
51 let parent_level = parent
53 .inheritance
54 .as_ref()
55 .map(|inh| inh.precedence_level)
56 .unwrap_or(0);
57 let child_level = child
58 .inheritance
59 .as_ref()
60 .map(|inh| inh.precedence_level)
61 .unwrap_or(0);
62
63 if parent_level >= child_level {
64 return Err(SpecError::InheritanceConflict(
65 "Parent precedence level must be lower than child precedence level".to_string(),
66 ));
67 }
68
69 let mut merged = child.clone();
71
72 if !parent.id.is_empty() && parent.id != child.id {
74 }
76
77 if !parent.name.is_empty() {
78 merged.name = parent.name.clone();
79 }
80
81 if !parent.version.is_empty() {
82 merged.version = parent.version.clone();
83 }
84
85 if !parent.requirements.is_empty() {
87 merged.requirements = parent.requirements.clone();
88 }
89
90 if parent.design.is_some() {
92 merged.design = parent.design.clone();
93 }
94
95 if !parent.tasks.is_empty() {
97 merged.tasks = parent.tasks.clone();
98 }
99
100 merged.metadata.author = parent.metadata.author.clone().or(merged.metadata.author);
102 merged.metadata.phase = parent.metadata.phase;
103 merged.metadata.status = parent.metadata.status;
104 merged.metadata.updated_at = chrono::Utc::now();
105
106 let mut merged_from = child
108 .inheritance
109 .as_ref()
110 .map(|inh| {
111 if inh.merged_from.is_empty() {
112 vec![child.id.clone()]
113 } else {
114 inh.merged_from.clone()
115 }
116 })
117 .unwrap_or_else(|| vec![child.id.clone()]);
118
119 if !merged_from.contains(&parent.id) {
120 merged_from.insert(0, parent.id.clone());
121 }
122
123 merged.inheritance = Some(SpecInheritance {
124 parent_id: Some(parent.id.clone()),
125 precedence_level: parent_level,
126 merged_from,
127 });
128
129 Ok(merged)
130 }
131
132 pub fn validate_chain(specs: &[Spec]) -> Result<(), SpecError> {
146 let spec_map: HashMap<String, &Spec> = specs.iter().map(|s| (s.id.clone(), s)).collect();
148
149 for spec in specs {
151 let mut visited = HashSet::new();
152 let mut current_id = Some(spec.id.clone());
153
154 while let Some(id) = current_id {
155 if visited.contains(&id) {
156 let mut cycle = vec![id.clone()];
158 let mut cycle_id = Some(id);
159
160 while let Some(cid) = cycle_id {
161 if let Some(s) = spec_map.get(&cid) {
162 if let Some(inh) = &s.inheritance {
163 if let Some(parent_id) = &inh.parent_id {
164 if parent_id == cycle.first().unwrap() {
165 break;
166 }
167 cycle.push(parent_id.clone());
168 cycle_id = Some(parent_id.clone());
169 } else {
170 break;
171 }
172 } else {
173 break;
174 }
175 } else {
176 break;
177 }
178 }
179
180 return Err(SpecError::CircularDependency { specs: cycle });
181 }
182
183 visited.insert(id.clone());
184
185 if let Some(inh) = &spec_map.get(&id).and_then(|s| s.inheritance.as_ref()) {
187 current_id = inh.parent_id.clone();
188 } else {
189 current_id = None;
190 }
191 }
192 }
193
194 for spec in specs {
196 if let Some(inh) = &spec.inheritance {
197 if let Some(parent_id) = &inh.parent_id {
198 if let Some(parent) = spec_map.get(parent_id) {
199 let parent_level = parent
200 .inheritance
201 .as_ref()
202 .map(|p| p.precedence_level)
203 .unwrap_or(0);
204
205 if parent_level >= inh.precedence_level {
206 return Err(SpecError::InheritanceConflict(format!(
207 "Parent {} has precedence level {} but child {} has level {}",
208 parent_id, parent_level, spec.id, inh.precedence_level
209 )));
210 }
211 }
212 }
213 }
214 }
215
216 Ok(())
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use crate::models::{SpecMetadata, SpecPhase, SpecStatus};
224 use chrono::Utc;
225
226 fn create_spec(id: &str, precedence_level: u32, parent_id: Option<&str>) -> Spec {
227 Spec {
228 id: id.to_string(),
229 name: format!("Spec {}", id),
230 version: "1.0.0".to_string(),
231 requirements: vec![],
232 design: None,
233 tasks: vec![],
234 metadata: SpecMetadata {
235 author: None,
236 created_at: Utc::now(),
237 updated_at: Utc::now(),
238 phase: SpecPhase::Requirements,
239 status: SpecStatus::Draft,
240 },
241 inheritance: Some(SpecInheritance {
242 parent_id: parent_id.map(|s| s.to_string()),
243 precedence_level,
244 merged_from: vec![],
245 }),
246 }
247 }
248
249 #[test]
250 fn test_resolve_empty_specs() {
251 let specs = vec![];
252 let result = SpecInheritanceResolver::resolve(&specs);
253 assert!(result.is_ok());
254 assert!(result.unwrap().is_empty());
255 }
256
257 #[test]
258 fn test_resolve_single_spec() {
259 let specs = vec![create_spec("project", 0, None)];
260 let result = SpecInheritanceResolver::resolve(&specs);
261 assert!(result.is_ok());
262 let resolved = result.unwrap();
263 assert_eq!(resolved.len(), 1);
264 assert_eq!(resolved[0].id, "project");
265 }
266
267 #[test]
268 fn test_resolve_hierarchy_ordering() {
269 let specs = vec![
270 create_spec("task", 2, Some("feature")),
271 create_spec("project", 0, None),
272 create_spec("feature", 1, Some("project")),
273 ];
274
275 let result = SpecInheritanceResolver::resolve(&specs);
276 assert!(result.is_ok());
277 let resolved = result.unwrap();
278
279 assert_eq!(resolved[0].id, "project");
281 assert_eq!(resolved[1].id, "feature");
282 assert_eq!(resolved[2].id, "task");
283 }
284
285 #[test]
286 fn test_validate_chain_no_circular_dependency() {
287 let specs = vec![
288 create_spec("project", 0, None),
289 create_spec("feature", 1, Some("project")),
290 create_spec("task", 2, Some("feature")),
291 ];
292
293 let result = SpecInheritanceResolver::validate_chain(&specs);
294 assert!(result.is_ok());
295 }
296
297 #[test]
298 fn test_validate_chain_circular_dependency() {
299 let mut specs = vec![
300 create_spec("project", 0, Some("task")), create_spec("feature", 1, Some("project")),
302 create_spec("task", 2, Some("feature")),
303 ];
304
305 if let Some(inh) = &mut specs[0].inheritance {
307 inh.parent_id = Some("task".to_string());
308 }
309
310 let result = SpecInheritanceResolver::validate_chain(&specs);
311 assert!(result.is_err());
312 match result {
313 Err(SpecError::CircularDependency { specs: cycle }) => {
314 assert!(!cycle.is_empty());
315 }
316 _ => panic!("Expected CircularDependency error"),
317 }
318 }
319
320 #[test]
321 fn test_validate_chain_invalid_precedence() {
322 let mut specs = vec![
323 create_spec("project", 0, None),
324 create_spec("feature", 1, Some("project")),
325 create_spec("task", 2, Some("feature")),
326 ];
327
328 if let Some(inh) = &mut specs[1].inheritance {
330 inh.precedence_level = 0;
331 }
332
333 let result = SpecInheritanceResolver::validate_chain(&specs);
334 assert!(result.is_err());
335 }
336
337 #[test]
338 fn test_merge_parent_overrides_child() {
339 let parent = Spec {
340 id: "parent".to_string(),
341 name: "Parent Name".to_string(),
342 version: "2.0.0".to_string(),
343 requirements: vec![],
344 design: None,
345 tasks: vec![],
346 metadata: SpecMetadata {
347 author: Some("Parent Author".to_string()),
348 created_at: Utc::now(),
349 updated_at: Utc::now(),
350 phase: SpecPhase::Design,
351 status: SpecStatus::Approved,
352 },
353 inheritance: Some(SpecInheritance {
354 parent_id: None,
355 precedence_level: 0,
356 merged_from: vec![],
357 }),
358 };
359
360 let child = Spec {
361 id: "child".to_string(),
362 name: "Child Name".to_string(),
363 version: "1.0.0".to_string(),
364 requirements: vec![],
365 design: None,
366 tasks: vec![],
367 metadata: SpecMetadata {
368 author: Some("Child Author".to_string()),
369 created_at: Utc::now(),
370 updated_at: Utc::now(),
371 phase: SpecPhase::Requirements,
372 status: SpecStatus::Draft,
373 },
374 inheritance: Some(SpecInheritance {
375 parent_id: Some("parent".to_string()),
376 precedence_level: 1,
377 merged_from: vec![],
378 }),
379 };
380
381 let result = SpecInheritanceResolver::merge(&parent, &child);
382 assert!(result.is_ok());
383
384 let merged = result.unwrap();
385 assert_eq!(merged.name, "Parent Name");
387 assert_eq!(merged.version, "2.0.0");
388 assert_eq!(merged.metadata.phase, SpecPhase::Design);
389 assert_eq!(merged.metadata.status, SpecStatus::Approved);
390 }
391
392 #[test]
393 fn test_merge_invalid_precedence() {
394 let parent = create_spec("parent", 1, None);
395 let child = create_spec("child", 0, Some("parent"));
396
397 let result = SpecInheritanceResolver::merge(&parent, &child);
398 assert!(result.is_err());
399 }
400
401 #[test]
402 fn test_merge_updates_inheritance() {
403 let parent = create_spec("parent", 0, None);
404 let child = create_spec("child", 1, Some("parent"));
405
406 let result = SpecInheritanceResolver::merge(&parent, &child);
407 assert!(result.is_ok());
408
409 let merged = result.unwrap();
410 assert!(merged.inheritance.is_some());
411
412 let inh = merged.inheritance.unwrap();
413 assert_eq!(inh.parent_id, Some("parent".to_string()));
414 assert_eq!(inh.precedence_level, 0);
415 assert!(inh.merged_from.contains(&"parent".to_string()));
416 }
417}