term_guard/core/
check.rs

1//! Check type and builder for grouping constraints.
2#![allow(deprecated)] // Allow deprecated constraints for backward compatibility
3#![allow(clippy::expect_used)] // Allow expect() in builder methods as they would require API changes
4//!
5//! This module provides the [`Check`] type and [`CheckBuilder`] for creating validation checks.
6//! Starting with v0.2.0, we provide a new unified API through the `builder_extensions` module
7//! that offers improved consistency and performance.
8//!
9//! ## New Unified API Example (v0.2.0+)
10//!
11//! ```rust
12//! use term_guard::core::{Check, Level};
13//! use term_guard::core::builder_extensions::{CompletenessOptions, StatisticalOptions};
14//! use term_guard::constraints::{Assertion, FormatType, FormatOptions};
15//!
16//! # use term_guard::prelude::*;
17//! # fn example() -> Result<Check> {
18//! let check = Check::builder("user_validation")
19//!     .level(Level::Error)
20//!     // Completeness with options
21//!     .completeness("user_id", CompletenessOptions::full().into_constraint_options())
22//!     .completeness("email", CompletenessOptions::threshold(0.95).into_constraint_options())
23//!     // Format validation
24//!     .has_format("email", FormatType::Email, 0.95, FormatOptions::default())
25//!     // Combined statistics (single query)
26//!     .statistics(
27//!         "age",
28//!         StatisticalOptions::new()
29//!             .min(Assertion::GreaterThanOrEqual(18.0))
30//!             .max(Assertion::LessThan(100.0))
31//!             .mean(Assertion::Between(25.0, 65.0))
32//!     )?
33//!     // Convenience method
34//!     .primary_key(vec!["user_id"])
35//!     .build();
36//! # Ok(check)
37//! # }
38//! ```
39
40use super::{constraint::BoxedConstraint, Constraint, Level};
41use crate::constraints::{
42    ApproxCountDistinctConstraint, Assertion, ColumnCountConstraint, CorrelationConstraint,
43    CustomSqlConstraint, DataTypeConstraint, FormatConstraint, FormatOptions, FormatType,
44    HistogramAssertion, HistogramConstraint, NullHandling, QuantileConstraint, SizeConstraint,
45    UniquenessConstraint, UniquenessOptions, UniquenessType,
46};
47use std::sync::Arc;
48
49/// A validation check containing one or more constraints.
50///
51/// A `Check` groups related constraints together and assigns them a severity level.
52/// Checks are the building blocks of validation suites. When a check runs, all its
53/// constraints are evaluated, and the check fails if any constraint fails.
54///
55/// # Examples
56///
57/// ## Basic Check
58///
59/// ```rust
60/// use term_guard::core::{Check, Level};
61///
62/// let check = Check::builder("user_data_quality")
63///     .level(Level::Error)
64///     .description("Validates user data quality")
65///     .build();
66/// ```
67///
68/// ## Check with Constraints
69///
70/// ```rust
71/// use term_guard::core::{Check, Level, ConstraintOptions};
72/// use term_guard::constraints::{Assertion, UniquenessType, StatisticType, FormatType, FormatOptions};
73///
74/// let check = Check::builder("customer_validation")
75///     .level(Level::Error)
76///     .description("Ensure customer data integrity")
77///     // Completeness checks using unified API
78///     .completeness("customer_id", ConstraintOptions::new().with_threshold(1.0))
79///     .completeness("email", ConstraintOptions::new().with_threshold(0.99))
80///     // Uniqueness checks using unified API
81///     .validates_uniqueness(vec!["customer_id"], 1.0)
82///     .validates_uniqueness(vec!["email", "region"], 1.0)
83///     // Pattern validation using format
84///     .has_format("phone", FormatType::Regex(r"^\+?\d{3}-\d{3}-\d{4}$".to_string()), 0.95, FormatOptions::default())
85///     // Range checks using statistic
86///     .statistic("age", StatisticType::Min, Assertion::GreaterThanOrEqual(18.0))
87///     .statistic("age", StatisticType::Max, Assertion::LessThanOrEqual(120.0))
88///     .build();
89/// ```
90///
91/// ## Data Quality Check
92///
93/// ```rust
94/// use term_guard::core::{Check, Level};
95/// use term_guard::constraints::{Assertion, StatisticType};
96///
97/// let check = Check::builder("data_types_and_formats")
98///     .level(Level::Warning)
99///     // Ensure consistent data types using the unified API
100///     .has_consistent_data_type("order_date", 0.99)
101///     .has_consistent_data_type("product_id", 0.95)
102///     // String length validation
103///     .has_min_length("password", 8)
104///     .has_max_length("username", 20)
105///     // Check for PII
106///     .validates_credit_card("comments", 0.0, true)  // Should be 0%
107///     .validates_email("email_field", 0.98)
108///     // Statistical checks
109///     .statistic("order_value", StatisticType::Mean, Assertion::Between(50.0, 500.0))
110///     .statistic("response_time", StatisticType::StandardDeviation, Assertion::LessThan(100.0))
111///     .build();
112/// ```
113///
114/// ## Enhanced Format Validation
115///
116/// ```rust
117/// use term_guard::core::{Check, Level};
118/// use term_guard::constraints::FormatOptions;
119///
120/// let check = Check::builder("enhanced_format_validation")
121///     .level(Level::Error)
122///     // Basic format validation
123///     .validates_email("email", 0.95)
124///     .validates_url("website", 0.90, false)
125///     .validates_phone("phone", 0.85, Some("US"))
126///     // Enhanced format validation with options
127///     .validates_email_with_options(
128///         "secondary_email",
129///         0.80,
130///         FormatOptions::lenient()  // Case insensitive, trimming, nulls allowed
131///     )
132///     .validates_url_with_options(
133///         "dev_url",
134///         0.75,
135///         true, // allow localhost
136///         FormatOptions::case_insensitive().trim_before_check(true)
137///     )
138///     .validates_regex_with_options(
139///         "product_code",
140///         r"^[A-Z]{2}\d{4}$",
141///         0.98,
142///         FormatOptions::strict()  // Case sensitive, no nulls, no trimming
143///     )
144///     .build();
145/// ```
146#[derive(Debug, Clone)]
147pub struct Check {
148    /// The name of the check
149    name: String,
150    /// The severity level of the check
151    level: Level,
152    /// Optional description of what this check validates
153    description: Option<String>,
154    /// The constraints that make up this check
155    constraints: Vec<Arc<dyn Constraint>>,
156}
157
158impl Check {
159    /// Creates a new builder for constructing a check.
160    ///
161    /// # Arguments
162    ///
163    /// * `name` - The name of the check
164    ///
165    /// # Examples
166    ///
167    /// ```rust
168    /// use term_guard::core::Check;
169    ///
170    /// let builder = Check::builder("data_quality");
171    /// ```
172    pub fn builder(name: impl Into<String>) -> CheckBuilder {
173        CheckBuilder::new(name)
174    }
175
176    /// Returns the name of the check.
177    pub fn name(&self) -> &str {
178        &self.name
179    }
180
181    /// Returns the severity level of the check.
182    pub fn level(&self) -> Level {
183        self.level
184    }
185
186    /// Returns the description of the check if available.
187    pub fn description(&self) -> Option<&str> {
188        self.description.as_deref()
189    }
190
191    /// Returns the constraints in this check.
192    pub fn constraints(&self) -> &[Arc<dyn Constraint>] {
193        &self.constraints
194    }
195}
196
197/// Builder for constructing `Check` instances.
198///
199/// # Examples
200///
201/// ```rust
202/// use term_guard::core::{Check, Level};
203///
204/// let check = Check::builder("completeness_check")
205///     .level(Level::Error)
206///     .description("Ensures all required fields are present")
207///     .build();
208/// ```
209#[derive(Debug)]
210pub struct CheckBuilder {
211    name: String,
212    level: Level,
213    description: Option<String>,
214    constraints: Vec<Arc<dyn Constraint>>,
215}
216
217impl CheckBuilder {
218    /// Creates a new check builder with the given name.
219    pub fn new(name: impl Into<String>) -> Self {
220        Self {
221            name: name.into(),
222            level: Level::default(),
223            description: None,
224            constraints: Vec::new(),
225        }
226    }
227
228    /// Sets the severity level for the check.
229    ///
230    /// # Arguments
231    ///
232    /// * `level` - The severity level
233    ///
234    /// # Examples
235    ///
236    /// ```rust
237    /// use term_guard::core::{Check, Level};
238    ///
239    /// let check = Check::builder("critical_check")
240    ///     .level(Level::Error)
241    ///     .build();
242    /// ```
243    pub fn level(mut self, level: Level) -> Self {
244        self.level = level;
245        self
246    }
247
248    /// Sets the description for the check.
249    ///
250    /// # Arguments
251    ///
252    /// * `description` - A description of what this check validates
253    pub fn description(mut self, description: impl Into<String>) -> Self {
254        self.description = Some(description.into());
255        self
256    }
257
258    /// Adds a constraint to the check.
259    ///
260    /// # Arguments
261    ///
262    /// * `constraint` - The constraint to add
263    pub fn constraint(mut self, constraint: impl Constraint + 'static) -> Self {
264        self.constraints.push(Arc::new(constraint));
265        self
266    }
267
268    /// Adds a boxed constraint to the check.
269    ///
270    /// # Arguments
271    ///
272    /// * `constraint` - The boxed constraint to add
273    pub fn boxed_constraint(mut self, constraint: BoxedConstraint) -> Self {
274        self.constraints.push(Arc::from(constraint));
275        self
276    }
277
278    /// Adds multiple constraints to the check.
279    ///
280    /// # Arguments
281    ///
282    /// * `constraints` - An iterator of constraints to add
283    pub fn constraints<I>(mut self, constraints: I) -> Self
284    where
285        I: IntoIterator<Item = BoxedConstraint>,
286    {
287        self.constraints
288            .extend(constraints.into_iter().map(Arc::from));
289        self
290    }
291
292    // Builder methods
293
294    /// Adds a constraint that checks the dataset size (row count).
295    ///
296    /// # Arguments
297    ///
298    /// * `assertion` - The assertion to evaluate against the row count
299    ///
300    /// # Examples
301    ///
302    /// ```rust
303    /// use term_guard::core::{Check, Level};
304    /// use term_guard::constraints::Assertion;
305    ///
306    /// let check = Check::builder("size_validation")
307    ///     .level(Level::Error)
308    ///     .has_size(Assertion::GreaterThan(1000.0))
309    ///     .build();
310    /// ```
311    pub fn has_size(mut self, assertion: Assertion) -> Self {
312        self.constraints
313            .push(Arc::new(SizeConstraint::new(assertion)));
314        self
315    }
316
317    /// Adds a constraint that checks the number of columns in the dataset.
318    ///
319    /// This constraint validates that the dataset has the expected number of columns
320    /// by examining the schema.
321    ///
322    /// # Arguments
323    ///
324    /// * `assertion` - The assertion to evaluate against the column count
325    ///
326    /// # Examples
327    ///
328    /// ```rust
329    /// use term_guard::core::{Check, Level};
330    /// use term_guard::constraints::Assertion;
331    ///
332    /// let check = Check::builder("schema_validation")
333    ///     .level(Level::Error)
334    ///     .has_column_count(Assertion::Equals(15.0))
335    ///     .has_column_count(Assertion::GreaterThanOrEqual(10.0))
336    ///     .build();
337    /// ```
338    pub fn has_column_count(mut self, assertion: Assertion) -> Self {
339        self.constraints
340            .push(Arc::new(ColumnCountConstraint::new(assertion)));
341        self
342    }
343
344    /// Adds a constraint that checks the approximate count of distinct values in a column.
345    ///
346    /// Uses DataFusion's APPROX_DISTINCT function which provides an approximate count
347    /// using HyperLogLog algorithm. This is much faster than exact COUNT(DISTINCT)
348    /// for large datasets while maintaining accuracy within 2-3% error margin.
349    ///
350    /// # Arguments
351    ///
352    /// * `column` - The column to count distinct values in
353    /// * `assertion` - The assertion to evaluate against the approximate distinct count
354    ///
355    /// # Examples
356    ///
357    /// ```rust
358    /// use term_guard::core::{Check, Level};
359    /// use term_guard::constraints::Assertion;
360    ///
361    /// let check = Check::builder("cardinality_validation")
362    ///     .level(Level::Warning)
363    ///     // High cardinality check (e.g., user IDs)
364    ///     .has_approx_count_distinct("user_id", Assertion::GreaterThan(1000000.0))
365    ///     // Low cardinality check (e.g., country codes)
366    ///     .has_approx_count_distinct("country_code", Assertion::LessThan(200.0))
367    ///     .build();
368    /// ```
369    pub fn has_approx_count_distinct(
370        mut self,
371        column: impl Into<String>,
372        assertion: Assertion,
373    ) -> Self {
374        self.constraints
375            .push(Arc::new(ApproxCountDistinctConstraint::new(
376                column, assertion,
377            )));
378        self
379    }
380
381    /// Adds a constraint that checks an approximate quantile of a column.
382    ///
383    /// # Arguments
384    ///
385    /// * `column` - The column to check
386    /// * `quantile` - The quantile to compute (0.0 to 1.0)
387    /// * `assertion` - The assertion to evaluate against the quantile value
388    ///
389    /// # Examples
390    ///
391    /// ```rust
392    /// use term_guard::core::{Check, Level};
393    /// use term_guard::constraints::Assertion;
394    ///
395    /// let check = Check::builder("quantile_validation")
396    ///     .level(Level::Warning)
397    ///     .has_approx_quantile("response_time", 0.95, Assertion::LessThan(1000.0))
398    ///     .build();
399    /// ```
400    ///
401    /// # Panics
402    ///
403    /// Panics if quantile is not between 0.0 and 1.0
404    pub fn has_approx_quantile(
405        mut self,
406        column: impl Into<String>,
407        quantile: f64,
408        assertion: Assertion,
409    ) -> Self {
410        self.constraints.push(Arc::new(
411            QuantileConstraint::percentile(column, quantile, assertion)
412                .expect("Invalid quantile parameters"),
413        ));
414        self
415    }
416
417    /// Adds a constraint that checks the mutual information between two columns.
418    ///
419    /// # Arguments
420    ///
421    /// * `column1` - The first column
422    /// * `column2` - The second column
423    /// * `assertion` - The assertion to evaluate against the mutual information
424    ///
425    /// # Examples
426    ///
427    /// ```rust
428    /// use term_guard::core::{Check, Level};
429    /// use term_guard::constraints::Assertion;
430    ///
431    /// let check = Check::builder("mi_validation")
432    ///     .level(Level::Info)
433    ///     .has_mutual_information("feature1", "feature2", Assertion::GreaterThan(0.5))
434    ///     .build();
435    /// ```
436    pub fn has_mutual_information(
437        mut self,
438        column1: impl Into<String>,
439        column2: impl Into<String>,
440        assertion: Assertion,
441    ) -> Self {
442        self.constraints.push(Arc::new(
443            CorrelationConstraint::mutual_information(column1, column2, 10, assertion)
444                .expect("Invalid mutual information parameters"),
445        ));
446        self
447    }
448
449    /// Adds a constraint that checks the correlation between two columns.
450    ///
451    /// # Arguments
452    ///
453    /// * `column1` - The first column
454    /// * `column2` - The second column
455    /// * `assertion` - The assertion to evaluate against the correlation
456    ///
457    /// # Examples
458    ///
459    /// ```rust
460    /// use term_guard::core::{Check, Level};
461    /// use term_guard::constraints::Assertion;
462    ///
463    /// let check = Check::builder("correlation_validation")
464    ///     .level(Level::Warning)
465    ///     .has_correlation("height", "weight", Assertion::GreaterThan(0.7))
466    ///     .build();
467    /// ```
468    pub fn has_correlation(
469        mut self,
470        column1: impl Into<String>,
471        column2: impl Into<String>,
472        assertion: Assertion,
473    ) -> Self {
474        self.constraints.push(Arc::new(
475            CorrelationConstraint::pearson(column1, column2, assertion)
476                .expect("Invalid correlation parameters"),
477        ));
478        self
479    }
480
481    /// Adds a constraint that checks minimum string length.
482    ///
483    /// **Note**: Consider using the new `length()` method for more flexibility.
484    ///
485    /// # Arguments
486    ///
487    /// * `column` - The column to check
488    /// * `min_length` - The minimum acceptable string length
489    ///
490    /// # Examples
491    ///
492    /// ```rust
493    /// use term_guard::core::{Check, Level};
494    /// use term_guard::constraints::LengthAssertion;
495    ///
496    /// // Using the convenience method:
497    /// let check = Check::builder("password_validation")
498    ///     .level(Level::Error)
499    ///     .has_min_length("password", 8)
500    ///     .build();
501    ///
502    /// // Or using the unified length API:
503    /// let check = Check::builder("password_validation")
504    ///     .level(Level::Error)
505    ///     .length("password", LengthAssertion::Min(8))
506    ///     .build();
507    /// ```
508    pub fn has_min_length(mut self, column: impl Into<String>, min_length: usize) -> Self {
509        use crate::constraints::LengthConstraint;
510        self.constraints
511            .push(Arc::new(LengthConstraint::min(column, min_length)));
512        self
513    }
514
515    /// Adds a constraint that checks maximum string length.
516    ///
517    /// # Arguments
518    ///
519    /// * `column` - The column to check
520    /// * `max_length` - The maximum acceptable string length
521    ///
522    /// # Examples
523    ///
524    /// ```rust
525    /// use term_guard::core::{Check, Level};
526    ///
527    /// let check = Check::builder("username_validation")
528    ///     .level(Level::Error)
529    ///     .has_max_length("username", 20)
530    ///     .build();
531    /// ```
532    pub fn has_max_length(mut self, column: impl Into<String>, max_length: usize) -> Self {
533        use crate::constraints::LengthConstraint;
534        self.constraints
535            .push(Arc::new(LengthConstraint::max(column, max_length)));
536        self
537    }
538
539    /// Adds a constraint that checks string length is between bounds (inclusive).
540    ///
541    /// # Arguments
542    ///  
543    /// * `column` - The column to check
544    /// * `min_length` - The minimum acceptable string length
545    /// * `max_length` - The maximum acceptable string length
546    ///
547    /// # Examples
548    ///
549    /// ```rust
550    /// use term_guard::core::{Check, Level};
551    ///
552    /// let check = Check::builder("description_validation")
553    ///     .level(Level::Warning)
554    ///     .has_length_between("description", 10, 500)
555    ///     .build();
556    /// ```
557    pub fn has_length_between(
558        mut self,
559        column: impl Into<String>,
560        min_length: usize,
561        max_length: usize,
562    ) -> Self {
563        use crate::constraints::LengthConstraint;
564        self.constraints.push(Arc::new(LengthConstraint::between(
565            column, min_length, max_length,
566        )));
567        self
568    }
569
570    /// Adds a constraint that checks string has exact length.
571    ///
572    /// # Arguments
573    ///
574    /// * `column` - The column to check  
575    /// * `length` - The required exact string length
576    ///
577    /// # Examples
578    ///
579    /// ```rust
580    /// use term_guard::core::{Check, Level};
581    ///
582    /// let check = Check::builder("code_validation")
583    ///     .level(Level::Error)
584    ///     .has_exact_length("verification_code", 6)
585    ///     .build();
586    /// ```
587    pub fn has_exact_length(mut self, column: impl Into<String>, length: usize) -> Self {
588        use crate::constraints::LengthConstraint;
589        self.constraints
590            .push(Arc::new(LengthConstraint::exactly(column, length)));
591        self
592    }
593
594    /// Adds a constraint that checks strings are not empty.
595    ///
596    /// # Arguments
597    ///
598    /// * `column` - The column to check
599    ///
600    /// # Examples
601    ///
602    /// ```rust
603    /// use term_guard::core::{Check, Level};
604    ///
605    /// let check = Check::builder("name_validation")
606    ///     .level(Level::Error)
607    ///     .is_not_empty("name")
608    ///     .build();
609    /// ```
610    pub fn is_not_empty(mut self, column: impl Into<String>) -> Self {
611        use crate::constraints::LengthConstraint;
612        self.constraints
613            .push(Arc::new(LengthConstraint::not_empty(column)));
614        self
615    }
616
617    /// Adds a constraint that checks data type consistency.
618    ///
619    /// This analyzes the actual data types present in a column and reports on consistency,
620    /// helping identify columns with mixed types.
621    ///
622    /// # Arguments
623    ///
624    /// * `column` - The column to check
625    /// * `threshold` - The minimum ratio of values that must have the most common type (0.0 to 1.0)
626    ///
627    /// # Examples
628    ///
629    /// ```rust
630    /// use term_guard::core::{Check, Level};
631    ///
632    /// let check = Check::builder("consistency_validation")
633    ///     .level(Level::Warning)
634    ///     .has_consistent_data_type("user_id", 0.95)
635    ///     .build();
636    /// ```
637    ///
638    /// # Panics
639    ///
640    /// Panics if threshold is not between 0.0 and 1.0
641    pub fn has_consistent_data_type(mut self, column: impl Into<String>, threshold: f64) -> Self {
642        self.constraints.push(Arc::new(
643            DataTypeConstraint::type_consistency(column, threshold)
644                .expect("Invalid data type consistency parameters"),
645        ));
646        self
647    }
648
649    /// Adds a constraint that evaluates a custom SQL expression.
650    ///
651    /// This allows users to define custom validation logic using SQL expressions.
652    /// The expression should evaluate to a boolean value for each row.
653    /// For safety, the expression cannot contain data-modifying operations.
654    ///
655    /// # Arguments
656    ///
657    /// * `sql_expression` - The SQL expression to evaluate (must return boolean)
658    /// * `hint` - Optional hint message to provide context when the constraint fails
659    ///
660    /// # Examples
661    ///
662    /// ```rust
663    /// use term_guard::core::{Check, Level};
664    ///
665    /// let check = Check::builder("business_rules")
666    ///     .level(Level::Error)
667    ///     .satisfies("price > 0 AND price < 1000000", Some("Price must be positive and reasonable"))
668    ///     .satisfies("order_date <= ship_date", Some("Orders cannot ship before being placed"))
669    ///     .build();
670    /// ```
671    ///
672    /// # Panics
673    ///
674    /// Panics if the SQL expression contains dangerous operations like DROP, DELETE, UPDATE, etc.
675    pub fn satisfies(
676        mut self,
677        sql_expression: impl Into<String>,
678        hint: Option<impl Into<String>>,
679    ) -> Self {
680        self.constraints.push(Arc::new(
681            CustomSqlConstraint::new(sql_expression, hint).expect("Invalid SQL expression"),
682        ));
683        self
684    }
685
686    /// Adds a constraint that analyzes value distribution and applies custom assertions.
687    ///
688    /// This constraint computes a histogram of value frequencies in the specified column
689    /// and allows custom assertion functions to validate distribution characteristics.
690    ///
691    /// # Arguments
692    ///
693    /// * `column` - The column to analyze
694    /// * `assertion` - The assertion function to apply to the histogram
695    ///
696    /// # Examples
697    ///
698    /// ```rust
699    /// use term_guard::core::{Check, Level};
700    /// use term_guard::constraints::Histogram;
701    /// use std::sync::Arc;
702    ///
703    /// let check = Check::builder("distribution_validation")
704    ///     .level(Level::Warning)
705    ///     // No single value should dominate
706    ///     .has_histogram("status", Arc::new(|hist: &Histogram| {
707    ///         hist.most_common_ratio() < 0.5
708    ///     }))
709    ///     // Check expected number of categories
710    ///     .has_histogram("category", Arc::new(|hist| {
711    ///         hist.bucket_count() >= 5 && hist.bucket_count() <= 10
712    ///     }))
713    ///     .build();
714    /// ```
715    pub fn has_histogram(
716        mut self,
717        column: impl Into<String>,
718        assertion: HistogramAssertion,
719    ) -> Self {
720        self.constraints
721            .push(Arc::new(HistogramConstraint::new(column, assertion)));
722        self
723    }
724
725    /// Adds a constraint that analyzes value distribution with a custom description.
726    ///
727    /// This is similar to `has_histogram` but allows providing a description of what
728    /// the assertion checks, which is useful for error messages.
729    ///
730    /// # Arguments
731    ///
732    /// * `column` - The column to analyze
733    /// * `assertion` - The assertion function to apply to the histogram
734    /// * `description` - A description of what the assertion checks
735    ///
736    /// # Examples
737    ///
738    /// ```rust
739    /// use term_guard::core::{Check, Level};
740    /// use term_guard::constraints::Histogram;
741    /// use std::sync::Arc;
742    ///
743    /// let check = Check::builder("distribution_validation")
744    ///     .level(Level::Error)
745    ///     .has_histogram_with_description(
746    ///         "age",
747    ///         Arc::new(|hist: &Histogram| hist.is_roughly_uniform(2.0)),
748    ///         "age distribution is roughly uniform"
749    ///     )
750    ///     .build();
751    /// ```
752    pub fn has_histogram_with_description(
753        mut self,
754        column: impl Into<String>,
755        assertion: HistogramAssertion,
756        description: impl Into<String>,
757    ) -> Self {
758        self.constraints
759            .push(Arc::new(HistogramConstraint::new_with_description(
760                column,
761                assertion,
762                description,
763            )));
764        self
765    }
766
767    // ========================================================================
768    // NEW UNIFIED FORMAT VALIDATION METHODS
769    // ========================================================================
770
771    /// Adds a general format validation constraint with full configuration options.
772    ///
773    /// This is the most flexible format validation method, supporting all format types
774    /// and configuration options through the unified FormatConstraint API.
775    ///
776    /// # Arguments
777    ///
778    /// * `column` - The column to validate
779    /// * `format` - The format type to validate against
780    /// * `threshold` - The minimum ratio of values that must match (0.0 to 1.0)
781    /// * `options` - Configuration options for the validation
782    ///
783    /// # Examples
784    ///
785    /// ```rust
786    /// use term_guard::core::{Check, Level};
787    /// use term_guard::constraints::{FormatType, FormatOptions};
788    ///
789    /// let check = Check::builder("format_validation")
790    ///     .level(Level::Error)
791    ///     // Custom regex with case-insensitive matching
792    ///     .has_format(
793    ///         "phone",
794    ///         FormatType::Regex(r"^\+?\d{3}-\d{3}-\d{4}$".to_string()),
795    ///         0.95,
796    ///         FormatOptions::default().case_sensitive(false).trim_before_check(true)
797    ///     )
798    ///     // Email validation with custom options
799    ///     .has_format(
800    ///         "email",
801    ///         FormatType::Email,
802    ///         0.99,
803    ///         FormatOptions::default().null_is_valid(true)
804    ///     )
805    ///     // UUID validation
806    ///     .has_format(
807    ///         "user_id",
808    ///         FormatType::UUID,
809    ///         1.0,
810    ///         FormatOptions::default()
811    ///     )
812    ///     .build();
813    /// ```
814    ///
815    /// # Errors
816    ///
817    /// Returns error if column name is invalid, threshold is out of range,
818    /// or regex pattern is invalid.
819    pub fn has_format(
820        mut self,
821        column: impl Into<String>,
822        format: FormatType,
823        threshold: f64,
824        options: FormatOptions,
825    ) -> Self {
826        self.constraints.push(Arc::new(
827            FormatConstraint::new(column, format, threshold, options)
828                .expect("Invalid column, format, threshold, or options"),
829        ));
830        self
831    }
832
833    /// Adds a regex pattern validation constraint.
834    ///
835    /// This is a convenience method for `has_format()` with `FormatType::Regex`.
836    ///
837    /// # Arguments
838    ///
839    /// * `column` - The column to validate
840    /// * `pattern` - The regular expression pattern
841    /// * `threshold` - The minimum ratio of values that must match (0.0 to 1.0)
842    ///
843    /// # Examples
844    ///
845    /// ```rust
846    /// use term_guard::core::{Check, Level};
847    ///
848    /// let check = Check::builder("regex_validation")
849    ///     .level(Level::Error)
850    ///     .validates_regex("phone", r"^\+?\d{3}-\d{3}-\d{4}$", 0.95)
851    ///     .validates_regex("product_code", r"^[A-Z]{2}\d{6}$", 1.0)
852    ///     .build();
853    /// ```
854    ///
855    /// # Errors
856    ///
857    /// Returns error if column name is invalid, threshold is out of range,
858    /// or regex pattern is invalid.
859    pub fn validates_regex(
860        mut self,
861        column: impl Into<String>,
862        pattern: impl Into<String>,
863        threshold: f64,
864    ) -> Self {
865        self.constraints.push(Arc::new(
866            FormatConstraint::regex(column, pattern, threshold)
867                .expect("Invalid column, pattern, or threshold"),
868        ));
869        self
870    }
871
872    /// Adds an email address validation constraint.
873    ///
874    /// This is a convenience method for `has_format()` with `FormatType::Email`.
875    ///
876    /// # Arguments
877    ///
878    /// * `column` - The column to validate
879    /// * `threshold` - The minimum ratio of values that must be valid emails (0.0 to 1.0)
880    ///
881    /// # Examples
882    ///
883    /// ```rust
884    /// use term_guard::core::{Check, Level};
885    ///
886    /// let check = Check::builder("email_validation")
887    ///     .level(Level::Error)
888    ///     .validates_email("primary_email", 0.99)
889    ///     .validates_email("secondary_email", 0.80)
890    ///     .build();
891    /// ```
892    ///
893    /// # Errors
894    ///
895    /// Returns error if column name is invalid or threshold is out of range.
896    pub fn validates_email(mut self, column: impl Into<String>, threshold: f64) -> Self {
897        self.constraints.push(Arc::new(
898            FormatConstraint::email(column, threshold).expect("Invalid column or threshold"),
899        ));
900        self
901    }
902
903    /// Adds a URL validation constraint.
904    ///
905    /// This is a convenience method for `has_format()` with `FormatType::Url`.
906    ///
907    /// # Arguments
908    ///
909    /// * `column` - The column to validate
910    /// * `threshold` - The minimum ratio of values that must be valid URLs (0.0 to 1.0)
911    /// * `allow_localhost` - Whether to allow localhost URLs
912    ///
913    /// # Examples
914    ///
915    /// ```rust
916    /// use term_guard::core::{Check, Level};
917    ///
918    /// let check = Check::builder("url_validation")
919    ///     .level(Level::Error)
920    ///     .validates_url("website", 0.90, false)
921    ///     .validates_url("dev_endpoint", 0.80, true)
922    ///     .build();
923    /// ```
924    ///
925    /// # Errors
926    ///
927    /// Returns error if column name is invalid or threshold is out of range.
928    pub fn validates_url(
929        mut self,
930        column: impl Into<String>,
931        threshold: f64,
932        allow_localhost: bool,
933    ) -> Self {
934        self.constraints.push(Arc::new(
935            FormatConstraint::url(column, threshold, allow_localhost)
936                .expect("Invalid column or threshold"),
937        ));
938        self
939    }
940
941    /// Adds a credit card number detection constraint.
942    ///
943    /// This is a convenience method for `has_format()` with `FormatType::CreditCard`.
944    /// Note: For PII detection, you typically want a low threshold (e.g., 0.01 or 0.05)
945    /// to catch any potential credit card numbers.
946    ///
947    /// # Arguments
948    ///
949    /// * `column` - The column to validate
950    /// * `threshold` - The maximum ratio of values that can be credit card numbers (0.0 to 1.0)
951    /// * `detect_only` - If true, optimizes for detection; if false, for validation
952    ///
953    /// # Examples
954    ///
955    /// ```rust
956    /// use term_guard::core::{Check, Level};
957    ///
958    /// let check = Check::builder("pii_detection")
959    ///     .level(Level::Error)
960    ///     // PII detection - should find very few or no credit cards
961    ///     .validates_credit_card("comments", 0.01, true)
962    ///     .validates_credit_card("description", 0.0, true)
963    ///     // Credit card validation - most values should be valid credit cards
964    ///     .validates_credit_card("payment_info", 0.95, false)
965    ///     .build();
966    /// ```
967    ///
968    /// # Errors
969    ///
970    /// Returns error if column name is invalid or threshold is out of range.
971    pub fn validates_credit_card(
972        mut self,
973        column: impl Into<String>,
974        threshold: f64,
975        detect_only: bool,
976    ) -> Self {
977        self.constraints.push(Arc::new(
978            FormatConstraint::credit_card(column, threshold, detect_only)
979                .expect("Invalid column or threshold"),
980        ));
981        self
982    }
983
984    /// Adds a phone number validation constraint.
985    ///
986    /// This is a convenience method for `has_format()` with `FormatType::Phone`.
987    ///
988    /// # Arguments
989    ///
990    /// * `column` - The column to validate
991    /// * `threshold` - The minimum ratio of values that must be valid phone numbers (0.0 to 1.0)
992    /// * `country` - Optional country code for country-specific validation (e.g., "US", "CA")
993    ///
994    /// # Examples
995    ///
996    /// ```rust
997    /// use term_guard::core::{Check, Level};
998    ///
999    /// let check = Check::builder("phone_validation")
1000    ///     .level(Level::Error)
1001    ///     .validates_phone("phone", 0.95, Some("US"))
1002    ///     .validates_phone("international_phone", 0.90, None)
1003    ///     .build();
1004    /// ```
1005    ///
1006    /// # Errors
1007    ///
1008    /// Returns error if column name is invalid or threshold is out of range.
1009    pub fn validates_phone(
1010        mut self,
1011        column: impl Into<String>,
1012        threshold: f64,
1013        country: Option<&str>,
1014    ) -> Self {
1015        self.constraints.push(Arc::new(
1016            FormatConstraint::phone(column, threshold, country.map(|s| s.to_string()))
1017                .expect("Invalid column or threshold"),
1018        ));
1019        self
1020    }
1021
1022    /// Adds a postal code validation constraint.
1023    ///
1024    /// This is a convenience method for `has_format()` with `FormatType::PostalCode`.
1025    ///
1026    /// # Arguments
1027    ///
1028    /// * `column` - The column to validate
1029    /// * `threshold` - The minimum ratio of values that must be valid postal codes (0.0 to 1.0)
1030    /// * `country` - Country code for country-specific validation (e.g., "US", "CA", "UK")
1031    ///
1032    /// # Examples
1033    ///
1034    /// ```rust
1035    /// use term_guard::core::{Check, Level};
1036    ///
1037    /// let check = Check::builder("postal_code_validation")
1038    ///     .level(Level::Error)
1039    ///     .validates_postal_code("zip_code", 0.98, "US")
1040    ///     .validates_postal_code("postal_code", 0.95, "CA")
1041    ///     .build();
1042    /// ```
1043    ///
1044    /// # Errors
1045    ///
1046    /// Returns error if column name is invalid or threshold is out of range.
1047    pub fn validates_postal_code(
1048        mut self,
1049        column: impl Into<String>,
1050        threshold: f64,
1051        country: &str,
1052    ) -> Self {
1053        self.constraints.push(Arc::new(
1054            FormatConstraint::postal_code(column, threshold, country)
1055                .expect("Invalid column or threshold"),
1056        ));
1057        self
1058    }
1059
1060    /// Adds a UUID validation constraint.
1061    ///
1062    /// This is a convenience method for `has_format()` with `FormatType::UUID`.
1063    ///
1064    /// # Arguments
1065    ///
1066    /// * `column` - The column to validate
1067    /// * `threshold` - The minimum ratio of values that must be valid UUIDs (0.0 to 1.0)
1068    ///
1069    /// # Examples
1070    ///
1071    /// ```rust
1072    /// use term_guard::core::{Check, Level};
1073    ///
1074    /// let check = Check::builder("uuid_validation")
1075    ///     .level(Level::Error)
1076    ///     .validates_uuid("user_id", 1.0)
1077    ///     .validates_uuid("session_id", 0.99)
1078    ///     .build();
1079    /// ```
1080    ///
1081    /// # Errors
1082    ///
1083    /// Returns error if column name is invalid or threshold is out of range.
1084    pub fn validates_uuid(mut self, column: impl Into<String>, threshold: f64) -> Self {
1085        self.constraints.push(Arc::new(
1086            FormatConstraint::uuid(column, threshold).expect("Invalid column or threshold"),
1087        ));
1088        self
1089    }
1090
1091    /// Adds an IPv4 address validation constraint.
1092    ///
1093    /// This is a convenience method for `has_format()` with `FormatType::IPv4`.
1094    ///
1095    /// # Arguments
1096    ///
1097    /// * `column` - The column to validate
1098    /// * `threshold` - The minimum ratio of values that must be valid IPv4 addresses (0.0 to 1.0)
1099    ///
1100    /// # Examples
1101    ///
1102    /// ```rust
1103    /// use term_guard::core::{Check, Level};
1104    ///
1105    /// let check = Check::builder("ip_validation")
1106    ///     .level(Level::Error)
1107    ///     .validates_ipv4("client_ip", 0.98)
1108    ///     .validates_ipv4("server_ip", 1.0)
1109    ///     .build();
1110    /// ```
1111    ///
1112    /// # Errors
1113    ///
1114    /// Returns error if column name is invalid or threshold is out of range.
1115    pub fn validates_ipv4(mut self, column: impl Into<String>, threshold: f64) -> Self {
1116        self.constraints.push(Arc::new(
1117            FormatConstraint::ipv4(column, threshold).expect("Invalid column or threshold"),
1118        ));
1119        self
1120    }
1121
1122    /// Adds an IPv6 address validation constraint.
1123    ///
1124    /// This is a convenience method for `has_format()` with `FormatType::IPv6`.
1125    ///
1126    /// # Arguments
1127    ///
1128    /// * `column` - The column to validate
1129    /// * `threshold` - The minimum ratio of values that must be valid IPv6 addresses (0.0 to 1.0)
1130    ///
1131    /// # Examples
1132    ///
1133    /// ```rust
1134    /// use term_guard::core::{Check, Level};
1135    ///
1136    /// let check = Check::builder("ipv6_validation")
1137    ///     .level(Level::Error)
1138    ///     .validates_ipv6("client_ipv6", 0.95)
1139    ///     .build();
1140    /// ```
1141    ///
1142    /// # Errors
1143    ///
1144    /// Returns error if column name is invalid or threshold is out of range.
1145    pub fn validates_ipv6(mut self, column: impl Into<String>, threshold: f64) -> Self {
1146        self.constraints.push(Arc::new(
1147            FormatConstraint::ipv6(column, threshold).expect("Invalid column or threshold"),
1148        ));
1149        self
1150    }
1151
1152    /// Adds a JSON format validation constraint.
1153    ///
1154    /// This is a convenience method for `has_format()` with `FormatType::Json`.
1155    ///
1156    /// # Arguments
1157    ///
1158    /// * `column` - The column to validate
1159    /// * `threshold` - The minimum ratio of values that must be valid JSON (0.0 to 1.0)
1160    ///
1161    /// # Examples
1162    ///
1163    /// ```rust
1164    /// use term_guard::core::{Check, Level};
1165    ///
1166    /// let check = Check::builder("json_validation")
1167    ///     .level(Level::Error)
1168    ///     .validates_json("metadata", 0.99)
1169    ///     .validates_json("config", 1.0)
1170    ///     .build();
1171    /// ```
1172    ///
1173    /// # Errors
1174    ///
1175    /// Returns error if column name is invalid or threshold is out of range.
1176    pub fn validates_json(mut self, column: impl Into<String>, threshold: f64) -> Self {
1177        self.constraints.push(Arc::new(
1178            FormatConstraint::json(column, threshold).expect("Invalid column or threshold"),
1179        ));
1180        self
1181    }
1182
1183    /// Adds an ISO 8601 datetime validation constraint.
1184    ///
1185    /// This is a convenience method for `has_format()` with `FormatType::Iso8601DateTime`.
1186    ///
1187    /// # Arguments
1188    ///
1189    /// * `column` - The column to validate
1190    /// * `threshold` - The minimum ratio of values that must be valid ISO 8601 datetimes (0.0 to 1.0)
1191    ///
1192    /// # Examples
1193    ///
1194    /// ```rust
1195    /// use term_guard::core::{Check, Level};
1196    ///
1197    /// let check = Check::builder("datetime_validation")
1198    ///     .level(Level::Error)
1199    ///     .validates_iso8601_datetime("order_date", 1.0)
1200    ///     .validates_iso8601_datetime("modified_date", 0.98)
1201    ///     .build();
1202    /// ```
1203    ///
1204    /// # Errors
1205    ///
1206    /// Returns error if column name is invalid or threshold is out of range.
1207    pub fn validates_iso8601_datetime(mut self, column: impl Into<String>, threshold: f64) -> Self {
1208        self.constraints.push(Arc::new(
1209            FormatConstraint::iso8601_datetime(column, threshold)
1210                .expect("Invalid column or threshold"),
1211        ));
1212        self
1213    }
1214
1215    // ========================================================================
1216    // ENHANCED FORMAT VALIDATION METHODS WITH OPTIONS
1217    // ========================================================================
1218
1219    /// Adds an enhanced email validation constraint with configurable options.
1220    ///
1221    /// This method provides more control than `validates_email()` by supporting
1222    /// case sensitivity, whitespace trimming, and null handling options.
1223    ///
1224    /// # Arguments
1225    ///
1226    /// * `column` - The column to validate
1227    /// * `threshold` - The minimum ratio of values that must be valid emails (0.0 to 1.0)
1228    /// * `options` - Format validation options
1229    ///
1230    /// # Examples
1231    ///
1232    /// ```rust
1233    /// use term_guard::core::{Check, Level};
1234    /// use term_guard::constraints::FormatOptions;
1235    ///
1236    /// let check = Check::builder("enhanced_email_validation")
1237    ///     .level(Level::Error)
1238    ///     // Case-insensitive email validation with trimming
1239    ///     .validates_email_with_options(
1240    ///         "email",
1241    ///         0.95,
1242    ///         FormatOptions::new()
1243    ///             .case_sensitive(false)
1244    ///             .trim_before_check(true)
1245    ///             .null_is_valid(false)
1246    ///     )
1247    ///     .build();
1248    /// ```
1249    pub fn validates_email_with_options(
1250        mut self,
1251        column: impl Into<String>,
1252        threshold: f64,
1253        options: FormatOptions,
1254    ) -> Self {
1255        self.constraints.push(Arc::new(
1256            FormatConstraint::new(column, FormatType::Email, threshold, options)
1257                .expect("Invalid column, threshold, or options"),
1258        ));
1259        self
1260    }
1261
1262    /// Adds an enhanced URL validation constraint with configurable options.
1263    ///
1264    /// This method provides more control than `validates_url()` by supporting
1265    /// case sensitivity, whitespace trimming, and null handling options.
1266    ///
1267    /// # Arguments
1268    ///
1269    /// * `column` - The column to validate
1270    /// * `threshold` - The minimum ratio of values that must be valid URLs (0.0 to 1.0)
1271    /// * `allow_localhost` - Whether to allow localhost URLs
1272    /// * `options` - Format validation options
1273    ///
1274    /// # Examples
1275    ///
1276    /// ```rust
1277    /// use term_guard::core::{Check, Level};
1278    /// use term_guard::constraints::FormatOptions;
1279    ///
1280    /// let check = Check::builder("enhanced_url_validation")
1281    ///     .level(Level::Error)
1282    ///     // Case-insensitive URL validation with trimming
1283    ///     .validates_url_with_options(
1284    ///         "website",
1285    ///         0.90,
1286    ///         true, // allow localhost
1287    ///         FormatOptions::new()
1288    ///             .case_sensitive(false)
1289    ///             .trim_before_check(true)
1290    ///     )
1291    ///     .build();
1292    /// ```
1293    pub fn validates_url_with_options(
1294        mut self,
1295        column: impl Into<String>,
1296        threshold: f64,
1297        allow_localhost: bool,
1298        options: FormatOptions,
1299    ) -> Self {
1300        self.constraints.push(Arc::new(
1301            FormatConstraint::new(
1302                column,
1303                FormatType::Url { allow_localhost },
1304                threshold,
1305                options,
1306            )
1307            .expect("Invalid column, threshold, or options"),
1308        ));
1309        self
1310    }
1311
1312    /// Adds an enhanced phone number validation constraint with configurable options.
1313    ///
1314    /// This method provides more control than `validates_phone()` by supporting
1315    /// case sensitivity, whitespace trimming, and null handling options.
1316    ///
1317    /// # Arguments
1318    ///
1319    /// * `column` - The column to validate
1320    /// * `threshold` - The minimum ratio of values that must be valid phone numbers (0.0 to 1.0)
1321    /// * `country` - Optional country code for region-specific validation
1322    /// * `options` - Format validation options
1323    ///
1324    /// # Examples
1325    ///
1326    /// ```rust
1327    /// use term_guard::core::{Check, Level};
1328    /// use term_guard::constraints::FormatOptions;
1329    ///
1330    /// let check = Check::builder("enhanced_phone_validation")
1331    ///     .level(Level::Error)
1332    ///     // US phone validation with trimming
1333    ///     .validates_phone_with_options(
1334    ///         "phone",
1335    ///         0.95,
1336    ///         Some("US".to_string()),
1337    ///         FormatOptions::new().trim_before_check(true)
1338    ///     )
1339    ///     .build();
1340    /// ```
1341    pub fn validates_phone_with_options(
1342        mut self,
1343        column: impl Into<String>,
1344        threshold: f64,
1345        country: Option<String>,
1346        options: FormatOptions,
1347    ) -> Self {
1348        self.constraints.push(Arc::new(
1349            FormatConstraint::new(column, FormatType::Phone { country }, threshold, options)
1350                .expect("Invalid column, threshold, or options"),
1351        ));
1352        self
1353    }
1354
1355    /// Adds an enhanced regex pattern validation constraint with configurable options.
1356    ///
1357    /// This method provides more control than `validates_regex()` by supporting
1358    /// case sensitivity, whitespace trimming, and null handling options.
1359    ///
1360    /// # Arguments
1361    ///
1362    /// * `column` - The column to validate
1363    /// * `pattern` - The regular expression pattern
1364    /// * `threshold` - The minimum ratio of values that must match (0.0 to 1.0)
1365    /// * `options` - Format validation options
1366    ///
1367    /// # Examples
1368    ///
1369    /// ```rust
1370    /// use term_guard::core::{Check, Level};
1371    /// use term_guard::constraints::FormatOptions;
1372    ///
1373    /// let check = Check::builder("enhanced_regex_validation")
1374    ///     .level(Level::Error)
1375    ///     // Case-insensitive product code validation
1376    ///     .validates_regex_with_options(
1377    ///         "product_code",
1378    ///         r"^[A-Z]{2}\d{4}$",
1379    ///         0.98,
1380    ///         FormatOptions::new()
1381    ///             .case_sensitive(false)
1382    ///             .trim_before_check(true)
1383    ///     )
1384    ///     .build();
1385    /// ```
1386    pub fn validates_regex_with_options(
1387        mut self,
1388        column: impl Into<String>,
1389        pattern: impl Into<String>,
1390        threshold: f64,
1391        options: FormatOptions,
1392    ) -> Self {
1393        self.constraints.push(Arc::new(
1394            FormatConstraint::new(
1395                column,
1396                FormatType::Regex(pattern.into()),
1397                threshold,
1398                options,
1399            )
1400            .expect("Invalid column, pattern, threshold, or options"),
1401        ));
1402        self
1403    }
1404
1405    // ========================================================================
1406    // NEW UNIFIED API METHODS
1407    // ========================================================================
1408
1409    /// Adds a unified uniqueness constraint with full control over validation type and options.
1410    ///
1411    /// This method provides a comprehensive alternative to `is_unique`, `are_unique`, `has_uniqueness`,
1412    /// `is_primary_key`, `has_distinctness`, and `has_unique_value_ratio` by supporting all uniqueness
1413    /// validation types with flexible configuration options.
1414    ///
1415    /// # Arguments
1416    ///
1417    /// * `columns` - The column(s) to check (single string, vec, or array)
1418    /// * `uniqueness_type` - The type of uniqueness validation to perform
1419    /// * `options` - Configuration options for null handling, case sensitivity, etc.
1420    ///
1421    /// # Examples
1422    ///
1423    /// ```rust
1424    /// use term_guard::core::{Check, Level};
1425    /// use term_guard::constraints::{UniquenessType, UniquenessOptions, NullHandling, Assertion};
1426    ///
1427    /// let check = Check::builder("unified_uniqueness")
1428    ///     // Full uniqueness with threshold
1429    ///     .uniqueness(
1430    ///         vec!["user_id"],
1431    ///         UniquenessType::FullUniqueness { threshold: 1.0 },
1432    ///         UniquenessOptions::default()
1433    ///     )
1434    ///     // Primary key validation
1435    ///     .uniqueness(
1436    ///         vec!["order_id", "line_item_id"],
1437    ///         UniquenessType::PrimaryKey,
1438    ///         UniquenessOptions::default()
1439    ///     )
1440    ///     // Distinctness check
1441    ///     .uniqueness(
1442    ///         vec!["category"],
1443    ///         UniquenessType::Distinctness(Assertion::LessThan(0.1)),
1444    ///         UniquenessOptions::default()
1445    ///     )
1446    ///     // Unique value ratio
1447    ///     .uniqueness(
1448    ///         vec!["transaction_id"],
1449    ///         UniquenessType::UniqueValueRatio(Assertion::GreaterThan(0.99)),
1450    ///         UniquenessOptions::default()
1451    ///     )
1452    ///     // Composite uniqueness with null handling
1453    ///     .uniqueness(
1454    ///         vec!["email", "domain"],
1455    ///         UniquenessType::UniqueComposite {
1456    ///             threshold: 0.95,
1457    ///             null_handling: NullHandling::Exclude,
1458    ///             case_sensitive: false
1459    ///         },
1460    ///         UniquenessOptions::new()
1461    ///             .with_null_handling(NullHandling::Exclude)
1462    ///             .case_sensitive(false)
1463    ///     )
1464    ///     .build();
1465    /// ```
1466    ///
1467    /// # Errors
1468    ///
1469    /// Returns error if column names are invalid or thresholds are out of range.
1470    pub fn uniqueness<I, S>(
1471        mut self,
1472        columns: I,
1473        uniqueness_type: UniquenessType,
1474        options: UniquenessOptions,
1475    ) -> Self
1476    where
1477        I: IntoIterator<Item = S>,
1478        S: Into<String>,
1479    {
1480        self.constraints.push(Arc::new(
1481            UniquenessConstraint::new(columns, uniqueness_type, options)
1482                .expect("Invalid columns, uniqueness type, or options"),
1483        ));
1484        self
1485    }
1486
1487    /// Adds a full uniqueness constraint for single or multiple columns.
1488    ///
1489    /// This is a convenience method for `uniqueness()` with `UniquenessType::FullUniqueness`.
1490    ///
1491    /// # Arguments
1492    ///
1493    /// * `columns` - The column(s) to check for uniqueness
1494    /// * `threshold` - The minimum acceptable uniqueness ratio (0.0 to 1.0)
1495    ///
1496    /// # Examples
1497    ///
1498    /// ```rust
1499    /// use term_guard::core::{Check, Level};
1500    ///
1501    /// let check = Check::builder("uniqueness_validation")
1502    ///     .level(Level::Error)
1503    ///     .validates_uniqueness(vec!["user_id"], 1.0)
1504    ///     .validates_uniqueness(vec!["email", "domain"], 0.95)
1505    ///     .build();
1506    /// ```
1507    ///
1508    /// # Errors
1509    ///
1510    /// Returns error if column names are invalid or threshold is out of range.
1511    pub fn validates_uniqueness<I, S>(mut self, columns: I, threshold: f64) -> Self
1512    where
1513        I: IntoIterator<Item = S>,
1514        S: Into<String>,
1515    {
1516        self.constraints.push(Arc::new(
1517            UniquenessConstraint::new(
1518                columns,
1519                UniquenessType::FullUniqueness { threshold },
1520                UniquenessOptions::default(),
1521            )
1522            .expect("Invalid columns or threshold"),
1523        ));
1524        self
1525    }
1526
1527    /// Adds a distinctness constraint with assertion-based validation.
1528    ///
1529    /// This is a convenience method for `uniqueness()` with `UniquenessType::Distinctness`.
1530    ///
1531    /// # Arguments
1532    ///
1533    /// * `columns` - The column(s) to check for distinctness
1534    /// * `assertion` - The assertion to apply to the distinctness ratio
1535    ///
1536    /// # Examples
1537    ///
1538    /// ```rust
1539    /// use term_guard::core::{Check, Level};
1540    /// use term_guard::constraints::Assertion;
1541    ///
1542    /// let check = Check::builder("distinctness_validation")
1543    ///     .level(Level::Warning)
1544    ///     .validates_distinctness(vec!["status"], Assertion::LessThan(0.1))
1545    ///     .validates_distinctness(vec!["user_id"], Assertion::GreaterThan(0.95))
1546    ///     .build();
1547    /// ```
1548    ///
1549    /// # Errors
1550    ///
1551    /// Returns error if column names are invalid.
1552    pub fn validates_distinctness<I, S>(mut self, columns: I, assertion: Assertion) -> Self
1553    where
1554        I: IntoIterator<Item = S>,
1555        S: Into<String>,
1556    {
1557        self.constraints.push(Arc::new(
1558            UniquenessConstraint::new(
1559                columns,
1560                UniquenessType::Distinctness(assertion),
1561                UniquenessOptions::default(),
1562            )
1563            .expect("Invalid columns"),
1564        ));
1565        self
1566    }
1567
1568    /// Adds a unique value ratio constraint with assertion-based validation.
1569    ///
1570    /// This is a convenience method for `uniqueness()` with `UniquenessType::UniqueValueRatio`.
1571    ///
1572    /// # Arguments
1573    ///
1574    /// * `columns` - The column(s) to check for unique value ratio
1575    /// * `assertion` - The assertion to apply to the unique value ratio
1576    ///
1577    /// # Examples
1578    ///
1579    /// ```rust
1580    /// use term_guard::core::{Check, Level};
1581    /// use term_guard::constraints::Assertion;
1582    ///
1583    /// let check = Check::builder("unique_ratio_validation")
1584    ///     .level(Level::Warning)
1585    ///     .validates_unique_value_ratio(vec!["transaction_id"], Assertion::GreaterThan(0.99))
1586    ///     .validates_unique_value_ratio(vec!["category"], Assertion::LessThan(0.01))
1587    ///     .build();
1588    /// ```
1589    ///
1590    /// # Errors
1591    ///
1592    /// Returns error if column names are invalid.
1593    pub fn validates_unique_value_ratio<I, S>(mut self, columns: I, assertion: Assertion) -> Self
1594    where
1595        I: IntoIterator<Item = S>,
1596        S: Into<String>,
1597    {
1598        self.constraints.push(Arc::new(
1599            UniquenessConstraint::new(
1600                columns,
1601                UniquenessType::UniqueValueRatio(assertion),
1602                UniquenessOptions::default(),
1603            )
1604            .expect("Invalid columns"),
1605        ));
1606        self
1607    }
1608
1609    /// Adds a primary key constraint (unique + non-null).
1610    ///
1611    /// This is a convenience method for `uniqueness()` with `UniquenessType::PrimaryKey`.
1612    ///
1613    /// # Arguments
1614    ///
1615    /// * `columns` - The column(s) that form the primary key
1616    ///
1617    /// # Examples
1618    ///
1619    /// ```rust
1620    /// use term_guard::core::{Check, Level};
1621    ///
1622    /// let check = Check::builder("primary_key_validation")
1623    ///     .level(Level::Error)
1624    ///     .validates_primary_key(vec!["user_id"])
1625    ///     .validates_primary_key(vec!["order_id", "line_item_id"])
1626    ///     .build();
1627    /// ```
1628    ///
1629    /// # Errors
1630    ///
1631    /// Returns error if column names are invalid.
1632    pub fn validates_primary_key<I, S>(mut self, columns: I) -> Self
1633    where
1634        I: IntoIterator<Item = S>,
1635        S: Into<String>,
1636    {
1637        self.constraints.push(Arc::new(
1638            UniquenessConstraint::new(
1639                columns,
1640                UniquenessType::PrimaryKey,
1641                UniquenessOptions::default(),
1642            )
1643            .expect("Invalid columns"),
1644        ));
1645        self
1646    }
1647
1648    /// Adds a uniqueness constraint that allows NULL values with configurable handling.
1649    ///
1650    /// This is a convenience method for `uniqueness()` with `UniquenessType::UniqueWithNulls`.
1651    ///
1652    /// # Arguments
1653    ///
1654    /// * `columns` - The column(s) to check for uniqueness
1655    /// * `threshold` - The minimum acceptable uniqueness ratio (0.0 to 1.0)
1656    /// * `null_handling` - How to handle NULL values in uniqueness calculations
1657    ///
1658    /// # Examples
1659    ///
1660    /// ```rust
1661    /// use term_guard::core::{Check, Level};
1662    /// use term_guard::constraints::NullHandling;
1663    ///
1664    /// let check = Check::builder("null_handling_validation")
1665    ///     .level(Level::Warning)
1666    ///     .validates_uniqueness_with_nulls(vec!["optional_id"], 0.9, NullHandling::Exclude)
1667    ///     .validates_uniqueness_with_nulls(vec!["reference"], 0.8, NullHandling::Include)
1668    ///     .build();
1669    /// ```
1670    ///
1671    /// # Errors
1672    ///
1673    /// Returns error if column names are invalid or threshold is out of range.
1674    pub fn validates_uniqueness_with_nulls<I, S>(
1675        mut self,
1676        columns: I,
1677        threshold: f64,
1678        null_handling: NullHandling,
1679    ) -> Self
1680    where
1681        I: IntoIterator<Item = S>,
1682        S: Into<String>,
1683    {
1684        self.constraints.push(Arc::new(
1685            UniquenessConstraint::new(
1686                columns,
1687                UniquenessType::UniqueWithNulls {
1688                    threshold,
1689                    null_handling,
1690                },
1691                UniquenessOptions::new().with_null_handling(null_handling),
1692            )
1693            .expect("Invalid columns or threshold"),
1694        ));
1695        self
1696    }
1697
1698    /// Adds a completeness constraint using the unified options pattern.
1699    ///
1700    /// This method provides a more flexible alternative to `is_complete`, `has_completeness`,
1701    /// `are_complete`, and `are_any_complete` by supporting arbitrary logical operators
1702    /// and thresholds.
1703    ///
1704    /// # Arguments
1705    ///
1706    /// * `columns` - The column(s) to check (single string, vec, or array)
1707    /// * `options` - Configuration options including threshold and logical operator
1708    ///
1709    /// # Examples
1710    ///
1711    /// ```rust
1712    /// use term_guard::core::{Check, ConstraintOptions, LogicalOperator};
1713    ///
1714    /// let check = Check::builder("unified_completeness")
1715    ///     // Single column with threshold
1716    ///     .completeness("email", ConstraintOptions::new().with_threshold(0.95))
1717    ///     // Multiple columns - all must be complete
1718    ///     .completeness(
1719    ///         vec!["first_name", "last_name"],
1720    ///         ConstraintOptions::new()
1721    ///             .with_operator(LogicalOperator::All)
1722    ///             .with_threshold(1.0)
1723    ///     )
1724    ///     // At least 2 of 4 contact methods must be 90% complete
1725    ///     .completeness(
1726    ///         vec!["email", "phone", "address", "postal_code"],
1727    ///         ConstraintOptions::new()
1728    ///             .with_operator(LogicalOperator::AtLeast(2))
1729    ///             .with_threshold(0.9)
1730    ///     )
1731    ///     .build();
1732    /// ```
1733    pub fn completeness(
1734        mut self,
1735        columns: impl Into<crate::core::ColumnSpec>,
1736        options: crate::core::ConstraintOptions,
1737    ) -> Self {
1738        use crate::constraints::CompletenessConstraint;
1739        self.constraints
1740            .push(Arc::new(CompletenessConstraint::new(columns, options)));
1741        self
1742    }
1743
1744    /// Adds a string length constraint using the unified options pattern.
1745    ///
1746    /// This method provides a more flexible alternative to the individual length methods
1747    /// by supporting all length assertion types in a single interface.
1748    ///
1749    /// # Arguments
1750    ///
1751    /// * `column` - The column to check
1752    /// * `assertion` - The length assertion (Min, Max, Between, Exactly, NotEmpty)
1753    ///
1754    /// # Examples
1755    ///
1756    /// ```rust
1757    /// use term_guard::core::Check;
1758    /// use term_guard::constraints::LengthAssertion;
1759    ///
1760    /// let check = Check::builder("length_validation")
1761    ///     .length("password", LengthAssertion::Min(8))
1762    ///     .length("username", LengthAssertion::Between(3, 20))
1763    ///     .length("verification_code", LengthAssertion::Exactly(6))
1764    ///     .length("name", LengthAssertion::NotEmpty)
1765    ///     .build();
1766    /// ```
1767    pub fn length(
1768        mut self,
1769        column: impl Into<String>,
1770        assertion: crate::constraints::LengthAssertion,
1771    ) -> Self {
1772        use crate::constraints::LengthConstraint;
1773        self.constraints
1774            .push(Arc::new(LengthConstraint::new(column, assertion)));
1775        self
1776    }
1777
1778    /// Adds a statistical constraint using the unified options pattern.
1779    ///
1780    /// This method provides a unified interface for all statistical constraints
1781    /// (min, max, mean, sum, standard deviation) with consistent assertion patterns.
1782    ///
1783    /// # Arguments
1784    ///
1785    /// * `column` - The column to analyze
1786    /// * `statistic` - The type of statistic to compute
1787    /// * `assertion` - The assertion to apply to the statistic
1788    ///
1789    /// # Examples
1790    ///
1791    /// ```rust
1792    /// use term_guard::core::Check;
1793    /// use term_guard::constraints::{StatisticType, Assertion};
1794    ///
1795    /// let check = Check::builder("statistical_validation")
1796    ///     .statistic("age", StatisticType::Min, Assertion::GreaterThanOrEqual(0.0))
1797    ///     .statistic("age", StatisticType::Max, Assertion::LessThanOrEqual(120.0))
1798    ///     .statistic("salary", StatisticType::Mean, Assertion::Between(50000.0, 100000.0))
1799    ///     .statistic("response_time", StatisticType::StandardDeviation, Assertion::LessThan(100.0))
1800    ///     .build();
1801    /// ```
1802    pub fn statistic(
1803        mut self,
1804        column: impl Into<String>,
1805        statistic: crate::constraints::StatisticType,
1806        assertion: Assertion,
1807    ) -> Self {
1808        use crate::constraints::StatisticalConstraint;
1809        self.constraints.push(Arc::new(
1810            StatisticalConstraint::new(column, statistic, assertion)
1811                .expect("Invalid column name or statistic"),
1812        ));
1813        self
1814    }
1815
1816    /// Adds a minimum value constraint for a column.
1817    ///
1818    /// This is a convenience method for `statistic()` with `StatisticType::Min`.
1819    ///
1820    /// # Examples
1821    ///
1822    /// ```rust
1823    /// use term_guard::core::{Check, Level};
1824    /// use term_guard::constraints::Assertion;
1825    ///
1826    /// let check = Check::builder("age_validation")
1827    ///     .level(Level::Error)
1828    ///     .has_min("age", Assertion::GreaterThanOrEqual(0.0))
1829    ///     .build();
1830    /// ```
1831    pub fn has_min(self, column: impl Into<String>, assertion: Assertion) -> Self {
1832        self.statistic(column, crate::constraints::StatisticType::Min, assertion)
1833    }
1834
1835    /// Adds a maximum value constraint for a column.
1836    ///
1837    /// This is a convenience method for `statistic()` with `StatisticType::Max`.
1838    ///
1839    /// # Examples
1840    ///
1841    /// ```rust
1842    /// use term_guard::core::{Check, Level};
1843    /// use term_guard::constraints::Assertion;
1844    ///
1845    /// let check = Check::builder("age_validation")
1846    ///     .level(Level::Error)
1847    ///     .has_max("age", Assertion::LessThanOrEqual(120.0))
1848    ///     .build();
1849    /// ```
1850    pub fn has_max(self, column: impl Into<String>, assertion: Assertion) -> Self {
1851        self.statistic(column, crate::constraints::StatisticType::Max, assertion)
1852    }
1853
1854    /// Adds a mean (average) value constraint for a column.
1855    ///
1856    /// This is a convenience method for `statistic()` with `StatisticType::Mean`.
1857    ///
1858    /// # Examples
1859    ///
1860    /// ```rust
1861    /// use term_guard::core::{Check, Level};
1862    /// use term_guard::constraints::Assertion;
1863    ///
1864    /// let check = Check::builder("salary_validation")
1865    ///     .level(Level::Warning)
1866    ///     .has_mean("salary", Assertion::Between(50000.0, 100000.0))
1867    ///     .build();
1868    /// ```
1869    pub fn has_mean(self, column: impl Into<String>, assertion: Assertion) -> Self {
1870        self.statistic(column, crate::constraints::StatisticType::Mean, assertion)
1871    }
1872
1873    /// Adds a sum constraint for a column.
1874    ///
1875    /// This is a convenience method for `statistic()` with `StatisticType::Sum`.
1876    ///
1877    /// # Examples
1878    ///
1879    /// ```rust
1880    /// use term_guard::core::{Check, Level};
1881    /// use term_guard::constraints::Assertion;
1882    ///
1883    /// let check = Check::builder("revenue_validation")
1884    ///     .level(Level::Error)
1885    ///     .has_sum("revenue", Assertion::GreaterThan(1000000.0))
1886    ///     .build();
1887    /// ```
1888    pub fn has_sum(self, column: impl Into<String>, assertion: Assertion) -> Self {
1889        self.statistic(column, crate::constraints::StatisticType::Sum, assertion)
1890    }
1891
1892    /// Adds a standard deviation constraint for a column.
1893    ///
1894    /// This is a convenience method for `statistic()` with `StatisticType::StandardDeviation`.
1895    ///
1896    /// # Examples
1897    ///
1898    /// ```rust
1899    /// use term_guard::core::{Check, Level};
1900    /// use term_guard::constraints::Assertion;
1901    ///
1902    /// let check = Check::builder("response_time_validation")
1903    ///     .level(Level::Warning)
1904    ///     .has_standard_deviation("response_time", Assertion::LessThan(100.0))
1905    ///     .build();
1906    /// ```
1907    pub fn has_standard_deviation(self, column: impl Into<String>, assertion: Assertion) -> Self {
1908        self.statistic(
1909            column,
1910            crate::constraints::StatisticType::StandardDeviation,
1911            assertion,
1912        )
1913    }
1914
1915    /// Adds a variance constraint for a column.
1916    ///
1917    /// This is a convenience method for `statistic()` with `StatisticType::Variance`.
1918    ///
1919    /// # Examples
1920    ///
1921    /// ```rust
1922    /// use term_guard::core::{Check, Level};
1923    /// use term_guard::constraints::Assertion;
1924    ///
1925    /// let check = Check::builder("score_validation")
1926    ///     .level(Level::Warning)
1927    ///     .has_variance("score", Assertion::LessThan(250.0))
1928    ///     .build();
1929    /// ```
1930    pub fn has_variance(self, column: impl Into<String>, assertion: Assertion) -> Self {
1931        self.statistic(
1932            column,
1933            crate::constraints::StatisticType::Variance,
1934            assertion,
1935        )
1936    }
1937
1938    /// Adds a constraint using a fluent constraint builder.
1939    ///
1940    /// This method provides the most flexible API for building complex constraints
1941    /// with full access to all unified constraint features.
1942    ///
1943    /// # Arguments
1944    ///
1945    /// * `constraint` - A constraint built using the fluent API
1946    ///
1947    /// # Examples
1948    ///
1949    /// ```rust
1950    /// use term_guard::core::{Check, ConstraintOptions, LogicalOperator};
1951    /// use term_guard::constraints::{CompletenessConstraint, LengthConstraint, LengthAssertion};
1952    ///
1953    /// let check = Check::builder("advanced_validation")
1954    ///     .with_constraint(
1955    ///         CompletenessConstraint::new(
1956    ///             vec!["phone", "email"],
1957    ///             ConstraintOptions::new()
1958    ///                 .with_operator(LogicalOperator::Any)
1959    ///                 .with_threshold(0.99)
1960    ///         )
1961    ///     )
1962    ///     .with_constraint(
1963    ///         LengthConstraint::new("description", LengthAssertion::Between(50, 2000))
1964    ///     )
1965    ///     .build();
1966    /// ```
1967    pub fn with_constraint(mut self, constraint: impl crate::core::Constraint + 'static) -> Self {
1968        self.constraints.push(Arc::new(constraint));
1969        self
1970    }
1971
1972    // ========================================================================
1973    // CONVENIENCE METHODS FOR COMMON PATTERNS
1974    // ========================================================================
1975
1976    /// Convenience method for requiring any of multiple columns to be complete.
1977    ///
1978    /// Equivalent to `completeness(columns, ConstraintOptions::new().with_operator(LogicalOperator::Any).with_threshold(1.0))`
1979    /// but more concise for this common pattern.
1980    ///
1981    /// # Examples
1982    ///
1983    /// ```rust
1984    /// use term_guard::core::Check;
1985    ///
1986    /// let check = Check::builder("contact_validation")
1987    ///     .any_complete(vec!["phone", "email", "address"])
1988    ///     .build();
1989    /// ```
1990    pub fn any_complete<I, S>(self, columns: I) -> Self
1991    where
1992        I: IntoIterator<Item = S>,
1993        S: Into<String>,
1994    {
1995        use crate::core::{ConstraintOptions, LogicalOperator};
1996        let cols: Vec<String> = columns.into_iter().map(Into::into).collect();
1997        self.completeness(
1998            cols,
1999            ConstraintOptions::new()
2000                .with_operator(LogicalOperator::Any)
2001                .with_threshold(1.0),
2002        )
2003    }
2004
2005    /// Convenience method for requiring at least N columns to meet a threshold.
2006    ///
2007    /// # Examples
2008    ///
2009    /// ```rust
2010    /// use term_guard::core::Check;
2011    ///
2012    /// let check = Check::builder("contact_validation")
2013    ///     .at_least_complete(2, vec!["email", "phone", "address", "postal_code"], 0.9)
2014    ///     .build();
2015    /// ```
2016    pub fn at_least_complete<I, S>(self, n: usize, columns: I, threshold: f64) -> Self
2017    where
2018        I: IntoIterator<Item = S>,
2019        S: Into<String>,
2020    {
2021        use crate::core::{ConstraintOptions, LogicalOperator};
2022        let cols: Vec<String> = columns.into_iter().map(Into::into).collect();
2023        self.completeness(
2024            cols,
2025            ConstraintOptions::new()
2026                .with_operator(LogicalOperator::AtLeast(n))
2027                .with_threshold(threshold),
2028        )
2029    }
2030
2031    /// Convenience method for exactly N columns meeting a threshold.
2032    ///
2033    /// # Examples
2034    ///
2035    /// ```rust
2036    /// use term_guard::core::Check;
2037    ///
2038    /// let check = Check::builder("balance_validation")
2039    ///     .exactly_complete(1, vec!["primary_phone", "secondary_phone"], 1.0)
2040    ///     .build();
2041    /// ```
2042    pub fn exactly_complete<I, S>(self, n: usize, columns: I, threshold: f64) -> Self
2043    where
2044        I: IntoIterator<Item = S>,
2045        S: Into<String>,
2046    {
2047        use crate::core::{ConstraintOptions, LogicalOperator};
2048        let cols: Vec<String> = columns.into_iter().map(Into::into).collect();
2049        self.completeness(
2050            cols,
2051            ConstraintOptions::new()
2052                .with_operator(LogicalOperator::Exactly(n))
2053                .with_threshold(threshold),
2054        )
2055    }
2056
2057    /// Builds the `Check` instance.
2058    ///
2059    /// # Returns
2060    ///
2061    /// The constructed `Check`
2062    pub fn build(self) -> Check {
2063        Check {
2064            name: self.name,
2065            level: self.level,
2066            description: self.description,
2067            constraints: self.constraints,
2068        }
2069    }
2070}
2071
2072#[cfg(test)]
2073mod tests {
2074    use super::*;
2075    use crate::prelude::*;
2076    use async_trait::async_trait;
2077    use datafusion::prelude::*;
2078
2079    #[derive(Debug)]
2080    struct DummyConstraint {
2081        name: String,
2082    }
2083
2084    #[async_trait]
2085    impl Constraint for DummyConstraint {
2086        async fn evaluate(&self, _ctx: &SessionContext) -> Result<crate::core::ConstraintResult> {
2087            Ok(crate::core::ConstraintResult::success())
2088        }
2089
2090        fn name(&self) -> &str {
2091            &self.name
2092        }
2093
2094        fn metadata(&self) -> crate::core::ConstraintMetadata {
2095            crate::core::ConstraintMetadata::new()
2096        }
2097    }
2098
2099    #[test]
2100    fn test_check_builder() {
2101        let check = Check::builder("test_check")
2102            .level(Level::Error)
2103            .description("Test check description")
2104            .constraint(DummyConstraint {
2105                name: "constraint1".to_string(),
2106            })
2107            .build();
2108
2109        assert_eq!(check.name(), "test_check");
2110        assert_eq!(check.level(), Level::Error);
2111        assert_eq!(check.description(), Some("Test check description"));
2112        assert_eq!(check.constraints().len(), 1);
2113    }
2114
2115    #[test]
2116    fn test_check_default_level() {
2117        let check = Check::builder("test_check").build();
2118        assert_eq!(check.level(), Level::Warning);
2119    }
2120
2121    #[test]
2122    fn test_check_builder_completeness() {
2123        use crate::core::ConstraintOptions;
2124
2125        let check = Check::builder("completeness_check")
2126            .level(Level::Error)
2127            .completeness("user_id", ConstraintOptions::new().with_threshold(1.0))
2128            .completeness("email", ConstraintOptions::new().with_threshold(0.95))
2129            .build();
2130
2131        assert_eq!(check.name(), "completeness_check");
2132        assert_eq!(check.level(), Level::Error);
2133        assert_eq!(check.constraints().len(), 2);
2134    }
2135
2136    #[test]
2137    fn test_check_builder_uniqueness() {
2138        let check = Check::builder("uniqueness_check")
2139            .validates_uniqueness(vec!["user_id"], 1.0)
2140            .validates_uniqueness(vec!["first_name", "last_name"], 1.0)
2141            .validates_uniqueness(vec!["email"], 0.99)
2142            .build();
2143
2144        assert_eq!(check.constraints().len(), 3);
2145    }
2146
2147    #[test]
2148    fn test_check_builder_method_chaining() {
2149        use crate::core::ConstraintOptions;
2150
2151        let check = Check::builder("comprehensive_check")
2152            .level(Level::Error)
2153            .description("Comprehensive data quality check")
2154            .completeness("id", ConstraintOptions::new().with_threshold(1.0))
2155            .completeness("name", ConstraintOptions::new().with_threshold(0.9))
2156            .validates_uniqueness(vec!["id"], 1.0)
2157            .validates_uniqueness(vec!["email", "phone"], 1.0)
2158            .build();
2159
2160        assert_eq!(check.name(), "comprehensive_check");
2161        assert_eq!(check.level(), Level::Error);
2162        assert_eq!(
2163            check.description(),
2164            Some("Comprehensive data quality check")
2165        );
2166        assert_eq!(check.constraints().len(), 4);
2167    }
2168
2169    #[test]
2170    fn test_check_builder_multiple_completeness() {
2171        use crate::core::{ConstraintOptions, LogicalOperator};
2172
2173        let check = Check::builder("multi_completeness_check")
2174            .completeness(
2175                vec!["user_id", "email", "name"],
2176                ConstraintOptions::new()
2177                    .with_operator(LogicalOperator::All)
2178                    .with_threshold(1.0),
2179            )
2180            .any_complete(vec!["phone", "mobile", "fax"])
2181            .build();
2182
2183        assert_eq!(check.constraints().len(), 2);
2184    }
2185
2186    #[test]
2187    #[should_panic(expected = "Threshold must be between 0.0 and 1.0")]
2188    fn test_check_builder_invalid_completeness_threshold() {
2189        use crate::core::ConstraintOptions;
2190
2191        Check::builder("test")
2192            .completeness("column", ConstraintOptions::new().with_threshold(1.5))
2193            .build();
2194    }
2195
2196    #[test]
2197    #[should_panic(expected = "Invalid columns or threshold")]
2198    fn test_check_builder_invalid_uniqueness_threshold() {
2199        Check::builder("test")
2200            .validates_uniqueness(vec!["column"], -0.1)
2201            .build();
2202    }
2203
2204    #[test]
2205    fn test_check_builder_string_length() {
2206        let check = Check::builder("string_length_check")
2207            .has_min_length("password", 8)
2208            .has_max_length("username", 20)
2209            .build();
2210
2211        assert_eq!(check.constraints().len(), 2);
2212    }
2213
2214    #[test]
2215    fn test_unified_completeness_api() {
2216        use crate::core::{ConstraintOptions, LogicalOperator};
2217
2218        let check = Check::builder("unified_completeness_test")
2219            // Single column with threshold
2220            .completeness("email", ConstraintOptions::new().with_threshold(0.95))
2221            // Multiple columns with ANY operator
2222            .completeness(
2223                vec!["phone", "email", "address"],
2224                ConstraintOptions::new()
2225                    .with_operator(LogicalOperator::Any)
2226                    .with_threshold(1.0),
2227            )
2228            // At least 2 columns with threshold
2229            .completeness(
2230                vec!["a", "b", "c", "d"],
2231                ConstraintOptions::new()
2232                    .with_operator(LogicalOperator::AtLeast(2))
2233                    .with_threshold(0.9),
2234            )
2235            .build();
2236
2237        assert_eq!(check.constraints().len(), 3);
2238    }
2239
2240    #[test]
2241    fn test_unified_length_api() {
2242        use crate::constraints::LengthAssertion;
2243
2244        let check = Check::builder("unified_length_test")
2245            .length("password", LengthAssertion::Min(8))
2246            .length("username", LengthAssertion::Between(3, 20))
2247            .length("code", LengthAssertion::Exactly(6))
2248            .length("name", LengthAssertion::NotEmpty)
2249            .build();
2250
2251        assert_eq!(check.constraints().len(), 4);
2252    }
2253
2254    #[test]
2255    fn test_unified_statistics_api() {
2256        use crate::constraints::{Assertion, StatisticType};
2257
2258        let check = Check::builder("unified_statistics_test")
2259            .statistic(
2260                "age",
2261                StatisticType::Min,
2262                Assertion::GreaterThanOrEqual(0.0),
2263            )
2264            .statistic("age", StatisticType::Max, Assertion::LessThanOrEqual(120.0))
2265            .statistic(
2266                "salary",
2267                StatisticType::Mean,
2268                Assertion::Between(50000.0, 100000.0),
2269            )
2270            .statistic(
2271                "response_time",
2272                StatisticType::StandardDeviation,
2273                Assertion::LessThan(100.0),
2274            )
2275            .build();
2276
2277        assert_eq!(check.constraints().len(), 4);
2278    }
2279
2280    #[test]
2281    fn test_convenience_methods() {
2282        let check = Check::builder("convenience_test")
2283            .any_complete(vec!["phone", "email", "address"])
2284            .at_least_complete(2, vec!["a", "b", "c", "d"], 0.9)
2285            .exactly_complete(1, vec!["primary", "secondary"], 1.0)
2286            .build();
2287
2288        assert_eq!(check.constraints().len(), 3);
2289    }
2290
2291    #[test]
2292    fn test_with_constraint_method() {
2293        use crate::constraints::{LengthAssertion, LengthConstraint};
2294
2295        let constraint = LengthConstraint::new("test", LengthAssertion::Between(5, 50));
2296        let check = Check::builder("with_constraint_test")
2297            .with_constraint(constraint)
2298            .build();
2299
2300        assert_eq!(check.constraints().len(), 1);
2301    }
2302
2303    #[test]
2304    fn test_enhanced_format_validation_methods() {
2305        let check = Check::builder("enhanced_format_test")
2306            // Enhanced email validation with options
2307            .validates_email_with_options(
2308                "email",
2309                0.95,
2310                FormatOptions::new()
2311                    .case_sensitive(false)
2312                    .trim_before_check(true)
2313                    .null_is_valid(false),
2314            )
2315            // Enhanced URL validation with options
2316            .validates_url_with_options(
2317                "website",
2318                0.90,
2319                true, // allow localhost
2320                FormatOptions::new()
2321                    .case_sensitive(false)
2322                    .trim_before_check(true),
2323            )
2324            // Enhanced phone validation with options
2325            .validates_phone_with_options(
2326                "phone",
2327                0.95,
2328                Some("US".to_string()),
2329                FormatOptions::new().trim_before_check(true),
2330            )
2331            // Enhanced regex validation with options
2332            .validates_regex_with_options(
2333                "product_code",
2334                r"^[A-Z]{2}\d{4}$",
2335                0.98,
2336                FormatOptions::new()
2337                    .case_sensitive(false)
2338                    .trim_before_check(true),
2339            )
2340            .build();
2341
2342        assert_eq!(check.constraints().len(), 4);
2343        assert_eq!(check.name(), "enhanced_format_test");
2344    }
2345
2346    #[test]
2347    fn test_enhanced_vs_basic_format_methods() {
2348        // Test that basic and enhanced methods can be used together
2349        let check = Check::builder("mixed_format_test")
2350            // Basic methods
2351            .validates_email("basic_email", 0.90)
2352            .validates_url("basic_url", 0.85, false)
2353            .validates_phone("basic_phone", 0.80, None)
2354            .validates_regex("basic_pattern", r"^\d+$", 0.75)
2355            // Enhanced methods
2356            .validates_email_with_options(
2357                "enhanced_email",
2358                0.95,
2359                FormatOptions::new().case_sensitive(false),
2360            )
2361            .validates_url_with_options(
2362                "enhanced_url",
2363                0.90,
2364                true,
2365                FormatOptions::new().trim_before_check(true),
2366            )
2367            .build();
2368
2369        assert_eq!(check.constraints().len(), 6);
2370    }
2371}