Skip to main content

oxicode/validation/
mod.rs

1//! Validation middleware for oxicode.
2//!
3//! This module provides validation constraints for deserialization,
4//! ensuring data integrity and security during decoding.
5//!
6//! ## Features
7//!
8//! - **Size Limits**: Limit string/collection lengths via [`Constraints::max_len`] /
9//!   [`Constraints::min_len`]
10//! - **Range Constraints**: Validate numeric values with [`Constraints::range`]
11//! - **Non-empty**: Reject empty strings or collections via [`Constraints::non_empty`]
12//! - **ASCII enforcement**: Require ASCII-only content with [`Constraints::ascii_only`]
13//! - **Custom Validators**: User-defined logic via [`Constraints::custom`]
14//! - **Collect or fail-fast**: Control error accumulation through [`ValidationConfig`]
15//! - **Default fallbacks**: Recover gracefully with [`Validator::validate_or_default`]
16//!
17//! ## Examples
18//!
19//! ### Basic field validation
20//!
21//! ```rust
22//! use oxicode::validation::{Validator, Constraints};
23//!
24//! // Build a validator for i32 values in [0, 120].
25//! let validator: Validator<i32> = Validator::new()
26//!     .constraint("age", Constraints::range(Some(0i32), Some(120i32)));
27//!
28//! assert!(validator.validate(&50).is_ok());
29//! assert!(validator.validate(&-1).is_err());
30//! assert!(validator.validate(&200).is_err());
31//! ```
32//!
33//! ### String constraints
34//!
35//! ```rust
36//! use oxicode::validation::{Validator, Constraints};
37//!
38//! let mut validator: Validator<String> = Validator::new();
39//! validator.add_constraint("username", Constraints::min_len(3));
40//! validator.add_constraint("username", Constraints::max_len(32));
41//! validator.add_constraint("username", Constraints::ascii_only());
42//!
43//! assert!(validator.validate(&"alice".to_string()).is_ok());
44//! assert!(validator.validate(&"".to_string()).is_err());
45//! assert!(validator.validate(&"x".to_string()).is_err());
46//! ```
47//!
48//! ### Returning a default on failure
49//!
50//! ```rust
51//! use oxicode::validation::{Validator, Constraints};
52//!
53//! let validator: Validator<i32> = Validator::new()
54//!     .constraint("score", Constraints::range(Some(0i32), Some(100i32)));
55//!
56//! // Returns the value unchanged when valid.
57//! assert_eq!(validator.validate_or_default(75, 0), 75);
58//!
59//! // Returns the default when validation fails.
60//! assert_eq!(validator.validate_or_default(-5, 0), 0);
61//!
62//! // Lazy default via closure — only evaluated on failure.
63//! assert_eq!(validator.validate_or_default_with(&200, || 100), 100);
64//! ```
65//!
66//! ### Collecting all errors (non-fail-fast)
67//!
68//! ```rust
69//! use oxicode::validation::{Validator, Constraints, ValidationConfig};
70//!
71//! let config = ValidationConfig::new().with_fail_fast(false);
72//! let mut validator: Validator<String> = Validator::with_config(config);
73//! validator.add_constraint("field", Constraints::min_len(10));
74//! validator.add_constraint("field", Constraints::max_len(5));
75//!
76//! // "hi" is too short (min_len 10) AND below max_len 5 is satisfied, but
77//! // actually "hi".len() < 10 fails the first, and "hi".len() <= 5 passes the second.
78//! // Use a value that fails both: "hello world" is > 5 and < 10 is false (len 11 >= 10).
79//! // Simplest: "ab" fails min_len(10).
80//! let result = validator.validate(&"ab".to_string());
81//! assert!(result.is_err());
82//! ```
83
84pub mod constraints;
85mod validator;
86
87pub use constraints::{Constraint, Constraints, Range, ValidationResult};
88pub use validator::{CollectionValidator, NumericValidator, ValidationError};
89
90#[cfg(feature = "alloc")]
91pub use validator::{FieldValidation, StringValidator, Validator};
92
93/// Configuration for validation behavior.
94#[derive(Debug, Clone)]
95pub struct ValidationConfig {
96    /// Whether to fail fast on the first validation error.
97    pub fail_fast: bool,
98
99    /// Maximum depth for nested structure validation.
100    pub max_depth: usize,
101
102    /// Whether to enable checksum verification.
103    pub verify_checksum: bool,
104}
105
106impl Default for ValidationConfig {
107    fn default() -> Self {
108        Self {
109            fail_fast: true,
110            max_depth: 64,
111            verify_checksum: false,
112        }
113    }
114}
115
116impl ValidationConfig {
117    /// Create a new validation configuration.
118    pub fn new() -> Self {
119        Self::default()
120    }
121
122    /// Set fail-fast behavior.
123    #[inline]
124    pub fn with_fail_fast(mut self, fail_fast: bool) -> Self {
125        self.fail_fast = fail_fast;
126        self
127    }
128
129    /// Set maximum validation depth.
130    #[inline]
131    pub fn with_max_depth(mut self, depth: usize) -> Self {
132        self.max_depth = depth;
133        self
134    }
135
136    /// Enable or disable checksum verification.
137    #[inline]
138    pub fn with_checksum(mut self, verify: bool) -> Self {
139        self.verify_checksum = verify;
140        self
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_config_defaults() {
150        let config = ValidationConfig::default();
151        assert!(config.fail_fast);
152        assert_eq!(config.max_depth, 64);
153        assert!(!config.verify_checksum);
154    }
155
156    #[test]
157    fn test_config_builder() {
158        let config = ValidationConfig::new()
159            .with_fail_fast(false)
160            .with_max_depth(128)
161            .with_checksum(true);
162
163        assert!(!config.fail_fast);
164        assert_eq!(config.max_depth, 128);
165        assert!(config.verify_checksum);
166    }
167}