Skip to content

Commit faa2bf1

Browse files
committed
feat: return all validation errors
Signed-off-by: Pierre Fenoll <[email protected]>
1 parent 3828d61 commit faa2bf1

File tree

17 files changed

+388
-164
lines changed

17 files changed

+388
-164
lines changed

prost-validate-derive-core/src/any.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@ pub struct AnyRules {
1616
impl ToValidationTokens for AnyRules {
1717
fn to_validation_tokens(&self, ctx: &Context, name: &Ident) -> TokenStream {
1818
let rules = prost_validate_types::AnyRules::from(self.to_owned());
19+
let maybe_return = if ctx.multierrs {
20+
quote! { errs.push }
21+
} else {
22+
quote! { return Err }
23+
};
1924
let r#in = rules.r#in.is_empty().not().then(|| {
2025
let v = rules.r#in;
2126
let field = &ctx.name;
2227
quote! {
2328
let values = vec![#(#v),*];
2429
if !values.contains(&#name.type_url.as_str()) {
25-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::any::Error::In(values.iter().map(|v|v.to_string()).collect())));
30+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::any::Error::In(values.iter().map(|v|v.to_string()).collect())));
2631
}
2732
}
2833
});
@@ -32,7 +37,7 @@ impl ToValidationTokens for AnyRules {
3237
quote! {
3338
let values = vec![#(#v),*];
3439
if values.contains(&#name.type_url.as_str()) {
35-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::any::Error::NotIn(values.iter().map(|v|v.to_string()).collect())));
40+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::any::Error::NotIn(values.iter().map(|v|v.to_string()).collect())));
3641
}
3742
}
3843
});

prost-validate-derive-core/src/bool.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ pub struct BoolRules {
1010

1111
impl ToValidationTokens for BoolRules {
1212
fn to_validation_tokens(&self, ctx: &Context, name: &Ident) -> TokenStream {
13+
let maybe_return = if ctx.multierrs {
14+
quote! { errs.push }
15+
} else {
16+
quote! { return Err }
17+
};
1318
let r#const = self.r#const.map(|v| {
1419
let field = &ctx.name;
1520
quote! {
1621
if *#name != #v {
17-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::r#bool::Error::Const(#v)));
22+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::r#bool::Error::Const(#v)));
1823
}
1924
}
2025
});

prost-validate-derive-core/src/bytes.rs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,17 @@ pub struct BytesRules {
2626
impl ToValidationTokens for BytesRules {
2727
fn to_validation_tokens(&self, ctx: &Context, name: &Ident) -> TokenStream {
2828
let rules = prost_validate_types::BytesRules::from(self.to_owned());
29+
let maybe_return = if ctx.multierrs {
30+
quote! { errs.push }
31+
} else {
32+
quote! { return Err }
33+
};
2934
let r#const = rules.r#const.map(|v| {
3035
let v = LitByteStr::new(v.as_slice(), Span::call_site());
3136
let field = &ctx.name;
3237
quote! {
3338
if !#name.iter().eq(#v.iter()) {
34-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Const(#v.to_vec())));
39+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Const(#v.to_vec())));
3540
}
3641
}
3742
});
@@ -40,7 +45,7 @@ impl ToValidationTokens for BytesRules {
4045
let field = &ctx.name;
4146
quote! {
4247
if #name.len() != #v {
43-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Len(#v)));
48+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Len(#v)));
4449
}
4550
}
4651
});
@@ -49,7 +54,7 @@ impl ToValidationTokens for BytesRules {
4954
let field = &ctx.name;
5055
quote! {
5156
if #name.len() < #v {
52-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::MinLen(#v)));
57+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::MinLen(#v)));
5358
}
5459
}
5560
});
@@ -58,7 +63,7 @@ impl ToValidationTokens for BytesRules {
5863
let field = &ctx.name;
5964
quote! {
6065
if #name.len() > #v {
61-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::MaxLen(#v)));
66+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::MaxLen(#v)));
6267
}
6368
}
6469
});
@@ -68,11 +73,13 @@ impl ToValidationTokens for BytesRules {
6873
panic!("{field}: Invalid regex pattern: {}", err);
6974
}
7075
quote! {
71-
let regex = ::regex::bytes::Regex::new(#v).map_err(|err| {
72-
::prost_validate::Error::new(#field, format!("Invalid regex pattern: {}", err))
73-
})?;
74-
if !regex.is_match(#name.iter().as_slice()) {
75-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Pattern(#v.to_string())));
76+
match ::regex::bytes::Regex::new(#v) {
77+
Err(e) => #maybe_return(::prost_validate::Error::new(#field, format!("Invalid regex pattern: {e}"))),
78+
Ok(regex) => {
79+
if !regex.is_match(#name.as_slice()) {
80+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Pattern(#v.to_string())));
81+
}
82+
}
7683
}
7784
}
7885
});
@@ -81,7 +88,7 @@ impl ToValidationTokens for BytesRules {
8188
let field = &ctx.name;
8289
quote! {
8390
if !#name.starts_with(#v) {
84-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Prefix(#v.to_vec())));
91+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Prefix(#v.to_vec())));
8592
}
8693
}
8794
});
@@ -90,7 +97,7 @@ impl ToValidationTokens for BytesRules {
9097
let field = &ctx.name;
9198
quote! {
9299
if !#name.ends_with(#v) {
93-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Suffix(#v.to_vec())));
100+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Suffix(#v.to_vec())));
94101
}
95102
}
96103
});
@@ -99,7 +106,7 @@ impl ToValidationTokens for BytesRules {
99106
let field = &ctx.name;
100107
quote! {
101108
if !::prost_validate::ValidateBytesExt::contains(&#name, #v.as_slice()) {
102-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Contains(#v.to_vec())));
109+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Contains(#v.to_vec())));
103110
}
104111
}
105112
});
@@ -113,7 +120,7 @@ impl ToValidationTokens for BytesRules {
113120
quote! {
114121
let values = [#(#v.to_vec()),*];
115122
if !values.contains(&#name) {
116-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::In(values.iter().map(|v| v.to_vec()).collect())));
123+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::In(values.iter().map(|v| v.to_vec()).collect())));
117124
}
118125
}
119126
});
@@ -127,7 +134,7 @@ impl ToValidationTokens for BytesRules {
127134
quote! {
128135
let values = [#(#v.to_vec()),*];
129136
if values.contains(&#name) {
130-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::NotIn(values.iter().map(|v| v.to_vec()).collect())));
137+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::NotIn(values.iter().map(|v| v.to_vec()).collect())));
131138
}
132139
}
133140
});
@@ -136,23 +143,23 @@ impl ToValidationTokens for BytesRules {
136143
let field = &ctx.name;
137144
quote! {
138145
if #name.len() != 4 && #name.len() != 16 {
139-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Ip));
146+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Ip));
140147
}
141148
}
142149
}
143150
bytes_rules::WellKnown::Ipv4(true) => {
144151
let field = &ctx.name;
145152
quote! {
146153
if #name.len() != 4 {
147-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Ipv4));
154+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Ipv4));
148155
}
149156
}
150157
}
151158
bytes_rules::WellKnown::Ipv6(true) => {
152159
let field = &ctx.name;
153160
quote! {
154161
if #name.len() != 16 {
155-
return Err(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Ipv6));
162+
#maybe_return(::prost_validate::Error::new(#field, ::prost_validate::errors::bytes::Error::Ipv6));
156163
}
157164
}
158165
}

