Allow regex(path = ...) value to be a non-string expression (#328)

This commit is contained in:
Graham Esau 2024-08-24 18:27:27 +01:00 committed by GitHub
parent dc1245bbd8
commit 66f17fff0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 64 additions and 69 deletions

View file

@ -1,10 +1,14 @@
use schemars::JsonSchema; use schemars::JsonSchema;
#[derive(JsonSchema)] // FIXME validation attrs like `email` should be disallowed non structs/enums/variants
pub struct Struct1(#[validate(regex = 0, foo, length(min = 1, equal = 2, bar))] String);
#[derive(JsonSchema)] #[derive(JsonSchema)]
pub struct Struct2(#[schemars(regex = 0, foo, length(min = 1, equal = 2, bar))] String); #[validate(email)]
pub struct Struct1(#[validate(regex, foo, length(min = 1, equal = 2, bar))] String);
#[derive(JsonSchema)]
#[schemars(email)]
pub struct Struct2(#[schemars(regex, foo, length(min = 1, equal = 2, bar))] String);
#[derive(JsonSchema)] #[derive(JsonSchema)]
pub struct Struct3( pub struct Struct3(

View file

@ -1,59 +1,53 @@
error: expected validate regex attribute to be a string: `regex = "..."`
--> $DIR/invalid_validation_attrs.rs:4:39
|
4 | pub struct Struct1(#[validate(regex = 0, foo, length(min = 1, equal = 2, bar))] String);
| ^
error: unknown schemars attribute `foo` error: unknown schemars attribute `foo`
--> $DIR/invalid_validation_attrs.rs:7:42 --> tests/ui/invalid_validation_attrs.rs:11:38
| |
7 | pub struct Struct2(#[schemars(regex = 0, foo, length(min = 1, equal = 2, bar))] String); 11 | pub struct Struct2(#[schemars(regex, foo, length(min = 1, equal = 2, bar))] String);
| ^^^ | ^^^
error: expected schemars regex attribute to be a string: `regex = "..."` error: could not parse `regex` item in schemars attribute
--> $DIR/invalid_validation_attrs.rs:7:39 --> tests/ui/invalid_validation_attrs.rs:11:31
| |
7 | pub struct Struct2(#[schemars(regex = 0, foo, length(min = 1, equal = 2, bar))] String); 11 | pub struct Struct2(#[schemars(regex, foo, length(min = 1, equal = 2, bar))] String);
| ^ | ^^^^^
error: schemars attribute cannot contain both `equal` and `min` error: schemars attribute cannot contain both `equal` and `min`
--> $DIR/invalid_validation_attrs.rs:7:63 --> tests/ui/invalid_validation_attrs.rs:11:59
| |
7 | pub struct Struct2(#[schemars(regex = 0, foo, length(min = 1, equal = 2, bar))] String); 11 | pub struct Struct2(#[schemars(regex, foo, length(min = 1, equal = 2, bar))] String);
| ^^^^^ | ^^^^^
error: unknown item in schemars length attribute error: unknown item in schemars length attribute
--> $DIR/invalid_validation_attrs.rs:7:74 --> tests/ui/invalid_validation_attrs.rs:11:70
| |
7 | pub struct Struct2(#[schemars(regex = 0, foo, length(min = 1, equal = 2, bar))] String); 11 | pub struct Struct2(#[schemars(regex, foo, length(min = 1, equal = 2, bar))] String);
| ^^^ | ^^^
error: schemars attribute cannot contain both `contains` and `regex` error: schemars attribute cannot contain both `contains` and `regex`
--> $DIR/invalid_validation_attrs.rs:26:9 --> tests/ui/invalid_validation_attrs.rs:30:9
| |
26 | contains = "bar", 30 | contains = "bar",
| ^^^^^^^^ | ^^^^^^^^
error: duplicate schemars attribute `regex` error: duplicate schemars attribute `regex`
--> $DIR/invalid_validation_attrs.rs:27:9 --> tests/ui/invalid_validation_attrs.rs:31:9
| |
27 | regex(path = "baz"), 31 | regex(path = "baz"),
| ^^^^^ | ^^^^^
error: schemars attribute cannot contain both `phone` and `email` error: schemars attribute cannot contain both `phone` and `email`
--> $DIR/invalid_validation_attrs.rs:29:9 --> tests/ui/invalid_validation_attrs.rs:33:9
| |
29 | email, 33 | email,
| ^^^^^ | ^^^^^
error: schemars attribute cannot contain both `phone` and `url` error: schemars attribute cannot contain both `phone` and `url`
--> $DIR/invalid_validation_attrs.rs:30:9 --> tests/ui/invalid_validation_attrs.rs:34:9
| |
30 | url 34 | url
| ^^^ | ^^^
error[E0425]: cannot find value `foo` in this scope error[E0425]: cannot find value `foo` in this scope
--> $DIR/invalid_validation_attrs.rs:12:17 --> tests/ui/invalid_validation_attrs.rs:16:17
| |
12 | regex = "foo", 16 | regex = "foo",
| ^^^^^ not found in this scope | ^^^^^ not found in this scope

View file

@ -16,7 +16,7 @@ pub struct Struct {
min_max: f32, min_max: f32,
#[validate(range(min = "MIN", max = "MAX"))] #[validate(range(min = "MIN", max = "MAX"))]
min_max2: f32, min_max2: f32,
#[validate(regex = "STARTS_WITH_HELLO")] #[validate(regex = &*STARTS_WITH_HELLO)]
regex_str1: String, regex_str1: String,
#[validate(regex(path = "STARTS_WITH_HELLO", code = "foo"))] #[validate(regex(path = "STARTS_WITH_HELLO", code = "foo"))]
regex_str2: String, regex_str2: String,

View file

@ -190,12 +190,6 @@ impl Attrs {
_ if ignore_errors => {} _ if ignore_errors => {}
Meta::List(m) if m.path.is_ident("inner") && attr_type == "schemars" => {
// This will be processed with the validation attributes.
// It's allowed only for the schemars attribute because the
// validator crate doesn't support it yet.
}
_ => { _ => {
if !is_known_serde_or_validation_keyword(&meta_item) { if !is_known_serde_or_validation_keyword(&meta_item) {
let path = meta_item let path = meta_item

View file

@ -1,13 +1,14 @@
use super::{expr_as_lit_str, get_meta_items, parse_lit_into_path, parse_lit_str}; use super::{expr_as_lit_str, get_meta_items, parse_lit_str};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::ToTokens; use quote::ToTokens;
use serde_derive_internals::Ctxt; use serde_derive_internals::Ctxt;
use syn::{ use syn::{
parse::Parser, punctuated::Punctuated, Expr, ExprPath, Lit, Meta, MetaList, MetaNameValue, Path, parse::Parser, punctuated::Punctuated, Expr, ExprLit, ExprPath, Lit, Meta, MetaList,
MetaNameValue, Path,
}; };
pub(crate) static VALIDATION_KEYWORDS: &[&str] = &[ pub(crate) static VALIDATION_KEYWORDS: &[&str] = &[
"range", "regex", "contains", "email", "phone", "url", "length", "required", "range", "regex", "contains", "email", "phone", "url", "length", "required", "inner",
]; ];
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
@ -214,8 +215,7 @@ impl ValidationAttrs {
(Some(_), _) => duplicate_error(&nv.path), (Some(_), _) => duplicate_error(&nv.path),
(None, Some(_)) => mutual_exclusive_error(&nv.path, "contains"), (None, Some(_)) => mutual_exclusive_error(&nv.path, "contains"),
(None, None) => { (None, None) => {
self.regex = self.regex = parse_regex_expr(errors, nv.value);
parse_lit_into_expr_path(errors, attr_type, "regex", &nv.value).ok()
} }
} }
} }
@ -230,10 +230,7 @@ impl ValidationAttrs {
Meta::NameValue(MetaNameValue { path, value, .. }) Meta::NameValue(MetaNameValue { path, value, .. })
if path.is_ident("path") => if path.is_ident("path") =>
{ {
self.regex = parse_lit_into_expr_path( self.regex = parse_regex_expr(errors, value);
errors, attr_type, "path", &value,
)
.ok()
} }
Meta::NameValue(MetaNameValue { path, value, .. }) Meta::NameValue(MetaNameValue { path, value, .. })
if path.is_ident("pattern") => if path.is_ident("pattern") =>
@ -242,7 +239,7 @@ impl ValidationAttrs {
expr_as_lit_str(errors, attr_type, "pattern", &value) expr_as_lit_str(errors, attr_type, "pattern", &value)
.ok() .ok()
.map(|litstr| { .map(|litstr| {
Expr::Lit(syn::ExprLit { Expr::Lit(ExprLit {
attrs: Vec::new(), attrs: Vec::new(),
lit: Lit::Str(litstr.clone()), lit: Lit::Str(litstr.clone()),
}) })
@ -316,7 +313,18 @@ impl ValidationAttrs {
} }
}, },
_ => {} _ if ignore_errors => {}
_ => {
if let Some(ident) = meta_item.path().get_ident() {
if VALIDATION_KEYWORDS.iter().any(|k| ident == k) {
errors.error_spanned_by(
&meta_item,
format!("could not parse `{ident}` item in schemars attribute"),
);
}
}
}
} }
} }
self self
@ -405,21 +413,6 @@ impl ValidationAttrs {
} }
} }
fn parse_lit_into_expr_path(
cx: &Ctxt,
attr_type: &'static str,
meta_item_name: &'static str,
lit: &Expr,
) -> Result<Expr, ()> {
parse_lit_into_path(cx, attr_type, meta_item_name, lit).map(|path| {
Expr::Path(ExprPath {
attrs: Vec::new(),
qself: None,
path,
})
})
}
fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, expr: Expr) -> Option<Expr> { fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, expr: Expr) -> Option<Expr> {
// this odd double-parsing is to make `-10` parsed as an Lit instead of an Expr::Unary // this odd double-parsing is to make `-10` parsed as an Lit instead of an Expr::Unary
let lit: Lit = match syn::parse2(expr.to_token_stream()) { let lit: Lit = match syn::parse2(expr.to_token_stream()) {
@ -445,3 +438,13 @@ fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, expr: Expr) -> Option<Exp
} }
} }
} }
fn parse_regex_expr(cx: &Ctxt, value: Expr) -> Option<Expr> {
match value {
Expr::Lit(ExprLit {
lit: Lit::Str(litstr),
..
}) => parse_lit_str(&litstr).map_err(|e| cx.syn_error(e)).ok(),
v => Some(v),
}
}