Skip to content

Commit 780c728

Browse files
committed
Refactor attribute parsing to make it more extensible
1 parent b1ded88 commit 780c728

File tree

4 files changed

+172
-46
lines changed

4 files changed

+172
-46
lines changed

schemars_derive/src/ast/from_serde.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::*;
2-
use crate::attr::get_with_from_attrs;
2+
use crate::attr::Attrs;
33
use serde_derive_internals::ast as serde_ast;
44
use serde_derive_internals::Ctxt;
55

@@ -56,7 +56,7 @@ impl<'a> FromSerde for Variant<'a> {
5656
style: serde.style,
5757
fields: Field::vec_from_serde(errors, serde.fields)?,
5858
original: serde.original,
59-
with: get_with_from_attrs(&serde.original.attrs, errors)?,
59+
attrs: Attrs::new(&serde.original.attrs, errors),
6060
})
6161
}
6262
}
@@ -70,7 +70,7 @@ impl<'a> FromSerde for Field<'a> {
7070
serde_attrs: serde.attrs,
7171
ty: serde.ty,
7272
original: serde.original,
73-
with: get_with_from_attrs(&serde.original.attrs, errors)?,
73+
attrs: Attrs::new(&serde.original.attrs, errors),
7474
})
7575
}
7676
}

schemars_derive/src/ast/mod.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod from_serde;
22

3+
use crate::attr::{Attrs, WithAttr};
34
use from_serde::FromSerde;
45
use serde_derive_internals::ast as serde_ast;
56
use serde_derive_internals::{Ctxt, Derive};
@@ -23,15 +24,15 @@ pub struct Variant<'a> {
2324
pub style: serde_ast::Style,
2425
pub fields: Vec<Field<'a>>,
2526
pub original: &'a syn::Variant,
26-
pub with: Option<syn::Type>,
27+
pub attrs: Attrs,
2728
}
2829

2930
pub struct Field<'a> {
3031
pub member: syn::Member,
3132
pub serde_attrs: serde_derive_internals::attr::Field,
3233
pub ty: &'a syn::Type,
3334
pub original: &'a syn::Field,
34-
pub with: Option<syn::Type>,
35+
pub attrs: Attrs,
3536
}
3637

3738
impl<'a> Container<'a> {
@@ -69,6 +70,10 @@ impl<'a> Field<'a> {
6970
}
7071

7172
pub fn type_for_schema(&self) -> &syn::Type {
72-
self.with.as_ref().unwrap_or(self.ty)
73+
match &self.attrs.with {
74+
None => self.ty,
75+
Some(WithAttr::Type(ty)) => ty,
76+
Some(WithAttr::_Function(_)) => todo!(),
77+
}
7378
}
7479
}

schemars_derive/src/attr/mod.rs

+156-35
Original file line numberDiff line numberDiff line change
@@ -7,48 +7,169 @@ pub use schemars_to_serde::process_serde_attrs;
77
use proc_macro2::{Group, Span, TokenStream, TokenTree};
88
use serde_derive_internals::Ctxt;
99
use syn::parse::{self, Parse};
10+
use syn::Meta::{List, NameValue};
11+
use syn::MetaNameValue;
12+
use syn::NestedMeta::{Lit, Meta};
1013