prost-validate-derive-core/src/derive.rs

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,11 @@ pub fn derive_with_module(
2323
let opts = Opts::from_derive_input(&input).expect("Wrong validate options");
2424
let DeriveInput { ident, .. } = input;
2525

26-
let implementation = match opts.data {
27-
Data::Enum(e) => e
28-
.iter()
29-
.map(|v| Field {
30-
module: module.clone().map(|v| v.to_string()),
31-
..v.clone()
32-
})
33-
.map(|v| v.to_token_stream())
34-
.collect::<proc_macro2::TokenStream>(),
35-
Data::Struct(s) => s
36-
.fields
37-
.iter()
38-
.map(|v| Field {
39-
module: module.clone().map(|v| v.to_string()),
40-
..v.clone()
41-
})
42-
.map(|field| field.into_token_stream())
43-
.collect::<proc_macro2::TokenStream>(),
44-
};
26+
let implementation = body_tokens(&opts, module.clone(), false);
27+
let impl_multierrs = body_tokens(&opts, module.clone(), true);
4528

4629
let allow = quote! {
30+
#[allow(clippy::collapsible_if)]
4731
#[allow(clippy::regex_creation_in_loops)]
4832
#[allow(irrefutable_let_patterns)]
4933
#[allow(unused_variables)]
@@ -62,6 +46,13 @@ pub fn derive_with_module(
6246
#implementation
6347
Ok(())
6448
}
49+
50+
#allow
51+
fn validate_all(&self) -> Vec<::prost_validate::Error> {
52+
let mut errs = vec![];
53+
#impl_multierrs
54+
errs
55+
}
6556
}
6657
}
6758
} else {
@@ -71,6 +62,30 @@ pub fn derive_with_module(
7162
}
7263
}
7364

65+
fn body_tokens(opts: &Opts, module: Option<TokenStream>, multierrs: bool) -> TokenStream {
66+
match &opts.data {
67+
Data::Enum(e) => e
68+
.iter()
69+
.map(|v| Field {
70+
module: module.clone().map(|v| v.to_string()),
71+
multierrs,
72+
..v.clone()
73+
})
74+
.map(|v| v.to_token_stream())
75+
.collect(),
76+
Data::Struct(s) => s
77+
.fields
78+
.iter()
79+
.map(|v| Field {
80+
module: module.clone().map(|v| v.to_string()),
81+
multierrs,
82+
..v.clone()
83+
})
84+
.map(|field| field.into_token_stream())
85+
.collect(),
86+
}
87+
}
88+
7489
#[cfg(test)]
7590
pub fn derive_2(input: proc_macro2::TokenStream) -> String {
7691
let output = derive(input);
@@ -107,6 +122,7 @@ mod tests {
107122
use super::*;
108123

109124
#[test]
125+
// cargo test --manifest-path prost-validate-derive-core/Cargo.toml derive::tests --nocapture
110126
fn tests() {
111127
let input = quote! {
112128
pub struct WrapperRequiredFloat {

0 commit comments

Comments
 (0)