Skip to main content

cuenv_task_graph/
validation.rs

1//! Validation utilities for task graphs.
2//!
3//! This module provides types and functions for validating task graph structure.
4
5use crate::{Error, TaskGraph, TaskNodeData};
6
7/// Result of graph validation.
8#[derive(Debug, Clone)]
9pub struct ValidationResult {
10    /// Whether the graph is valid (no cycles, no missing dependencies).
11    pub is_valid: bool,
12    /// List of validation errors, if any.
13    pub errors: Vec<Error>,
14}
15
16impl ValidationResult {
17    /// Create a valid result.
18    #[must_use]
19    pub fn valid() -> Self {
20        Self {
21            is_valid: true,
22            errors: vec![],
23        }
24    }
25
26    /// Create an invalid result with errors.
27    #[must_use]
28    pub fn invalid(errors: Vec<Error>) -> Self {
29        Self {
30            is_valid: false,
31            errors,
32        }
33    }
34}
35
36impl<T: TaskNodeData> TaskGraph<T> {
37    /// Validate the graph structure.
38    ///
39    /// Checks for:
40    /// - Cycles in the dependency graph
41    ///
42    /// Note: Missing dependencies are caught during `add_dependency_edges()`,
43    /// so this method primarily checks for cycles after edges are added.
44    #[must_use]
45    pub fn validate(&self) -> ValidationResult {
46        let mut errors = Vec::new();
47
48        if self.has_cycles() {
49            errors.push(Error::CycleDetected {
50                message: "Task dependency graph contains cycles".to_string(),
51            });
52        }
53
54        if errors.is_empty() {
55            ValidationResult::valid()
56        } else {
57            ValidationResult::invalid(errors)
58        }
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[derive(Clone, Debug, Default)]
67    struct TestTask {
68        depends_on: Vec<String>,
69    }
70
71    impl TaskNodeData for TestTask {
72        fn dependency_names(&self) -> impl Iterator<Item = &str> {
73            self.depends_on.iter().map(String::as_str)
74        }
75    }
76
77    #[test]
78    fn test_validate_empty_graph() {
79        let graph: TaskGraph<TestTask> = TaskGraph::new();
80        let result = graph.validate();
81        assert!(result.is_valid);
82        assert!(result.errors.is_empty());
83    }
84
85    #[test]
86    fn test_validate_valid_graph() {
87        let mut graph = TaskGraph::new();
88        graph
89            .add_task("a", TestTask { depends_on: vec![] })
90            .unwrap();
91        graph
92            .add_task(
93                "b",
94                TestTask {
95                    depends_on: vec!["a".to_string()],
96                },
97            )
98            .unwrap();
99        graph.add_dependency_edges().unwrap();
100
101        let result = graph.validate();
102        assert!(result.is_valid);
103    }
104
105    #[test]
106    fn test_validate_cyclic_graph() {
107        let mut graph = TaskGraph::new();
108        graph
109            .add_task(
110                "a",
111                TestTask {
112                    depends_on: vec!["b".to_string()],
113                },
114            )
115            .unwrap();
116        graph
117            .add_task(
118                "b",
119                TestTask {
120                    depends_on: vec!["a".to_string()],
121                },
122            )
123            .unwrap();
124        graph.add_dependency_edges().unwrap();
125
126        let result = graph.validate();
127        assert!(!result.is_valid);
128        assert_eq!(result.errors.len(), 1);
129    }
130}