11-
pub fn get_with_from_attrs(
12-
attrs: &[syn::Attribute],
13-
errors: &Ctxt,
14-
) -> Result<Option<syn::Type>, ()> {
15-
attrs
16-
.iter()
17-
.filter(|at| match at.path.get_ident() {
18-
// FIXME this is relying on order of attributes (schemars before serde) from schemars_to_serde.rs
19-
Some(i) => i == "schemars" || i == "serde",
20-
None => false,
21-
})
22-
.filter_map(get_with_from_attr)
23-
.next()
24-
.map_or(Ok(None), |lit| match parse_lit_str::<syn::Type>(&lit) {
25-
Ok(t) => Ok(Some(t)),
26-
Err(e) => {
27-
errors.error_spanned_by(lit, e);
28-
Err(())
29-
}
30-
})
14+
#[derive(Debug, Default)]
15+
pub struct Attrs {
16+
pub with: Option<WithAttr>,
17+
pub title: Option<String>,
18+
pub description: Option<String>,
19+
// TODO pub example: Option<syn::Path>,
20+
}
21+
22+
#[derive(Debug)]
23+
pub enum WithAttr {
24+
Type(syn::Type),
25+
_Function(syn::Path),
3126
}
3227

33-
fn get_with_from_attr(attr: &syn::Attribute) -> Option<syn::LitStr> {
34-
use syn::*;
35-
let nested_metas = match attr.parse_meta() {
36-
Ok(Meta::List(meta)) => meta.nested,
37-
_ => return None,
38-
};
39-
for nm in nested_metas {
40-
if let NestedMeta::Meta(Meta::NameValue(MetaNameValue {
41-
path,
42-
lit: Lit::Str(with),
43-
..
44-
})) = nm
28+
impl Attrs {
29+
pub fn new(attrs: &[syn::Attribute], errors: &Ctxt) -> Self {
30+
let (title, description) = doc::get_title_and_desc_from_doc(attrs);
31+
Attrs {
32+
title,
33+
description,
34+
..Attrs::default()
35+
}
36+
.populate(attrs, "schemars", false, errors)
37+
.populate(attrs, "serde", true, errors)
38+
}
39+
40+
fn populate(
41+
mut self,
42+
attrs: &[syn::Attribute],
43+
attr_type: &'static str,
44+
ignore_errors: bool,
45+
errors: &Ctxt,
46+
) -> Self {
47+
let duplicate_error = |meta: &MetaNameValue| {
48+
if !ignore_errors {
49+
let msg = format!(
50+
"duplicate schemars attribute `{}`",
51+
meta.path.get_ident().unwrap()
52+
);
53+
errors.error_spanned_by(meta, msg)
54+
}
55+
};
56+
let mutual_exclusive_error = |meta: &MetaNameValue, other: &str| {
57+
if !ignore_errors {
58+
let msg = format!(
59+
"schemars attribute cannot contain both `{}` and `{}`",
60+
meta.path.get_ident().unwrap(),
61+
other,
62+
);
63+
errors.error_spanned_by(meta, msg)
64+
}
65+
};
66+
67+
for meta_item in attrs
68+
.iter()
69+
.flat_map(|attr| get_meta_items(attr, attr_type, errors))
70+
.flatten()
4571
{
46-
if path.is_ident("with") {
47-
return Some(with);
72+
match &meta_item {
73+
Meta(NameValue(m)) if m.path.is_ident("with") => {
74+
if let Ok(ty) = parse_lit_into_ty(errors, attr_type, "with", &m.lit) {
75+
match self.with {
76+
Some(WithAttr::Type(_)) => duplicate_error(m),
77+
Some(WithAttr::_Function(_)) => {
78+
mutual_exclusive_error(m, "schema_with")
79+
}
80+
None => self.with = Some(WithAttr::Type(ty)),
81+
}
82+
}
83+
}
84+
85+
Meta(_meta_item) => {
86+
// TODO uncomment this for 0.8.0 (breaking change)
87+
// https://github.com/GREsau/schemars/issues/18
88+
// if !ignore_errors {
89+
// let path = meta_item
90+
// .path()
91+
// .into_token_stream()
92+
// .to_string()
93+
// .replace(' ', "");
94+
// errors.error_spanned_by(
95+
// meta_item.path(),
96+
// format!("unknown schemars container attribute `{}`", path),
97+
// );
98+
// }
99+
}
100+
101+
Lit(_lit) => {
102+
// TODO uncomment this for 0.8.0 (breaking change)
103+
// https://github.com/GREsau/schemars/issues/18
104+
// if !ignore_errors {
105+
// errors.error_spanned_by(
106+
// lit,
107+
// "unexpected literal in schemars container attribute",
108+
// );
109+
// }
110+
}
48111
}
49112
}
113+
self
114+
}
115+
}
116+
117+
fn get_meta_items(
118+
attr: &syn::Attribute,
119+
attr_type: &'static str,
120+
errors: &Ctxt,
121+
) -> Result<Vec<syn::NestedMeta>, ()> {
122+
if !attr.path.is_ident(attr_type) {
123+
return Ok(Vec::new());
124+
}
125+
126+
match attr.parse_meta() {
127+
Ok(List(meta)) => Ok(meta.nested.into_iter().collect()),
128+
Ok(other) => {
129+
errors.error_spanned_by(other, format!("expected #[{}(...)]", attr_type));
130+
Err(())
131+
}
132+
Err(err) => {
133+
errors.error_spanned_by(attr, err);
134+
Err(())
135+
}
50136
}
51-
None
137+
}
138+
139+
fn get_lit_str<'a>(
140+
cx: &Ctxt,
141+
attr_type: &'static str,
142+
meta_item_name: &'static str,
143+
lit: &'a syn::Lit,
144+
) -> Result<&'a syn::LitStr, ()> {
145+
if let syn::Lit::Str(lit) = lit {
146+
Ok(lit)
147+
} else {
148+
cx.error_spanned_by(
149+
lit,
150+
format!(
151+
"expected {} attribute to be a string: `{} = \"...\"`",
152+
attr_type, meta_item_name
153+
),
154+
);
155+
Err(())
156+
}
157+
}
158+
159+
fn parse_lit_into_ty(
160+
cx: &Ctxt,
161+
attr_type: &'static str,
162+
meta_item_name: &'static str,
163+
lit: &syn::Lit,
164+
) -> Result<syn::Type, ()> {
165+
let string = get_lit_str(cx, attr_type, meta_item_name, lit)?;
166+
167+
parse_lit_str(string).map_err(|_| {
168+
cx.error_spanned_by(
169+
lit,
170+
format!("failed to parse type: {} = {:?}", attr_type, string.value()),
171+
)
172+
})
52173
}
53174

54175
fn parse_lit_str<T>(s: &syn::LitStr) -> parse::Result<T>

schemars_derive/src/schema_exprs.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{ast::*, metadata::SchemaMetadata};
1+
use crate::{ast::*, attr::WithAttr, metadata::SchemaMetadata};
22
use proc_macro2::TokenStream;
33
use serde_derive_internals::ast::Style;
44
use serde_derive_internals::attr::{self as serde_attr, Default as SerdeDefault, TagType};
@@ -33,7 +33,7 @@ fn expr_for_external_tagged_enum<'a>(
3333
variants: impl Iterator<Item = &'a Variant<'a>>,
3434
) -> TokenStream {
3535
let (unit_variants, complex_variants): (Vec<_>, Vec<_>) =
36-
variants.partition(|v| v.is_unit() && v.with.is_none());
36+
variants.partition(|v| v.is_unit() && v.attrs.with.is_none());
3737

3838
let unit_names = unit_variants.iter().map(|v| v.name());
3939
let unit_schema = schema_object(quote! {
@@ -147,7 +147,7 @@ fn expr_for_adjacent_tagged_enum<'a>(
147147
content_name: &str,
148148
) -> TokenStream {
149149
let schemas = variants.map(|variant| {
150-
let content_schema = if variant.is_unit() && variant.with.is_none() {
150+
let content_schema = if variant.is_unit() && variant.attrs.with.is_none() {
151151
None
152152
} else {
153153
Some(expr_for_untagged_enum_variant(variant))
@@ -200,7 +200,7 @@ fn expr_for_adjacent_tagged_enum<'a>(
200200
}
201201

202202
fn expr_for_untagged_enum_variant(variant: &Variant) -> TokenStream {
203-
if let Some(with) = &variant.with {
203+
if let Some(WithAttr::Type(with)) = &variant.attrs.with {
204204
return quote_spanned! {variant.original.span()=>
205205
gen.subschema_for::<#with>()
206206
};
@@ -215,7 +215,7 @@ fn expr_for_untagged_enum_variant(variant: &Variant) -> TokenStream {
215215
}
216216

217217
fn expr_for_untagged_enum_variant_for_flatten(variant: &Variant) -> Option<TokenStream> {
218-
if let Some(with) = &variant.with {
218+
if let Some(WithAttr::Type(with)) = &variant.attrs.with {
219219
return Some(quote_spanned! {variant.original.span()=>
220220
<#with>::json_schema(gen)
221221
});

0 commit comments

Comments
 (0)