Allow regex(path = ...)
value to be a non-string expression (#328)
This commit is contained in:
parent
dc1245bbd8
commit
66f17fff0e
5 changed files with 64 additions and 69 deletions
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue