Update to syn 2 and serde_derive_internals 0.29

This commit is contained in:
Graham Esau 2024-04-28 18:44:43 +01:00
parent e04e3a3a81
commit 9501fe319f
15 changed files with 206 additions and 235 deletions

View file

@ -20,6 +20,7 @@ enum OrDef<A, B> {
struct Str<'a>(&'a str); struct Str<'a>(&'a str);
#[allow(dead_code)]
#[derive(JsonSchema, Serialize)] #[derive(JsonSchema, Serialize)]
#[serde(remote = "Str")] #[serde(remote = "Str")]
struct StrDef<'a>(&'a str); struct StrDef<'a>(&'a str);

View file

@ -23,6 +23,7 @@ fn struct_normal() -> TestResult {
test_default_generated_schema::<Struct>("schema_with-struct") test_default_generated_schema::<Struct>("schema_with-struct")
} }
#[allow(dead_code)]
#[derive(JsonSchema)] #[derive(JsonSchema)]
pub struct Tuple( pub struct Tuple(
#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema, #[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema,

View file

@ -21,6 +21,7 @@ fn skip_struct_fields() -> TestResult {
test_default_generated_schema::<MyStruct>("skip_struct_fields") test_default_generated_schema::<MyStruct>("skip_struct_fields")
} }
#[allow(dead_code)]
#[derive(JsonSchema)] #[derive(JsonSchema)]
struct TupleStruct( struct TupleStruct(
#[schemars(skip)] i32, #[schemars(skip)] i32,

View file

@ -18,6 +18,7 @@ fn struct_normal() -> TestResult {
test_default_generated_schema::<Struct>("struct-normal") test_default_generated_schema::<Struct>("struct-normal")
} }
#[allow(dead_code)]
#[derive(JsonSchema)] #[derive(JsonSchema)]
pub struct Tuple(i32, bool, Option<&'static str>); pub struct Tuple(i32, bool, Option<&'static str>);
@ -26,6 +27,7 @@ fn struct_tuple() -> TestResult {
test_default_generated_schema::<Tuple>("struct-tuple") test_default_generated_schema::<Tuple>("struct-tuple")
} }
#[allow(dead_code)]
#[derive(JsonSchema)] #[derive(JsonSchema)]
pub struct Newtype(i32); pub struct Newtype(i32);

View file

@ -16,10 +16,12 @@ pub struct TransparentStruct {
inner: (), inner: (),
} }
#[allow(dead_code)]
#[derive(JsonSchema)] #[derive(JsonSchema)]
#[schemars(transparent)] #[schemars(transparent)]
pub struct TransparentNewType(Option<InnerStruct>); pub struct TransparentNewType(Option<InnerStruct>);
#[allow(dead_code)]
#[derive(JsonSchema)] #[derive(JsonSchema)]
pub struct InnerStruct(String, i32); pub struct InnerStruct(String, i32);

View file

