1use crate::core::field::array::ArrayField;
2use crate::core::field::object::ObjectField;
3use crate::core::field::Field;
4use crate::core::field::FieldEnum;
5use crate::core::value::{FieldValue, Reflect};
6use std::collections::BTreeMap;
7use std::error::Error;
8use std::fmt::{Display, Formatter};
9
10#[derive(Debug)]
11pub struct ValidationError {
12 message: String,
13}
14
15impl Display for ValidationError {
16 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
17 write!(f, "{}", self.message)
18 }
19}
20
21impl Error for ValidationError {}
22
23struct State {
24 value: FieldValue,
25 field_names: Vec<String>,
26 errors: BTreeMap<String, Vec<ValidationError>>,
27}
28
29#[derive(Debug)]
30pub struct Validator {
31 schema: FieldEnum,
32}
33
34impl Validator {
35 fn report_error(&self, error: ValidationError, state: &mut State) {
36 let field = state.field_names.clone().join("/");
37 if state.errors.contains_key(field.as_str()) {
38 state.errors.get_mut(field.as_str()).unwrap().push(error);
39 } else {
40 state.errors.insert(field, vec![error]);
41 }
42 }
43
44 fn validate_field(&self, field: &(impl Field + ?Sized), state: &mut State) {
45 state.field_names.push(field.name().clone());
46 for constraint in field.constrains() {
47 match constraint.validate(&state.value) {
48 Ok(_) => {}
49 Err(e) => {
50 self.report_error(
51 ValidationError {
52 message: e.to_string(),
53 },
54 state,
55 );
56 }
57 }
58 }
59 state.field_names.pop();
60 }
61
62 fn visit_array(&self, array: &ArrayField, state: &mut State) {
63 self.validate_field(array, state);
64 state.field_names.push(array.name().clone());
65 if let FieldValue::Array(values) = state.value.clone() {
66 if let Some(item) = &array.item {
67 for value in values {
68 state.value = value;
69 self.visit(item, state);
70 }
71 }
72 }
73 state.field_names.pop();
74 }
75
76 fn visit_object(&self, object: &ObjectField, state: &mut State) {
77 self.validate_field(object, state);
78 state.field_names.push(object.name().clone());
79 if let FieldValue::Object(o) = state.value.clone() {
80 for (name, value) in o {
81 if let Some(field) = object.properties.get(name.as_str()) {
82 state.value = value;
83 self.visit(field, state);
84 };
85 }
86 }
87 state.field_names.pop();
88 }
89
90 fn visit(&self, field: &FieldEnum, state: &mut State) {
91 match field {
92 FieldEnum::Array(f) => self.visit_array(f, state),
93 FieldEnum::Boolean(f) => self.validate_field(f, state),
94 FieldEnum::Float(f) => self.validate_field(f, state),
95 FieldEnum::Integer(f) => self.validate_field(f, state),
96 FieldEnum::UInteger(f) => self.validate_field(f, state),
97 FieldEnum::Object(f) => self.visit_object(f, state),
98 FieldEnum::String(f) => self.validate_field(f, state),
99 FieldEnum::Email(f) => self.validate_field(f, state),
100 FieldEnum::Datetime(f) => self.validate_field(f, state),
101 FieldEnum::Date(f) => self.validate_field(f, state),
102 FieldEnum::Time(f) => self.validate_field(f, state),
103 }
104 }
105
106 pub fn new(field: impl Field) -> Self {
107 Validator {
108 schema: field.into(),
109 }
110 }
111
112 pub fn validate(
113 &self,
114 value: &impl Reflect,
115 ) -> Result<(), BTreeMap<String, Vec<ValidationError>>> {
116 let mut state = State {
117 value: value.field_value(),
118 field_names: vec![],
119 errors: Default::default(),
120 };
121
122 self.visit(&self.schema, &mut state);
123 if state.errors.is_empty() {
124 Ok(())
125 } else {
126 Err(state.errors)
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use crate::core::field::object::ObjectField;
134 use crate::core::value::{FieldValue, Reflect};
135 use crate::visitor::validator::Validator;
136 use std::collections::BTreeMap;
137
138 #[test]
139 fn test_validate() {
140 struct Client {
141 first_name: String,
142 last_name: String,
143 age: u64,
144 }
145
146 impl Reflect for Client {
147 fn field_value(&self) -> FieldValue {
148 FieldValue::Object(BTreeMap::from([
149 ("first_name".to_string(), self.first_name.field_value()),
150 ("last_name".to_string(), self.last_name.field_value()),
151 ("age".to_string(), self.age.field_value()),
152 ]))
153 }
154 }
155
156 let schema_json = r#"
157 {
158 "type":"object",
159 "name": "client",
160 "properties": {
161 "first_name": {
162 "type": "string",
163 "name": "first_name",
164 "max_length": 32,
165 "min_length": 8
166 },
167 "last_name": {
168 "type": "string",
169 "name": "last_name",
170 "max_length": 32,
171 "min_length": 8
172 },
173 "age": {
174 "type": "uinteger",
175 "name": "age",
176 "maximum": 200,
177 "minimum": 0
178 }
179 }
180 }"#;
181 let schema: ObjectField = serde_json::from_str(schema_json).unwrap();
182 let validator = Validator::new(schema);
183
184 let valid_client = Client {
185 first_name: "Robert".to_string(),
186 last_name: "Li".to_string(),
187 age: 32,
188 };
189 assert!(validator.validate(&valid_client).is_ok());
190
191 let invalid_client = Client {
192 first_name: "Robert".to_string(),
193 last_name: "Li".to_string(),
194 age: 201,
195 };
196 let result = validator.validate(&invalid_client);
197 assert!(result.is_err());
198 assert!(result
199 .err()
200 .unwrap()
201 .get("client/age")
202 .unwrap()
203 .get(0)
204 .unwrap()
205 .message
206 .contains("value 201 is larger then 200 (Maximum)"));
207 }
208}