clnrm_core/validation/
order_validator.rs

1//! Temporal ordering validation for span sequences
2//!
3//! Validates that spans occur in expected temporal order based on timestamps.
4
5use crate::error::{CleanroomError, Result};
6use crate::validation::span_validator::SpanData;
7use serde::{Deserialize, Serialize};
8
9/// Temporal ordering expectations
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct OrderExpectation {
12    /// Edges where first must precede second (first.end <= second.start)
13    pub must_precede: Vec<(String, String)>,
14    /// Edges where first must follow second (first.start >= second.end)
15    pub must_follow: Vec<(String, String)>,
16}
17
18impl Default for OrderExpectation {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23
24impl OrderExpectation {
25    /// Create a new OrderExpectation with no constraints
26    pub fn new() -> Self {
27        Self {
28            must_precede: Vec::new(),
29            must_follow: Vec::new(),
30        }
31    }
32
33    /// Add must_precede constraints
34    pub fn with_must_precede(mut self, edges: Vec<(String, String)>) -> Self {
35        self.must_precede = edges;
36        self
37    }
38
39    /// Add must_follow constraints
40    pub fn with_must_follow(mut self, edges: Vec<(String, String)>) -> Self {
41        self.must_follow = edges;
42        self
43    }
44
45    /// Validate temporal ordering constraints
46    pub fn validate(&self, spans: &[SpanData]) -> Result<()> {
47        // Validate must_precede constraints
48        for (first_name, second_name) in &self.must_precede {
49            self.validate_precedes(spans, first_name, second_name)?;
50        }
51
52        // Validate must_follow constraints
53        for (first_name, second_name) in &self.must_follow {
54            self.validate_follows(spans, first_name, second_name)?;
55        }
56
57        Ok(())
58    }
59
60    fn validate_precedes(&self, spans: &[SpanData], first: &str, second: &str) -> Result<()> {
61        let first_spans: Vec<_> = spans.iter().filter(|s| s.name == first).collect();
62        let second_spans: Vec<_> = spans.iter().filter(|s| s.name == second).collect();
63
64        if first_spans.is_empty() {
65            return Err(CleanroomError::validation_error(format!(
66                "Order validation failed: span '{}' not found for must_precede constraint",
67                first
68            )));
69        }
70
71        if second_spans.is_empty() {
72            return Err(CleanroomError::validation_error(format!(
73                "Order validation failed: span '{}' not found for must_precede constraint",
74                second
75            )));
76        }
77
78        // Check if any first span precedes any second span
79        let mut found_valid_order = false;
80        for first_span in &first_spans {
81            for second_span in &second_spans {
82                if self.span_precedes(first_span, second_span)? {
83                    found_valid_order = true;
84                    break;
85                }
86            }
87            if found_valid_order {
88                break;
89            }
90        }
91
92        if !found_valid_order {
93            return Err(CleanroomError::validation_error(format!(
94                "Order validation failed: '{}' must precede '{}' but no valid ordering found",
95                first, second
96            )));
97        }
98
99        Ok(())
100    }
101
102    fn validate_follows(&self, spans: &[SpanData], first: &str, second: &str) -> Result<()> {
103        // "first must follow second" is same as "second must precede first"
104        self.validate_precedes(spans, second, first)
105    }
106
107    fn span_precedes(&self, first: &SpanData, second: &SpanData) -> Result<bool> {
108        let first_end = first.end_time_unix_nano.ok_or_else(|| {
109            CleanroomError::validation_error(format!(
110                "Span '{}' missing end timestamp for order validation",
111                first.name
112            ))
113        })?;
114
115        let second_start = second.start_time_unix_nano.ok_or_else(|| {
116            CleanroomError::validation_error(format!(
117                "Span '{}' missing start timestamp for order validation",
118                second.name
119            ))
120        })?;
121
122        // First precedes second if first.end <= second.start
123        Ok(first_end <= second_start)
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use std::collections::HashMap;
131
132    fn create_span(name: &str, start: u64, end: u64) -> SpanData {
133        SpanData {
134            name: name.to_string(),
135            trace_id: "test".to_string(),
136            span_id: format!("span_{}", name),
137            parent_span_id: None,
138            start_time_unix_nano: Some(start),
139            end_time_unix_nano: Some(end),
140            attributes: HashMap::new(),
141            kind: None,
142            events: None,
143            resource_attributes: HashMap::new(),
144        }
145    }
146
147    #[test]
148    fn test_valid_precede_ordering() -> Result<()> {
149        let spans = vec![create_span("A", 1000, 2000), create_span("B", 2000, 3000)];
150
151        let expectation =
152            OrderExpectation::new().with_must_precede(vec![("A".to_string(), "B".to_string())]);
153
154        expectation.validate(&spans)?;
155        Ok(())
156    }
157
158    #[test]
159    fn test_invalid_precede_ordering() {
160        let spans = vec![create_span("A", 2000, 3000), create_span("B", 1000, 2000)];
161
162        let expectation =
163            OrderExpectation::new().with_must_precede(vec![("A".to_string(), "B".to_string())]);
164
165        assert!(expectation.validate(&spans).is_err());
166    }
167
168    #[test]
169    fn test_valid_follow_ordering() -> Result<()> {
170        let spans = vec![create_span("A", 1000, 2000), create_span("B", 2000, 3000)];
171
172        let expectation =
173            OrderExpectation::new().with_must_follow(vec![("B".to_string(), "A".to_string())]);
174
175        expectation.validate(&spans)?;
176        Ok(())
177    }
178
179    #[test]
180    fn test_missing_first_span() {
181        let spans = vec![create_span("B", 2000, 3000)];
182
183        let expectation =
184            OrderExpectation::new().with_must_precede(vec![("A".to_string(), "B".to_string())]);
185
186        let result = expectation.validate(&spans);
187        assert!(result.is_err());
188        assert!(result
189            .unwrap_err()
190            .to_string()
191            .contains("span 'A' not found"));
192    }
193
194    #[test]
195    fn test_missing_second_span() {
196        let spans = vec![create_span("A", 1000, 2000)];
197
198        let expectation =
199            OrderExpectation::new().with_must_precede(vec![("A".to_string(), "B".to_string())]);
200
201        let result = expectation.validate(&spans);
202        assert!(result.is_err());
203        assert!(result
204            .unwrap_err()
205            .to_string()
206            .contains("span 'B' not found"));
207    }
208
209    #[test]
210    fn test_missing_end_timestamp() {
211        let mut span_a = create_span("A", 1000, 2000);
212        span_a.end_time_unix_nano = None;
213        let span_b = create_span("B", 2000, 3000);
214
215        let spans = vec![span_a, span_b];
216
217        let expectation =
218            OrderExpectation::new().with_must_precede(vec![("A".to_string(), "B".to_string())]);
219
220        let result = expectation.validate(&spans);
221        assert!(result.is_err());
222        assert!(result
223            .unwrap_err()
224            .to_string()
225            .contains("missing end timestamp"));
226    }
227
228    #[test]
229    fn test_missing_start_timestamp() {
230        let span_a = create_span("A", 1000, 2000);
231        let mut span_b = create_span("B", 2000, 3000);
232        span_b.start_time_unix_nano = None;
233
234        let spans = vec![span_a, span_b];
235
236        let expectation =
237            OrderExpectation::new().with_must_precede(vec![("A".to_string(), "B".to_string())]);
238
239        let result = expectation.validate(&spans);
240        assert!(result.is_err());
241        assert!(result
242            .unwrap_err()
243            .to_string()
244            .contains("missing start timestamp"));
245    }
246
247    #[test]
248    fn test_overlapping_spans_invalid_precede() {
249        let spans = vec![create_span("A", 1000, 2500), create_span("B", 2000, 3000)];
250
251        let expectation =
252            OrderExpectation::new().with_must_precede(vec![("A".to_string(), "B".to_string())]);
253
254        // A ends at 2500, B starts at 2000, so A does not precede B (overlap)
255        assert!(expectation.validate(&spans).is_err());
256    }
257
258    #[test]
259    fn test_exact_boundary_valid_precede() -> Result<()> {
260        let spans = vec![create_span("A", 1000, 2000), create_span("B", 2000, 3000)];
261
262        let expectation =
263            OrderExpectation::new().with_must_precede(vec![("A".to_string(), "B".to_string())]);
264
265        // A ends exactly when B starts - valid precede (first.end <= second.start)
266        expectation.validate(&spans)?;
267        Ok(())
268    }
269
270    #[test]
271    fn test_multiple_precede_constraints() -> Result<()> {
272        let spans = vec![
273            create_span("A", 1000, 2000),
274            create_span("B", 2000, 3000),
275            create_span("C", 3000, 4000),
276        ];
277
278        let expectation = OrderExpectation::new().with_must_precede(vec![
279            ("A".to_string(), "B".to_string()),
280            ("B".to_string(), "C".to_string()),
281        ]);
282
283        expectation.validate(&spans)?;
284        Ok(())
285    }
286
287    #[test]
288    fn test_multiple_follow_constraints() -> Result<()> {
289        let spans = vec![
290            create_span("A", 1000, 2000),
291            create_span("B", 2000, 3000),
292            create_span("C", 3000, 4000),
293        ];
294
295        let expectation = OrderExpectation::new().with_must_follow(vec![
296            ("B".to_string(), "A".to_string()),
297            ("C".to_string(), "B".to_string()),
298        ]);
299
300        expectation.validate(&spans)?;
301        Ok(())
302    }
303
304    #[test]
305    fn test_multiple_spans_with_same_name() -> Result<()> {
306        let spans = vec![
307            create_span("A", 1000, 1500),
308            create_span("A", 2000, 2500), // Second A span
309            create_span("B", 3000, 4000),
310        ];
311
312        let expectation =
313            OrderExpectation::new().with_must_precede(vec![("A".to_string(), "B".to_string())]);
314
315        // Should pass because at least one A precedes B
316        expectation.validate(&spans)?;
317        Ok(())
318    }
319
320    #[test]
321    fn test_empty_constraints() -> Result<()> {
322        let spans = vec![create_span("A", 1000, 2000), create_span("B", 2000, 3000)];
323
324        let expectation = OrderExpectation::new();
325
326        // No constraints, should always pass
327        expectation.validate(&spans)?;
328        Ok(())
329    }
330
331    #[test]
332    fn test_default_implementation() {
333        let expectation = OrderExpectation::default();
334        assert!(expectation.must_precede.is_empty());
335        assert!(expectation.must_follow.is_empty());
336    }
337}