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, 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, Clone, PartialEq)]
50pub struct ValidationErrors(pub HashMap<&'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(errors) => {
76 for (_, e) in errors.0 {
77 self.add_nested(field, e);
78 }
79 self
80 }
81 }
82 }
83
84 pub fn merge(
87 parent: Result<(), ValidationErrors>,
88 field: &'static str,
89 child: Result<(), ValidationErrors>,
90 ) -> Result<(), ValidationErrors> {
91 match child {
92 Ok(()) => parent,
93 Err(errors) => {
94 parent.and_then(|_| Err(ValidationErrors::new())).map_err(|mut parent_errors| {
95 parent_errors.add_nested(field, ValidationErrorsKind::Struct(Box::new(errors)));
96 parent_errors
97 })
98 }
99 }
100 }
101
102 pub fn merge_all(
105 parent: Result<(), ValidationErrors>,
106 field: &'static str,
107 children: Vec<Result<(), ValidationErrors>>,
108 ) -> Result<(), ValidationErrors> {
109 let errors = children
110 .into_iter()
111 .enumerate()
112 .filter_map(|(i, res)| res.err().map(|mut err| (i, err.remove(field))))
113 .filter_map(|(i, entry)| match entry {
114 Some(ValidationErrorsKind::Struct(errors)) => Some((i, errors)),
115 _ => None,
116 })
117 .collect::<BTreeMap<_, _>>();
118
119 if errors.is_empty() {
120 parent
121 } else {
122 parent.and_then(|_| Err(ValidationErrors::new())).map_err(|mut parent_errors| {
123 parent_errors.add_nested(field, ValidationErrorsKind::List(errors));
124 parent_errors
125 })
126 }
127 }
128
129 pub fn errors(&self) -> &HashMap<&'static str, ValidationErrorsKind> {
132 &self.0
133 }
134
135 pub fn errors_mut(&mut self) -> &mut HashMap<&'static str, ValidationErrorsKind> {
138 &mut self.0
139 }
140
141 pub fn into_errors(self) -> HashMap<&'static str, ValidationErrorsKind> {
143 self.0
144 }
145
146 pub fn field_errors(&self) -> HashMap<&'static str, &Vec<ValidationError>> {
148 self.0
149 .iter()
150 .filter_map(|(k, v)| {
151 if let ValidationErrorsKind::Field(errors) = v {
152 Some((*k, errors))
153 } else {
154 None
155 }
156 })
157 .collect::<HashMap<_, _>>()
158 }
159
160 pub fn add(&mut self, field: &'static str, error: ValidationError) {
161 if let ValidationErrorsKind::Field(ref mut vec) =
162 self.0.entry(field).or_insert_with(|| ValidationErrorsKind::Field(vec![]))
163 {
164 vec.push(error);
165 } else {
166 panic!("Attempt to add field validation to a non-Field ValidationErrorsKind instance");
167 }
168 }
169
170 #[must_use]
171 pub fn is_empty(&self) -> bool {
172 self.0.is_empty()
173 }
174
175 fn add_nested(&mut self, field: &'static str, errors: ValidationErrorsKind) {
176 if let Vacant(entry) = self.0.entry(field) {
177 entry.insert(errors);
178 } else {
179 panic!("Attempt to replace non-empty ValidationErrors entry");
180 }
181 }
182
183 #[must_use]
184 fn contains_key(&self, field: &'static str) -> bool {
185 self.0.contains_key(field)
186 }
187
188 fn remove(&mut self, field: &'static str) -> Option<ValidationErrorsKind> {
189 self.0.remove(field)
190 }
191}
192
193impl std::error::Error for ValidationErrors {
194 fn description(&self) -> &str {
195 "Validation failed"
196 }
197 fn cause(&self) -> Option<&dyn std::error::Error> {
198 None
199 }
200}