json_model/attribute/
numeric_min.rs

1use super::{AllowedType, Attribute};
2use crate::definition::Type;
3use crate::error::{Error, ValidationError};
4use crate::validator::{Context, DocumentPath, State};
5
6use std::collections::HashSet;
7
8use serde_json;
9
10#[derive(Clone, Debug, PartialEq)]
11pub enum NumericType {
12    Number,
13    Integer,
14}
15
16#[derive(Debug)]
17pub struct NumericMin {
18    name: String,
19    value: f64,
20    typ: NumericType,
21    is_exclusive: bool,
22}
23
24impl NumericMin {
25    pub fn new(is_exclusive: bool, mut path: DocumentPath, ctx: &Context) -> Result<Self, Error> {
26        let obj = ctx.raw_definition();
27
28        let typ = match Type::new(obj, path.clone())? {
29            Type::Number => NumericType::Number,
30            Type::Integer => NumericType::Integer,
31            typ => return Err(Error::ForbiddenType { path, typ }),
32        };
33
34        let value = match obj.get(ctx.name().as_str()) {
35            Some(value) => {
36                path.add(ctx.name().as_str());
37                extract_numeric(value, typ.clone(), path)?
38            }
39            None => {
40                return Err(Error::MissingAttribute {
41                    path,
42                    attr: ctx.name(),
43                })
44            }
45        };
46
47        Ok(NumericMin {
48            name: ctx.name(),
49            value,
50            typ,
51            is_exclusive,
52        })
53    }
54
55    pub fn allowed_types() -> HashSet<AllowedType> {
56        let mut set = HashSet::<AllowedType>::new();
57        set.insert(AllowedType::new(Type::Number, false));
58        set.insert(AllowedType::new(Type::Integer, false));
59        set
60    }
61
62    pub fn build_inclusive(
63        _: &mut State,
64        path: DocumentPath,
65        ctx: &Context,
66    ) -> Result<Box<Attribute>, Error> {
67        Ok(Box::new(NumericMin::new(false, path, ctx)?))
68    }
69
70    pub fn build_exclusive(
71        _: &mut State,
72        path: DocumentPath,
73        ctx: &Context,
74    ) -> Result<Box<Attribute>, Error> {
75        Ok(Box::new(NumericMin::new(true, path, ctx)?))
76    }
77}
78
79impl Attribute for NumericMin {
80    fn validate(
81        &self,
82        _: &State,
83        path: Vec<String>,
84        input: &serde_json::Value,
85    ) -> Result<(), ValidationError> {
86        if self.typ == NumericType::Number {
87            let val = match input.as_f64() {
88                Some(val) => val,
89                None => {
90                    return Err(ValidationError::Failure {
91                        rule: "type".to_string(),
92                        path: path,
93                        message: "Value must be a number.".to_string(),
94                    })
95                }
96            };
97
98            if val <= self.value && self.is_exclusive {
99                return Err(ValidationError::Failure {
100                    rule: self.name.clone(),
101                    path: path,
102                    message: format!("Value must be greater than {}.", self.value),
103                });
104            } else if val < self.value {
105                return Err(ValidationError::Failure {
106                    rule: self.name.clone(),
107                    path: path,
108                    message: format!("Value must be greater than or equal to {}.", self.value),
109                });
110            }
111        } else {
112            let val = match input.as_i64() {
113                Some(val) => val,
114                None => {
115                    return Err(ValidationError::Failure {
116                        rule: "type".to_string(),
117                        path: path,
118                        message: "Value must be an integer.".to_string(),
119                    })
120                }
121            };
122
123            if val <= self.value as i64 && self.is_exclusive {
124                return Err(ValidationError::Failure {
125                    rule: self.name.clone(),
126                    path: path,
127                    message: format!("Value must be greater than {}.", self.value as i64),
128                });
129            } else if val < self.value as i64 {
130                return Err(ValidationError::Failure {
131                    rule: self.name.clone(),
132                    path: path,
133                    message: format!(
134                        "Value must be greater than or equal to {}.",
135                        self.value as i64
136                    ),
137                });
138            }
139        }
140
141        Ok(())
142    }
143}
144
145fn extract_numeric(
146    value: &serde_json::Value,
147    typ: NumericType,
148    path: DocumentPath,
149) -> Result<f64, Error> {
150    if typ == NumericType::Number {
151        match value.as_f64() {
152            Some(val) => Ok(val),
153            None => Err(Error::InvalidValue {
154                path,
155                value: value.clone(),
156            }),
157        }
158    } else {
159        match value.as_i64() {
160            Some(val) => Ok(val as f64),
161            None => Err(Error::InvalidValue {
162                path,
163                value: value.clone(),
164            }),
165        }
166    }
167}