Allow validate (but not schemars) attributes to have extra values where necessary

This commit is contained in:
Graham Esau 2024-08-28 09:59:24 +01:00
parent a85f0fc7bc
commit 56cdd45c5a
5 changed files with 63 additions and 24 deletions

View file

@ -30,6 +30,9 @@ pub struct Struct4(
regex(path = "baz"), regex(path = "baz"),
regex(pattern = "baz"), regex(pattern = "baz"),
phone, phone,
email(code = "code_str", message = "message"),
email = "foo",
email,
email, email,
url url
)] )]

View file

@ -82,10 +82,28 @@ error: `schemars(regex(...))` attribute requires `pattern = ...`
30 | regex(path = "baz"), 30 | regex(path = "baz"),
| ^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^
error: schemars attribute cannot contain both `url` and `email` error: unexpected value of schemars email attribute item
--> tests/ui/invalid_validation_attrs.rs:34:9 --> tests/ui/invalid_validation_attrs.rs:33:14
| |
34 | url 33 | email(code = "code_str", message = "message"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unexpected value of schemars email attribute item
--> tests/ui/invalid_validation_attrs.rs:34:15
|
34 | email = "foo",
| ^^^^^^^
error: duplicate schemars attribute item `email`
--> tests/ui/invalid_validation_attrs.rs:36:9
|
36 | email,
| ^^^^^
error: schemars attribute cannot contain both `url` and `email`
--> tests/ui/invalid_validation_attrs.rs:37:9
|
37 | url
| ^^^ | ^^^
error: unknown schemars attribute `phone` error: unknown schemars attribute `phone`

View file

@ -34,7 +34,7 @@ pub struct Struct {
contains_str2: String, contains_str2: String,
#[validate(email)] #[validate(email)]
email_address: String, email_address: String,
#[validate(url)] #[validate(url(code = "code_str", message = "message"))]
homepage: String, homepage: String,
#[validate(length(min = 1, max = 100))] #[validate(length(min = 1, max = 100))]
non_empty_str: String, non_empty_str: String,

View file

@ -2,7 +2,7 @@ use proc_macro2::{TokenStream, TokenTree};
use syn::{ use syn::{
parse::{Parse, ParseStream, Parser}, parse::{Parse, ParseStream, Parser},
punctuated::Punctuated, punctuated::Punctuated,
Expr, ExprLit, Lit, LitStr, Meta, MetaNameValue, Expr, ExprLit, Lit, LitStr, Meta, MetaList, MetaNameValue,
}; };
use super::{path_str, AttrCtxt}; use super::{path_str, AttrCtxt};
@ -10,10 +10,27 @@ use super::{path_str, AttrCtxt};
pub fn require_path_only(meta: Meta, cx: &AttrCtxt) -> Result<(), ()> { pub fn require_path_only(meta: Meta, cx: &AttrCtxt) -> Result<(), ()> {
match meta { match meta {
Meta::Path(_) => Ok(()), Meta::Path(_) => Ok(()),
_ => { Meta::List(MetaList {
let name = path_str(meta.path()); path, delimiter, ..
}) => {
let name = path_str(&path);
cx.syn_error(syn::Error::new(
delimiter.span().join(),
format_args!(
"unexpected value of {} {} attribute item",
cx.attr_type, name
),
));
Err(())
}
Meta::NameValue(MetaNameValue {
path,
eq_token,
value,
}) => {
let name = path_str(&path);
cx.error_spanned_by( cx.error_spanned_by(
meta, quote!(#eq_token #value),
format_args!( format_args!(
"unexpected value of {} {} attribute item", "unexpected value of {} {} attribute item",
cx.attr_type, name cx.attr_type, name

View file

@ -32,14 +32,12 @@ impl Format {
} }
} }
fn from_attr_str(s: &str) -> Self { fn from_attr_str(s: &str) -> Option<Self> {
match s { Some(match s {
"email" => Format::Email, "email" => Format::Email,
"url" => Format::Uri, "url" => Format::Uri,
_ => { _ => return None,
panic!("Invalid format attr string `{s}`. This is a bug in schemars, please raise an issue!") })
}
}
} }
} }
@ -132,6 +130,10 @@ impl ValidationAttrs {
} }
fn process_meta(&mut self, meta: Meta, meta_name: &str, cx: &AttrCtxt) -> Option<Meta> { fn process_meta(&mut self, meta: Meta, meta_name: &str, cx: &AttrCtxt) -> Option<Meta> {
if let Some(format) = Format::from_attr_str(meta_name) {
self.handle_format(meta, format, cx);
return None;
}
match meta_name { match meta_name {
"length" => match self.length { "length" => match self.length {
Some(_) => cx.duplicate_error(&meta), Some(_) => cx.duplicate_error(&meta),
@ -143,8 +145,6 @@ impl ValidationAttrs {
None => self.range = parse_length_or_range(meta, cx).ok(), None => self.range = parse_length_or_range(meta, cx).ok(),
}, },
"email" | "url" => self.handle_format(meta, meta_name, cx),
"required" => { "required" => {
if self.required { if self.required {
cx.duplicate_error(&meta); cx.duplicate_error(&meta);
@ -159,7 +159,7 @@ impl ValidationAttrs {
(None, None, "schemars") => self.regex = parse_schemars_regex(meta, cx).ok(), (None, None, "schemars") => self.regex = parse_schemars_regex(meta, cx).ok(),
(None, None, "validate") => self.regex = parse_validate_regex(meta, cx).ok(), (None, None, "validate") => self.regex = parse_validate_regex(meta, cx).ok(),
(None, None, wat) => { (None, None, wat) => {
panic!("Unexpected attr type `{wat}` for regex item. This is a bug in schemars, please raise an issue!") unreachable!("Unexpected attr type `{wat}` for regex item. This is a bug in schemars, please raise an issue!")
} }
}, },
"contains" => match (&self.regex, &self.contains) { "contains" => match (&self.regex, &self.contains) {
@ -184,14 +184,15 @@ impl ValidationAttrs {
None None
} }
fn handle_format(&mut self, meta: Meta, meta_name: &str, cx: &AttrCtxt) { fn handle_format(&mut self, meta: Meta, format: Format, cx: &AttrCtxt) {
match &self.format { match self.format {
Some(f) if f.attr_str() == meta_name => cx.duplicate_error(&meta), Some(current) if current == format => cx.duplicate_error(&meta),
Some(f) => cx.mutual_exclusive_error(&meta, f.attr_str()), Some(current) => cx.mutual_exclusive_error(&meta, current.attr_str()),
None => { None => {
// FIXME this is too strict - it may be a MetaList in validator attr (e.g. with message/code items) // Allow a MetaList in validator attr (e.g. with message/code items),
if require_path_only(meta, cx).is_ok() { // but restrict it to path only in schemars attr.
self.format = Some(Format::from_attr_str(meta_name)) if cx.attr_type == "validate" || require_path_only(meta, cx).is_ok() {
self.format = Some(format);
} }
} }
} }