@ -104,6 +104,7 @@ fn validate_schemars_attrs() -> TestResult {
test_default_generated_schema::<Struct2>("validate_schemars_attrs") test_default_generated_schema::<Struct2>("validate_schemars_attrs")
} }
#[allow(dead_code)]
#[derive(JsonSchema)] #[derive(JsonSchema)]
pub struct Tuple( pub struct Tuple(
#[validate(range(max = 10))] u8, #[validate(range(max = 10))] u8,
@ -115,6 +116,7 @@ fn validate_tuple() -> TestResult {
test_default_generated_schema::<Tuple>("validate_tuple") test_default_generated_schema::<Tuple>("validate_tuple")
} }
#[allow(dead_code)]
#[derive(JsonSchema)] #[derive(JsonSchema)]
pub struct NewType(#[validate(range(max = 10))] u8); pub struct NewType(#[validate(range(max = 10))] u8);

View file

@ -17,8 +17,8 @@ proc-macro = true
[dependencies] [dependencies]
proc-macro2 = "1.0" proc-macro2 = "1.0"
quote = "1.0" quote = "1.0"
syn = { version = "1.0", features = ["extra-traits"] } syn = { version = "2.0", features = ["extra-traits"] }
serde_derive_internals = "0.26.0" serde_derive_internals = "0.29"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.2.1" pretty_assertions = "1.2.1"

View file

@ -38,7 +38,7 @@ pub struct Field<'a> {
} }
impl<'a> Container<'a> { impl<'a> Container<'a> {
pub fn from_ast(item: &'a syn::DeriveInput) -> Result<Container<'a>, Vec<syn::Error>> { pub fn from_ast(item: &'a syn::DeriveInput) -> syn::Result<Container<'a>> {
let ctxt = Ctxt::new(); let ctxt = Ctxt::new();
let result = serde_ast::Container::from_ast(&ctxt, item, Derive::Deserialize) let result = serde_ast::Container::from_ast(&ctxt, item, Derive::Deserialize)
.ok_or(()) .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")) .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() self.serde_attrs.name().deserialize_name()
} }
@ -64,7 +64,7 @@ impl<'a> Container<'a> {
} }
impl<'a> Variant<'a> { impl<'a> Variant<'a> {
pub fn name(&self) -> String { pub fn name(&self) -> &str {
self.serde_attrs.name().deserialize_name() self.serde_attrs.name().deserialize_name()
} }
@ -74,7 +74,7 @@ impl<'a> Variant<'a> {
} }
impl<'a> Field<'a> { impl<'a> Field<'a> {
pub fn name(&self) -> String { pub fn name(&self) -> &str {
self.serde_attrs.name().deserialize_name() self.serde_attrs.name().deserialize_name()
} }
} }

View file

@ -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<String>, Option<String>) { pub fn get_title_and_desc_from_doc(attrs: &[Attribute]) -> (Option<String>, Option<String>) {
let doc = match get_doc(attrs) { let doc = match get_doc(attrs) {
@ -35,13 +35,17 @@ fn get_doc(attrs: &[Attribute]) -> Option<String> {
let attrs = attrs let attrs = attrs
.iter() .iter()
.filter_map(|attr| { .filter_map(|attr| {
if !attr.path.is_ident("doc") { if !attr.path().is_ident("doc") {
return None; return None;
} }
let meta = attr.parse_meta().ok()?; let meta = attr.meta.require_name_value().ok()?;
if let NameValue(MetaNameValue { lit: Str(s), .. }) = meta { if let syn::Expr::Lit(syn::ExprLit {
return Some(s.value()); lit: syn::Lit::Str(lit_str),
..
}) = &meta.value
{
return Some(lit_str.value());
} }
None None

View file

@ -10,9 +10,7 @@ use proc_macro2::{Group, Span, TokenStream, TokenTree};
use quote::ToTokens; use quote::ToTokens;
use serde_derive_internals::Ctxt; use serde_derive_internals::Ctxt;
use syn::parse::{self, Parse}; use syn::parse::{self, Parse};
use syn::Meta::{List, NameValue}; use syn::{Meta, MetaNameValue};
use syn::MetaNameValue;
use syn::NestedMeta::{Lit, Meta};
// FIXME using the same struct for containers+variants+fields means that // FIXME using the same struct for containers+variants+fields means that
// with/schema_with are accepted (but ignored) on containers, and // with/schema_with are accepted (but ignored) on containers, and
@ -42,10 +40,10 @@ impl Attrs {
.populate(attrs, "schemars", false, errors) .populate(attrs, "schemars", false, errors)
.populate(attrs, "serde", true, 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 result.repr = attrs
.iter() .iter()
.find(|a| a.path.is_ident("repr")) .find(|a| a.path().is_ident("repr"))
.and_then(|a| a.parse_args().ok()); .and_then(|a| a.parse_args().ok());
let (doc_title, doc_description) = doc::get_title_and_desc_from_doc(attrs); 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) { for meta_item in get_meta_items(attrs, attr_type, errors, ignore_errors) {
match &meta_item { match &meta_item {
Meta(NameValue(m)) if m.path.is_ident("with") => { Meta::NameValue(m) if m.path.is_ident("with") => {
if let Ok(ty) = parse_lit_into_ty(errors, attr_type, "with", &m.lit) { if let Ok(ty) = parse_lit_into_ty(errors, attr_type, "with", &m.value) {
match self.with { match self.with {
Some(WithAttr::Type(_)) => duplicate_error(m), Some(WithAttr::Type(_)) => duplicate_error(m),
Some(WithAttr::Function(_)) => mutual_exclusive_error(m, "schema_with"), 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") => { 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) { if let Ok(fun) = parse_lit_into_path(errors, attr_type, "schema_with", &m.value)
{
match self.with { match self.with {
Some(WithAttr::Function(_)) => duplicate_error(m), Some(WithAttr::Function(_)) => duplicate_error(m),
Some(WithAttr::Type(_)) => mutual_exclusive_error(m, "with"), Some(WithAttr::Type(_)) => mutual_exclusive_error(m, "with"),
@ -125,8 +124,8 @@ impl Attrs {
} }
} }
Meta(NameValue(m)) if m.path.is_ident("title") => { Meta::NameValue(m) if m.path.is_ident("title") => {
if let Ok(title) = get_lit_str(errors, attr_type, "title", &m.lit) { if let Ok(title) = expr_as_lit_str(errors, attr_type, "title", &m.value) {
match self.title { match self.title {
Some(_) => duplicate_error(m), Some(_) => duplicate_error(m),
None => self.title = Some(title.value()), None => self.title = Some(title.value()),
@ -134,8 +133,10 @@ impl Attrs {
} }
} }
Meta(NameValue(m)) if m.path.is_ident("description") => { Meta::NameValue(m) if m.path.is_ident("description") => {
if let Ok(description) = get_lit_str(errors, attr_type, "description", &m.lit) { if let Ok(description) =
expr_as_lit_str(errors, attr_type, "description", &m.value)
{
match self.description { match self.description {
Some(_) => duplicate_error(m), Some(_) => duplicate_error(m),
None => self.description = Some(description.value()), None => self.description = Some(description.value()),
@ -143,16 +144,16 @@ impl Attrs {
} }
} }
Meta(NameValue(m)) if m.path.is_ident("example") => { Meta::NameValue(m) if m.path.is_ident("example") => {
if let Ok(fun) = parse_lit_into_path(errors, attr_type, "example", &m.lit) { if let Ok(fun) = parse_lit_into_path(errors, attr_type, "example", &m.value) {
self.examples.push(fun) 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" => { 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) { if let Ok(p) = parse_lit_into_path(errors, attr_type, "crate", &m.value) {
if self.crate_name.is_some() { if self.crate_name.is_some() {
duplicate_error(m) duplicate_error(m)
} else { } else {
@ -163,14 +164,14 @@ impl Attrs {
_ if ignore_errors => {} _ 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. // This will be processed with the validation attributes.
// It's allowed only for the schemars attribute because the // It's allowed only for the schemars attribute because the
// validator crate doesn't support it yet. // 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 let path = meta_item
.path() .path()
.into_token_stream() .into_token_stream()
@ -182,10 +183,6 @@ impl Attrs {
); );
} }
} }
Lit(lit) => {
errors.error_spanned_by(lit, "unexpected literal in schemars attribute");
}
} }
} }
self self
@ -220,34 +217,35 @@ fn get_meta_items(
attr_type: &'static str, attr_type: &'static str,
errors: &Ctxt, errors: &Ctxt,
ignore_errors: bool, ignore_errors: bool,
) -> Vec<syn::NestedMeta> { ) -> Vec<Meta> {
attrs.iter().fold(vec![], |mut acc, attr| { let mut result = vec![];
if !attr.path.is_ident(attr_type) { for attr in attrs.iter().filter(|a| a.path().is_ident(attr_type)) {
return acc; match attr.parse_args_with(syn::punctuated::Punctuated::<Meta, Token![,]>::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
})
} }
fn get_lit_str<'a>( result
}
fn expr_as_lit_str<'a>(
cx: &Ctxt, cx: &Ctxt,
attr_type: &'static str, attr_type: &'static str,
meta_item_name: &'static str, meta_item_name: &'static str,
lit: &'a syn::Lit, expr: &'a syn::Expr,
) -> Result<&'a syn::LitStr, ()> { ) -> Result<&'a syn::LitStr, ()> {
if let syn::Lit::Str(lit) = lit { if let syn::Expr::Lit(syn::ExprLit {
Ok(lit) lit: syn::Lit::Str(lit_str),
..
}) = expr
{
Ok(lit_str)
} else { } else {
cx.error_spanned_by( cx.error_spanned_by(
lit, expr,
format!( format!(
"expected {} {} attribute to be a string: `{} = \"...\"`", "expected {} {} attribute to be a string: `{} = \"...\"`",
attr_type, meta_item_name, meta_item_name attr_type, meta_item_name, meta_item_name
@ -261,9 +259,9 @@ fn parse_lit_into_ty(
cx: &Ctxt, cx: &Ctxt,
attr_type: &'static str, attr_type: &'static str,
meta_item_name: &'static str, meta_item_name: &'static str,
lit: &syn::Lit, lit: &syn::Expr,
) -> Result<syn::Type, ()> { ) -> Result<syn::Type, ()> {
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(|_| { parse_lit_str(string).map_err(|_| {
cx.error_spanned_by( cx.error_spanned_by(
@ -281,17 +279,17 @@ fn parse_lit_into_path(
cx: &Ctxt, cx: &Ctxt,
attr_type: &'static str, attr_type: &'static str,
meta_item_name: &'static str, meta_item_name: &'static str,
lit: &syn::Lit, expr: &syn::Expr,
) -> Result<syn::Path, ()> { ) -> Result<syn::Path, ()> {
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( cx.error_spanned_by(
lit, expr,
format!( format!(
"failed to parse path: `{} = {:?}`", "failed to parse path: `{} = {:?}`",
meta_item_name, meta_item_name,
string.value() lit_str.value()
), ),
) )
}) })

View file

@ -2,7 +2,9 @@ use quote::ToTokens;
use serde_derive_internals::Ctxt; use serde_derive_internals::Ctxt;
use std::collections::HashSet; use std::collections::HashSet;
use syn::parse::Parser; 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. // 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] = &[ 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 // 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. // as #[serde] attributes so that serde_derive_internals will parse them for us.
pub fn process_serde_attrs(input: &mut syn::DeriveInput) -> Result<(), Vec<syn::Error>> { pub fn process_serde_attrs(input: &mut syn::DeriveInput) -> syn::Result<()> {
let ctxt = Ctxt::new(); let ctxt = Ctxt::new();
process_attrs(&ctxt, &mut input.attrs); process_attrs(&ctxt, &mut input.attrs);
match input.data { match input.data {
@ -60,25 +62,19 @@ fn process_serde_field_attrs<'a>(ctxt: &Ctxt, fields: impl Iterator<Item = &'a m
fn process_attrs(ctxt: &Ctxt, attrs: &mut Vec<Attribute>) { fn process_attrs(ctxt: &Ctxt, attrs: &mut Vec<Attribute>) {
// Remove #[serde(...)] attributes (some may be re-added later) // Remove #[serde(...)] attributes (some may be re-added later)
let (serde_attrs, other_attrs): (Vec<_>, Vec<_>) = 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; *attrs = other_attrs;
let schemars_attrs: Vec<_> = attrs
.iter()
.filter(|at| at.path.is_ident("schemars"))
.collect();
// Copy appropriate #[schemars(...)] attributes to #[serde(...)] attributes // Copy appropriate #[schemars(...)] attributes to #[serde(...)] attributes
let (mut serde_meta, mut schemars_meta_names): (Vec<_>, HashSet<_>) = schemars_attrs let (mut serde_meta, mut schemars_meta_names): (Vec<_>, HashSet<_>) =
.iter() get_meta_items(attrs, "schemars", ctxt, false)
.flat_map(|at| get_meta_items(ctxt, at)) .into_iter()
.flatten()
.filter_map(|meta| { .filter_map(|meta| {
let keyword = get_meta_ident(ctxt, &meta).ok()?; let keyword = get_meta_ident(&meta)?;
if keyword.ends_with("with") || !SERDE_KEYWORDS.contains(&keyword.as_ref()) { if SERDE_KEYWORDS.contains(&keyword.as_ref()) && !keyword.ends_with("with") {
None
} else {
Some((meta, keyword)) Some((meta, keyword))
} else {
None
} }
}) })
.unzip(); .unzip();
@ -89,12 +85,8 @@ fn process_attrs(ctxt: &Ctxt, attrs: &mut Vec<Attribute>) {
} }
// Re-add #[serde(...)] attributes that weren't overridden by #[schemars(...)] attributes // Re-add #[serde(...)] attributes that weren't overridden by #[schemars(...)] attributes
for meta in serde_attrs for meta in get_meta_items(&serde_attrs, "serde", ctxt, false) {
.into_iter() if let Some(i) = get_meta_ident(&meta) {
.flat_map(|at| get_meta_items(ctxt, &at))
.flatten()
{
if let Ok(i) = get_meta_ident(ctxt, &meta) {
if !schemars_meta_names.contains(&i) if !schemars_meta_names.contains(&i)
&& SERDE_KEYWORDS.contains(&i.as_ref()) && SERDE_KEYWORDS.contains(&i.as_ref())
&& i != "bound" && i != "bound"
@ -125,34 +117,8 @@ fn to_tokens(attrs: &[Attribute]) -> impl ToTokens {
tokens tokens
} }
fn get_meta_items(ctxt: &Ctxt, attr: &Attribute) -> Result<Vec<NestedMeta>, ()> { fn get_meta_ident(meta: &Meta) -> Option<String> {
match attr.parse_meta() { meta.path().get_ident().map(|i| i.to_string())
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<String, ()> {
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(())
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -198,7 +164,7 @@ mod tests {
}; };
if let Err(e) = process_serde_attrs(&mut input) { 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); assert_eq!(input, expected);

View file

@ -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 proc_macro2::TokenStream;
use quote::ToTokens;
use serde_derive_internals::Ctxt; 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] = &[ pub(crate) static VALIDATION_KEYWORDS: &[&str] = &[
"range", "regex", "contains", "email", "phone", "url", "length", "required", "range", "regex", "contains", "email", "phone", "url", "length", "required",
@ -62,7 +65,7 @@ impl ValidationAttrs {
fn populate( fn populate(
mut self, mut self,
meta_items: Vec<syn::NestedMeta>, meta_items: Vec<Meta>,
attr_type: &'static str, attr_type: &'static str,
ignore_errors: bool, ignore_errors: bool,
errors: &Ctxt, errors: &Ctxt,
@ -100,31 +103,43 @@ impl ValidationAttrs {
errors.error_spanned_by(path, msg) errors.error_spanned_by(path, msg)
} }
}; };
let parse_nested_meta = |meta_list: MetaList| {
let parser = Punctuated::<syn::Meta, Token![,]>::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 { for meta_item in meta_items {
match &meta_item { match meta_item {
NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("length") => { Meta::List(meta_list) if meta_list.path.is_ident("length") => {
for nested in meta_list.nested.iter() { for nested in parse_nested_meta(meta_list) {
match nested { 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() { if self.length_min.is_some() {
duplicate_error(&nv.path) duplicate_error(&nv.path)
} else if self.length_equal.is_some() { } else if self.length_equal.is_some() {
mutual_exclusive_error(&nv.path, "equal") mutual_exclusive_error(&nv.path, "equal")
} else { } 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() { if self.length_max.is_some() {
duplicate_error(&nv.path) duplicate_error(&nv.path)
} else if self.length_equal.is_some() { } else if self.length_equal.is_some() {
mutual_exclusive_error(&nv.path, "equal") mutual_exclusive_error(&nv.path, "equal")
} else { } 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() { if self.length_equal.is_some() {
duplicate_error(&nv.path) duplicate_error(&nv.path)
} else if self.length_min.is_some() { } else if self.length_min.is_some() {
@ -133,7 +148,7 @@ impl ValidationAttrs {
mutual_exclusive_error(&nv.path, "max") mutual_exclusive_error(&nv.path, "max")
} else { } else {
self.length_equal = self.length_equal =
str_or_num_to_expr(errors, "equal", &nv.lit); str_or_num_to_expr(errors, "equal", nv.value);
} }
} }
meta => { meta => {
@ -148,21 +163,21 @@ impl ValidationAttrs {
} }
} }
NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("range") => { Meta::List(meta_list) if meta_list.path.is_ident("range") => {
for nested in meta_list.nested.iter() { for nested in parse_nested_meta(meta_list) {
match nested { 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() { if self.range_min.is_some() {
duplicate_error(&nv.path) duplicate_error(&nv.path)
} else { } 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() { if self.range_max.is_some() {
duplicate_error(&nv.path) duplicate_error(&nv.path)
} else { } 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 => { meta => {
@ -177,64 +192,54 @@ impl ValidationAttrs {
} }
} }
NestedMeta::Meta(Meta::Path(m)) Meta::Path(m) if m.is_ident("required") || m.is_ident("required_nested") => {
if m.is_ident("required") || m.is_ident("required_nested") =>
{
self.required = true; self.required = true;
} }
NestedMeta::Meta(Meta::Path(p)) if p.is_ident(Format::Email.attr_str()) => { Meta::Path(p) if p.is_ident(Format::Email.attr_str()) => match self.format {
match self.format { Some(f) => duplicate_format_error(f, Format::Email, &p),
Some(f) => duplicate_format_error(f, Format::Email, p),
None => self.format = Some(Format::Email), None => self.format = Some(Format::Email),
} },
} Meta::Path(p) if p.is_ident(Format::Uri.attr_str()) => match self.format {
NestedMeta::Meta(Meta::Path(p)) if p.is_ident(Format::Uri.attr_str()) => { Some(f) => duplicate_format_error(f, Format::Uri, &p),
match self.format {
Some(f) => duplicate_format_error(f, Format::Uri, p),
None => self.format = Some(Format::Uri), None => self.format = Some(Format::Uri),
} },
} Meta::Path(p) if p.is_ident(Format::Phone.attr_str()) => match self.format {
NestedMeta::Meta(Meta::Path(p)) if p.is_ident(Format::Phone.attr_str()) => { Some(f) => duplicate_format_error(f, Format::Phone, &p),
match self.format {
Some(f) => duplicate_format_error(f, Format::Phone, p),
None => self.format = Some(Format::Phone), 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) { match (&self.regex, &self.contains) {
(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_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) { match (&self.regex, &self.contains) {
(Some(_), _) => duplicate_error(&meta_list.path), (Some(_), _) => duplicate_error(&meta_list.path),
(None, Some(_)) => mutual_exclusive_error(&meta_list.path, "contains"), (None, Some(_)) => mutual_exclusive_error(&meta_list.path, "contains"),
(None, None) => { (None, None) => {
for x in meta_list.nested.iter() { for x in parse_nested_meta(meta_list) {
match x { match x {
NestedMeta::Meta(Meta::NameValue(MetaNameValue { Meta::NameValue(MetaNameValue { path, value, .. })
path, if path.is_ident("path") =>
lit, {
.. self.regex = parse_lit_into_expr_path(
})) if path.is_ident("path") => { errors, attr_type, "path", &value,
self.regex = )
parse_lit_into_expr_path(errors, attr_type, "path", lit)
.ok() .ok()
} }
NestedMeta::Meta(Meta::NameValue(MetaNameValue { Meta::NameValue(MetaNameValue { path, value, .. })
path, if path.is_ident("pattern") =>
lit, {
.. self.regex =
})) if path.is_ident("pattern") => { expr_as_lit_str(errors, attr_type, "pattern", &value)
self.regex = get_lit_str(errors, attr_type, "pattern", lit)
.ok() .ok()
.map(|litstr| { .map(|litstr| {
Expr::Lit(syn::ExprLit { Expr::Lit(syn::ExprLit {
@ -258,34 +263,30 @@ impl ValidationAttrs {
} }
} }
NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) Meta::NameValue(MetaNameValue { path, value, .. }) if path.is_ident("contains") => {
if path.is_ident("contains") =>
{
match (&self.contains, &self.regex) { match (&self.contains, &self.regex) {
(Some(_), _) => duplicate_error(path), (Some(_), _) => duplicate_error(&path),
(None, Some(_)) => mutual_exclusive_error(path, "regex"), (None, Some(_)) => mutual_exclusive_error(&path, "regex"),
(None, None) => { (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()) .map(|litstr| litstr.value())
.ok() .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) { match (&self.contains, &self.regex) {
(Some(_), _) => duplicate_error(&meta_list.path), (Some(_), _) => duplicate_error(&meta_list.path),
(None, Some(_)) => mutual_exclusive_error(&meta_list.path, "regex"), (None, Some(_)) => mutual_exclusive_error(&meta_list.path, "regex"),
(None, None) => { (None, None) => {
for x in meta_list.nested.iter() { for x in parse_nested_meta(meta_list) {
match x { match x {
NestedMeta::Meta(Meta::NameValue(MetaNameValue { Meta::NameValue(MetaNameValue { path, value, .. })
path, if path.is_ident("pattern") =>
lit, {
..
})) if path.is_ident("pattern") => {
self.contains = self.contains =
get_lit_str(errors, attr_type, "contains", lit) expr_as_lit_str(errors, attr_type, "contains", &value)
.ok() .ok()
.map(|litstr| litstr.value()) .map(|litstr| litstr.value())
} }
@ -304,20 +305,18 @@ impl ValidationAttrs {
} }
} }
NestedMeta::Meta(Meta::List(meta_list)) if meta_list.path.is_ident("inner") => { Meta::List(meta_list) if meta_list.path.is_ident("inner") => match self.inner {
match self.inner {
Some(_) => duplicate_error(&meta_list.path), Some(_) => duplicate_error(&meta_list.path),
None => { None => {
let inner_attrs = ValidationAttrs::default().populate( let inner_attrs = ValidationAttrs::default().populate(
meta_list.nested.clone().into_iter().collect(), parse_nested_meta(meta_list).into_iter().collect(),
attr_type, attr_type,
ignore_errors, ignore_errors,
errors, errors,
); );
self.inner = Some(Box::new(inner_attrs)); self.inner = Some(Box::new(inner_attrs));
} }
} },
}
_ => {} _ => {}
} }
@ -446,7 +445,7 @@ fn parse_lit_into_expr_path(
cx: &Ctxt, cx: &Ctxt,
attr_type: &'static str, attr_type: &'static str,
meta_item_name: &'static str, meta_item_name: &'static str,
lit: &syn::Lit, lit: &Expr,
) -> Result<Expr, ()> { ) -> Result<Expr, ()> {
parse_lit_into_path(cx, attr_type, meta_item_name, lit).map(|path| { parse_lit_into_path(cx, attr_type, meta_item_name, lit).map(|path| {
Expr::Path(ExprPath { Expr::Path(ExprPath {
@ -510,19 +509,24 @@ fn wrap_string_validation(v: Vec<TokenStream>) -> Option<TokenStream> {
} }
} }
fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, lit: &Lit) -> Option<Expr> { fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, expr: Expr) -> Option<Expr> {
let lit: Lit = match syn::parse2(expr.to_token_stream()) {
Ok(l) => l,
Err(err) => {
cx.syn_error(err);
return None;
}
};
match lit { match lit {
Lit::Str(s) => parse_lit_str::<ExprPath>(s).ok().map(Expr::Path), Lit::Str(s) => parse_lit_str::<ExprPath>(&s).ok().map(Expr::Path),
Lit::Int(_) | Lit::Float(_) => Some(Expr::Lit(ExprLit { Lit::Int(_) | Lit::Float(_) => Some(expr),
attrs: Vec::new(),
lit: lit.clone(),
})),
_ => { _ => {
cx.error_spanned_by( cx.error_spanned_by(
lit, &expr,
format!( format!(
"expected `{}` to be a string or number literal", "expected `{}` to be a string or number literal, not {:?}",
meta_item_name meta_item_name, &expr
), ),
); );
None None

View file

@ -20,7 +20,7 @@ use syn::spanned::Spanned;
pub fn derive_json_schema_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn derive_json_schema_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput); let input = parse_macro_input!(input as syn::DeriveInput);
derive_json_schema(input, false) derive_json_schema(input, false)
.unwrap_or_else(compile_error) .unwrap_or_else(syn::Error::into_compile_error)
.into() .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 { pub fn derive_json_schema_repr_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput); let input = parse_macro_input!(input as syn::DeriveInput);
derive_json_schema(input, true) derive_json_schema(input, true)
.unwrap_or_else(compile_error) .unwrap_or_else(syn::Error::into_compile_error)
.into() .into()
} }
fn derive_json_schema( fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result<TokenStream> {
mut input: syn::DeriveInput,
repr: bool,
) -> Result<TokenStream, Vec<syn::Error>> {
attr::process_serde_attrs(&mut input)?; attr::process_serde_attrs(&mut input)?;
let mut cont = Container::from_ast(&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 !cont.attrs.is_renamed {
if let Some(path) = cont.serde_attrs.remote() { if let Some(path) = cont.serde_attrs.remote() {
@ -165,7 +162,7 @@ fn derive_json_schema(
}; };
let schema_expr = if repr { let schema_expr = if repr {
schema_exprs::expr_for_repr(&cont).map_err(|e| vec![e])? schema_exprs::expr_for_repr(&cont)?
} else { } else {
schema_exprs::expr_for_container(&cont) schema_exprs::expr_for_container(&cont)
}; };
@ -207,10 +204,3 @@ fn add_trait_bounds(cont: &mut Container) {
} }
} }
} }
fn compile_error(errors: Vec<syn::Error>) -> TokenStream {
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
quote! {
#(#compile_errors)*
}
}

View file

@ -151,7 +151,7 @@ fn expr_for_external_tagged_enum<'a>(
variants: impl Iterator<Item = &'a Variant<'a>>, variants: impl Iterator<Item = &'a Variant<'a>>,
deny_unknown_fields: bool, deny_unknown_fields: bool,
) -> TokenStream { ) -> TokenStream {
let mut unique_names = HashSet::<String>::new(); let mut unique_names = HashSet::<&str>::new();
let mut count = 0; let mut count = 0;
let (unit_variants, complex_variants): (Vec<_>, Vec<_>) = variants let (unit_variants, complex_variants): (Vec<_>, Vec<_>) = variants
.inspect(|v| { .inspect(|v| {