use std::time::Duration;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Constraint {
Range { min: f64, max: f64 },
Pattern(String),
AllowedValues(Vec<String>),
Length { min: usize, max: usize },
Precision { decimal_places: usize },
Unique,
NotNull,
Custom(String),
ArrayElements(Box<Constraint>),
ArraySize { min: usize, max: usize },
Statistical(StatisticalConstraints),
Temporal(TimeConstraints),
Shape(ShapeConstraints),
And(Vec<Constraint>),
Or(Vec<Constraint>),
Not(Box<Constraint>),
If {
condition: Box<Constraint>,
then_constraint: Box<Constraint>,
else_constraint: Option<Box<Constraint>>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StatisticalConstraints {
pub min_mean: Option<f64>,
pub max_mean: Option<f64>,
pub min_std: Option<f64>,
pub max_std: Option<f64>,
pub expected_distribution: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ShapeConstraints {
pub dimensions: Vec<Option<usize>>,
pub min_elements: Option<usize>,
pub max_elements: Option<usize>,
pub require_square: bool,
pub allow_broadcasting: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TimeConstraints {
pub min_interval: Option<Duration>,
pub max_interval: Option<Duration>,
pub require_monotonic: bool,
pub allow_duplicates: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SparseFormat {
CSR,
CSC,
COO,
DOK,
}
pub type ElementValidatorFn<T> = Box<dyn Fn(&T) -> bool + Send + Sync>;
pub struct ArrayValidationConstraints {
pub expectedshape: Option<Vec<usize>>,
pub fieldname: Option<String>,
pub check_numeric_quality: bool,
pub statistical_constraints: Option<StatisticalConstraints>,
pub check_performance: bool,
pub element_validator: Option<ElementValidatorFn<f64>>,
}
impl ArrayValidationConstraints {
pub fn new() -> Self {
Self {
expectedshape: None,
fieldname: None,
check_numeric_quality: false,
statistical_constraints: None,
check_performance: false,
element_validator: None,
}
}
pub fn withshape(mut self, shape: Vec<usize>) -> Self {
self.expectedshape = Some(shape);
self
}
pub fn with_fieldname(mut self, name: &str) -> Self {
self.fieldname = Some(name.to_string());
self
}
pub fn check_numeric_quality(mut self) -> Self {
self.check_numeric_quality = true;
self
}
pub fn with_statistical_constraints(mut self, constraints: StatisticalConstraints) -> Self {
self.statistical_constraints = Some(constraints);
self
}
pub fn check_performance(mut self) -> Self {
self.check_performance = true;
self
}
}
impl Default for ArrayValidationConstraints {
fn default() -> Self {
Self::new()
}
}
impl StatisticalConstraints {
pub fn new() -> Self {
Self {
min_mean: None,
max_mean: None,
min_std: None,
max_std: None,
expected_distribution: None,
}
}
pub fn with_mean_range(mut self, min: f64, max: f64) -> Self {
self.min_mean = Some(min);
self.max_mean = Some(max);
self
}
pub fn with_std_range(mut self, min: f64, max: f64) -> Self {
self.min_std = Some(min);
self.max_std = Some(max);
self
}
pub fn with_distribution(mut self, distribution: &str) -> Self {
self.expected_distribution = Some(distribution.to_string());
self
}
}
impl Default for StatisticalConstraints {
fn default() -> Self {
Self::new()
}
}
impl ShapeConstraints {
pub fn new() -> Self {
Self {
dimensions: Vec::new(),
min_elements: None,
max_elements: None,
require_square: false,
allow_broadcasting: false,
}
}
pub fn with_dimensions(mut self, dimensions: Vec<Option<usize>>) -> Self {
self.dimensions = dimensions;
self
}
pub fn with_element_range(mut self, min: usize, max: usize) -> Self {
self.min_elements = Some(min);
self.max_elements = Some(max);
self
}
pub fn require_square(mut self) -> Self {
self.require_square = true;
self
}
pub fn allow_broadcasting(mut self) -> Self {
self.allow_broadcasting = true;
self
}
}
impl Default for ShapeConstraints {
fn default() -> Self {
Self::new()
}
}
impl TimeConstraints {
pub fn new() -> Self {
Self {
min_interval: None,
max_interval: None,
require_monotonic: false,
allow_duplicates: true,
}
}
pub fn with_interval_range(mut self, min: Duration, max: Duration) -> Self {
self.min_interval = Some(min);
self.max_interval = Some(max);
self
}
pub fn with_min_interval(mut self, interval: Duration) -> Self {
self.min_interval = Some(interval);
self
}
pub fn with_max_interval(mut self, interval: Duration) -> Self {
self.max_interval = Some(interval);
self
}
pub fn require_monotonic(mut self) -> Self {
self.require_monotonic = true;
self
}
pub fn disallow_duplicates(mut self) -> Self {
self.allow_duplicates = false;
self
}
}
impl Default for TimeConstraints {
fn default() -> Self {
Self::new()
}
}
pub struct ConstraintBuilder {
constraints: Vec<Constraint>,
}
impl ConstraintBuilder {
pub fn new() -> Self {
Self {
constraints: Vec::new(),
}
}
#[allow(clippy::should_implement_trait)]
pub fn add(mut self, constraint: Constraint) -> Self {
self.constraints.push(constraint);
self
}
pub fn range(self, min: f64, max: f64) -> Self {
self.add(Constraint::Range { min, max })
}
pub fn pattern(self, pattern: &str) -> Self {
self.add(Constraint::Pattern(pattern.to_string()))
}
pub fn length(self, min: usize, max: usize) -> Self {
self.add(Constraint::Length { min, max })
}
pub fn not_null(self) -> Self {
self.add(Constraint::NotNull)
}
pub fn and(self) -> Constraint {
match self.constraints.len() {
0 => panic!("Cannot create AND constraint with no constraints"),
1 => self
.constraints
.into_iter()
.next()
.expect("Operation failed"),
_ => Constraint::And(self.constraints),
}
}
pub fn or(self) -> Constraint {
match self.constraints.len() {
0 => panic!("Cannot create OR constraint with no constraints"),
1 => self
.constraints
.into_iter()
.next()
.expect("Operation failed"),
_ => Constraint::Or(self.constraints),
}
}
}
impl Default for ConstraintBuilder {
fn default() -> Self {
Self::new()
}
}
impl Constraint {
pub fn all_of(constraints: Vec<Constraint>) -> Self {
Constraint::And(constraints)
}
pub fn any_of(constraints: Vec<Constraint>) -> Self {
Constraint::Or(constraints)
}
#[allow(clippy::should_implement_trait)]
pub fn not(constraint: Constraint) -> Self {
Constraint::Not(Box::new(constraint))
}
pub fn if_then(
condition: Constraint,
then_constraint: Constraint,
else_constraint: Option<Constraint>,
) -> Self {
Constraint::If {
condition: Box::new(condition),
then_constraint: Box::new(then_constraint),
else_constraint: else_constraint.map(Box::new),
}
}
pub fn and(self, other: Constraint) -> Self {
match self {
Constraint::And(mut constraints) => {
constraints.push(other);
Constraint::And(constraints)
}
_ => Constraint::And(vec![self, other]),
}
}
pub fn or(self, other: Constraint) -> Self {
match self {
Constraint::Or(mut constraints) => {
constraints.push(other);
Constraint::Or(constraints)
}
_ => Constraint::Or(vec![self, other]),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_range_constraint() {
let constraint = Constraint::Range {
min: 0.0,
max: 100.0,
};
match constraint {
Constraint::Range { min, max } => {
assert_eq!(min, 0.0);
assert_eq!(max, 100.0);
}
_ => panic!("Expected Range constraint"),
}
}
#[test]
fn test_statistical_constraints() {
let constraints = StatisticalConstraints::new()
.with_mean_range(0.0, 10.0)
.with_std_range(1.0, 5.0)
.with_distribution("normal");
assert_eq!(constraints.min_mean, Some(0.0));
assert_eq!(constraints.max_mean, Some(10.0));
assert_eq!(constraints.min_std, Some(1.0));
assert_eq!(constraints.max_std, Some(5.0));
assert_eq!(
constraints.expected_distribution,
Some("normal".to_string())
);
}
#[test]
fn testshape_constraints() {
let constraints = ShapeConstraints::new()
.with_dimensions(vec![Some(10), Some(20)])
.with_element_range(100, 500)
.require_square();
assert_eq!(constraints.dimensions, vec![Some(10), Some(20)]);
assert_eq!(constraints.min_elements, Some(100));
assert_eq!(constraints.max_elements, Some(500));
assert!(constraints.require_square);
}
#[test]
fn test_constraint_builder() {
let constraint = ConstraintBuilder::new().range(0.0, 100.0).not_null().and();
match constraint {
Constraint::And(constraints) => {
assert_eq!(constraints.len(), 2);
}
_ => panic!("Expected And constraint"),
}
let constraint = ConstraintBuilder::new()
.pattern("^[a-z]+$")
.pattern("^[A-Z]+$")
.or();
match constraint {
Constraint::Or(constraints) => {
assert_eq!(constraints.len(), 2);
}
_ => panic!("Expected Or constraint"),
}
}
#[test]
fn test_constraint_chaining() {
let constraint = Constraint::Range {
min: 0.0,
max: 100.0,
}
.and(Constraint::NotNull);
match constraint {
Constraint::And(constraints) => {
assert_eq!(constraints.len(), 2);
}
_ => panic!("Expected And constraint"),
}
let constraint = Constraint::Pattern("^[a-z]+$".to_string())
.or(Constraint::Pattern("^[A-Z]+$".to_string()));
match constraint {
Constraint::Or(constraints) => {
assert_eq!(constraints.len(), 2);
}
_ => panic!("Expected Or constraint"),
}
}
#[test]
fn test_composite_constraints() {
let constraint = Constraint::not(Constraint::Pattern("forbidden".to_string()));
match constraint {
Constraint::Not(_) => {}
_ => panic!("Expected Not constraint"),
}
let constraint = Constraint::if_then(
Constraint::NotNull,
Constraint::Range {
min: 0.0,
max: 100.0,
},
Some(Constraint::Pattern("N/A".to_string())),
);
match constraint {
Constraint::If {
condition: _,
then_constraint: _,
else_constraint,
} => {
assert!(else_constraint.is_some());
}
_ => panic!("Expected If constraint"),
}
}
#[test]
fn test_complex_composition() {
let age_constraint = Constraint::all_of(vec![
Constraint::Range {
min: 0.0,
max: 150.0,
},
Constraint::NotNull,
]);
let name_constraint = Constraint::any_of(vec![
Constraint::Pattern("^[A-Za-z ]+$".to_string()),
Constraint::Pattern("^[\\p{L} ]+$".to_string()),
]);
let combined = Constraint::And(vec![age_constraint, name_constraint]);
match combined {
Constraint::And(constraints) => {
assert_eq!(constraints.len(), 2);
}
_ => panic!("Expected And constraint"),
}
}
#[test]
fn test_time_constraints() {
let constraints = TimeConstraints::new()
.with_min_interval(Duration::from_secs(1))
.with_max_interval(Duration::from_secs(60))
.require_monotonic()
.disallow_duplicates();
assert_eq!(constraints.min_interval, Some(Duration::from_secs(1)));
assert_eq!(constraints.max_interval, Some(Duration::from_secs(60)));
assert!(constraints.require_monotonic);
assert!(!constraints.allow_duplicates);
}
#[test]
fn test_constraint_builder_edge_cases() {
let constraint = ConstraintBuilder::new().range(0.0, 100.0).and();
match constraint {
Constraint::Range { min, max } => {
assert_eq!(min, 0.0);
assert_eq!(max, 100.0);
}
_ => panic!("Expected Range constraint, not And"),
}
let result = std::panic::catch_unwind(|| ConstraintBuilder::new().and());
assert!(result.is_err());
let constraint = ConstraintBuilder::new()
.range(0.0, 100.0)
.pattern("^[A-Z]+$")
.length(5, 10)
.not_null()
.and();
match constraint {
Constraint::And(constraints) => {
assert_eq!(constraints.len(), 4);
}
_ => panic!("Expected And constraint"),
}
}
#[test]
fn test_nested_constraint_composition() {
let inner = Constraint::Range {
min: 0.0,
max: 50.0,
};
let middle = Constraint::And(vec![inner, Constraint::NotNull]);
let outer = Constraint::Or(vec![middle, Constraint::Pattern("special".to_string())]);
let complex = Constraint::Not(Box::new(outer));
match complex {
Constraint::Not(inner) => match inner.as_ref() {
Constraint::Or(constraints) => {
assert_eq!(constraints.len(), 2);
}
_ => panic!("Expected Or constraint"),
},
_ => panic!("Expected Not constraint"),
}
}
#[test]
fn test_constraint_equality() {
let c1 = Constraint::Range {
min: 0.0,
max: 100.0,
};
let c2 = Constraint::Range {
min: 0.0,
max: 100.0,
};
let c3 = Constraint::Range {
min: 0.0,
max: 200.0,
};
assert_eq!(c1, c2);
assert_ne!(c1, c3);
let and1 = Constraint::And(vec![c1.clone(), Constraint::NotNull]);
let and2 = Constraint::And(vec![c2.clone(), Constraint::NotNull]);
assert_eq!(and1, and2);
}
#[test]
fn test_array_validation_constraints() {
let constraints = ArrayValidationConstraints::new()
.withshape(vec![10, 20])
.with_fieldname("test_array")
.check_numeric_quality()
.check_performance();
assert_eq!(constraints.expectedshape, Some(vec![10, 20]));
assert_eq!(constraints.fieldname, Some("test_array".to_string()));
assert!(constraints.check_numeric_quality);
assert!(constraints.check_performance);
}
#[test]
fn test_sparse_format() {
let format = SparseFormat::CSR;
assert_eq!(format, SparseFormat::CSR);
}
}