schemars_derive/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(unused_imports, clippy::cargo, clippy::pedantic)]
3#![allow(
4    clippy::result_large_err,
5    clippy::wildcard_imports,
6    clippy::from_iter_instead_of_collect,
7    clippy::too_many_lines
8)]
9
10#[macro_use]
11extern crate quote;
12#[macro_use]
13extern crate syn;
14extern crate proc_macro;
15
16mod ast;
17mod attr;
18mod bound;
19mod idents;
20mod name;
21mod schema_exprs;
22
23use ast::Container;
24use idents::GENERATOR;
25use proc_macro2::TokenStream;
26use std::collections::BTreeSet;
27use syn::spanned::Spanned;
28
29#[doc = "Derive macro for `JsonSchema` trait."]
30#[cfg_attr(not(doctest), doc = include_str!("../deriving.md"), doc = include_str!("../attributes.md"))]
31#[proc_macro_derive(JsonSchema, attributes(schemars, serde, validate, garde))]
32pub fn derive_json_schema_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
33    let input = parse_macro_input!(input as syn::DeriveInput);
34    derive_json_schema(input, false)
35        .unwrap_or_else(syn::Error::into_compile_error)
36        .into()
37}
38
39#[proc_macro_derive(JsonSchema_repr, attributes(schemars, serde))]
40pub fn derive_json_schema_repr_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
41    let input = parse_macro_input!(input as syn::DeriveInput);
42    derive_json_schema(input, true)
43        .unwrap_or_else(syn::Error::into_compile_error)
44        .into()
45}
46
47fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result<TokenStream> {
48    attr::process_serde_attrs(&mut input)?;
49
50    let cont = Container::from_ast(&input)?;
51
52    let crate_alias = cont.attrs.crate_name.as_ref().map(|path| {
53        quote_spanned! {path.span()=>
54            use #path as schemars;
55        }
56    });
57
58    let type_name = &cont.ident;
59
60    let (impl_generics, ty_generics, where_clause) = cont.generics.split_for_impl();
61
62    if let Some(ty) = get_transparent_type(&cont) {
63        return Ok(quote! {
64            const _: () = {
65                #crate_alias
66
67                #[automatically_derived]
68                impl #impl_generics schemars::JsonSchema for #type_name #ty_generics #where_clause {
69                    fn inline_schema() -> bool {
70                        <#ty as schemars::JsonSchema>::inline_schema()
71                    }
72
73                    fn schema_name() -> schemars::_private::alloc::borrow::Cow<'static, str> {
74                        <#ty as schemars::JsonSchema>::schema_name()
75                    }
76
77                    fn schema_id() -> schemars::_private::alloc::borrow::Cow<'static, str> {
78                        <#ty as schemars::JsonSchema>::schema_id()
79                    }
80
81                    fn json_schema(#GENERATOR: &mut schemars::SchemaGenerator) -> schemars::Schema {
82                        <#ty as schemars::JsonSchema>::json_schema(#GENERATOR)
83                    }
84
85                    fn _schemars_private_non_optional_json_schema(#GENERATOR: &mut schemars::SchemaGenerator) -> schemars::Schema {
86                        <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#GENERATOR)
87                    }
88
89                    fn _schemars_private_is_option() -> bool {
90                        <#ty as schemars::JsonSchema>::_schemars_private_is_option()
91                    }
92                };
93            };
94        });
95    }
96
97    let name = cont.name();
98    let const_params = BTreeSet::from_iter(cont.generics.const_params().map(|c| &c.ident));
99
100    // We can't just check if `cont.rename_type_params` is empty, because even if it is, there may
101    // be const params in the rename format string
102    let schema_name = if cont.attrs.rename_format_string.is_none() || !name.contains('{') {
103        quote! {
104            schemars::_private::alloc::borrow::Cow::Borrowed(#name)
105        }
106    } else {
107        let type_params = &cont.rename_type_params;
108
109        quote! {
110            schemars::_private::alloc::borrow::Cow::Owned(schemars::_private::alloc::format!(
111                    #name,
112                    #(#type_params=<#type_params as schemars::JsonSchema>::schema_name(),)*
113            ))
114        }
115    };
116
117    let schema_id = if const_params.is_empty() && cont.relevant_type_params.is_empty() {
118        quote! {
119            schemars::_private::alloc::borrow::Cow::Borrowed(::core::concat!(
120                ::core::module_path!(),
121                "::",
122                #name
123            ))
124        }
125    } else {
126        let relevant_type_params = &cont.relevant_type_params;
127        let format_string_braces = vec!["{}"; const_params.len() + relevant_type_params.len()];
128
129        quote! {
130            schemars::_private::alloc::borrow::Cow::Owned(
131                schemars::_private::alloc::format!(
132                    ::core::concat!(
133                        ::core::module_path!(),
134                        "::{}<",
135                        #(#format_string_braces,)*
136                        ">"
137                    ),
138                    #name,
139                    #(#const_params,)*
140                    #(schemars::_schemars_maybe_schema_id!(#relevant_type_params),)*
141                )
142            )
143        }
144    };
145
146    let schema_expr = if repr {
147        schema_exprs::expr_for_repr(&cont)?
148    } else {
149        schema_exprs::expr_for_container(&cont)
150    };
151
152    let inline = cont.attrs.inline;
153
154    Ok(quote! {
155        const _: () = {
156            #crate_alias
157
158            #[automatically_derived]
159            #[allow(unused_braces)]
160            impl #impl_generics schemars::JsonSchema for #type_name #ty_generics #where_clause {
161                fn schema_name() -> schemars::_private::alloc::borrow::Cow<'static, str> {
162                    #schema_name
163                }
164
165                fn schema_id() -> schemars::_private::alloc::borrow::Cow<'static, str> {
166                    #schema_id
167                }
168
169                fn json_schema(#GENERATOR: &mut schemars::SchemaGenerator) -> schemars::Schema {
170                    #schema_expr
171                }
172
173                fn inline_schema() -> bool {
174                    #inline
175                }
176            };
177        };
178    })
179}
180
181fn get_transparent_type<'a>(cont: &'a Container) -> Option<&'a syn::Type> {
182    // If any schemars attributes for setting metadata (e.g. description) are present, then
183    // it's not fully transparent, so use the normal `schema_exprs::expr_for_container`
184    // implementation (which always treats the struct as a newtype if it has `transparent`)
185
186    if let Some(attr::WithAttr::Type(ty)) = &cont.attrs.with {
187        if cont.attrs.common.is_default() {
188            return Some(ty);
189        }
190    }
191
192    if let Some(transparent_field) = cont.transparent_field() {
193        if cont.attrs.common.is_default() && transparent_field.attrs.is_default() {
194            return Some(transparent_field.ty);
195        }
196    }
197
198    None
199}