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}