ricecoder_specs/
inheritance.rs

1//! Spec inheritance and hierarchy resolution
2
3use crate::error::SpecError;
4use crate::models::{Spec, SpecInheritance};
5use std::collections::{HashMap, HashSet};
6
7/// Manages hierarchical spec resolution with explicit precedence
8pub struct SpecInheritanceResolver;
9
10impl SpecInheritanceResolver {
11    /// Resolve spec hierarchy (project > feature > task)
12    ///
13    /// Organizes specs by their precedence level and returns them in order
14    /// from highest precedence (project level, 0) to lowest (task level, 2).
15    ///
16    /// # Arguments
17    /// * `specs` - Collection of specs to resolve
18    ///
19    /// # Returns
20    /// * `Ok(Vec<Spec>)` - Specs sorted by precedence level (highest first)
21    /// * `Err(SpecError)` - If circular dependencies or conflicts are detected
22    pub fn resolve(specs: &[Spec]) -> Result<Vec<Spec>, SpecError> {
23        // Validate the chain first to detect circular dependencies
24        Self::validate_chain(specs)?;
25
26        // Sort specs by precedence level (0 = project, 1 = feature, 2 = task)
27        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    /// Merge two specs with precedence rules
39    ///
40    /// Merges a child spec into a parent spec, with the parent taking precedence.
41    /// Higher precedence level specs override lower precedence level specs.
42    ///
43    /// # Arguments
44    /// * `parent` - Parent spec (higher precedence)
45    /// * `child` - Child spec (lower precedence)
46    ///
47    /// # Returns
48    /// * `Ok(Spec)` - Merged spec with parent values taking precedence
49    /// * `Err(SpecError)` - If merge would create conflicts
50    pub fn merge(parent: &Spec, child: &Spec) -> Result<Spec, SpecError> {
51        // Validate precedence levels
52        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        // Create merged spec with parent values taking precedence
70        let mut merged = child.clone();
71
72        // Override with parent values where parent has higher precedence
73        if !parent.id.is_empty() && parent.id != child.id {
74            // Keep child ID but track parent in inheritance
75        }
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        // Merge requirements: parent requirements take precedence
86        if !parent.requirements.is_empty() {
87            merged.requirements = parent.requirements.clone();
88        }
89
90        // Merge design: parent design takes precedence
91        if parent.design.is_some() {
92            merged.design = parent.design.clone();
93        }
94
95        // Merge tasks: parent tasks take precedence
96        if !parent.tasks.is_empty() {
97            merged.tasks = parent.tasks.clone();
98        }
99
100        // Merge metadata: parent metadata takes precedence
101        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        // Update inheritance information
107        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    /// Validate inheritance chain for conflicts and circular dependencies
133    ///
134    /// Checks that:
135    /// 1. No circular dependencies exist
136    /// 2. Precedence levels are consistent
137    /// 3. Parent-child relationships are valid
138    ///
139    /// # Arguments
140    /// * `specs` - Collection of specs to validate
141    ///
142    /// # Returns
143    /// * `Ok(())` - If chain is valid
144    /// * `Err(SpecError)` - If circular dependencies or conflicts are detected
145    pub fn validate_chain(specs: &[Spec]) -> Result<(), SpecError> {
146        // Build a map of spec IDs to specs for quick lookup
147        let spec_map: HashMap<String, &Spec> = specs.iter().map(|s| (s.id.clone(), s)).collect();
148
149        // Check for circular dependencies
150        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                    // Circular dependency detected
157                    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                // Move to parent
186                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        // Check precedence level consistency
195        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        // Should be ordered by precedence level (0, 1, 2)
280        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")), // Creates cycle
301            create_spec("feature", 1, Some("project")),
302            create_spec("task", 2, Some("feature")),
303        ];
304
305        // Manually set up circular dependency
306        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        // Make feature have higher precedence than project (invalid)
329        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        // Parent values should override child values
386        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}