Skip to main content

lmn_core/request_template/validators/
float.rs

1use serde::Deserialize;
2
3use crate::request_template::definition::TemplateDef;
4use crate::request_template::error::TemplateError;
5use crate::request_template::validators::Validator;
6
7// ── Raw ───────────────────────────────────────────────────────────────────────
8
9#[derive(Deserialize, Default)]
10pub struct RawFloatDetails {
11    pub decimals: Option<u8>,
12}
13
14// ── Validated ─────────────────────────────────────────────────────────────────
15
16pub struct FloatDef {
17    pub strategy: FloatStrategy,
18    pub decimals: u8,
19}
20
21pub enum FloatStrategy {
22    Exact(f64),
23    Range { min: f64, max: f64 },
24}
25
26// ── Validator ─────────────────────────────────────────────────────────────────
27
28pub struct FloatValidator {
29    pub exact: Option<f64>,
30    pub min: Option<f64>,
31    pub max: Option<f64>,
32    pub details: Option<RawFloatDetails>,
33}
34
35impl Validator for FloatValidator {
36    fn validate(self, name: &str) -> Result<TemplateDef, TemplateError> {
37        let decimals = self.details.unwrap_or_default().decimals.unwrap_or(2);
38
39        let strategy = if let Some(v) = self.exact {
40            FloatStrategy::Exact(v)
41        } else {
42            let min_v = self.min.ok_or_else(|| {
43                TemplateError::InvalidConstraint(format!(
44                    "'{name}': float requires either 'exact' or both 'min' and 'max'"
45                ))
46            })?;
47            let max_v = self.max.ok_or_else(|| {
48                TemplateError::InvalidConstraint(format!(
49                    "'{name}': float requires either 'exact' or both 'min' and 'max'"
50                ))
51            })?;
52            if min_v > max_v {
53                return Err(TemplateError::InvalidConstraint(format!(
54                    "'{name}': float min ({min_v}) > max ({max_v})"
55                )));
56            }
57            FloatStrategy::Range {
58                min: min_v,
59                max: max_v,
60            }
61        };
62
63        Ok(TemplateDef::Float(FloatDef { strategy, decimals }))
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use crate::request_template::validators::Validator;
71
72    fn v(exact: Option<f64>, min: Option<f64>, max: Option<f64>) -> FloatValidator {
73        FloatValidator {
74            exact,
75            min,
76            max,
77            details: None,
78        }
79    }
80
81    #[test]
82    fn validates_exact() {
83        assert!(v(Some(1.0), None, None).validate("x").is_ok());
84    }
85
86    #[test]
87    fn validates_range() {
88        assert!(v(None, Some(1.0), Some(5.0)).validate("x").is_ok());
89    }
90
91    #[test]
92    fn rejects_min_greater_than_max() {
93        assert!(v(None, Some(5.0), Some(1.0)).validate("x").is_err());
94    }
95
96    #[test]
97    fn rejects_missing_min() {
98        assert!(v(None, None, Some(5.0)).validate("x").is_err());
99    }
100
101    #[test]
102    fn rejects_missing_max() {
103        assert!(v(None, Some(1.0), None).validate("x").is_err());
104    }
105}