1use std::time::Duration;
7
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub enum Constraint {
13 Range { min: f64, max: f64 },
15 Pattern(String),
17 AllowedValues(Vec<String>),
19 Length { min: usize, max: usize },
21 Precision { decimal_places: usize },
23 Unique,
25 NotNull,
27 Custom(String),
29 ArrayElements(Box<Constraint>),
31 ArraySize { min: usize, max: usize },
33 Statistical(StatisticalConstraints),
35 Temporal(TimeConstraints),
37 Shape(ShapeConstraints),
39 And(Vec<Constraint>),
41 Or(Vec<Constraint>),
43 Not(Box<Constraint>),
45 If {
47 condition: Box<Constraint>,
48 then_constraint: Box<Constraint>,
49 else_constraint: Option<Box<Constraint>>,
50 },
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55pub struct StatisticalConstraints {
56 pub min_mean: Option<f64>,
58 pub max_mean: Option<f64>,
60 pub min_std: Option<f64>,
62 pub max_std: Option<f64>,
64 pub expected_distribution: Option<String>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
70pub struct ShapeConstraints {
71 pub dimensions: Vec<Option<usize>>,
73 pub min_elements: Option<usize>,
75 pub max_elements: Option<usize>,
77 pub require_square: bool,
79 pub allow_broadcasting: bool,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
85pub struct TimeConstraints {
86 pub min_interval: Option<Duration>,
88 pub max_interval: Option<Duration>,
90 pub require_monotonic: bool,
92 pub allow_duplicates: bool,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
98pub enum SparseFormat {
99 CSR,
101 CSC,
103 COO,
105 DOK,
107}
108
109pub type ElementValidatorFn<T> = Box<dyn Fn(&T) -> bool + Send + Sync>;
111
112pub struct ArrayValidationConstraints {
114 pub expectedshape: Option<Vec<usize>>,
116 pub fieldname: Option<String>,
118 pub check_numeric_quality: bool,
120 pub statistical_constraints: Option<StatisticalConstraints>,
122 pub check_performance: bool,
124 pub element_validator: Option<ElementValidatorFn<f64>>,
126}
127
128impl ArrayValidationConstraints {
129 pub fn new() -> Self {
131 Self {
132 expectedshape: None,
133 fieldname: None,
134 check_numeric_quality: false,
135 statistical_constraints: None,
136 check_performance: false,
137 element_validator: None,
138 }
139 }
140
141 pub fn withshape(mut self, shape: Vec<usize>) -> Self {
143 self.expectedshape = Some(shape);
144 self
145 }
146
147 pub fn with_fieldname(mut self, name: &str) -> Self {
149 self.fieldname = Some(name.to_string());
150 self
151 }
152
153 pub fn check_numeric_quality(mut self) -> Self {
155 self.check_numeric_quality = true;
156 self
157 }
158
159 pub fn with_statistical_constraints(mut self, constraints: StatisticalConstraints) -> Self {
161 self.statistical_constraints = Some(constraints);
162 self
163 }
164
165 pub fn check_performance(mut self) -> Self {
167 self.check_performance = true;
168 self
169 }
170}
171
172impl Default for ArrayValidationConstraints {
173 fn default() -> Self {
174 Self::new()
175 }
176}
177
178impl StatisticalConstraints {
179 pub fn new() -> Self {
181 Self {
182 min_mean: None,
183 max_mean: None,
184 min_std: None,
185 max_std: None,
186 expected_distribution: None,
187 }
188 }
189
190 pub fn with_mean_range(mut self, min: f64, max: f64) -> Self {
192 self.min_mean = Some(min);
193 self.max_mean = Some(max);
194 self
195 }
196
197 pub fn with_std_range(mut self, min: f64, max: f64) -> Self {
199 self.min_std = Some(min);
200 self.max_std = Some(max);
201 self
202 }
203
204 pub fn with_distribution(mut self, distribution: &str) -> Self {
206 self.expected_distribution = Some(distribution.to_string());
207 self
208 }
209}
210
211impl Default for StatisticalConstraints {
212 fn default() -> Self {
213 Self::new()
214 }
215}
216
217impl ShapeConstraints {
218 pub fn new() -> Self {
220 Self {
221 dimensions: Vec::new(),
222 min_elements: None,
223 max_elements: None,
224 require_square: false,
225 allow_broadcasting: false,
226 }
227 }
228
229 pub fn with_dimensions(mut self, dimensions: Vec<Option<usize>>) -> Self {
231 self.dimensions = dimensions;
232 self
233 }
234
235 pub fn with_element_range(mut self, min: usize, max: usize) -> Self {
237 self.min_elements = Some(min);
238 self.max_elements = Some(max);
239 self
240 }
241
242 pub fn require_square(mut self) -> Self {
244 self.require_square = true;
245 self
246 }
247
248 pub fn allow_broadcasting(mut self) -> Self {
250 self.allow_broadcasting = true;
251 self
252 }
253}
254
255impl Default for ShapeConstraints {
256 fn default() -> Self {
257 Self::new()
258 }
259}
260
261impl TimeConstraints {
262 pub fn new() -> Self {
264 Self {
265 min_interval: None,
266 max_interval: None,
267 require_monotonic: false,
268 allow_duplicates: true,
269 }
270 }
271
272 pub fn with_interval_range(mut self, min: Duration, max: Duration) -> Self {
274 self.min_interval = Some(min);
275 self.max_interval = Some(max);
276 self
277 }
278
279 pub fn with_min_interval(mut self, interval: Duration) -> Self {
281 self.min_interval = Some(interval);
282 self
283 }
284
285 pub fn with_max_interval(mut self, interval: Duration) -> Self {
287 self.max_interval = Some(interval);
288 self
289 }
290
291 pub fn require_monotonic(mut self) -> Self {
293 self.require_monotonic = true;
294 self
295 }
296
297 pub fn disallow_duplicates(mut self) -> Self {
299 self.allow_duplicates = false;
300 self
301 }
302}
303
304impl Default for TimeConstraints {
305 fn default() -> Self {
306 Self::new()
307 }
308}
309
310pub struct ConstraintBuilder {
312 constraints: Vec<Constraint>,
313}
314
315impl ConstraintBuilder {
316 pub fn new() -> Self {
318 Self {
319 constraints: Vec::new(),
320 }
321 }
322
323 #[allow(clippy::should_implement_trait)]
325 pub fn add(mut self, constraint: Constraint) -> Self {
326 self.constraints.push(constraint);
327 self
328 }
329
330 pub fn range(self, min: f64, max: f64) -> Self {
332 self.add(Constraint::Range { min, max })
333 }
334
335 pub fn pattern(self, pattern: &str) -> Self {
337 self.add(Constraint::Pattern(pattern.to_string()))
338 }
339
340 pub fn length(self, min: usize, max: usize) -> Self {
342 self.add(Constraint::Length { min, max })
343 }
344
345 pub fn not_null(self) -> Self {
347 self.add(Constraint::NotNull)
348 }
349
350 pub fn and(self) -> Constraint {
352 match self.constraints.len() {
353 0 => panic!("Cannot create AND constraint with no constraints"),
354 1 => self
355 .constraints
356 .into_iter()
357 .next()
358 .expect("Operation failed"),
359 _ => Constraint::And(self.constraints),
360 }
361 }
362
363 pub fn or(self) -> Constraint {
365 match self.constraints.len() {
366 0 => panic!("Cannot create OR constraint with no constraints"),
367 1 => self
368 .constraints
369 .into_iter()
370 .next()
371 .expect("Operation failed"),
372 _ => Constraint::Or(self.constraints),
373 }
374 }
375}
376
377impl Default for ConstraintBuilder {
378 fn default() -> Self {
379 Self::new()
380 }
381}
382
383impl Constraint {
384 pub fn all_of(constraints: Vec<Constraint>) -> Self {
386 Constraint::And(constraints)
387 }
388
389 pub fn any_of(constraints: Vec<Constraint>) -> Self {
391 Constraint::Or(constraints)
392 }
393
394 #[allow(clippy::should_implement_trait)]
396 pub fn not(constraint: Constraint) -> Self {
397 Constraint::Not(Box::new(constraint))
398 }
399
400 pub fn if_then(
402 condition: Constraint,
403 then_constraint: Constraint,
404 else_constraint: Option<Constraint>,
405 ) -> Self {
406 Constraint::If {
407 condition: Box::new(condition),
408 then_constraint: Box::new(then_constraint),
409 else_constraint: else_constraint.map(Box::new),
410 }
411 }
412
413 pub fn and(self, other: Constraint) -> Self {
415 match self {
416 Constraint::And(mut constraints) => {
417 constraints.push(other);
418 Constraint::And(constraints)
419 }
420 _ => Constraint::And(vec![self, other]),
421 }
422 }
423
424 pub fn or(self, other: Constraint) -> Self {
426 match self {
427 Constraint::Or(mut constraints) => {
428 constraints.push(other);
429 Constraint::Or(constraints)
430 }
431 _ => Constraint::Or(vec![self, other]),
432 }
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439
440 #[test]
441 fn test_range_constraint() {
442 let constraint = Constraint::Range {
443 min: 0.0,
444 max: 100.0,
445 };
446 match constraint {
447 Constraint::Range { min, max } => {
448 assert_eq!(min, 0.0);
449 assert_eq!(max, 100.0);
450 }
451 _ => panic!("Expected Range constraint"),
452 }
453 }
454
455 #[test]
456 fn test_statistical_constraints() {
457 let constraints = StatisticalConstraints::new()
458 .with_mean_range(0.0, 10.0)
459 .with_std_range(1.0, 5.0)
460 .with_distribution("normal");
461
462 assert_eq!(constraints.min_mean, Some(0.0));
463 assert_eq!(constraints.max_mean, Some(10.0));
464 assert_eq!(constraints.min_std, Some(1.0));
465 assert_eq!(constraints.max_std, Some(5.0));
466 assert_eq!(
467 constraints.expected_distribution,
468 Some("normal".to_string())
469 );
470 }
471
472 #[test]
473 fn testshape_constraints() {
474 let constraints = ShapeConstraints::new()
475 .with_dimensions(vec![Some(10), Some(20)])
476 .with_element_range(100, 500)
477 .require_square();
478
479 assert_eq!(constraints.dimensions, vec![Some(10), Some(20)]);
480 assert_eq!(constraints.min_elements, Some(100));
481 assert_eq!(constraints.max_elements, Some(500));
482 assert!(constraints.require_square);
483 }
484
485 #[test]
486 fn test_constraint_builder() {
487 let constraint = ConstraintBuilder::new().range(0.0, 100.0).not_null().and();
489
490 match constraint {
491 Constraint::And(constraints) => {
492 assert_eq!(constraints.len(), 2);
493 }
494 _ => panic!("Expected And constraint"),
495 }
496
497 let constraint = ConstraintBuilder::new()
499 .pattern("^[a-z]+$")
500 .pattern("^[A-Z]+$")
501 .or();
502
503 match constraint {
504 Constraint::Or(constraints) => {
505 assert_eq!(constraints.len(), 2);
506 }
507 _ => panic!("Expected Or constraint"),
508 }
509 }
510
511 #[test]
512 fn test_constraint_chaining() {
513 let constraint = Constraint::Range {
515 min: 0.0,
516 max: 100.0,
517 }
518 .and(Constraint::NotNull);
519
520 match constraint {
521 Constraint::And(constraints) => {
522 assert_eq!(constraints.len(), 2);
523 }
524 _ => panic!("Expected And constraint"),
525 }
526
527 let constraint = Constraint::Pattern("^[a-z]+$".to_string())
529 .or(Constraint::Pattern("^[A-Z]+$".to_string()));
530
531 match constraint {
532 Constraint::Or(constraints) => {
533 assert_eq!(constraints.len(), 2);
534 }
535 _ => panic!("Expected Or constraint"),
536 }
537 }
538
539 #[test]
540 fn test_composite_constraints() {
541 let constraint = Constraint::not(Constraint::Pattern("forbidden".to_string()));
543 match constraint {
544 Constraint::Not(_) => {}
545 _ => panic!("Expected Not constraint"),
546 }
547
548 let constraint = Constraint::if_then(
550 Constraint::NotNull,
551 Constraint::Range {
552 min: 0.0,
553 max: 100.0,
554 },
555 Some(Constraint::Pattern("N/A".to_string())),
556 );
557
558 match constraint {
559 Constraint::If {
560 condition: _,
561 then_constraint: _,
562 else_constraint,
563 } => {
564 assert!(else_constraint.is_some());
565 }
566 _ => panic!("Expected If constraint"),
567 }
568 }
569
570 #[test]
571 fn test_complex_composition() {
572 let age_constraint = Constraint::all_of(vec![
574 Constraint::Range {
575 min: 0.0,
576 max: 150.0,
577 },
578 Constraint::NotNull,
579 ]);
580
581 let name_constraint = Constraint::any_of(vec![
582 Constraint::Pattern("^[A-Za-z ]+$".to_string()),
583 Constraint::Pattern("^[\\p{L} ]+$".to_string()),
584 ]);
585
586 let combined = Constraint::And(vec![age_constraint, name_constraint]);
588
589 match combined {
590 Constraint::And(constraints) => {
591 assert_eq!(constraints.len(), 2);
592 }
593 _ => panic!("Expected And constraint"),
594 }
595 }
596
597 #[test]
598 fn test_time_constraints() {
599 let constraints = TimeConstraints::new()
600 .with_min_interval(Duration::from_secs(1))
601 .with_max_interval(Duration::from_secs(60))
602 .require_monotonic()
603 .disallow_duplicates();
604
605 assert_eq!(constraints.min_interval, Some(Duration::from_secs(1)));
606 assert_eq!(constraints.max_interval, Some(Duration::from_secs(60)));
607 assert!(constraints.require_monotonic);
608 assert!(!constraints.allow_duplicates);
609 }
610
611 #[test]
612 fn test_constraint_builder_edge_cases() {
613 let constraint = ConstraintBuilder::new().range(0.0, 100.0).and();
615
616 match constraint {
617 Constraint::Range { min, max } => {
618 assert_eq!(min, 0.0);
619 assert_eq!(max, 100.0);
620 }
621 _ => panic!("Expected Range constraint, not And"),
622 }
623
624 let result = std::panic::catch_unwind(|| ConstraintBuilder::new().and());
626 assert!(result.is_err());
627
628 let constraint = ConstraintBuilder::new()
630 .range(0.0, 100.0)
631 .pattern("^[A-Z]+$")
632 .length(5, 10)
633 .not_null()
634 .and();
635
636 match constraint {
637 Constraint::And(constraints) => {
638 assert_eq!(constraints.len(), 4);
639 }
640 _ => panic!("Expected And constraint"),
641 }
642 }
643
644 #[test]
645 fn test_nested_constraint_composition() {
646 let inner = Constraint::Range {
648 min: 0.0,
649 max: 50.0,
650 };
651 let middle = Constraint::And(vec![inner, Constraint::NotNull]);
652 let outer = Constraint::Or(vec![middle, Constraint::Pattern("special".to_string())]);
653 let complex = Constraint::Not(Box::new(outer));
654
655 match complex {
656 Constraint::Not(inner) => match inner.as_ref() {
657 Constraint::Or(constraints) => {
658 assert_eq!(constraints.len(), 2);
659 }
660 _ => panic!("Expected Or constraint"),
661 },
662 _ => panic!("Expected Not constraint"),
663 }
664 }
665
666 #[test]
667 fn test_constraint_equality() {
668 let c1 = Constraint::Range {
669 min: 0.0,
670 max: 100.0,
671 };
672 let c2 = Constraint::Range {
673 min: 0.0,
674 max: 100.0,
675 };
676 let c3 = Constraint::Range {
677 min: 0.0,
678 max: 200.0,
679 };
680
681 assert_eq!(c1, c2);
682 assert_ne!(c1, c3);
683
684 let and1 = Constraint::And(vec![c1.clone(), Constraint::NotNull]);
685 let and2 = Constraint::And(vec![c2.clone(), Constraint::NotNull]);
686 assert_eq!(and1, and2);
687 }
688
689 #[test]
690 fn test_array_validation_constraints() {
691 let constraints = ArrayValidationConstraints::new()
692 .withshape(vec![10, 20])
693 .with_fieldname("test_array")
694 .check_numeric_quality()
695 .check_performance();
696
697 assert_eq!(constraints.expectedshape, Some(vec![10, 20]));
698 assert_eq!(constraints.fieldname, Some("test_array".to_string()));
699 assert!(constraints.check_numeric_quality);
700 assert!(constraints.check_performance);
701 }
702
703 #[test]
704 fn test_sparse_format() {
705 let format = SparseFormat::CSR;
706 assert_eq!(format, SparseFormat::CSR);
707 }
708}