schemars_derive/attr/
mod.rs

1mod custom_meta;
2mod doc;
3mod parse_meta;
4mod schemars_to_serde;
5mod validation;
6
7use parse_meta::{
8    parse_extensions, parse_name_value_expr, parse_name_value_lit_str, require_name_value_lit_str,
9    require_path_only,
10};
11use proc_macro2::TokenStream;
12use quote::ToTokens;
13use serde_derive_internals::Ctxt;
14use syn::punctuated::Punctuated;
15use syn::spanned::Spanned;
16use syn::{Attribute, Expr, ExprLit, Ident, Lit, LitStr, Path, Type};
17use validation::ValidationAttrs;
18
19use crate::ast::Data;
20use crate::idents::SCHEMA;
21
22pub use custom_meta::*;
23pub use schemars_to_serde::process_serde_attrs;
24
25#[derive(Default)]
26pub struct CommonAttrs {
27    pub doc: Option<Expr>,
28    pub deprecated: bool,
29    pub title: Option<Expr>,
30    pub description: Option<Expr>,
31    pub examples: Vec<Expr>,
32    pub extensions: Vec<(String, TokenStream)>,
33    pub transforms: Vec<Expr>,
34}
35
36#[derive(Default)]
37pub struct FieldAttrs {
38    pub common: CommonAttrs,
39    pub with: Option<WithAttr>,
40    pub validation: ValidationAttrs,
41}
42
43#[derive(Default)]
44pub struct ContainerAttrs {
45    pub common: CommonAttrs,
46    pub repr: Option<Type>,
47    pub crate_name: Option<Path>,
48    // The actual parsing of this is done in `get_rename_format_type_params()`,
49    // because it depends on the type's generic params.
50    pub rename_format_string: Option<LitStr>,
51    pub inline: bool,
52    pub ref_variants: bool,
53    pub with: Option<WithAttr>,
54}
55
56#[derive(Default)]
57pub struct VariantAttrs {
58    pub common: CommonAttrs,
59    pub with: Option<WithAttr>,
60}
61
62pub enum WithAttr {
63    Type(Type),
64    Function(Path),
65}
66
67impl CommonAttrs {
68    fn populate(
69        &mut self,
70        attrs: &[Attribute],
71        schemars_cx: &mut AttrCtxt,
72        serde_cx: &mut AttrCtxt,
73    ) {
74        self.process_attr(schemars_cx);
75        self.process_attr(serde_cx);
76
77        self.doc = doc::get_doc(attrs);
78        self.deprecated = attrs.iter().any(|a| a.path().is_ident("deprecated"));
79    }
80
81    fn process_attr(&mut self, cx: &mut AttrCtxt) {
82        cx.parse_meta(|m, n, c| self.process_meta(m, n, c));
83    }
84
85    fn process_meta(
86        &mut self,
87        meta: CustomMeta,
88        meta_name: &str,
89        cx: &AttrCtxt,
90    ) -> Result<(), CustomMeta> {
91        match meta_name {
92            "title" => match self.title {
93                Some(_) => cx.duplicate_error(&meta),
94                None => self.title = parse_name_value_expr(meta, cx).ok(),
95            },
96
97            "description" => match self.description {
98                Some(_) => cx.duplicate_error(&meta),
99                None => self.description = parse_name_value_expr(meta, cx).ok(),
100            },
101
102            "example" => {
103                if let Ok(expr) = parse_name_value_expr(meta, cx) {
104                    if let Expr::Lit(ExprLit {
105                        lit: Lit::Str(lit_str),
106                        ..
107                    }) = &expr
108                    {
109                        if lit_str.parse::<Path>().is_ok() {
110                            let lit_str_value = lit_str.value();
111                            cx.error_spanned_by(&expr, format_args!(
112                                "`example` value must be an expression, and string literals that may be interpreted as function paths are currently disallowed to avoid migration errors \
113                                 (this restriction may be relaxed in a future version of schemars).\n\
114                                If you want to use the result of a function, use `#[schemars(example = {lit_str_value}())]`.\n\
115                                Or to use the string literal value, use `#[schemars(example = &\"{lit_str_value}\")]`."));
116                        }
117                    }
118
119                    self.examples.push(expr);
120                }
121            }
122
123            "extend" => {
124                for ex in parse_extensions(&meta, cx).into_iter().flatten() {
125                    // This is O(n^2) but should be fine with the typically small number of
126                    // extensions. If this does become a problem, it can be changed to use
127                    // IndexMap, or a separate Map with cloned keys.
128                    if self.extensions.iter().any(|e| e.0 == ex.key_str) {
129                        cx.error_spanned_by(
130                            ex.key_lit,
131                            format_args!("Duplicate extension key '{}'", ex.key_str),
132                        );
133                    } else {
134                        self.extensions.push((ex.key_str, ex.value));
135                    }
136                }
137            }
138
139            "transform" => {
140                if let Ok(expr) = parse_name_value_expr(meta, cx) {
141                    if let Expr::Lit(ExprLit {
142                        lit: Lit::Str(lit_str),
143                        ..
144                    }) = &expr
145                    {
146                        if lit_str.parse::<Expr>().is_ok() {
147                            cx.error_spanned_by(
148                                &expr,
149                                format_args!(
150                                    "Expected a `fn(&mut Schema)` or other value implementing `schemars::transform::Transform`, found `&str`.\nDid you mean `#[schemars(transform = {})]`?",
151                                    lit_str.value()
152                                ),
153                            );
154                        }
155                    } else {
156                        self.transforms.push(expr);
157                    }
158                }
159            }
160
161            _ => return Err(meta),
162        }
163
164        Ok(())
165    }
166
167    pub fn is_default(&self) -> bool {
168        matches!(
169            self,
170            Self {
171                title: None,
172                description: None,
173                doc: None,
174                deprecated: false,
175                examples,
176                extensions,
177                transforms,
178            } if examples.is_empty() && extensions.is_empty() && transforms.is_empty()
179        )
180    }
181
182    pub fn add_mutators(&self, mutators: &mut Vec<TokenStream>) {
183        let mut title = self.title.as_ref().map(ToTokens::to_token_stream);
184        let mut description = self.description.as_ref().map(ToTokens::to_token_stream);
185        if let Some(doc) = &self.doc {
186            title.get_or_insert_with(|| {
187                quote!({
188                    const TITLE: &str = schemars::_private::get_title_and_description(#doc).0;
189                    TITLE
190                })
191            });
192            description.get_or_insert_with(|| {
193                quote!({
194                    const DESCRIPTION: &str = schemars::_private::get_title_and_description(#doc).1;
195                    DESCRIPTION
196                })
197            });
198        }
199        if let Some(title) = title {
200            mutators.push(quote! {
201                schemars::_private::insert_metadata_property_if_nonempty(&mut #SCHEMA, "title", #title);
202            });
203        }
204        if let Some(description) = description {
205            mutators.push(quote! {
206                schemars::_private::insert_metadata_property_if_nonempty(&mut #SCHEMA, "description", #description);
207            });
208        }
209
210        if self.deprecated {
211            mutators.push(quote! {
212                #SCHEMA.insert("deprecated".into(), true.into());
213            });
214        }
215
216        if !self.examples.is_empty() {
217            let examples = self.examples.iter().map(|eg| {
218                quote! {
219                    schemars::_private::serde_json::value::to_value(#eg)
220                }
221            });
222            mutators.push(quote! {
223                #SCHEMA.insert("examples".into(), schemars::_private::serde_json::Value::Array([#(#examples),*].into_iter().flatten().collect()));
224            });
225        }
226
227        for (k, v) in &self.extensions {
228            mutators.push(quote! {
229                #SCHEMA.insert(#k.into(), schemars::_private::serde_json::json!(#v));
230            });
231        }
232
233        for transform in &self.transforms {
234            mutators.push(quote_spanned! {transform.span()=>
235                schemars::transform::Transform::transform(&mut #transform, &mut #SCHEMA);
236            });
237        }
238    }
239}
240
241impl FieldAttrs {
242    pub fn new(attrs: &[Attribute], cx: &Ctxt) -> Self {
243        let mut result = Self::default();
244        result.populate(attrs, cx);
245        result
246    }
247
248    fn populate(&mut self, attrs: &[Attribute], cx: &Ctxt) {
249        let schemars_cx = &mut AttrCtxt::new(cx, attrs, "schemars");
250        let serde_cx = &mut AttrCtxt::new(cx, attrs, "serde");
251        let validate_cx = &mut AttrCtxt::new(cx, attrs, "validate");
252        let garde_cx = &mut AttrCtxt::new(cx, attrs, "garde");
253
254        self.common.populate(attrs, schemars_cx, serde_cx);
255        self.validation.populate(schemars_cx, validate_cx, garde_cx);
256        self.process_attr(schemars_cx);
257        self.process_attr(serde_cx);
258    }
259
260    fn process_attr(&mut self, cx: &mut AttrCtxt) {
261        cx.parse_meta(|m, n, c| self.process_meta(m, n, c));
262    }
263
264    fn process_meta(
265        &mut self,
266        meta: CustomMeta,
267        meta_name: &str,
268        cx: &AttrCtxt,
269    ) -> Result<(), CustomMeta> {
270        match meta_name {
271            "with" => match self.with {
272                Some(WithAttr::Type(_)) => cx.duplicate_error(&meta),
273                Some(WithAttr::Function(_)) => cx.mutual_exclusive_error(&meta, "schema_with"),
274                None => self.with = parse_name_value_lit_str(meta, cx).ok().map(WithAttr::Type),
275            },
276            "schema_with" if cx.attr_type == "schemars" => match self.with {
277                Some(WithAttr::Function(_)) => cx.duplicate_error(&meta),
278                Some(WithAttr::Type(_)) => cx.mutual_exclusive_error(&meta, "with"),
279                None => {
280                    self.with = parse_name_value_lit_str(meta, cx)
281                        .ok()
282                        .map(WithAttr::Function);
283                }
284            },
285
286            _ => return Err(meta),
287        }
288
289        Ok(())
290    }
291
292    pub fn is_default(&self) -> bool {
293        matches!(
294            self,
295            Self {
296                common,
297                validation,
298                with: None,
299            } if common.is_default() && validation.is_default())
300    }
301}
302
303impl ContainerAttrs {
304    pub fn new(attrs: &[Attribute], data: &Data, cx: &Ctxt) -> Self {
305        let mut result = Self::default();
306        result.populate(attrs, data, cx);
307        result
308    }
309
310    fn populate(&mut self, attrs: &[Attribute], data: &Data, cx: &Ctxt) {
311        let schemars_cx = &mut AttrCtxt::new(cx, attrs, "schemars");
312        let serde_cx = &mut AttrCtxt::new(cx, attrs, "serde");
313
314        self.common.populate(attrs, schemars_cx, serde_cx);
315        self.process_attr(data, schemars_cx);
316        self.process_attr(data, serde_cx);
317
318        self.repr = attrs
319            .iter()
320            .find(|a| a.path().is_ident("repr"))
321            .and_then(|a| a.parse_args().ok());
322    }
323
324    fn process_attr(&mut self, data: &Data, cx: &mut AttrCtxt) {
325        cx.parse_meta(|m, n, c| self.process_meta(m, n, data, c));
326    }
327
328    fn process_meta(
329        &mut self,
330        meta: CustomMeta,
331        meta_name: &str,
332        data: &Data,
333        cx: &AttrCtxt,
334    ) -> Result<(), CustomMeta> {
335        match meta_name {
336            "crate" => match self.crate_name {
337                Some(_) => cx.duplicate_error(&meta),
338                None => self.crate_name = parse_name_value_lit_str(meta, cx).ok(),
339            },
340
341            "rename" if cx.attr_type == "schemars" => match self.rename_format_string {
342                Some(_) => cx.duplicate_error(&meta),
343                None => self.rename_format_string = require_name_value_lit_str(meta, cx).ok(),
344            },
345
346            "inline" => {
347                if self.inline {
348                    cx.duplicate_error(&meta);
349                } else if require_path_only(&meta, cx).is_ok() {
350                    self.inline = true;
351                }
352            }
353
354            "with" if cx.attr_type == "schemars" => match self.with {
355                Some(WithAttr::Type(_)) => cx.duplicate_error(&meta),
356                Some(WithAttr::Function(_)) => cx.mutual_exclusive_error(&meta, "schema_with"),
357                None => self.with = parse_name_value_lit_str(meta, cx).ok().map(WithAttr::Type),
358            },
359            "schema_with" if cx.attr_type == "schemars" => match self.with {
360                Some(WithAttr::Function(_)) => cx.duplicate_error(&meta),
361                Some(WithAttr::Type(_)) => cx.mutual_exclusive_error(&meta, "with"),
362                None => {
363                    self.with = parse_name_value_lit_str(meta, cx)
364                        .ok()
365                        .map(WithAttr::Function);
366                }
367            },
368
369            "_unstable_ref_variants" if cx.attr_type == "schemars" => {
370                if !matches!(data, Data::Enum(_)) {
371                    cx.error_spanned_by(
372                        meta.path(),
373                        "`_unstable_ref_variants` can only be used on enums",
374                    );
375                } else if self.ref_variants {
376                    cx.duplicate_error(&meta);
377                } else if require_path_only(&meta, cx).is_ok() {
378                    self.ref_variants = true;
379                }
380            }
381
382            _ => return Err(meta),
383        }
384
385        Ok(())
386    }
387}
388
389impl VariantAttrs {
390    pub fn new(attrs: &[Attribute], cx: &Ctxt) -> Self {
391        let mut result = Self::default();
392        result.populate(attrs, cx);
393        result
394    }
395
396    fn populate(&mut self, attrs: &[Attribute], cx: &Ctxt) {
397        let schemars_cx = &mut AttrCtxt::new(cx, attrs, "schemars");
398        let serde_cx = &mut AttrCtxt::new(cx, attrs, "serde");
399
400        self.common.populate(attrs, schemars_cx, serde_cx);
401        self.process_attr(schemars_cx);
402        self.process_attr(serde_cx);
403    }
404
405    fn process_attr(&mut self, cx: &mut AttrCtxt) {
406        cx.parse_meta(|m, n, c| self.process_meta(m, n, c));
407    }
408
409    fn process_meta(
410        &mut self,
411        meta: CustomMeta,
412        meta_name: &str,
413        cx: &AttrCtxt,
414    ) -> Result<(), CustomMeta> {
415        match meta_name {
416            "with" => match self.with {
417                Some(WithAttr::Type(_)) => cx.duplicate_error(&meta),
418                Some(WithAttr::Function(_)) => cx.mutual_exclusive_error(&meta, "schema_with"),
419                None => self.with = parse_name_value_lit_str(meta, cx).ok().map(WithAttr::Type),
420            },
421            "schema_with" if cx.attr_type == "schemars" => match self.with {
422                Some(WithAttr::Function(_)) => cx.duplicate_error(&meta),
423                Some(WithAttr::Type(_)) => cx.mutual_exclusive_error(&meta, "with"),
424                None => {
425                    self.with = parse_name_value_lit_str(meta, cx)
426                        .ok()
427                        .map(WithAttr::Function);
428                }
429            },
430
431            _ => return Err(meta),
432        }
433
434        Ok(())
435    }
436
437    pub fn is_default(&self) -> bool {
438        matches!(
439            self,
440            Self {
441                common,
442                with: None,
443            } if common.is_default()
444        )
445    }
446}
447
448fn get_meta_items(attrs: &[Attribute], attr_type: &'static str, cx: &Ctxt) -> Vec<CustomMeta> {
449    let mut result = vec![];
450
451    for attr in attrs.iter().filter(|a| a.path().is_ident(attr_type)) {
452        match attr.parse_args_with(Punctuated::<CustomMeta, Token![,]>::parse_terminated) {
453            Ok(list) => result.extend(list),
454            Err(err) => {
455                if attr_type == "schemars" {
456                    cx.syn_error(err);
457                }
458            }
459        }
460    }
461
462    result
463}
464
465fn path_str(path: &Path) -> String {
466    path.get_ident().map_or_else(
467        || path.into_token_stream().to_string().replace(' ', ""),
468        Ident::to_string,
469    )
470}
471
472pub struct AttrCtxt<'a> {
473    inner: &'a Ctxt,
474    attr_type: &'static str,
475    metas: Vec<CustomMeta>,
476}
477
478impl<'a> AttrCtxt<'a> {
479    pub fn new(inner: &'a Ctxt, attrs: &'a [Attribute], attr_type: &'static str) -> Self {
480        Self {
481            inner,
482            attr_type,
483            metas: get_meta_items(attrs, attr_type, inner),
484        }
485    }
486
487    pub fn new_nested_meta(&self, metas: Vec<CustomMeta>) -> Self {
488        Self { metas, ..*self }
489    }
490
491    pub fn parse_meta(
492        &mut self,
493        mut handle: impl FnMut(CustomMeta, &str, &Self) -> Result<(), CustomMeta>,
494    ) {
495        let metas = std::mem::take(&mut self.metas);
496        self.metas = metas
497            .into_iter()
498            .filter_map(|meta| match meta.path().get_ident().map(Ident::to_string) {
499                Some(ident) => handle(meta, &ident, self).err(),
500                _ => Some(meta),
501            })
502            .collect();
503    }
504
505    pub fn error_spanned_by<A: ToTokens, T: std::fmt::Display>(&self, obj: A, msg: T) {
506        self.inner.error_spanned_by(obj, msg);
507    }
508
509    pub fn syn_error(&self, err: syn::Error) {
510        self.inner.syn_error(err);
511    }
512
513    pub fn mutual_exclusive_error(&self, meta: &CustomMeta, other_attr: &str) {
514        if self.attr_type == "schemars" {
515            self.error_spanned_by(
516                meta,
517                format_args!(
518                    "schemars attribute cannot contain both `{}` and `{}`",
519                    path_str(meta.path()),
520                    other_attr,
521                ),
522            );
523        }
524    }
525
526    pub fn duplicate_error(&self, meta: &CustomMeta) {
527        if self.attr_type == "schemars" {
528            self.error_spanned_by(
529                meta,
530                format_args!(
531                    "duplicate schemars attribute item `{}`",
532                    path_str(meta.path())
533                ),
534            );
535        }
536    }
537}
538
539impl Drop for AttrCtxt<'_> {
540    fn drop(&mut self) {
541        if self.attr_type == "schemars" {
542            for unhandled_meta in self.metas.iter().filter(|m| !is_schemars_serde_keyword(m)) {
543                self.error_spanned_by(
544                    unhandled_meta.path(),
545                    format_args!(
546                        "unknown schemars attribute `{}`",
547                        path_str(unhandled_meta.path())
548                    ),
549                );
550            }
551        }
552    }
553}
554
555fn is_schemars_serde_keyword(meta: &CustomMeta) -> bool {
556    let known_keywords = schemars_to_serde::SCHEMARS_KEYWORDS_PARSED_BY_SERDE;
557    meta.path()
558        .get_ident()
559        .is_some_and(|i| known_keywords.contains(&i.to_string().as_str()))
560}