json_model/attribute/
numeric_max.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 NumericMax {
18    name: String,
19    value: f64,
20    typ: NumericType,
21    is_exclusive: bool,
22}
23
24impl NumericMax {
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(NumericMax {
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(NumericMax::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(NumericMax::new(true, path, ctx)?))
76    }
77}
78
79impl Attribute for NumericMax {
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 less 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 less 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 less 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!("Value must be less than or equal to {}.", self.value as i64),
134                });
135            }
136        }
137
138        Ok(())
139    }
140}
141
142fn extract_numeric(
143    value: &serde_json::Value,
144    typ: NumericType,
145    path: DocumentPath,
146) -> Result<f64, Error> {
147    if typ == NumericType::Number {
148        match value.as_f64() {
149            Some(val) => Ok(val),
150            None => Err(Error::InvalidValue {
151                path,
152                value: value.clone(),
153            }),
154        }
155    } else {
156        match value.as_i64() {
157            Some(val) => Ok(val as f64),
158            None => Err(Error::InvalidValue {
159                path,
160                value: value.clone(),
161            }),
162        }
163    }
164}