Skip to content

Commit 06e69eb

Browse files
authored
feat: support Deserialize for ValidationErrors (#364)
Use `Cow<'static, str>` as suggested in #358.
1 parent 629ee58 commit 06e69eb

File tree

6 files changed

+34
-27
lines changed

6 files changed

+34
-27
lines changed

validator/src/traits.rs

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::types::{ValidationErrors, ValidationErrorsKind};
2+
use std::borrow::Cow;
23
use std::collections::btree_map::BTreeMap;
34
use std::collections::HashMap;
45

@@ -32,7 +33,7 @@ macro_rules! impl_validate_list {
3233
} else {
3334
let err_kind = ValidationErrorsKind::List(vec_err);
3435
let errors = ValidationErrors(std::collections::HashMap::from([(
35-
"_tmp_validator",
36+
Cow::Borrowed("_tmp_validator"),
3637
err_kind,
3738
)]));
3839
Err(errors)
@@ -64,8 +65,10 @@ impl<T: Validate, const N: usize> Validate for [T; N] {
6465
Ok(())
6566
} else {
6667
let err_kind = ValidationErrorsKind::List(vec_err);
67-
let errors =
68-
ValidationErrors(std::collections::HashMap::from([("_tmp_validator", err_kind)]));
68+
let errors = ValidationErrors(std::collections::HashMap::from([(
69+
Cow::Borrowed("_tmp_validator"),
70+
err_kind,
71+
)]));
6972
Err(errors)
7073
}
7174
}
@@ -85,7 +88,8 @@ impl<K, V: Validate, S> Validate for &HashMap<K, V, S> {
8588
Ok(())
8689
} else {
8790
let err_kind = ValidationErrorsKind::List(vec_err);
88-
let errors = ValidationErrors(HashMap::from([("_tmp_validator", err_kind)]));
91+
let errors =
92+
ValidationErrors(HashMap::from([(Cow::Borrowed("_tmp_validator"), err_kind)]));
8993
Err(errors)
9094
}
9195
}
@@ -105,7 +109,8 @@ impl<K, V: Validate> Validate for &BTreeMap<K, V> {
105109
Ok(())
106110
} else {
107111
let err_kind = ValidationErrorsKind::List(vec_err);
108-
let errors = ValidationErrors(HashMap::from([("_tmp_validator", err_kind)]));
112+
let errors =
113+
ValidationErrors(HashMap::from([(Cow::Borrowed("_tmp_validator"), err_kind)]));
109114
Err(errors)
110115
}
111116
}

validator/src/types.rs

+13-11
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,16 @@ impl std::error::Error for ValidationError {
3838
}
3939
}
4040

41-
#[derive(Debug, Serialize, Clone, PartialEq)]
41+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
4242
#[serde(untagged)]
4343
pub enum ValidationErrorsKind {
4444
Struct(Box<ValidationErrors>),
4545
List(BTreeMap<usize, Box<ValidationErrors>>),
4646
Field(Vec<ValidationError>),
4747
}
4848

