1use std::borrow::Cow;
2use std::collections::{hash_map::Entry::Vacant, BTreeMap, HashMap};
3
4use serde::ser::Serialize;
5use serde_derive::{Deserialize, Serialize};
6use serde_json::{to_value, Value};
7
8#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
9pub struct ValidationError {
10 pub code: Cow<'static, str>,
11 pub message: Option<Cow<'static, str>>,
12 pub params: HashMap<Cow<'static, str>, Value>,
13}
14
15impl ValidationError {
16 pub fn new(code: &'static str) -> ValidationError {
17 ValidationError { code: Cow::from(code), message: None, params: HashMap::new() }
18 }
19
20 pub fn add_param<T: Serialize>(&mut self, name: Cow<'static, str>, val: &T) {
21 self.params.insert(name, to_value(val).unwrap());
22 }
23
24 pub fn with_message(mut self, message: Cow<'static, str>) -> ValidationError {
27 self.message = Some(message);
28 self
29 }
30}
31
32impl std::error::Error for ValidationError {
33 fn description(&self) -> &str {
34 &self.code
35 }
36 fn cause(&self) -> Option<&dyn std::error::Error> {
37 None
38 }
39}
40
41#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
42#[serde(untagged)]
43pub enum ValidationErrorsKind {
44 Struct(Box<ValidationErrors>),
45 List(BTreeMap<usize, Box<ValidationErrors>>),
46 Field(Vec<ValidationError>),
47}
48
49#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
50pub struct ValidationErrors(pub HashMap<Cow<'static, str>, ValidationErrorsKind>);
51
52impl ValidationErrors {
53 pub fn new() -> ValidationErrors {
54 ValidationErrors(HashMap::new())
55 }
56
57 #[must_use]
61 pub fn has_error(result: &Result<(), ValidationErrors>, field: &'static str) -> bool {
62 match result {
63 Ok(()) => false,
64 Err(ref errs) => errs.contains_key(field),
65 }
66 }
67
68 pub fn merge_self(
69 &mut self,
70 field: &'static str,
71 child: Result<(), ValidationErrors>,
72 ) -> &mut ValidationErrors {
73 match child {
74 Ok(()) => self,
75 Err(mut errors) => {
76 if let Some(collection) = errors.0.remove("_tmp_validator") {
80 self.add_nested(field, collection);
81 } else {
82 self.add_nested(field, ValidationErrorsKind::Struct(Box::new(errors)));
83 }
84
85 self
86 }
87 }
88 }
89
90 pub fn merge(
93 parent: Result<(), ValidationErrors>,
94 field: &'static str,
95 child: Result<(), ValidationErrors>,
96 ) -> Result<(), ValidationErrors> {
97 match child {
98 Ok(()) => parent,
99 Err(errors) => {
100 parent.and_then(|_| Err(ValidationErrors::new())).map_err(|mut parent_errors| {
101 parent_errors.add_nested(field, ValidationErrorsKind::Struct(Box::new(errors)));
102 parent_errors
103 })
104 }
105 }
106 }
107
108 pub fn merge_all(
111 parent: Result<(), ValidationErrors>,
112 field: &'static str,
113 children: Vec<Result<(), ValidationErrors>>,
114 ) -> Result<(), ValidationErrors> {
115 let errors = children
116 .into_iter()
117 .enumerate()
118 .filter_map(|(i, res)| res.err().map(|mut err| (i, err.remove(field))))
119 .filter_map(|(i, entry)| match entry {
120 Some(ValidationErrorsKind::Struct(errors)) => Some((i, errors)),
121 _ => None,
122 })
123 .collect::<BTreeMap<_, _>>();
124
125 if errors.is_empty() {
126 parent
127 } else {
128 parent.and_then(|_| Err(ValidationErrors::new())).map_err(|mut parent_errors| {
129 parent_errors.add_nested(field, ValidationErrorsKind::List(errors));
130 parent_errors
131 })
132 }
133 }
134
135 pub fn errors(&self) -> &HashMap<Cow<'static, str>, ValidationErrorsKind> {
138 &self.0
139 }
140
141 pub fn errors_mut(&mut self) -> &mut HashMap<Cow<'static, str>, ValidationErrorsKind> {
144 &mut self.0
145 }
146
147 pub fn into_errors(self) -> HashMap<Cow<'static, str>, ValidationErrorsKind> {
149 self.0
150 }
151
152 pub fn field_errors(&self) -> HashMap<Cow<'static, str>, &Vec<ValidationError>> {
154 self.0
155 .iter()
156 .filter_map(|(k, v)| {
157 if let ValidationErrorsKind::Field(errors) = v {
158 Some((k.clone(), errors))
159 } else {
160 None
161 }
162 })
163 .collect::<HashMap<_, _>>()
164 }
165
166 pub fn add(&mut self, field: &'static str, error: ValidationError) {
167 if let ValidationErrorsKind::Field(ref mut vec) = self
168 .0
169 .entry(Cow::Borrowed(field))
170 .or_insert_with(|| ValidationErrorsKind::Field(vec![]))
171 {
172 vec.push(error);
173 } else {
174 panic!("Attempt to add field validation to a non-Field ValidationErrorsKind instance");
175 }
176 }
177
178 #[must_use]
179 pub fn is_empty(&self) -> bool {
180 self.0.is_empty()
181 }
182
183 fn add_nested(&mut self, field: &'static str, errors: ValidationErrorsKind) {
184 if let Vacant(entry) = self.0.entry(Cow::Borrowed(field)) {
185 entry.insert(errors);
186 } else {
187 panic!("Attempt to replace non-empty ValidationErrors entry");
188 }
189 }
190
191 #[must_use]
192 fn contains_key(&self, field: &'static str) -> bool {
193 self.0.contains_key(field)
194 }
195
196 fn remove(&mut self, field: &'static str) -> Option<ValidationErrorsKind> {
197 self.0.remove(field)
198 }
199}
200
201impl std::error::Error for ValidationErrors {
202 fn description(&self) -> &str {
203 "Validation failed"
204 }
205 fn cause(&self) -> Option<&dyn std::error::Error> {
206 None
207 }
208}