schemars_derive/
lib.rs

1#![forbid(unsafe_code)]
2
3#[macro_use]
4extern crate quote;
5#[macro_use]
6extern crate syn;
7extern crate proc_macro;
8
9mod ast;
10mod attr;
11mod metadata;
12mod regex_syntax;
13mod schema_exprs;
14
15use ast::*;
16use proc_macro2::TokenStream;
17use syn::spanned::Spanned;
18
19#[proc_macro_derive(JsonSchema, attributes(schemars, serde, validate))]
20pub fn derive_json_schema_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
21    let input = parse_macro_input!(input as syn::DeriveInput);
22    derive_json_schema(input, false)
23        .unwrap_or_else(syn::Error::into_compile_error)
24        .into()
25}
26
27#[proc_macro_derive(JsonSchema_repr, attributes(schemars, serde))]
28pub fn derive_json_schema_repr_wrapper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
29    let input = parse_macro_input!(input as syn::DeriveInput);
30    derive_json_schema(input, true)
31        .unwrap_or_else(syn::Error::into_compile_error)
32        .into()
33}
34
35fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result<TokenStream> {
36    attr::process_serde_attrs(&mut input)?;
37
38    let mut cont = Container::from_ast(&input)?;
39    add_trait_bounds(&mut cont);
40
41    let crate_alias = cont.attrs.crate_name.as_ref().map(|path| {
42        quote_spanned! {path.span()=>
43            use #path as schemars;
44        }
45    });
46
47    let type_name = &cont.ident;
48    let (impl_generics, ty_generics, where_clause) = cont.generics.split_for_impl();
49
50    if let Some(transparent_field) = cont.transparent_field() {
51        let (ty, type_def) = schema_exprs::type_for_field_schema(transparent_field);
52        return Ok(quote! {
53            const _: () = {
54                #crate_alias
55                #type_def
56
57                #[automatically_derived]
58                impl #impl_generics schemars::JsonSchema for #type_name #ty_generics #where_clause {
59                    fn is_referenceable() -> bool {
60                        <#ty as schemars::JsonSchema>::is_referenceable()
61                    }
62
63                    fn schema_name() -> std::string::String {
64                        <#ty as schemars::JsonSchema>::schema_name()
65                    }
66
67                    fn schema_id() -> std::borrow::Cow<'static, str> {
68                        <#ty as schemars::JsonSchema>::schema_id()
69                    }
70
71                    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
72                        <#ty as schemars::JsonSchema>::json_schema(gen)
73                    }
74
75                    fn _schemars_private_non_optional_json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
76                        <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(gen)
77                    }
78
79                    fn _schemars_private_is_option() -> bool {
80                        <#ty as schemars::JsonSchema>::_schemars_private_is_option()
81                    }
82                };
83            };
84        });
85    }
86
87    let mut schema_base_name = cont.name().to_string();
88
89    if !cont.attrs.is_renamed {
90        if let Some(path) = cont.serde_attrs.remote() {
91            if let Some(segment) = path.segments.last() {
92                schema_base_name = segment.ident.to_string();
93            }
94        }
95    }
96
97    // FIXME improve handling of generic type params which may not implement JsonSchema
98    let type_params: Vec<_> = cont.generics.type_params().map(|ty| &ty.ident).collect();
99    let const_params: Vec<_> = cont.generics.const_params().map(|c| &c.ident).collect();
100    let params: Vec<_> = type_params.iter().chain(const_params.iter()).collect();
101
102    let (schema_name, schema_id) = if params.is_empty()
103        || (cont.attrs.is_renamed && !schema_base_name.contains('{'))
104    {
105        (
106            quote! {
107                #schema_base_name.to_owned()
108            },
109            quote! {
110                std::borrow::Cow::Borrowed(std::concat!(
111                    std::module_path!(),
112                    "::",
113                    #schema_base_name
114                ))
115            },
116        )
117    } else if cont.attrs.is_renamed {
118        let mut schema_name_fmt = schema_base_name;
119        for tp in &params {
120            schema_name_fmt.push_str(&format!("{{{}:.0}}", tp));
121        }
122        (
123            quote! {
124                format!(#schema_name_fmt #(,#type_params=#type_params::schema_name())* #(,#const_params=#const_params)*)
125            },
126            quote! {
127                std::borrow::Cow::Owned(
128                    format!(
129                        std::concat!(
130                            std::module_path!(),
131                            "::",
132                            #schema_name_fmt
133                        )
134                        #(,#type_params=#type_params::schema_id())*
135                        #(,#const_params=#const_params)*
136                    )
137                )
138            },
139        )
140    } else {
141        let mut schema_name_fmt = schema_base_name;
142        schema_name_fmt.push_str("_for_{}");
143        schema_name_fmt.push_str(&"_and_{}".repeat(params.len() - 1));
144        (
145            quote! {
146                format!(#schema_name_fmt #(,#type_params::schema_name())* #(,#const_params)*)
147            },
148            quote! {
149                std::borrow::Cow::Owned(
150                    format!(
151                        std::concat!(
152                            std::module_path!(),
153                            "::",
154                            #schema_name_fmt
155                        )
156                        #(,#type_params::schema_id())*
157                        #(,#const_params)*
158                    )
159                )
160            },
161        )
162    };
163
164    let schema_expr = if repr {
165        schema_exprs::expr_for_repr(&cont)?
166    } else {
167        schema_exprs::expr_for_container(&cont)
168    };
169
170    Ok(quote! {
171        const _: () = {
172            #crate_alias
173
174            #[automatically_derived]
175            #[allow(unused_braces)]
176            impl #impl_generics schemars::JsonSchema for #type_name #ty_generics #where_clause {
177                fn schema_name() -> std::string::String {
178                    #schema_name
179                }
180
181                fn schema_id() -> std::borrow::Cow<'static, str> {
182                    #schema_id
183                }
184
185                fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
186                    #schema_expr
187                }
188            };
189        };
190    })
191}
192
193fn add_trait_bounds(cont: &mut Container) {
194    if let Some(bounds) = cont.serde_attrs.ser_bound() {
195        let where_clause = cont.generics.make_where_clause();
196        where_clause.predicates.extend(bounds.iter().cloned());
197    } else {
198        // No explicit trait bounds specified, assume the Rust convention of adding the trait to each type parameter
199        // TODO consider also adding trait bound to associated types when used as fields - I think Serde does this?
200        for param in &mut cont.generics.params {
201            if let syn::GenericParam::Type(ref mut type_param) = *param {
202                type_param.bounds.push(parse_quote!(schemars::JsonSchema));
203            }
204        }
205    }
206}