clnrm_core/validation/
order_validator.rs1use crate::error::{CleanroomError, Result};
6use crate::validation::span_validator::SpanData;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct OrderExpectation {
12 pub must_precede: Vec<(String, String)>,
14 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 pub fn new() -> Self {
27 Self {
28 must_precede: Vec::new(),
29 must_follow: Vec::new(),
30 }
31 }
32
33 pub fn with_must_precede(mut self, edges: Vec<(String, String)>) -> Self {
35 self.must_precede = edges;
36 self
37 }
38
39 pub fn with_must_follow(mut self, edges: Vec<(String, String)>) -> Self {
41 self.must_follow = edges;
42 self
43 }
44
45 pub fn validate(&self, spans: &[SpanData]) -> Result<()> {
47 for (first_name, second_name) in &self.must_precede {
49 self.validate_precedes(spans, first_name, second_name)?;
50 }
51
52 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 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 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 Ok(first_end <= second_start)
124 }
125}