json_model/attribute/
numeric_min.rs1use 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}