From 9501fe319f4eaab4ac9df13103442d703ee62522 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 28 Apr 2024 18:44:43 +0100 Subject: [PATCH] Update to syn 2 and serde_derive_internals 0.29 --- schemars/tests/remote_derive_generic.rs | 1 + schemars/tests/schema_with_struct.rs | 1 + schemars/tests/skip.rs | 1 + schemars/tests/struct.rs | 2 + schemars/tests/transparent.rs | 2 + schemars/tests/validate.rs | 2 + schemars/tests/validate_inner.rs | 2 +- schemars_derive/Cargo.toml | 4 +- schemars_derive/src/ast/mod.rs | 8 +- schemars_derive/src/attr/doc.rs | 14 +- schemars_derive/src/attr/mod.rs | 100 +++++---- schemars_derive/src/attr/schemars_to_serde.rs | 78 ++----- schemars_derive/src/attr/validation.rs | 204 +++++++++--------- schemars_derive/src/lib.rs | 20 +- schemars_derive/src/schema_exprs.rs | 2 +- 15 files changed, 206 insertions(+), 235 deletions(-) diff --git a/schemars/tests/remote_derive_generic.rs b/schemars/tests/remote_derive_generic.rs index 2a5ac54..8599266 100644 --- a/schemars/tests/remote_derive_generic.rs +++ b/schemars/tests/remote_derive_generic.rs @@ -20,6 +20,7 @@ enum OrDef { struct Str<'a>(&'a str); +#[allow(dead_code)] #[derive(JsonSchema, Serialize)] #[serde(remote = "Str")] struct StrDef<'a>(&'a str); diff --git a/schemars/tests/schema_with_struct.rs b/schemars/tests/schema_with_struct.rs index 15fe5a2..9c87abe 100644 --- a/schemars/tests/schema_with_struct.rs +++ b/schemars/tests/schema_with_struct.rs @@ -23,6 +23,7 @@ fn struct_normal() -> TestResult { test_default_generated_schema::("schema_with-struct") } +#[allow(dead_code)] #[derive(JsonSchema)] pub struct Tuple( #[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema, diff --git a/schemars/tests/skip.rs b/schemars/tests/skip.rs index 8fbfa65..a95a8db 100644 --- a/schemars/tests/skip.rs +++ b/schemars/tests/skip.rs @@ -21,6 +21,7 @@ fn skip_struct_fields() -> TestResult { test_default_generated_schema::("skip_struct_fields") } +#[allow(dead_code)] #[derive(JsonSchema)] struct TupleStruct( #[schemars(skip)] i32, diff --git a/schemars/tests/struct.rs b/schemars/tests/struct.rs index 108a5d9..80f9ec8 100644 --- a/schemars/tests/struct.rs +++ b/schemars/tests/struct.rs @@ -18,6 +18,7 @@ fn struct_normal() -> TestResult { test_default_generated_schema::("struct-normal") } +#[allow(dead_code)] #[derive(JsonSchema)] pub struct Tuple(i32, bool, Option<&'static str>); @@ -26,6 +27,7 @@ fn struct_tuple() -> TestResult { test_default_generated_schema::("struct-tuple") } +#[allow(dead_code)] #[derive(JsonSchema)] pub struct Newtype(i32); diff --git a/schemars/tests/transparent.rs b/schemars/tests/transparent.rs index 2591fb8..f8b2218 100644 --- a/schemars/tests/transparent.rs +++ b/schemars/tests/transparent.rs @@ -16,10 +16,12 @@ pub struct TransparentStruct { inner: (), } +#[allow(dead_code)] #[derive(JsonSchema)] #[schemars(transparent)] pub struct TransparentNewType(Option); +#[allow(dead_code)] #[derive(JsonSchema)] pub struct InnerStruct(String, i32); diff --git a/schemars/tests/validate.rs b/schemars/tests/validate.rs index bf28d62..9770eec 100644 --- a/schemars/tests/validate.rs +++ b/schemars/tests/validate.rs @@ -104,6 +104,7 @@ fn validate_schemars_attrs() -> TestResult { test_default_generated_schema::("validate_schemars_attrs") } +#[allow(dead_code)] #[derive(JsonSchema)] pub struct Tuple( #[validate(range(max = 10))] u8, @@ -115,6 +116,7 @@ fn validate_tuple() -> TestResult { test_default_generated_schema::("validate_tuple") } +#[allow(dead_code)] #[derive(JsonSchema)] pub struct NewType(#[validate(range(max = 10))] u8); diff --git a/schemars/tests/validate_inner.rs b/schemars/tests/validate_inner.rs index 535410f..6416671 100644 --- a/schemars/tests/validate_inner.rs +++ b/schemars/tests/validate_inner.rs @@ -12,7 +12,7 @@ pub struct Struct<'a> { #[schemars(inner(length(min = 5, max = 100)))] array_str_length: [&'a str; 2], #[schemars(inner(contains(pattern = "substring...")))] - slice_str_contains: &'a[&'a str], + slice_str_contains: &'a [&'a str], #[schemars(inner(regex = "STARTS_WITH_HELLO"))] vec_str_regex: Vec, #[schemars(inner(length(min = 1, max = 100)))] diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml index 4535814..cd5adfd 100644 --- a/schemars_derive/Cargo.toml +++ b/schemars_derive/Cargo.toml @@ -17,8 +17,8 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -syn = { version = "1.0", features = ["extra-traits"] } -serde_derive_internals = "0.26.0" +syn = { version = "2.0", features = ["extra-traits"] } +serde_derive_internals = "0.29" [dev-dependencies] pretty_assertions = "1.2.1" diff --git a/schemars_derive/src/ast/mod.rs b/schemars_derive/src/ast/mod.rs index 250a3b2..32cb122 100644 --- a/schemars_derive/src/ast/mod.rs +++ b/schemars_derive/src/ast/mod.rs @@ -38,7 +38,7 @@ pub struct Field<'a> { } impl<'a> Container<'a> { - pub fn from_ast(item: &'a syn::DeriveInput) -> Result, Vec> { + pub fn from_ast(item: &'a syn::DeriveInput) -> syn::Result> { let ctxt = Ctxt::new(); let result = serde_ast::Container::from_ast(&ctxt, item, Derive::Deserialize) .ok_or(()) @@ -48,7 +48,7 @@ impl<'a> Container<'a> { .map(|_| result.expect("from_ast set no errors on Ctxt, so should have returned Ok")) } - pub fn name(&self) -> String { + pub fn name(&self) -> &str { self.serde_attrs.name().deserialize_name() } @@ -64,7 +64,7 @@ impl<'a> Container<'a> { } impl<'a> Variant<'a> { - pub fn name(&self) -> String { + pub fn name(&self) -> &str { self.serde_attrs.name().deserialize_name() } @@ -74,7 +74,7 @@ impl<'a> Variant<'a> { } impl<'a> Field<'a> { - pub fn name(&self) -> String { + pub fn name(&self) -> &str { self.serde_attrs.name().deserialize_name() } } diff --git a/schemars_derive/src/attr/doc.rs b/schemars_derive/src/attr/doc.rs index 0827dc0..aab15d2 100644 --- a/schemars_derive/src/attr/doc.rs +++ b/schemars_derive/src/attr/doc.rs @@ -1,4 +1,4 @@ -use syn::{Attribute, Lit::Str, Meta::NameValue, MetaNameValue}; +use syn::Attribute; pub fn get_title_and_desc_from_doc(attrs: &[Attribute]) -> (Option, Option) { let doc = match get_doc(attrs) { @@ -35,13 +35,17 @@ fn get_doc(attrs: &[Attribute]) -> Option { let attrs = attrs .iter() .filter_map(|attr| { - if !attr.path.is_ident("doc") { + if !attr.path().is_ident("doc") { return None; } - let meta = attr.parse_meta().ok()?; - if let NameValue(MetaNameValue { lit: Str(s), .. }) = meta { - return Some(s.value()); + let meta = attr.meta.require_name_value().ok()?; + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit_str), + .. + }) = &meta.value + { + return Some(lit_str.value()); } None diff --git a/schemars_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs index f95bc48..86c9349 100644 --- a/schemars_derive/src/attr/mod.rs +++ b/schemars_derive/src/attr/mod.rs @@ -10,9 +10,7 @@ use proc_macro2::{Group, Span, TokenStream, TokenTree}; use quote::ToTokens; use serde_derive_internals::Ctxt; use syn::parse::{self, Parse}; -use syn::Meta::{List, NameValue}; -use syn::MetaNameValue; -use syn::NestedMeta::{Lit, Meta}; +use syn::{Meta, MetaNameValue}; // FIXME using the same struct for containers+variants+fields means that // with/schema_with are accepted (but ignored) on containers, and @@ -42,10 +40,10 @@ impl Attrs { .populate(attrs, "schemars", false, errors) .populate(attrs, "serde", true, errors); - result.deprecated = attrs.iter().any(|a| a.path.is_ident("deprecated")); + result.deprecated = attrs.iter().any(|a| a.path().is_ident("deprecated")); result.repr = attrs .iter() - .find(|a| a.path.is_ident("repr")) + .find(|a| a.path().is_ident("repr")) .and_then(|a| a.parse_args().ok()); let (doc_title, doc_description) = doc::get_title_and_desc_from_doc(attrs); @@ -105,8 +103,8 @@ impl Attrs { for meta_item in get_meta_items(attrs, attr_type, errors, ignore_errors) { match &meta_item { - Meta(NameValue(m)) if m.path.is_ident("with") => { - if let Ok(ty) = parse_lit_into_ty(errors, attr_type, "with", &m.lit) { + Meta::NameValue(m) if m.path.is_ident("with") => { + if let Ok(ty) = parse_lit_into_ty(errors, attr_type, "with", &m.value) { match self.with { Some(WithAttr::Type(_)) => duplicate_error(m), Some(WithAttr::Function(_)) => mutual_exclusive_error(m, "schema_with"), @@ -115,8 +113,9 @@ impl Attrs { } } - Meta(NameValue(m)) if m.path.is_ident("schema_with") => { - if let Ok(fun) = parse_lit_into_path(errors, attr_type, "schema_with", &m.lit) { + Meta::NameValue(m) if m.path.is_ident("schema_with") => { + if let Ok(fun) = parse_lit_into_path(errors, attr_type, "schema_with", &m.value) + { match self.with { Some(WithAttr::Function(_)) => duplicate_error(m), Some(WithAttr::Type(_)) => mutual_exclusive_error(m, "with"), @@ -125,8 +124,8 @@ impl Attrs { } } - Meta(NameValue(m)) if m.path.is_ident("title") => { - if let Ok(title) = get_lit_str(errors, attr_type, "title", &m.lit) { + Meta::NameValue(m) if m.path.is_ident("title") => { + if let Ok(title) = expr_as_lit_str(errors, attr_type, "title", &m.value) { match self.title { Some(_) => duplicate_error(m), None => self.title = Some(title.value()), @@ -134,8 +133,10 @@ impl Attrs { } } - Meta(NameValue(m)) if m.path.is_ident("description") => { - if let Ok(description) = get_lit_str(errors, attr_type, "description", &m.lit) { + Meta::NameValue(m) if m.path.is_ident("description") => { + if let Ok(description) = + expr_as_lit_str(errors, attr_type, "description", &m.value) + { match self.description { Some(_) => duplicate_error(m), None => self.description = Some(description.value()), @@ -143,16 +144,16 @@ impl Attrs { } } - Meta(NameValue(m)) if m.path.is_ident("example") => { - if let Ok(fun) = parse_lit_into_path(errors, attr_type, "example", &m.lit) { + Meta::NameValue(m) if m.path.is_ident("example") => { + if let Ok(fun) = parse_lit_into_path(errors, attr_type, "example", &m.value) { self.examples.push(fun) } } - Meta(NameValue(m)) if m.path.is_ident("rename") => self.is_renamed = true, + Meta::NameValue(m) if m.path.is_ident("rename") => self.is_renamed = true, - Meta(NameValue(m)) if m.path.is_ident("crate") && attr_type == "schemars" => { - if let Ok(p) = parse_lit_into_path(errors, attr_type, "crate", &m.lit) { + Meta::NameValue(m) if m.path.is_ident("crate") && attr_type == "schemars" => { + if let Ok(p) = parse_lit_into_path(errors, attr_type, "crate", &m.value) { if self.crate_name.is_some() { duplicate_error(m) } else { @@ -163,14 +164,14 @@ impl Attrs { _ if ignore_errors => {} - Meta(List(m)) if m.path.is_ident("inner") && attr_type == "schemars" => { + 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. } - Meta(meta_item) => { - if !is_known_serde_or_validation_keyword(meta_item) { + _ => { + if !is_known_serde_or_validation_keyword(&meta_item) { let path = meta_item .path() .into_token_stream() @@ -182,10 +183,6 @@ impl Attrs { ); } } - - Lit(lit) => { - errors.error_spanned_by(lit, "unexpected literal in schemars attribute"); - } } } self @@ -220,34 +217,35 @@ fn get_meta_items( attr_type: &'static str, errors: &Ctxt, ignore_errors: bool, -) -> Vec { - attrs.iter().fold(vec![], |mut acc, attr| { - if !attr.path.is_ident(attr_type) { - return acc; +) -> Vec { + let mut result = vec![]; + for attr in attrs.iter().filter(|a| a.path().is_ident(attr_type)) { + match attr.parse_args_with(syn::punctuated::Punctuated::::parse_terminated) + { + Ok(list) => result.extend(list), + Err(err) if !ignore_errors => errors.syn_error(err), + Err(_) => {} } - match attr.parse_meta() { - Ok(List(meta)) => acc.extend(meta.nested), - Ok(other) if !ignore_errors => { - errors.error_spanned_by(other, format!("expected #[{}(...)]", attr_type)) - } - Err(err) if !ignore_errors => errors.error_spanned_by(attr, err), - _ => (), - } - acc - }) + } + + result } -fn get_lit_str<'a>( +fn expr_as_lit_str<'a>( cx: &Ctxt, attr_type: &'static str, meta_item_name: &'static str, - lit: &'a syn::Lit, + expr: &'a syn::Expr, ) -> Result<&'a syn::LitStr, ()> { - if let syn::Lit::Str(lit) = lit { - Ok(lit) + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit_str), + .. + }) = expr + { + Ok(lit_str) } else { cx.error_spanned_by( - lit, + expr, format!( "expected {} {} attribute to be a string: `{} = \"...\"`", attr_type, meta_item_name, meta_item_name @@ -261,9 +259,9 @@ fn parse_lit_into_ty( cx: &Ctxt, attr_type: &'static str, meta_item_name: &'static str, - lit: &syn::Lit, + lit: &syn::Expr, ) -> Result { - let string = get_lit_str(cx, attr_type, meta_item_name, lit)?; + let string = expr_as_lit_str(cx, attr_type, meta_item_name, lit)?; parse_lit_str(string).map_err(|_| { cx.error_spanned_by( @@ -281,17 +279,17 @@ fn parse_lit_into_path( cx: &Ctxt, attr_type: &'static str, meta_item_name: &'static str, - lit: &syn::Lit, + expr: &syn::Expr, ) -> Result { - let string = get_lit_str(cx, attr_type, meta_item_name, lit)?; + let lit_str = expr_as_lit_str(cx, attr_type, meta_item_name, expr)?; - parse_lit_str(string).map_err(|_| { + parse_lit_str(lit_str).map_err(|_| { cx.error_spanned_by( - lit, + expr, format!( "failed to parse path: `{} = {:?}`", meta_item_name, - string.value() + lit_str.value() ), ) }) diff --git a/schemars_derive/src/attr/schemars_to_serde.rs b/schemars_derive/src/attr/schemars_to_serde.rs index 7878a2c..5b69942 100644 --- a/schemars_derive/src/attr/schemars_to_serde.rs +++ b/schemars_derive/src/attr/schemars_to_serde.rs @@ -2,7 +2,9 @@ use quote::ToTokens; use serde_derive_internals::Ctxt; use std::collections::HashSet; use syn::parse::Parser; -use syn::{Attribute, Data, Field, Meta, NestedMeta, Variant}; +use syn::{Attribute, Data, Field, Meta, Variant}; + +use super::get_meta_items; // List of keywords that can appear in #[serde(...)]/#[schemars(...)] attributes which we want serde_derive_internals to parse for us. pub(crate) static SERDE_KEYWORDS: &[&str] = &[ @@ -32,7 +34,7 @@ pub(crate) static SERDE_KEYWORDS: &[&str] = &[ // If a struct/variant/field has any #[schemars] attributes, then create copies of them // as #[serde] attributes so that serde_derive_internals will parse them for us. -pub fn process_serde_attrs(input: &mut syn::DeriveInput) -> Result<(), Vec> { +pub fn process_serde_attrs(input: &mut syn::DeriveInput) -> syn::Result<()> { let ctxt = Ctxt::new(); process_attrs(&ctxt, &mut input.attrs); match input.data { @@ -60,28 +62,22 @@ fn process_serde_field_attrs<'a>(ctxt: &Ctxt, fields: impl Iterator) { // Remove #[serde(...)] attributes (some may be re-added later) let (serde_attrs, other_attrs): (Vec<_>, Vec<_>) = - attrs.drain(..).partition(|at| at.path.is_ident("serde")); + attrs.drain(..).partition(|at| at.path().is_ident("serde")); *attrs = other_attrs; - let schemars_attrs: Vec<_> = attrs - .iter() - .filter(|at| at.path.is_ident("schemars")) - .collect(); - // Copy appropriate #[schemars(...)] attributes to #[serde(...)] attributes - let (mut serde_meta, mut schemars_meta_names): (Vec<_>, HashSet<_>) = schemars_attrs - .iter() - .flat_map(|at| get_meta_items(ctxt, at)) - .flatten() - .filter_map(|meta| { - let keyword = get_meta_ident(ctxt, &meta).ok()?; - if keyword.ends_with("with") || !SERDE_KEYWORDS.contains(&keyword.as_ref()) { - None - } else { - Some((meta, keyword)) - } - }) - .unzip(); + let (mut serde_meta, mut schemars_meta_names): (Vec<_>, HashSet<_>) = + get_meta_items(attrs, "schemars", ctxt, false) + .into_iter() + .filter_map(|meta| { + let keyword = get_meta_ident(&meta)?; + if SERDE_KEYWORDS.contains(&keyword.as_ref()) && !keyword.ends_with("with") { + Some((meta, keyword)) + } else { + None + } + }) + .unzip(); if schemars_meta_names.contains("skip") { schemars_meta_names.insert("skip_serializing".to_string()); @@ -89,12 +85,8 @@ fn process_attrs(ctxt: &Ctxt, attrs: &mut Vec) { } // Re-add #[serde(...)] attributes that weren't overridden by #[schemars(...)] attributes - for meta in serde_attrs - .into_iter() - .flat_map(|at| get_meta_items(ctxt, &at)) - .flatten() - { - if let Ok(i) = get_meta_ident(ctxt, &meta) { + for meta in get_meta_items(&serde_attrs, "serde", ctxt, false) { + if let Some(i) = get_meta_ident(&meta) { if !schemars_meta_names.contains(&i) && SERDE_KEYWORDS.contains(&i.as_ref()) && i != "bound" @@ -125,34 +117,8 @@ fn to_tokens(attrs: &[Attribute]) -> impl ToTokens { tokens } -fn get_meta_items(ctxt: &Ctxt, attr: &Attribute) -> Result, ()> { - match attr.parse_meta() { - Ok(Meta::List(meta)) => Ok(meta.nested.into_iter().collect()), - Ok(_) => { - ctxt.error_spanned_by(attr, "expected #[schemars(...)] or #[serde(...)]"); - Err(()) - } - Err(err) => { - ctxt.error_spanned_by(attr, err); - Err(()) - } - } -} - -fn get_meta_ident(ctxt: &Ctxt, meta: &NestedMeta) -> Result { - match meta { - NestedMeta::Meta(m) => m.path().get_ident().map(|i| i.to_string()).ok_or(()), - NestedMeta::Lit(lit) => { - ctxt.error_spanned_by( - meta, - format!( - "unexpected literal in attribute: {}", - lit.into_token_stream() - ), - ); - Err(()) - } - } +fn get_meta_ident(meta: &Meta) -> Option { + meta.path().get_ident().map(|i| i.to_string()) } #[cfg(test)] @@ -198,7 +164,7 @@ mod tests { }; if let Err(e) = process_serde_attrs(&mut input) { - panic!("process_serde_attrs returned error: {}", e[0]) + panic!("process_serde_attrs returned error: {}", e) }; assert_eq!(input, expected); diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs index ec7cc14..b0af2c5 100644 --- a/schemars_derive/src/attr/validation.rs +++ b/schemars_derive/src/attr/validation.rs @@ -1,7 +1,10 @@ -use super::{get_lit_str, get_meta_items, parse_lit_into_path, parse_lit_str}; +use super::{expr_as_lit_str, get_meta_items, parse_lit_into_path, parse_lit_str}; use proc_macro2::TokenStream; +use quote::ToTokens; use serde_derive_internals::Ctxt; -use syn::{Expr, ExprLit, ExprPath, Lit, Meta, MetaNameValue, NestedMeta, Path}; +use syn::{ + parse::Parser, punctuated::Punctuated, Expr, ExprPath, Lit, Meta, MetaList, MetaNameValue, Path, +}; pub(crate) static VALIDATION_KEYWORDS: &[&str] = &[ "range", "regex", "contains", "email", "phone", "url", "length", "required", @@ -62,7 +65,7 @@ impl ValidationAttrs { fn populate( mut self, - meta_items: Vec, + meta_items: Vec, attr_type: &'static str, ignore_errors: bool, errors: &Ctxt, @@ -100,31 +103,43 @@ impl ValidationAttrs { errors.error_spanned_by(path, msg) } }; + let parse_nested_meta = |meta_list: MetaList| { + let parser = Punctuated::::parse_terminated; + match parser.parse2(meta_list.tokens) { + Ok(p) => p, + Err(e) => { + if !ignore_errors { + errors.syn_error(e); + } + Default::default() + } + } + }; for meta_item in meta_items { - match &meta_item { - NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("length") => { - for nested in meta_list.nested.iter() { + match meta_item { + Meta::List(meta_list) if meta_list.path.is_ident("length") => { + for nested in parse_nested_meta(meta_list) { match nested { - NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("min") => { + Meta::NameValue(nv) if nv.path.is_ident("min") => { if self.length_min.is_some() { duplicate_error(&nv.path) } else if self.length_equal.is_some() { mutual_exclusive_error(&nv.path, "equal") } else { - self.length_min = str_or_num_to_expr(errors, "min", &nv.lit); + self.length_min = str_or_num_to_expr(errors, "min", nv.value); } } - NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("max") => { + Meta::NameValue(nv) if nv.path.is_ident("max") => { if self.length_max.is_some() { duplicate_error(&nv.path) } else if self.length_equal.is_some() { mutual_exclusive_error(&nv.path, "equal") } else { - self.length_max = str_or_num_to_expr(errors, "max", &nv.lit); + self.length_max = str_or_num_to_expr(errors, "max", nv.value); } } - NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("equal") => { + Meta::NameValue(nv) if nv.path.is_ident("equal") => { if self.length_equal.is_some() { duplicate_error(&nv.path) } else if self.length_min.is_some() { @@ -133,7 +148,7 @@ impl ValidationAttrs { mutual_exclusive_error(&nv.path, "max") } else { self.length_equal = - str_or_num_to_expr(errors, "equal", &nv.lit); + str_or_num_to_expr(errors, "equal", nv.value); } } meta => { @@ -148,21 +163,21 @@ impl ValidationAttrs { } } - NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("range") => { - for nested in meta_list.nested.iter() { + Meta::List(meta_list) if meta_list.path.is_ident("range") => { + for nested in parse_nested_meta(meta_list) { match nested { - NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("min") => { + Meta::NameValue(nv) if nv.path.is_ident("min") => { if self.range_min.is_some() { duplicate_error(&nv.path) } else { - self.range_min = str_or_num_to_expr(errors, "min", &nv.lit); + self.range_min = str_or_num_to_expr(errors, "min", nv.value); } } - NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("max") => { + Meta::NameValue(nv) if nv.path.is_ident("max") => { if self.range_max.is_some() { duplicate_error(&nv.path) } else { - self.range_max = str_or_num_to_expr(errors, "max", &nv.lit); + self.range_max = str_or_num_to_expr(errors, "max", nv.value); } } meta => { @@ -177,71 +192,61 @@ impl ValidationAttrs { } } - NestedMeta::Meta(Meta::Path(m)) - if m.is_ident("required") || m.is_ident("required_nested") => - { + Meta::Path(m) if m.is_ident("required") || m.is_ident("required_nested") => { self.required = true; } - NestedMeta::Meta(Meta::Path(p)) if p.is_ident(Format::Email.attr_str()) => { - match self.format { - Some(f) => duplicate_format_error(f, Format::Email, p), - None => self.format = Some(Format::Email), - } - } - NestedMeta::Meta(Meta::Path(p)) if p.is_ident(Format::Uri.attr_str()) => { - match self.format { - Some(f) => duplicate_format_error(f, Format::Uri, p), - None => self.format = Some(Format::Uri), - } - } - NestedMeta::Meta(Meta::Path(p)) if p.is_ident(Format::Phone.attr_str()) => { - match self.format { - Some(f) => duplicate_format_error(f, Format::Phone, p), - None => self.format = Some(Format::Phone), - } - } + Meta::Path(p) if p.is_ident(Format::Email.attr_str()) => match self.format { + Some(f) => duplicate_format_error(f, Format::Email, &p), + None => self.format = Some(Format::Email), + }, + Meta::Path(p) if p.is_ident(Format::Uri.attr_str()) => match self.format { + Some(f) => duplicate_format_error(f, Format::Uri, &p), + None => self.format = Some(Format::Uri), + }, + Meta::Path(p) if p.is_ident(Format::Phone.attr_str()) => match self.format { + Some(f) => duplicate_format_error(f, Format::Phone, &p), + None => self.format = Some(Format::Phone), + }, - NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("regex") => { + Meta::NameValue(nv) if nv.path.is_ident("regex") => { match (&self.regex, &self.contains) { (Some(_), _) => duplicate_error(&nv.path), (None, Some(_)) => mutual_exclusive_error(&nv.path, "contains"), (None, None) => { self.regex = - parse_lit_into_expr_path(errors, attr_type, "regex", &nv.lit).ok() + parse_lit_into_expr_path(errors, attr_type, "regex", &nv.value).ok() } } } - NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("regex") => { + Meta::List(meta_list) if meta_list.path.is_ident("regex") => { match (&self.regex, &self.contains) { (Some(_), _) => duplicate_error(&meta_list.path), (None, Some(_)) => mutual_exclusive_error(&meta_list.path, "contains"), (None, None) => { - for x in meta_list.nested.iter() { + for x in parse_nested_meta(meta_list) { match x { - NestedMeta::Meta(Meta::NameValue(MetaNameValue { - path, - lit, - .. - })) if path.is_ident("path") => { - self.regex = - parse_lit_into_expr_path(errors, attr_type, "path", lit) - .ok() + Meta::NameValue(MetaNameValue { path, value, .. }) + if path.is_ident("path") => + { + self.regex = parse_lit_into_expr_path( + errors, attr_type, "path", &value, + ) + .ok() } - NestedMeta::Meta(Meta::NameValue(MetaNameValue { - path, - lit, - .. - })) if path.is_ident("pattern") => { - self.regex = get_lit_str(errors, attr_type, "pattern", lit) - .ok() - .map(|litstr| { - Expr::Lit(syn::ExprLit { - attrs: Vec::new(), - lit: Lit::Str(litstr.clone()), + Meta::NameValue(MetaNameValue { path, value, .. }) + if path.is_ident("pattern") => + { + self.regex = + expr_as_lit_str(errors, attr_type, "pattern", &value) + .ok() + .map(|litstr| { + Expr::Lit(syn::ExprLit { + attrs: Vec::new(), + lit: Lit::Str(litstr.clone()), + }) }) - }) } meta => { if !ignore_errors { @@ -258,34 +263,30 @@ impl ValidationAttrs { } } - NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) - if path.is_ident("contains") => - { + Meta::NameValue(MetaNameValue { path, value, .. }) if path.is_ident("contains") => { match (&self.contains, &self.regex) { - (Some(_), _) => duplicate_error(path), - (None, Some(_)) => mutual_exclusive_error(path, "regex"), + (Some(_), _) => duplicate_error(&path), + (None, Some(_)) => mutual_exclusive_error(&path, "regex"), (None, None) => { - self.contains = get_lit_str(errors, attr_type, "contains", lit) + self.contains = expr_as_lit_str(errors, attr_type, "contains", &value) .map(|litstr| litstr.value()) .ok() } } } - NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("contains") => { + Meta::List(meta_list) if meta_list.path.is_ident("contains") => { match (&self.contains, &self.regex) { (Some(_), _) => duplicate_error(&meta_list.path), (None, Some(_)) => mutual_exclusive_error(&meta_list.path, "regex"), (None, None) => { - for x in meta_list.nested.iter() { + for x in parse_nested_meta(meta_list) { match x { - NestedMeta::Meta(Meta::NameValue(MetaNameValue { - path, - lit, - .. - })) if path.is_ident("pattern") => { + Meta::NameValue(MetaNameValue { path, value, .. }) + if path.is_ident("pattern") => + { self.contains = - get_lit_str(errors, attr_type, "contains", lit) + expr_as_lit_str(errors, attr_type, "contains", &value) .ok() .map(|litstr| litstr.value()) } @@ -304,20 +305,18 @@ impl ValidationAttrs { } } - NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("inner") => { - match self.inner { - Some(_) => duplicate_error(&meta_list.path), - None => { - let inner_attrs = ValidationAttrs::default().populate( - meta_list.nested.clone().into_iter().collect(), - attr_type, - ignore_errors, - errors, - ); - self.inner = Some(Box::new(inner_attrs)); - } + Meta::List(meta_list) if meta_list.path.is_ident("inner") => match self.inner { + Some(_) => duplicate_error(&meta_list.path), + None => { + let inner_attrs = ValidationAttrs::default().populate( + parse_nested_meta(meta_list).into_iter().collect(), + attr_type, + ignore_errors, + errors, + ); + self.inner = Some(Box::new(inner_attrs)); } - } + }, _ => {} } @@ -446,7 +445,7 @@ fn parse_lit_into_expr_path( cx: &Ctxt, attr_type: &'static str, meta_item_name: &'static str, - lit: &syn::Lit, + lit: &Expr, ) -> Result { parse_lit_into_path(cx, attr_type, meta_item_name, lit).map(|path| { Expr::Path(ExprPath { @@ -510,19 +509,24 @@ fn wrap_string_validation(v: Vec) -> Option { } } -fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, lit: &Lit) -> Option { +fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, expr: Expr) -> Option { + let lit: Lit = match syn::parse2(expr.to_token_stream()) { + Ok(l) => l, + Err(err) => { + cx.syn_error(err); + return None; + } + }; + match lit { - Lit::Str(s) => parse_lit_str::(s).ok().map(Expr::Path), - Lit::Int(_) | Lit::Float(_) => Some(Expr::Lit(ExprLit { - attrs: Vec::new(), - lit: lit.clone(), - })), + Lit::Str(s) => parse_lit_str::(&s).ok().map(Expr::Path), + Lit::Int(_) | Lit::Float(_) => Some(expr), _ => { cx.error_spanned_by( - lit, + &expr, format!( - "expected `{}` to be a string or number literal", - meta_item_name + "expected `{}` to be a string or number literal, not {:?}", + meta_item_name, &expr ), ); None diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index 3ad1031..ea6c0a6 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -20,7 +20,7 @@ use syn::spanned::Spanned; 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) - .unwrap_or_else(compile_error) + .unwrap_or_else(syn::Error::into_compile_error) .into() } @@ -28,14 +28,11 @@ pub fn derive_json_schema_wrapper(input: proc_macro::TokenStream) -> proc_macro: pub fn derive_json_schema_repr_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as syn::DeriveInput); derive_json_schema(input, true) - .unwrap_or_else(compile_error) + .unwrap_or_else(syn::Error::into_compile_error) .into() } -fn derive_json_schema( - mut input: syn::DeriveInput, - repr: bool, -) -> Result> { +fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result { attr::process_serde_attrs(&mut input)?; let mut cont = Container::from_ast(&input)?; @@ -87,7 +84,7 @@ fn derive_json_schema( }); } - let mut schema_base_name = cont.name(); + let mut schema_base_name = cont.name().to_string(); if !cont.attrs.is_renamed { if let Some(path) = cont.serde_attrs.remote() { @@ -165,7 +162,7 @@ fn derive_json_schema( }; let schema_expr = if repr { - schema_exprs::expr_for_repr(&cont).map_err(|e| vec![e])? + schema_exprs::expr_for_repr(&cont)? } else { schema_exprs::expr_for_container(&cont) }; @@ -207,10 +204,3 @@ fn add_trait_bounds(cont: &mut Container) { } } } - -fn compile_error(errors: Vec) -> TokenStream { - let compile_errors = errors.iter().map(syn::Error::to_compile_error); - quote! { - #(#compile_errors)* - } -} diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 9bd8132..7b7a39f 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -151,7 +151,7 @@ fn expr_for_external_tagged_enum<'a>( variants: impl Iterator>, deny_unknown_fields: bool, ) -> TokenStream { - let mut unique_names = HashSet::::new(); + let mut unique_names = HashSet::<&str>::new(); let mut count = 0; let (unit_variants, complex_variants): (Vec<_>, Vec<_>) = variants .inspect(|v| {