cjtoolkit_structured_validator/base/number_rules.rs
1//! This module contains the `NumberMandatoryRules` and `NumberRangeRules` structs,
2//! which are used to define rules for validating numerical values.
3
4use crate::common::locale::{LocaleData, LocaleMessage, LocaleValue, ValidateErrorCollector};
5use std::fmt::Display;
6use std::sync::Arc;
7
8/// `NumberMandatoryLocale` is a struct representing a type that may be used
9/// to enforce the concept.
10///
11///
12/// # Possible key values:
13/// * `validate-cannot-be-empty`
14pub struct NumberMandatoryLocale;
15
16impl LocaleMessage for NumberMandatoryLocale {
17 fn get_locale_data(&self) -> Arc<LocaleData> {
18 LocaleData::new("validate-cannot-be-empty")
19 }
20}
21
22/// Represents a set of rules determining whether a number field or value is mandatory.
23///
24/// # Fields
25/// - `is_mandatory` (`bool`):
26/// Specifies whether the associated number is mandatory or not.
27/// - `true`: The number is required (mandatory).
28/// - `false`: The number is optional.
29pub struct NumberMandatoryRules {
30 pub is_mandatory: bool,
31}
32
33impl NumberMandatoryRules {
34 /// Checks whether a given subject is valid based on the rules of the current instance
35 /// and collects any validation errors in the provided `ValidateErrorCollector`.
36 ///
37 /// # Type Parameters
38 /// - `T`: A type that implements the `Into<LocaleValue>` trait, representing the value to be checked.
39 ///
40 /// # Parameters
41 /// - `&self`: Immutable reference to the instance containing validation settings (`is_mandatory` in this case).
42 /// - `messages`: A mutable reference to a `ValidateErrorCollector`, used to collect error messages
43 /// if the validation fails.
44 /// - `subject`: An optional input value of type `T` to be validated. If `None` and the rule `is_mandatory`
45 /// is `true`, it triggers a validation error.
46 ///
47 /// # Behavior
48 /// - If `is_mandatory` is `true` and the `subject` is `None` (i.e., no value is provided):
49 /// - The method appends an error message to `messages`, with the description `"Cannot be empty"`
50 /// and a boxed `NumberMandatoryLocale` as additional context.
51 ///
52 /// # Example
53 /// ```rust
54 /// use cjtoolkit_structured_validator::base::number_rules::NumberMandatoryRules;
55 /// use cjtoolkit_structured_validator::common::locale::ValidateErrorCollector;
56 /// let validator = NumberMandatoryRules { is_mandatory: true };
57 /// let mut errors = ValidateErrorCollector::new();
58 ///
59 /// // Example with a subject as None (will cause validation error)
60 /// validator.check::<f64>(&mut errors, None);
61 /// assert_eq!(errors.len(), 1);
62 ///
63 /// // Example with a valid subject (no validation error)
64 /// validator.check::<f64>(&mut errors, Some(1.0));
65 /// assert_eq!(errors.len(), 1); // No additional errors added.
66 /// ```
67 ///
68 /// # Note
69 /// - Ensure that `ValidateErrorCollector` is properly initialized and passed by mutable reference
70 /// to capture errors.
71 /// - The subject, when provided, must implement the `Into<LocaleValue>` trait for compatibility.
72 pub fn check<T: Into<LocaleValue>>(
73 &self,
74 messages: &mut ValidateErrorCollector,
75 subject: Option<T>,
76 ) {
77 if self.is_mandatory && subject.is_none() {
78 messages.push((
79 "Cannot be empty".to_string(),
80 Box::new(NumberMandatoryLocale),
81 ));
82 }
83 }
84}
85
86/// An enumeration representing a range of values with localization support.
87///
88/// `NumberRangeLocale` is a generic enum used to define a localized numerical
89/// range. It encapsulates a minimum or maximum value that can be converted into
90/// a locale-specific representation using the `LocaleValue` type.
91///
92/// # Type Parameters
93/// - `T`: A type that implements the traits `Into<LocaleValue>`, `Send`, `Sync`,
94/// and `Clone`. This allows for flexible and thread-safe representation of values
95/// that can be converted into localized formats.
96///
97/// # Variants
98/// - `MinValue(T)`: Represents the minimum localized value for the range.
99/// - `MaxValue(T)`: Represents the maximum localized value for the range.
100///
101pub enum NumberRangeLocale<T: Into<LocaleValue> + Send + Sync + Clone> {
102 /// Represents the minimum localized value for the range.
103 /// # Key
104 /// * `validate-number-min-value`
105 MinValue(T),
106 /// Represents the maximum localized value for the range.
107 /// # Key
108 /// * `validate-number-max-value`
109 MaxValue(T),
110}
111
112impl<T: Into<LocaleValue> + Send + Sync + Clone> LocaleMessage for NumberRangeLocale<T>
113where
114 LocaleValue: From<T>,
115{
116 fn get_locale_data(&self) -> Arc<LocaleData> {
117 use LocaleData as ld;
118 use LocaleValue as lv;
119 match self {
120 Self::MinValue(min) => ld::new_with_vec(
121 "validate-number-min-value",
122 vec![("min".to_string(), lv::from(min.clone()))],
123 ),
124 Self::MaxValue(max) => ld::new_with_vec(
125 "validate-number-max-value",
126 vec![("max".to_string(), lv::from(max.clone()))],
127 ),
128 }
129 }
130}
131
132/// A struct that represents rules for defining a range of numeric values with optional minimum and maximum bounds.
133///
134/// This struct is generic and can work with any type `T` that meets the following trait bounds:
135/// - `Clone`: The type can be cloned.
136/// - `Into<LocaleValue>`: The type can be converted into a `LocaleValue`. This allows for localization support.
137/// - `Default`: The type has a default value.
138/// - `PartialOrd`: The type supports partial ordering, enabling comparisons like less than or greater than.
139/// - `Display`: The type can be formatted as a string for display purposes.
140///
141/// # Fields
142/// - `min` (`Option<T>`): The optional lower bound of the range. If `None`, there is no restriction on the minimum value.
143/// - `max` (`Option<T>`): The optional upper bound of the range. If `None`, there is no restriction on the maximum value.
144///
145pub struct NumberRangeRules<T>
146where
147 T: Clone + Into<LocaleValue> + Default + PartialOrd + Display,
148{
149 pub min: Option<T>,
150 pub max: Option<T>,
151}
152
153impl<T> NumberRangeRules<T>
154where
155 T: Clone + Into<LocaleValue> + Default + PartialOrd + Display,
156{
157 /// Validates a given `subject` against optional minimum and maximum value constraints.
158 ///
159 /// # Parameters
160 ///
161 /// - `&self`: A reference to the current instance of the object containing validation constraints.
162 /// - `messages`: A mutable reference to a `ValidateErrorCollector`, where validation error messages
163 /// will be stored if the `subject` does not meet the constraints.
164 /// - `subject`: An optional value of type `T` to be validated against the constraints.
165 ///
166 /// # Behavior
167 ///
168 /// - If the `subject` is `Some`:
169 /// - It checks whether the value is less than the optional minimum value (`self.min`).
170 /// - If the value is less, an error message is added to `messages` stating that the value
171 /// must be at least the specified minimum.
172 /// - It checks whether the value is greater than the optional maximum value (`self.max`).
173 /// - If the value is greater, an error message is added to `messages` stating that the value
174 /// must be at most the specified maximum.
175 /// - If the `subject` is `None`, the default value is used during validation (`T::default()`).
176 /// - Does nothing if both `self.min` and `self.max` are `None`.
177 ///
178 /// # Usage
179 ///
180 /// This function is intended to validate numerical ranges or similar constraints. The errors detected
181 /// during validation are collected into the `ValidateErrorCollector` provided in the `messages` parameter.
182 ///
183 /// # Examples
184 ///
185 /// ```
186 /// use cjtoolkit_structured_validator::common::locale::ValidateErrorCollector;
187 /// use cjtoolkit_structured_validator::base::number_rules::NumberRangeRules;
188 /// let mut error_collector = ValidateErrorCollector::new();
189 /// let validator = NumberRangeRules::<usize> {
190 /// min: Some(10),
191 /// max: Some(100),
192 /// };
193 ///
194 /// validator.check(&mut error_collector, Some(5)); // Value too small, error is added.
195 /// validator.check(&mut error_collector, Some(105)); // Value too large, error is added.
196 /// validator.check(&mut error_collector, Some(50)); // Valid value, no error.
197 /// ```
198 ///
199 /// # Note
200 ///
201 /// - It is assumed that `T` implements the `Default`, `PartialOrd`, and `Clone` traits.
202 /// - The `ValidateErrorCollector` and `NumberRangeLocale` types are expected to support the operations shown above.
203 ///
204 pub fn check(&self, messages: &mut ValidateErrorCollector, subject: Option<T>) {
205 let is_some = subject.is_some();
206 let subject = subject.unwrap_or_default();
207 if let Some(min) = &self.min {
208 if is_some && subject < *min {
209 messages.push((
210 format!("Must be at least {}", min),
211 Box::new(NumberRangeLocale::MinValue(min.clone().into())),
212 ));
213 }
214 }
215 if let Some(max) = &self.max {
216 if is_some && subject > *max {
217 messages.push((
218 format!("Must be at most {}", max),
219 Box::new(NumberRangeLocale::MaxValue(max.clone().into())),
220 ));
221 }
222 }
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 mod number_mandatory_rule {
231 use super::*;
232
233 #[test]
234 fn test_empty_value() {
235 let mut messages = ValidateErrorCollector::new();
236 let subject: Option<f64> = None;
237 let rules = NumberMandatoryRules { is_mandatory: true };
238 rules.check(&mut messages, subject);
239 assert_eq!(messages.len(), 1);
240 assert_eq!(messages.0[0].0, "Cannot be empty");
241 }
242
243 #[test]
244 fn test_not_empty_value() {
245 let mut messages = ValidateErrorCollector::new();
246 let subject: Option<f64> = Some(1.0);
247 let rules = NumberMandatoryRules { is_mandatory: true };
248 rules.check(&mut messages, subject);
249 assert_eq!(messages.len(), 0);
250 }
251 }
252
253 mod number_range_rule {
254 use super::*;
255
256 #[test]
257 fn test_invalid_min_value_rule() {
258 let mut messages = ValidateErrorCollector::new();
259 let subject: Option<f64> = Some(1.0);
260 let rules = NumberRangeRules {
261 min: Some(2.0),
262 max: None,
263 };
264 rules.check(&mut messages, subject);
265 assert_eq!(messages.len(), 1);
266 assert_eq!(messages.0[0].0, "Must be at least 2");
267 }
268
269 #[test]
270 fn test_valid_min_value_rule() {
271 let mut messages = ValidateErrorCollector::new();
272 let subject: Option<f64> = Some(2.0);
273 let rules = NumberRangeRules {
274 min: Some(2.0),
275 max: None,
276 };
277 rules.check(&mut messages, subject);
278 assert_eq!(messages.len(), 0);
279 }
280
281 #[test]
282 fn test_max_value_rule() {
283 let mut messages = ValidateErrorCollector::new();
284 let subject: Option<f64> = Some(1.0);
285 let rules = NumberRangeRules {
286 min: None,
287 max: Some(2.0),
288 };
289 rules.check(&mut messages, subject);
290 assert_eq!(messages.len(), 0);
291 }
292
293 #[test]
294 fn test_valid_max_value_rule() {
295 let mut messages = ValidateErrorCollector::new();
296 let subject: Option<f64> = Some(2.1);
297 let rules = NumberRangeRules {
298 min: None,
299 max: Some(2.0),
300 };
301 rules.check(&mut messages, subject);
302 assert_eq!(messages.len(), 1);
303 assert_eq!(messages.0[0].0, "Must be at most 2");
304 }
305 }
306}