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 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 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}