1use proc_macro::TokenStream;
4use quote::quote;
5use syn::{spanned::Spanned, Data, DeriveInput, Fields, FieldsNamed, Ident};
6
7pub fn emit_v2_schema_struct(input: TokenStream) -> TokenStream {
9 let mut item_ast = match crate::expect_struct_or_enum(input) {
10 Ok(i) => i,
11 Err(ts) => return ts,
12 };
13
14 let name = item_ast.ident.clone();
15 let generics = item_ast.generics.clone();
16 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
17
18 let mut raw_item_ast = item_ast.clone();
20 let raw_defaults = match raw_schema(&mut raw_item_ast) {
21 Ok(s) => s,
22 Err(ts) => return ts,
23 };
24
25 let raw_struct_name = &raw_item_ast.ident;
26 let mut gen = quote!(
27 #raw_item_ast
34 );
35
36 let defaults = match actual_schema(&mut item_ast) {
37 Ok(s) => s,
38 Err(ts) => return ts,
39 };
40
41 gen.extend(quote! {
42 #item_ast
43
44 impl Default for #name {
45 fn default() -> Self {
46 #name {
47 #defaults
48 }
49 }
50 }
51
52 impl Default for #raw_struct_name {
53 fn default() -> Self {
54 #raw_struct_name {
55 #raw_defaults
56 }
57 }
58 }
59
60 impl #raw_struct_name {
61 pub fn remove_refs(&mut self) {
63 self.properties.values_mut().for_each(|s| s.remove_refs());
64 self.items.as_mut().map(|s| s.remove_refs());
65 self.extra_props.as_mut().and_then(|s| s.right_mut()).map(|s| s.remove_refs());
66 self.reference = None;
67 }
68
69 pub fn retain_ref(&mut self) {
72 if self.reference.is_some() {
73 let ref_ = self.reference.take();
74 *self = Self::default();
75 self.reference = ref_;
76 } else {
77 self.properties.values_mut().for_each(|s| s.retain_ref());
78 self.items.as_mut().map(|s| s.retain_ref());
79 self.extra_props.as_mut().and_then(|s| s.right_mut()).map(|s| s.retain_ref());
80 }
81 }
82 }
83
84 impl #impl_generics paperclip::v2::Schema for #name #ty_generics #where_clause {
85 #[inline]
86 fn name(&self) -> Option<&str> {
87 self.name.as_ref().map(String::as_str)
88 }
89
90 #[inline]
91 fn set_name(&mut self, name: &str) {
92 self.name = Some(name.into());
93 }
94
95 #[inline]
96 fn set_cyclic(&mut self, cyclic: bool) {
97 self.cyclic = cyclic;
98 }
99
100 #[inline]
101 fn is_cyclic(&self) -> bool {
102 self.cyclic
103 }
104
105 #[inline]
106 fn description(&self) -> Option<&str> {
107 self.description.as_ref().map(String::as_str)
108 }
109
110 #[inline]
111 fn reference(&self) -> Option<&str> {
112 self.reference.as_ref().map(String::as_str)
113 }
114
115 #[inline]
116 fn set_reference(&mut self, ref_: String) {
117 self.reference = Some(ref_);
118 }
119
120 #[inline]
121 fn data_type(&self) -> Option<paperclip::v2::models::DataType> {
122 self.data_type
123 }
124
125 #[inline]
126 fn format(&self) -> Option<&paperclip::v2::models::DataTypeFormat> {
127 self.format.as_ref()
128 }
129
130 #[inline]
131 fn items(&self) -> Option<&paperclip::v2::models::Resolvable<Self>> {
132 self.items.as_ref()
133 }
134
135 #[inline]
136 fn items_mut(&mut self) -> Option<&mut paperclip::v2::models::Resolvable<Self>> {
137 self.items.as_mut()
138 }
139
140 #[inline]
141 fn additional_properties(&self) -> Option<&paperclip::v2::models::Either<bool, paperclip::v2::models::Resolvable<Self>>> {
142 self.extra_props.as_ref()
143 }
144
145 #[inline]
146 fn additional_properties_mut(&mut self) -> Option<&mut paperclip::v2::models::Either<bool, paperclip::v2::models::Resolvable<Self>>> {
147 self.extra_props.as_mut()
148 }
149
150 #[inline]
151 fn properties(&self) -> Option<&std::collections::BTreeMap<String, paperclip::v2::models::Resolvable<Self>>> {
152 if self.properties.is_empty() {
153 None
154 } else {
155 Some(&self.properties)
156 }
157 }
158
159 #[inline]
160 fn properties_mut(&mut self) -> Option<&mut std::collections::BTreeMap<String, paperclip::v2::models::Resolvable<Self>>> {
161 if self.properties.is_empty() {
162 None
163 } else {
164 Some(&mut self.properties)
165 }
166 }
167
168 #[inline]
169 fn required_properties(&self) -> Option<&std::collections::BTreeSet<String>> {
170 if self.required.is_empty() {
171 None
172 } else {
173 Some(&self.required)
174 }
175 }
176
177 #[inline]
178 fn enum_variants(&self) -> Option<&[paperclip::v2::serde_json::Value]> {
179 if self.enum_.is_empty() {
180 return None
181 } else {
182 Some(&self.enum_)
183 }
184 }
185 }
186 });
187
188 gen.into()
189}
190
191fn raw_schema(item_ast: &mut DeriveInput) -> Result<proc_macro2::TokenStream, TokenStream> {
193 let ident = Ident::new(
194 &format!("{}Raw", item_ast.ident),
195 proc_macro2::Span::call_site(),
196 );
197 item_ast.ident = ident.clone();
198
199 let fields = named_fields(item_ast)?;
200 let default_fields: FieldsNamed =
201 syn::parse2(schema_fields(&ident, false)).expect("parsing schema fields?");
202 fields.named.extend(default_fields.named);
203
204 let mut defaults = quote!();
205 for field in &fields.named {
206 let f_name = field.ident.as_ref().expect("fields not named?");
207 defaults.extend(quote!(#f_name: Default::default(),));
208 }
209
210 Ok(defaults)
211}
212
213fn actual_schema(item_ast: &mut DeriveInput) -> Result<proc_macro2::TokenStream, TokenStream> {
215 let name = item_ast.ident.clone();
216 let fields = named_fields(item_ast)?;
217
218 let default_fields: FieldsNamed =
219 syn::parse2(schema_fields(&name, true)).expect("parsing schema fields?");
220 fields.named.extend(default_fields.named);
221
222 let mut defaults = quote!();
223 for field in &fields.named {
224 let f_name = field.ident.as_ref().expect("fields not named?");
225 defaults.extend(quote!(#f_name: Default::default(),));
226 }
227
228 Ok(defaults)
229}
230
231fn named_fields(item_ast: &mut DeriveInput) -> Result<&mut FieldsNamed, TokenStream> {
233 let span = item_ast.span();
234 if let Data::Struct(s) = &mut item_ast.data {
235 match &mut s.fields {
236 Fields::Named(ref mut f) => Ok(f),
237 Fields::Unnamed(ref f) => Err(crate::span_error_with_msg(
238 f,
239 "expected struct with zero or more fields for schema",
240 )),
241 f @ Fields::Unit => {
242 *f = Fields::Named(syn::parse2(quote!({})).expect("parsing empty named fields"));
243 match f {
244 Fields::Named(ref mut f) => Ok(f),
245 _ => unreachable!(),
246 }
247 }
248 }
249 } else {
250 emit_error!(span.unwrap(), "expected struct for schema");
251 Err(quote!().into())
252 }
253}
254
255fn schema_fields(name: &Ident, is_ref: bool) -> proc_macro2::TokenStream {
258 let mut gen = quote!();
259 let add_self = |gen: &mut proc_macro2::TokenStream| {
260 if is_ref {
261 gen.extend(quote!(paperclip::v2::models::Resolvable<#name>));
262 } else {
263 gen.extend(quote!(Box<#name>));
264 }
265 };
266
267 gen.extend(quote!(
268 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
269 pub reference: Option<String>,
270 ));
271 gen.extend(quote!(
272 #[serde(skip_serializing_if = "Option::is_none")]
273 pub title: Option<String>,
274 ));
275 gen.extend(quote!(
276 #[serde(skip_serializing_if = "Option::is_none")]
277 pub description: Option<String>,
278 ));
279 gen.extend(quote!(
280 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
281 pub data_type: Option<paperclip::v2::models::DataType>,
282 ));
283 gen.extend(quote!(
284 #[serde(skip_serializing_if = "Option::is_none")]
285 pub format: Option<paperclip::v2::models::DataTypeFormat>,
286 ));
287 gen.extend(quote!(
288 #[serde(skip_serializing_if = "Option::is_none")]
289 pub maximum: Option<f32>,
290 ));
291 gen.extend(quote!(
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub minimum: Option<f32>,
294 ));
295 gen.extend(quote!(
296 #[serde(skip_serializing_if = "Option::is_none")]
297 pub example: Option<paperclip::v2::serde_json::Value>,
298 ));
299
300 gen.extend(quote!(
301 #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
302 pub properties: std::collections::BTreeMap<String,
303 ));
304 add_self(&mut gen);
305 gen.extend(quote!(>,));
306
307 gen.extend(quote!(
308 #[serde(skip_serializing_if = "Option::is_none")]
309 pub items: Option<
310 ));
311 add_self(&mut gen);
312 gen.extend(quote!(>,));
313
314 gen.extend(quote!(
315 #[serde(default, rename = "enum", skip_serializing_if = "Vec::is_empty")]
316 pub enum_: Vec<paperclip::v2::serde_json::Value>,
317 ));
318
319 gen.extend(quote!(
320 #[serde(rename = "additionalProperties", skip_serializing_if = "Option::is_none")]
321 pub extra_props: Option<paperclip::v2::models::Either<bool,
322 ));
323 add_self(&mut gen);
324 gen.extend(quote!(>>,));
325
326 gen.extend(quote!(
327 #[serde(default, skip_serializing_if = "std::collections::BTreeSet::is_empty")]
328 pub required: std::collections::BTreeSet<String>,
329 ));
330
331 if is_ref {
332 gen.extend(quote!(
333 #[serde(skip)]
334 cyclic: bool,
335 ));
336 }
337
338 quote!({
339 #[doc(hidden)]
340 #[serde(skip)]
341 pub name: Option<String>,
342 #gen
343 })
344}