1use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum LogicalOperator {
33 All,
35 Any,
37 Exactly(usize),
39 AtLeast(usize),
41 AtMost(usize),
43}
44
45impl LogicalOperator {
46 pub fn evaluate(&self, results: &[bool]) -> bool {
70 if results.is_empty() {
71 return match self {
72 LogicalOperator::All => true, LogicalOperator::Any => false, LogicalOperator::Exactly(n) => *n == 0,
75 LogicalOperator::AtLeast(n) => *n == 0,
76 LogicalOperator::AtMost(_) => true, };
78 }
79
80 let true_count = results.iter().filter(|&&x| x).count();
81
82 match self {
83 LogicalOperator::All => true_count == results.len(),
84 LogicalOperator::Any => true_count > 0,
85 LogicalOperator::Exactly(n) => true_count == *n,
86 LogicalOperator::AtLeast(n) => true_count >= *n,
87 LogicalOperator::AtMost(n) => true_count <= *n,
88 }
89 }
90
91 pub fn description(&self) -> String {
93 match self {
94 LogicalOperator::All => "all".to_string(),
95 LogicalOperator::Any => "any".to_string(),
96 LogicalOperator::Exactly(n) => format!("exactly {n}"),
97 LogicalOperator::AtLeast(n) => format!("at least {n}"),
98 LogicalOperator::AtMost(n) => format!("at most {n}"),
99 }
100 }
101}
102
103impl fmt::Display for LogicalOperator {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 write!(f, "{}", self.description())
106 }
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
130pub enum ColumnSpec {
131 Single(String),
133 Multiple(Vec<String>),
135}
136
137impl ColumnSpec {
138 pub fn as_vec(&self) -> Vec<&str> {
140 match self {
141 ColumnSpec::Single(col) => vec![col.as_str()],
142 ColumnSpec::Multiple(cols) => cols.iter().map(|s| s.as_str()).collect(),
143 }
144 }
145
146 pub fn len(&self) -> usize {
148 match self {
149 ColumnSpec::Single(_) => 1,
150 ColumnSpec::Multiple(cols) => cols.len(),
151 }
152 }
153
154 pub fn is_empty(&self) -> bool {
156 match self {
157 ColumnSpec::Single(_) => false,
158 ColumnSpec::Multiple(cols) => cols.is_empty(),
159 }
160 }
161
162 pub fn is_single(&self) -> bool {
164 matches!(self, ColumnSpec::Single(_))
165 }
166
167 pub fn is_multiple(&self) -> bool {
169 matches!(self, ColumnSpec::Multiple(_))
170 }
171
172 pub fn to_multiple(self) -> Vec<String> {
174 match self {
175 ColumnSpec::Single(col) => vec![col],
176 ColumnSpec::Multiple(cols) => cols,
177 }
178 }
179}
180
181impl From<String> for ColumnSpec {
182 fn from(s: String) -> Self {
183 ColumnSpec::Single(s)
184 }
185}
186
187impl From<&str> for ColumnSpec {
188 fn from(s: &str) -> Self {
189 ColumnSpec::Single(s.to_string())
190 }
191}
192
193impl From<Vec<String>> for ColumnSpec {
194 fn from(v: Vec<String>) -> Self {
195 match v.len() {
196 1 => {
197 #[allow(clippy::expect_used)]
199 ColumnSpec::Single(
200 v.into_iter()
201 .next()
202 .expect("Vector with length 1 should have one element"),
203 )
204 }
205 _ => ColumnSpec::Multiple(v),
206 }
207 }
208}
209
210impl From<Vec<&str>> for ColumnSpec {
211 fn from(v: Vec<&str>) -> Self {
212 let strings: Vec<String> = v.into_iter().map(|s| s.to_string()).collect();
213 strings.into()
214 }
215}
216
217impl<const N: usize> From<[&str; N]> for ColumnSpec {
218 fn from(arr: [&str; N]) -> Self {
219 let vec: Vec<String> = arr.iter().map(|s| s.to_string()).collect();
220 vec.into()
221 }
222}
223
224pub trait ConstraintOptionsBuilder: Sized {
229 fn with_operator(self, operator: LogicalOperator) -> Self;
231
232 fn with_threshold(self, threshold: f64) -> Self;
234
235 fn with_option(self, name: &str, value: bool) -> Self;
237}
238
239#[derive(Debug, Clone)]
244pub struct LogicalResult {
245 pub result: bool,
247 pub individual_results: Vec<(String, bool)>,
249 pub operator: LogicalOperator,
251 pub message: Option<String>,
253}
254
255impl LogicalResult {
256 pub fn new(
258 result: bool,
259 individual_results: Vec<(String, bool)>,
260 operator: LogicalOperator,
261 ) -> Self {
262 Self {
263 result,
264 individual_results,
265 operator,
266 message: None,
267 }
268 }
269
270 pub fn with_message(mut self, message: String) -> Self {
272 self.message = Some(message);
273 self
274 }
275
276 pub fn passed_columns(&self) -> Vec<&str> {
278 self.individual_results
279 .iter()
280 .filter(|(_, passed)| *passed)
281 .map(|(col, _)| col.as_str())
282 .collect()
283 }
284
285 pub fn failed_columns(&self) -> Vec<&str> {
287 self.individual_results
288 .iter()
289 .filter(|(_, passed)| !*passed)
290 .map(|(col, _)| col.as_str())
291 .collect()
292 }
293
294 pub fn pass_rate(&self) -> f64 {
296 if self.individual_results.is_empty() {
297 return 0.0;
298 }
299 let passed = self.individual_results.iter().filter(|(_, p)| *p).count();
300 passed as f64 / self.individual_results.len() as f64
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307
308 #[test]
309 fn test_logical_operator_all() {
310 let op = LogicalOperator::All;
311
312 assert!(op.evaluate(&[])); assert!(op.evaluate(&[true]));
314 assert!(!op.evaluate(&[false]));
315 assert!(op.evaluate(&[true, true, true]));
316 assert!(!op.evaluate(&[true, false, true]));
317 }
318
319 #[test]
320 fn test_logical_operator_any() {
321 let op = LogicalOperator::Any;
322
323 assert!(!op.evaluate(&[])); assert!(op.evaluate(&[true]));
325 assert!(!op.evaluate(&[false]));
326 assert!(op.evaluate(&[true, false, false]));
327 assert!(!op.evaluate(&[false, false, false]));
328 }
329
330 #[test]
331 fn test_logical_operator_exactly() {
332 let op = LogicalOperator::Exactly(2);
333
334 assert!(!op.evaluate(&[]));
335 assert!(!op.evaluate(&[true]));
336 assert!(!op.evaluate(&[true, true, true]));
337 assert!(op.evaluate(&[true, true, false]));
338 assert!(op.evaluate(&[true, false, true]));
339
340 let op_zero = LogicalOperator::Exactly(0);
342 assert!(op_zero.evaluate(&[]));
343 assert!(!op_zero.evaluate(&[true]));
344 assert!(op_zero.evaluate(&[false, false]));
345 }
346
347 #[test]
348 fn test_logical_operator_at_least() {
349 let op = LogicalOperator::AtLeast(2);
350
351 assert!(!op.evaluate(&[]));
352 assert!(!op.evaluate(&[true]));
353 assert!(op.evaluate(&[true, true]));
354 assert!(op.evaluate(&[true, true, true]));
355 assert!(op.evaluate(&[true, true, false]));
356 assert!(!op.evaluate(&[true, false, false]));
357 }
358
359 #[test]
360 fn test_logical_operator_at_most() {
361 let op = LogicalOperator::AtMost(2);
362
363 assert!(op.evaluate(&[]));
364 assert!(op.evaluate(&[true]));
365 assert!(op.evaluate(&[true, true]));
366 assert!(!op.evaluate(&[true, true, true]));
367 assert!(op.evaluate(&[true, false, false]));
368 assert!(op.evaluate(&[false, false, false]));
369 }
370
371 #[test]
372 fn test_column_spec_single() {
373 let spec = ColumnSpec::Single("user_id".to_string());
374
375 assert_eq!(spec.len(), 1);
376 assert!(!spec.is_empty());
377 assert!(spec.is_single());
378 assert!(!spec.is_multiple());
379 assert_eq!(spec.as_vec(), vec!["user_id"]);
380 }
381
382 #[test]
383 fn test_column_spec_multiple() {
384 let spec = ColumnSpec::Multiple(vec!["email".to_string(), "phone".to_string()]);
385
386 assert_eq!(spec.len(), 2);
387 assert!(!spec.is_empty());
388 assert!(!spec.is_single());
389 assert!(spec.is_multiple());
390 assert_eq!(spec.as_vec(), vec!["email", "phone"]);
391 }
392
393 #[test]
394 fn test_column_spec_conversions() {
395 let spec = ColumnSpec::from("test");
397 assert!(matches!(spec, ColumnSpec::Single(s) if s == "test"));
398
399 let spec = ColumnSpec::from("test".to_string());
401 assert!(matches!(spec, ColumnSpec::Single(s) if s == "test"));
402
403 let spec = ColumnSpec::from(vec!["test"]);
405 assert!(matches!(spec, ColumnSpec::Single(s) if s == "test"));
406
407 let spec = ColumnSpec::from(vec!["test1", "test2"]);
409 assert!(matches!(spec, ColumnSpec::Multiple(v) if v.len() == 2));
410
411 let spec = ColumnSpec::from(["a", "b", "c"]);
413 assert!(matches!(spec, ColumnSpec::Multiple(v) if v.len() == 3));
414 }
415
416 #[test]
417 fn test_logical_result() {
418 let individual = vec![
419 ("col1".to_string(), true),
420 ("col2".to_string(), false),
421 ("col3".to_string(), true),
422 ];
423
424 let result = LogicalResult::new(true, individual, LogicalOperator::AtLeast(2));
425
426 assert_eq!(result.passed_columns(), vec!["col1", "col3"]);
427 assert_eq!(result.failed_columns(), vec!["col2"]);
428 assert_eq!(result.pass_rate(), 2.0 / 3.0);
429 }
430}