49-
#[derive(Default, Debug, Serialize, Clone, PartialEq)]
50-
pub struct ValidationErrors(pub HashMap<&'static str, ValidationErrorsKind>);
49+
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
50+
pub struct ValidationErrors(pub HashMap<Cow<'static, str>, ValidationErrorsKind>);
5151

5252
impl ValidationErrors {
5353
pub fn new() -> ValidationErrors {
@@ -134,28 +134,28 @@ impl ValidationErrors {
134134

135135
/// Returns a map of field-level validation errors found for the struct that was validated and
136136
/// any of it's nested structs that are tagged for validation.
137-
pub fn errors(&self) -> &HashMap<&'static str, ValidationErrorsKind> {
137+
pub fn errors(&self) -> &HashMap<Cow<'static, str>, ValidationErrorsKind> {
138138
&self.0
139139
}
140140

141141
/// Returns a mutable map of field-level validation errors found for the struct that was validated and
142142
/// any of it's nested structs that are tagged for validation.
143-
pub fn errors_mut(&mut self) -> &mut HashMap<&'static str, ValidationErrorsKind> {
143+
pub fn errors_mut(&mut self) -> &mut HashMap<Cow<'static, str>, ValidationErrorsKind> {
144144
&mut self.0
145145
}
146146

147147
/// Consume the struct, returning the validation errors found
148-
pub fn into_errors(self) -> HashMap<&'static str, ValidationErrorsKind> {
148+
pub fn into_errors(self) -> HashMap<Cow<'static, str>, ValidationErrorsKind> {
149149
self.0
150150
}
151151

152152
/// Returns a map of only field-level validation errors found for the struct that was validated.
153-
pub fn field_errors(&self) -> HashMap<&'static str, &Vec<ValidationError>> {
153+
pub fn field_errors(&self) -> HashMap<Cow<'static, str>, &Vec<ValidationError>> {
154154
self.0
155155
.iter()
156156
.filter_map(|(k, v)| {
157157
if let ValidationErrorsKind::Field(errors) = v {
158-
Some((*k, errors))
158+
Some((k.clone(), errors))
159159
} else {
160160
None
161161
}
@@ -164,8 +164,10 @@ impl ValidationErrors {
164164
}
165165

166166
pub fn add(&mut self, field: &'static str, error: ValidationError) {
167-
if let ValidationErrorsKind::Field(ref mut vec) =
168-
self.0.entry(field).or_insert_with(|| ValidationErrorsKind::Field(vec![]))
167+
if let ValidationErrorsKind::Field(ref mut vec) = self
168+
.0
169+
.entry(Cow::Borrowed(field))
170+
.or_insert_with(|| ValidationErrorsKind::Field(vec![]))
169171
{
170172
vec.push(error);
171173
} else {
@@ -179,7 +181,7 @@ impl ValidationErrors {
179181
}
180182

181183
fn add_nested(&mut self, field: &'static str, errors: ValidationErrorsKind) {
182-
if let Vacant(entry) = self.0.entry(field) {
184+
if let Vacant(entry) = self.0.entry(Cow::Borrowed(field)) {
183185
entry.insert(errors);
184186
} else {
185187
panic!("Attempt to replace non-empty ValidationErrors entry");

validator_derive/src/tokens/nested.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub fn nested_tokens(
55
field_name_str: &str,
66
) -> proc_macro2::TokenStream {
77
quote! {
8-
if let std::collections::hash_map::Entry::Vacant(entry) = errors.0.entry(#field_name_str) {
8+
if let std::collections::hash_map::Entry::Vacant(entry) = errors.0.entry(::std::borrow::Cow::Borrowed(#field_name_str)) {
99
errors.merge_self(#field_name_str, (&#field_name).validate());
1010
}
1111
}

validator_derive_tests/tests/complex.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::HashMap;
1+
use std::{borrow::Cow, collections::HashMap};
22

33
use once_cell::sync::Lazy;
44
use regex::Regex;
@@ -213,7 +213,7 @@ fn test_works_with_none_values() {
213213
#[allow(dead_code)]
214214
fn unwrap_map<F>(errors: &ValidationErrors, f: F)
215215
where
216-
F: FnOnce(HashMap<&'static str, ValidationErrorsKind>),
216+
F: FnOnce(HashMap<Cow<'static, str>, ValidationErrorsKind>),
217217
{
218218
let errors = errors.clone();
219219
f(errors.errors().clone());

validator_derive_tests/tests/custom.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::HashMap;
1+
use std::{borrow::Cow, collections::HashMap};
22

33
use validator::{Validate, ValidationError, ValidationErrors, ValidationErrorsKind};
44

@@ -171,9 +171,9 @@ fn custom_fn_on_optional_types_work() {
171171
assert_eq!(
172172
t.validate(),
173173
Err(ValidationErrors(HashMap::from_iter([
174-
("plain", error_kind.clone()),
175-
("option", error_kind.clone()),
176-
("option_option", error_kind),
174+
(Cow::Borrowed("plain"), error_kind.clone()),
175+
(Cow::Borrowed("option"), error_kind.clone()),
176+
(Cow::Borrowed("option_option"), error_kind),
177177
])))
178178
);
179179
}

validator_derive_tests/tests/nested.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ fn fails_nested_validation_multiple_members() {
7272
assert_eq!(
7373
root.validate(),
7474
Err(ValidationErrors(HashMap::from_iter([(
75-
"a",
75+
Cow::Borrowed("a"),
7676
ValidationErrorsKind::Struct(Box::new(ValidationErrors(HashMap::from_iter([
77-
("value1", error_kind.clone()),
78-
("value2", error_kind),
77+
(Cow::Borrowed("value1"), error_kind.clone()),
78+
(Cow::Borrowed("value2"), error_kind),
7979
]))))
8080
)])))
8181
);
@@ -905,7 +905,7 @@ fn test_field_validations_evaluated_after_nested_validations_fails() {
905905
#[allow(dead_code)]
906906
fn unwrap_map<F>(errors: &ValidationErrors, f: F)
907907
where
908-
F: FnOnce(HashMap<&'static str, ValidationErrorsKind>),
908+
F: FnOnce(HashMap<Cow<'static, str>, ValidationErrorsKind>),
909909
{
910910
let errors = errors.clone();
911911
f(errors.errors().clone());

0 commit comments

Comments
 (0)