skp_validator_rules/numeric/
multiple_of.rs

1//! Multiple of validation rule.
2
3use skp_validator_core::{Rule, ValidationContext, ValidationErrors, ValidationError, ValidationResult};
4
5/// Multiple of validation rule - value must be divisible by the specified number.
6///
7/// # Example
8///
9/// ```rust
10/// use skp_validator_rules::numeric::multiple_of::MultipleOfRule;
11/// use skp_validator_core::{Rule, ValidationContext};
12///
13/// let rule = MultipleOfRule::new(5.0);
14/// let ctx = ValidationContext::default();
15///
16/// assert!(rule.validate(&10.0, &ctx).is_ok());
17/// assert!(rule.validate(&7.0, &ctx).is_err());
18/// ```
19#[derive(Debug, Clone)]
20pub struct MultipleOfRule<T> {
21    /// The divisor
22    pub value: T,
23    /// Custom error message
24    pub message: Option<String>,
25}
26
27impl<T> MultipleOfRule<T> {
28    /// Create a new multiple_of rule.
29    pub fn new(value: T) -> Self {
30        Self {
31            value,
32            message: None,
33        }
34    }
35
36    /// Set custom error message.
37    pub fn message(mut self, msg: impl Into<String>) -> Self {
38        self.message = Some(msg.into());
39        self
40    }
41}
42
43impl MultipleOfRule<f64> {
44    fn get_message(&self) -> String {
45        self.message.clone().unwrap_or_else(|| {
46            format!("Must be a multiple of {}", self.value)
47        })
48    }
49}
50
51impl MultipleOfRule<i64> {
52    fn get_message(&self) -> String {
53        self.message.clone().unwrap_or_else(|| {
54            format!("Must be a multiple of {}", self.value)
55        })
56    }
57}
58
59impl Rule<f64> for MultipleOfRule<f64> {
60    fn validate(&self, value: &f64, _ctx: &ValidationContext) -> ValidationResult<()> {
61        if self.value == 0.0 {
62            return Ok(()); // Division by zero check
63        }
64
65        let remainder = value % self.value;
66        // Use epsilon for floating point comparison
67        if remainder.abs() < f64::EPSILON || (self.value - remainder.abs()).abs() < f64::EPSILON {
68            Ok(())
69        } else {
70            Err(ValidationErrors::from_iter([
71                ValidationError::root("multiple_of", self.get_message())
72                    .with_param("divisor", self.value)
73            ]))
74        }
75    }
76
77    fn name(&self) -> &'static str {
78        "multiple_of"
79    }
80
81    fn default_message(&self) -> String {
82        self.get_message()
83    }
84}
85
86impl Rule<f32> for MultipleOfRule<f64> {
87    fn validate(&self, value: &f32, ctx: &ValidationContext) -> ValidationResult<()> {
88        <Self as Rule<f64>>::validate(self, &(*value as f64), ctx)
89    }
90
91    fn name(&self) -> &'static str {
92        "multiple_of"
93    }
94
95    fn default_message(&self) -> String {
96        self.get_message()
97    }
98}
99
100impl Rule<i64> for MultipleOfRule<i64> {
101    fn validate(&self, value: &i64, _ctx: &ValidationContext) -> ValidationResult<()> {
102        if self.value == 0 {
103            return Ok(()); // Division by zero check
104        }
105
106        if value % self.value == 0 {
107            Ok(())
108        } else {
109            Err(ValidationErrors::from_iter([
110                ValidationError::root("multiple_of", self.get_message())
111                    .with_param("divisor", self.value)
112            ]))
113        }
114    }
115
116    fn name(&self) -> &'static str {
117        "multiple_of"
118    }
119
120    fn default_message(&self) -> String {
121        self.get_message()
122    }
123}
124
125impl Rule<i32> for MultipleOfRule<i64> {
126    fn validate(&self, value: &i32, ctx: &ValidationContext) -> ValidationResult<()> {
127        <Self as Rule<i64>>::validate(self, &(*value as i64), ctx)
128    }
129
130    fn name(&self) -> &'static str {
131        "multiple_of"
132    }
133
134    fn default_message(&self) -> String {
135        self.get_message()
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_multiple_of_float() {
145        let rule = MultipleOfRule::new(5.0);
146        let ctx = ValidationContext::default();
147
148        assert!(rule.validate(&10.0, &ctx).is_ok());
149        assert!(rule.validate(&15.0, &ctx).is_ok());
150        assert!(rule.validate(&7.0, &ctx).is_err());
151    }
152
153    #[test]
154    fn test_multiple_of_int() {
155        let rule = MultipleOfRule::new(3_i64);
156        let ctx = ValidationContext::default();
157
158        assert!(rule.validate(&9_i64, &ctx).is_ok());
159        assert!(rule.validate(&12_i64, &ctx).is_ok());
160        assert!(rule.validate(&7_i64, &ctx).is_err());
161    }
162
163    #[test]
164    fn test_custom_message() {
165        let rule = MultipleOfRule::new(10.0).message("Must be a multiple of 10");
166        assert_eq!(rule.get_message(), "Must be a multiple of 10");
167    }
168}