1mod doc;
2mod schemars_to_serde;
3mod validation;
4
5pub use schemars_to_serde::process_serde_attrs;
6pub use validation::ValidationAttrs;
7
8use crate::metadata::SchemaMetadata;
9use proc_macro2::{Group, Span, TokenStream, TokenTree};
10use quote::ToTokens;
11use serde_derive_internals::Ctxt;
12use syn::parse::{self, Parse};
13use syn::{Meta, MetaNameValue};
14
15#[derive(Debug, Default)]
20pub struct Attrs {
21 pub with: Option<WithAttr>,
22 pub title: Option<String>,
23 pub description: Option<String>,
24 pub deprecated: bool,
25 pub examples: Vec<syn::Path>,
26 pub repr: Option<syn::Type>,
27 pub crate_name: Option<syn::Path>,
28 pub is_renamed: bool,
29}
30
31#[derive(Debug)]
32pub enum WithAttr {
33 Type(syn::Type),
34 Function(syn::Path),
35}
36
37impl Attrs {
38 pub fn new(attrs: &[syn::Attribute], errors: &Ctxt) -> Self {
39 let mut result = Attrs::default()
40 .populate(attrs, "schemars", false, errors)
41 .populate(attrs, "serde", true, errors);
42
43 result.deprecated = attrs.iter().any(|a| a.path().is_ident("deprecated"));
44 result.repr = attrs
45 .iter()
46 .find(|a| a.path().is_ident("repr"))
47 .and_then(|a| a.parse_args().ok());
48
49 let (doc_title, doc_description) = doc::get_title_and_desc_from_doc(attrs);
50 result.title = result.title.or(doc_title);
51 result.description = result.description.or(doc_description);
52
53 result
54 }
55
56 pub fn as_metadata(&self) -> SchemaMetadata<'_> {
57 #[allow(clippy::ptr_arg)]
58 fn none_if_empty(s: &String) -> Option<&str> {
59 if s.is_empty() {
60 None
61 } else {
62 Some(s)
63 }
64 }
65
66 SchemaMetadata {
67 title: self.title.as_ref().and_then(none_if_empty),
68 description: self.description.as_ref().and_then(none_if_empty),
69 deprecated: self.deprecated,
70 examples: &self.examples,
71 read_only: false,
72 write_only: false,
73 default: None,
74 }
75 }
76
77 fn populate(
78 mut self,
79 attrs: &[syn::Attribute],
80 attr_type: &'static str,
81 ignore_errors: bool,
82 errors: &Ctxt,
83 ) -> Self {
84 let duplicate_error = |meta: &MetaNameValue| {
85 if !ignore_errors {
86 let msg = format!(
87 "duplicate schemars attribute `{}`",
88 meta.path.get_ident().unwrap()
89 );
90 errors.error_spanned_by(meta, msg)
91 }
92 };
93 let mutual_exclusive_error = |meta: &MetaNameValue, other: &str| {
94 if !ignore_errors {
95 let msg = format!(
96 "schemars attribute cannot contain both `{}` and `{}`",
97 meta.path.get_ident().unwrap(),
98 other,
99 );
100 errors.error_spanned_by(meta, msg)
101 }
102 };
103
104 for meta_item in get_meta_items(attrs, attr_type, errors, ignore_errors) {
105 match &meta_item {
106 Meta::NameValue(m) if m.path.is_ident("with") => {
107 if let Ok(ty) = parse_lit_into_ty(errors, attr_type, "with", &m.value) {
108 match self.with {
109 Some(WithAttr::Type(_)) => duplicate_error(m),
110 Some(WithAttr::Function(_)) => mutual_exclusive_error(m, "schema_with"),
111 None => self.with = Some(WithAttr::Type(ty)),
112 }
113 }
114 }
115
116 Meta::NameValue(m) if m.path.is_ident("schema_with") => {
117 if let Ok(fun) = parse_lit_into_path(errors, attr_type, "schema_with", &m.value)
118 {
119 match self.with {
120 Some(WithAttr::Function(_)) => duplicate_error(m),
121 Some(WithAttr::Type(_)) => mutual_exclusive_error(m, "with"),
122 None => self.with = Some(WithAttr::Function(fun)),
123 }
124 }
125 }
126
127 Meta::NameValue(m) if m.path.is_ident("title") => {
128 if let Ok(title) = expr_as_lit_str(errors, attr_type, "title", &m.value) {
129 match self.title {
130 Some(_) => duplicate_error(m),
131 None => self.title = Some(title.value()),
132 }
133 }
134 }
135
136 Meta::NameValue(m) if m.path.is_ident("description") => {
137 if let Ok(description) =
138 expr_as_lit_str(errors, attr_type, "description", &m.value)
139 {
140 match self.description {
141 Some(_) => duplicate_error(m),
142 None => self.description = Some(description.value()),
143 }
144 }
145 }
146
147 Meta::NameValue(m) if m.path.is_ident("example") => {
148 if let Ok(fun) = parse_lit_into_path(errors, attr_type, "example", &m.value) {
149 self.examples.push(fun)
150 }
151 }
152
153 Meta::NameValue(m) if m.path.is_ident("rename") => self.is_renamed = true,
154
155 Meta::NameValue(m) if m.path.is_ident("crate") && attr_type == "schemars" => {
156 if let Ok(p) = parse_lit_into_path(errors, attr_type, "crate", &m.value) {
157 if self.crate_name.is_some() {
158 duplicate_error(m)
159 } else {
160 self.crate_name = Some(p)
161 }
162 }
163 }
164
165 _ if ignore_errors => {}
166
167 Meta::List(m) if m.path.is_ident("inner") && attr_type == "schemars" => {
168 }
172
173 _ => {
174 if !is_known_serde_or_validation_keyword(&meta_item) {
175 let path = meta_item
176 .path()
177 .into_token_stream()
178 .to_string()
179 .replace(' ', "");
180 errors.error_spanned_by(
181 meta_item.path(),
182 format!("unknown schemars attribute `{}`", path),
183 );
184 }
185 }
186 }
187 }
188 self
189 }
190
191 pub fn is_default(&self) -> bool {
192 matches!(self, Self {
193 with: None,
194 title: None,
195 description: None,
196 deprecated: false,
197 examples,
198 repr: None,
199 crate_name: None,
200 is_renamed: _,
201 } if examples.is_empty())
202 }
203}
204
205fn is_known_serde_or_validation_keyword(meta: &syn::Meta) -> bool {
206 let mut known_keywords = schemars_to_serde::SERDE_KEYWORDS
207 .iter()
208 .chain(validation::VALIDATION_KEYWORDS);
209 meta.path()
210 .get_ident()
211 .map(|i| known_keywords.any(|k| i == k))
212 .unwrap_or(false)
213}
214
215fn get_meta_items(
216 attrs: &[syn::Attribute],
217 attr_type: &'static str,
218 errors: &Ctxt,
219 ignore_errors: bool,
220) -> Vec<Meta> {
221 let mut result = vec![];
222 for attr in attrs.iter().filter(|a| a.path().is_ident(attr_type)) {
223 match attr.parse_args_with(syn::punctuated::Punctuated::<Meta, Token![,]>::parse_terminated)
224 {
225 Ok(list) => result.extend(list),
226 Err(err) if !ignore_errors => errors.syn_error(err),
227 Err(_) => {}
228 }
229 }
230
231 result
232}
233
234fn expr_as_lit_str<'a>(
235 cx: &Ctxt,
236 attr_type: &'static str,
237 meta_item_name: &'static str,
238 expr: &'a syn::Expr,
239) -> Result<&'a syn::LitStr, ()> {
240 if let syn::Expr::Lit(syn::ExprLit {
241 lit: syn::Lit::Str(lit_str),
242 ..
243 }) = expr
244 {
245 Ok(lit_str)
246 } else {
247 cx.error_spanned_by(
248 expr,
249 format!(
250 "expected {} {} attribute to be a string: `{} = \"...\"`",
251 attr_type, meta_item_name, meta_item_name
252 ),
253 );
254 Err(())
255 }
256}
257
258fn parse_lit_into_ty(
259 cx: &Ctxt,
260 attr_type: &'static str,
261 meta_item_name: &'static str,
262 lit: &syn::Expr,
263) -> Result<syn::Type, ()> {
264 let string = expr_as_lit_str(cx, attr_type, meta_item_name, lit)?;
265
266 parse_lit_str(string).map_err(|_| {
267 cx.error_spanned_by(
268 lit,
269 format!(
270 "failed to parse type: `{} = {:?}`",
271 meta_item_name,
272 string.value()
273 ),
274 )
275 })
276}
277
278fn parse_lit_into_path(
279 cx: &Ctxt,
280 attr_type: &'static str,
281 meta_item_name: &'static str,
282 expr: &syn::Expr,
283) -> Result<syn::Path, ()> {
284 let lit_str = expr_as_lit_str(cx, attr_type, meta_item_name, expr)?;
285
286 parse_lit_str(lit_str).map_err(|_| {
287 cx.error_spanned_by(
288 expr,
289 format!(
290 "failed to parse path: `{} = {:?}`",
291 meta_item_name,
292 lit_str.value()
293 ),
294 )
295 })
296}
297
298fn parse_lit_str<T>(s: &syn::LitStr) -> parse::Result<T>
299where
300 T: Parse,
301{
302 let tokens = spanned_tokens(s)?;
303 syn::parse2(tokens)
304}
305
306fn spanned_tokens(s: &syn::LitStr) -> parse::Result<TokenStream> {
307 let stream = syn::parse_str(&s.value())?;
308 Ok(respan_token_stream(stream, s.span()))
309}
310
311fn respan_token_stream(stream: TokenStream, span: Span) -> TokenStream {
312 stream
313 .into_iter()
314 .map(|token| respan_token_tree(token, span))
315 .collect()
316}
317
318fn respan_token_tree(mut token: TokenTree, span: Span) -> TokenTree {
319 if let TokenTree::Group(g) = &mut token {
320 *g = Group::new(g.delimiter(), respan_token_stream(g.stream(), span));
321 }
322 token.set_span(span);
323 token
324}