Read #[garde(...)]
attributes in addition to #[validate(...)]
(#331)
This commit is contained in:
parent
56cdd45c5a
commit
9770301218
16 changed files with 421 additions and 87 deletions
|
@ -210,9 +210,10 @@ impl FieldAttrs {
|
|||
let schemars_cx = &mut AttrCtxt::new(cx, attrs, "schemars");
|
||||
let serde_cx = &mut AttrCtxt::new(cx, attrs, "serde");
|
||||
let validate_cx = &mut AttrCtxt::new(cx, attrs, "validate");
|
||||
let garde_cx = &mut AttrCtxt::new(cx, attrs, "garde");
|
||||
|
||||
self.common.populate(attrs, schemars_cx, serde_cx);
|
||||
self.validation.populate(schemars_cx, validate_cx);
|
||||
self.validation.populate(schemars_cx, validate_cx, garde_cx);
|
||||
self.process_attr(schemars_cx);
|
||||
self.process_attr(serde_cx);
|
||||
}
|
||||
|
@ -277,6 +278,7 @@ impl ContainerAttrs {
|
|||
None => self.crate_name = parse_name_value_lit_str(meta, cx).ok(),
|
||||
},
|
||||
|
||||
// The actual parsing of `rename` is done by serde
|
||||
"rename" => self.is_renamed = true,
|
||||
|
||||
_ => return Some(meta),
|
||||
|
|
|
@ -103,7 +103,7 @@ pub fn parse_extensions(
|
|||
cx: &AttrCtxt,
|
||||
) -> Result<impl IntoIterator<Item = Extension>, ()> {
|
||||
let parser = Punctuated::<Extension, Token![,]>::parse_terminated;
|
||||
parse_meta_list(meta, cx, parser)
|
||||
parse_meta_list_with(&meta, cx, parser)
|
||||
}
|
||||
|
||||
pub fn parse_length_or_range(outer_meta: Meta, cx: &AttrCtxt) -> Result<LengthOrRange, ()> {
|
||||
|
@ -144,6 +144,10 @@ pub fn parse_length_or_range(outer_meta: Meta, cx: &AttrCtxt) -> Result<LengthOr
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn parse_pattern(meta: Meta, cx: &AttrCtxt) -> Result<Expr, ()> {
|
||||
parse_meta_list_with(&meta, cx, Expr::parse)
|
||||
}
|
||||
|
||||
pub fn parse_schemars_regex(outer_meta: Meta, cx: &AttrCtxt) -> Result<Expr, ()> {
|
||||
let mut pattern = None;
|
||||
|
||||
|
@ -200,9 +204,47 @@ pub fn parse_validate_regex(outer_meta: Meta, cx: &AttrCtxt) -> Result<Expr, ()>
|
|||
}
|
||||
|
||||
pub fn parse_contains(outer_meta: Meta, cx: &AttrCtxt) -> Result<Expr, ()> {
|
||||
#[derive(Debug)]
|
||||
enum ContainsFormat {
|
||||
Metas(Punctuated<Meta, Token![,]>),
|
||||
Expr(Expr),
|
||||
}
|
||||
|
||||
impl Parse for ContainsFormat {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
// An imperfect but good-enough heuristic for determining whether it looks more like a
|
||||
// comma-separated meta list (validator-style), or a single expression (garde-style).
|
||||
// This heuristic may not generalise well-enough for attributes other than `contains`!
|
||||
// `foo = bar` => Metas (not Expr::Assign)
|
||||
// `foo, bar` => Metas
|
||||
// `foo` => Expr (not Meta::Path)
|
||||
// `foo(bar)` => Expr (not Meta::List)
|
||||
if input.peek2(Token![,]) || input.peek2(Token![=]) {
|
||||
Punctuated::parse_terminated(input).map(Self::Metas)
|
||||
} else {
|
||||
input.parse().map(Self::Expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nested_meta_or_expr = match cx.attr_type {
|
||||
"validate" => parse_meta_list_with(&outer_meta, cx, Punctuated::parse_terminated)
|
||||
.map(ContainsFormat::Metas),
|
||||
"garde" => parse_meta_list_with(&outer_meta, cx, Expr::parse).map(ContainsFormat::Expr),
|
||||
"schemars" => parse_meta_list_with(&outer_meta, cx, ContainsFormat::parse),
|
||||
wat => {
|
||||
unreachable!("Unexpected attr type `{wat}` for `contains` item. This is a bug in schemars, please raise an issue!")
|
||||
}
|
||||
}?;
|
||||
|
||||
let nested_metas = match nested_meta_or_expr {
|
||||
ContainsFormat::Expr(expr) => return Ok(expr),
|
||||
ContainsFormat::Metas(m) => m,
|
||||
};
|
||||
|
||||
let mut pattern = None;
|
||||
|
||||
for nested_meta in parse_nested_meta(outer_meta.clone(), cx)? {
|
||||
for nested_meta in nested_metas {
|
||||
match path_str(nested_meta.path()).as_str() {
|
||||
"pattern" => match &pattern {
|
||||
Some(_) => cx.duplicate_error(&nested_meta),
|
||||
|
@ -229,10 +271,10 @@ pub fn parse_contains(outer_meta: Meta, cx: &AttrCtxt) -> Result<Expr, ()> {
|
|||
|
||||
pub fn parse_nested_meta(meta: Meta, cx: &AttrCtxt) -> Result<impl IntoIterator<Item = Meta>, ()> {
|
||||
let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
|
||||
parse_meta_list(meta, cx, parser)
|
||||
parse_meta_list_with(&meta, cx, parser)
|
||||
}
|
||||
|
||||
fn parse_meta_list<F: Parser>(meta: Meta, cx: &AttrCtxt, parser: F) -> Result<F::Output, ()> {
|
||||
fn parse_meta_list_with<F: Parser>(meta: &Meta, cx: &AttrCtxt, parser: F) -> Result<F::Output, ()> {
|
||||
let Meta::List(meta_list) = meta else {
|
||||
let name = path_str(meta.path());
|
||||
cx.error_spanned_by(
|
||||
|
|
|
@ -5,8 +5,8 @@ use crate::idents::SCHEMA;
|
|||
|
||||
use super::{
|
||||
parse_meta::{
|
||||
parse_contains, parse_length_or_range, parse_nested_meta, parse_schemars_regex,
|
||||
parse_validate_regex, require_path_only, LengthOrRange,
|
||||
parse_contains, parse_length_or_range, parse_nested_meta, parse_pattern,
|
||||
parse_schemars_regex, parse_validate_regex, require_path_only, LengthOrRange,
|
||||
},
|
||||
AttrCtxt,
|
||||
};
|
||||
|
@ -15,6 +15,9 @@ use super::{
|
|||
pub enum Format {
|
||||
Email,
|
||||
Uri,
|
||||
Ip,
|
||||
Ipv4,
|
||||
Ipv6,
|
||||
}
|
||||
|
||||
impl Format {
|
||||
|
@ -22,6 +25,9 @@ impl Format {
|
|||
match self {
|
||||
Format::Email => "email",
|
||||
Format::Uri => "url",
|
||||
Format::Ip => "ip",
|
||||
Format::Ipv4 => "ipv4",
|
||||
Format::Ipv6 => "ipv6",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,6 +35,9 @@ impl Format {
|
|||
match self {
|
||||
Format::Email => "email",
|
||||
Format::Uri => "uri",
|
||||
Format::Ip => "ip",
|
||||
Format::Ipv4 => "ipv4",
|
||||
Format::Ipv6 => "ipv6",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +45,9 @@ impl Format {
|
|||
Some(match s {
|
||||
"email" => Format::Email,
|
||||
"url" => Format::Uri,
|
||||
"ip" => Format::Ip,
|
||||
"ipv4" => Format::Ipv4,
|
||||
"ipv6" => Format::Ipv6,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
@ -45,6 +57,7 @@ impl Format {
|
|||
pub struct ValidationAttrs {
|
||||
pub length: Option<LengthOrRange>,
|
||||
pub range: Option<LengthOrRange>,
|
||||
pub pattern: Option<Expr>,
|
||||
pub regex: Option<Expr>,
|
||||
pub contains: Option<Expr>,
|
||||
pub required: bool,
|
||||
|
@ -67,7 +80,7 @@ impl ValidationAttrs {
|
|||
Self::add_length_or_range(range, mutators, "number", "imum", mut_ref_schema);
|
||||
}
|
||||
|
||||
if let Some(regex) = &self.regex {
|
||||
if let Some(regex) = self.regex.as_ref().or(self.pattern.as_ref()) {
|
||||
mutators.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_ref_schema, "string", "pattern", (#regex).to_string());
|
||||
});
|
||||
|
@ -75,7 +88,7 @@ impl ValidationAttrs {
|
|||
|
||||
if let Some(contains) = &self.contains {
|
||||
mutators.push(quote! {
|
||||
schemars::_private::must_contain(#mut_ref_schema, #contains.to_string());
|
||||
schemars::_private::must_contain(#mut_ref_schema, &#contains.to_string());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -120,9 +133,15 @@ impl ValidationAttrs {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn populate(&mut self, schemars_cx: &mut AttrCtxt, validate_cx: &mut AttrCtxt) {
|
||||
pub(super) fn populate(
|
||||
&mut self,
|
||||
schemars_cx: &mut AttrCtxt,
|
||||
validate_cx: &mut AttrCtxt,
|
||||
garde_cx: &mut AttrCtxt,
|
||||
) {
|
||||
self.process_attr(schemars_cx);
|
||||
self.process_attr(validate_cx);
|
||||
self.process_attr(garde_cx);
|
||||
}
|
||||
|
||||
fn process_attr(&mut self, cx: &mut AttrCtxt) {
|
||||
|
@ -153,22 +172,36 @@ impl ValidationAttrs {
|
|||
}
|
||||
}
|
||||
|
||||
"regex" => match (&self.regex, &self.contains, cx.attr_type) {
|
||||
(Some(_), _, _) => cx.duplicate_error(&meta),
|
||||
(_, Some(_), _) => cx.mutual_exclusive_error(&meta, "contains"),
|
||||
(None, None, "schemars") => self.regex = parse_schemars_regex(meta, cx).ok(),
|
||||
(None, None, "validate") => self.regex = parse_validate_regex(meta, cx).ok(),
|
||||
(None, None, wat) => {
|
||||
unreachable!("Unexpected attr type `{wat}` for regex item. This is a bug in schemars, please raise an issue!")
|
||||
"pattern" if cx.attr_type != "validate" => {
|
||||
match (&self.pattern, &self.regex, &self.contains) {
|
||||
(Some(_p), _, _) => cx.duplicate_error(&meta),
|
||||
(_, Some(_r), _) => cx.mutual_exclusive_error(&meta, "regex"),
|
||||
(_, _, Some(_c)) => cx.mutual_exclusive_error(&meta, "contains"),
|
||||
(None, None, None) => self.pattern = parse_pattern(meta, cx).ok(),
|
||||
}
|
||||
},
|
||||
"contains" => match (&self.regex, &self.contains) {
|
||||
(Some(_), _) => cx.mutual_exclusive_error(&meta, "regex"),
|
||||
(_, Some(_)) => cx.duplicate_error(&meta),
|
||||
(None, None) => self.contains = parse_contains(meta, cx).ok(),
|
||||
}
|
||||
"regex" if cx.attr_type != "garde" => {
|
||||
match (&self.pattern, &self.regex, &self.contains) {
|
||||
(Some(_p), _, _) => cx.mutual_exclusive_error(&meta, "pattern"),
|
||||
(_, Some(_r), _) => cx.duplicate_error(&meta),
|
||||
(_, _, Some(_c)) => cx.mutual_exclusive_error(&meta, "contains"),
|
||||
(None, None, None) => {
|
||||
if cx.attr_type == "validate" {
|
||||
self.regex = parse_validate_regex(meta, cx).ok()
|
||||
} else {
|
||||
self.regex = parse_schemars_regex(meta, cx).ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"contains" => match (&self.pattern, &self.regex, &self.contains) {
|
||||
(Some(_p), _, _) => cx.mutual_exclusive_error(&meta, "pattern"),
|
||||
(_, Some(_r), _) => cx.mutual_exclusive_error(&meta, "regex"),
|
||||
(_, _, Some(_c)) => cx.duplicate_error(&meta),
|
||||
(None, None, None) => self.contains = parse_contains(meta, cx).ok(),
|
||||
},
|
||||
|
||||
"inner" => {
|
||||
"inner" if cx.attr_type != "validate" => {
|
||||
if let Ok(nested_meta) = parse_nested_meta(meta, cx) {
|
||||
let inner = self
|
||||
.inner
|
||||
|
|
|
@ -18,7 +18,7 @@ use syn::spanned::Spanned;
|
|||
|
||||
#[doc = "Derive macro for `JsonSchema` trait."]
|
||||
#[cfg_attr(not(doctest), doc = include_str!("../deriving.md"), doc = include_str!("../attributes.md"))]
|
||||
#[proc_macro_derive(JsonSchema, attributes(schemars, serde, validate))]
|
||||
#[proc_macro_derive(JsonSchema, attributes(schemars, serde, validate, garde))]
|
||||
pub fn derive_json_schema_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as syn::DeriveInput);
|
||||
derive_json_schema(input, false)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue