1use std::borrow::{Borrow, Cow};
2
3use proc_macro2::{Ident, TokenStream};
4use quote::{quote, quote_spanned, ToTokens};
5use syn::{
6 parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, Field,
7 Fields, FieldsNamed, FieldsUnnamed, Generics, Variant,
8};
9
10use crate::{
11 as_tokens_or_diagnostics,
12 component::features::{
13 attributes::{Rename, Title, ValueType},
14 validation::Pattern,
15 },
16 doc_comment::CommentAttributes,
17 parse_utils::LitBoolOrExprPath,
18 Array, AttributesExt, Diagnostics, OptionExt, ToTokensDiagnostics,
19};
20
21use self::{
22 enums::{MixedEnum, PlainEnum},
23 features::{
24 EnumFeatures, FromAttributes, MixedEnumFeatures, NamedFieldFeatures,
25 NamedFieldStructFeatures, UnnamedFieldStructFeatures,
26 },
27};
28
29use super::{
30 features::{
31 attributes::{self, As, Bound, Description, NoRecursion, RenameAll},
32 parse_features, pop_feature, Feature, FeaturesExt, IntoInner, ToTokensExt,
33 },
34 serde::{self, SerdeContainer, SerdeValue},
35 ComponentDescription, ComponentSchema, FieldRename, FlattenedMapSchema, SchemaReference,
36 TypeTree, VariantRename,
37};
38
39mod enums;
40mod features;
41pub mod xml;
42
43#[cfg_attr(feature = "debug", derive(Debug))]
44pub struct Root<'p> {
45 pub ident: &'p Ident,
46 pub generics: &'p Generics,
47 pub attributes: &'p [Attribute],
48}
49
50pub struct Schema<'a> {
51 ident: &'a Ident,
52 attributes: &'a [Attribute],
53 generics: &'a Generics,
54 data: &'a Data,
55}
56
57impl<'a> Schema<'a> {
58 pub fn new(
59 data: &'a Data,
60 attributes: &'a [Attribute],
61 ident: &'a Ident,
62 generics: &'a Generics,
63 ) -> Result<Self, Diagnostics> {
64 Ok(Self {
65 data,
66 ident,
67 attributes,
68 generics,
69 })
70 }
71}
72
73impl ToTokensDiagnostics for Schema<'_> {
74 fn to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostics> {
75 let ident = self.ident;
76 let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
77 let mut where_clause = where_clause.map_or(parse_quote!(where), |w| w.clone());
78
79 let root = Root {
80 ident,
81 generics: self.generics,
82 attributes: self.attributes,
83 };
84 let variant = SchemaVariant::new(self.data, &root)?;
85 let (generic_references, schema_references): (Vec<_>, Vec<_>) = variant
86 .get_schema_references()
87 .filter(|schema_reference| !schema_reference.no_recursion)
88 .partition(|schema_reference| schema_reference.is_partial());
89
90 struct SchemaRef<'a>(&'a TokenStream, &'a TokenStream, &'a TokenStream, bool);
91 impl ToTokens for SchemaRef<'_> {
92 fn to_tokens(&self, tokens: &mut TokenStream) {
93 let SchemaRef(name, ref_tokens, ..) = self;
94 tokens.extend(quote! { (#name, #ref_tokens) });
95 }
96 }
97 let schema_refs = schema_references
98 .iter()
99 .map(|schema_reference| {
100 SchemaRef(
101 &schema_reference.name,
102 &schema_reference.tokens,
103 &schema_reference.references,
104 schema_reference.is_inline,
105 )
106 })
107 .collect::<Array<SchemaRef>>();
108
109 let references = schema_refs.iter().fold(
110 TokenStream::new(),
111 |mut tokens, SchemaRef(_, _, references, _)| {
112 tokens.extend(quote!( #references; ));
113
114 tokens
115 },
116 );
117 let generic_references = generic_references
118 .into_iter()
119 .map(|schema_reference| {
120 let reference = &schema_reference.references;
121 quote! {#reference;}
122 })
123 .collect::<TokenStream>();
124
125 let schema_refs = schema_refs
126 .iter()
127 .filter(|SchemaRef(_, _, _, is_inline)| {
128 #[cfg(feature = "config")]
129 {
130 (matches!(
131 crate::CONFIG.schema_collect,
132 utoipa_config::SchemaCollect::NonInlined
133 ) && !is_inline)
134 || matches!(
135 crate::CONFIG.schema_collect,
136 utoipa_config::SchemaCollect::All
137 )
138 }
139 #[cfg(not(feature = "config"))]
140 !is_inline
141 })
142 .collect::<Array<_>>();
143
144 let name = if let Some(schema_as) = variant.get_schema_as() {
145 schema_as.to_schema_formatted_string()
146 } else {
147 ident.to_string()
148 };
149
150 if let Some(Bound(bound)) = variant.get_schema_bound() {
152 where_clause.predicates.extend(bound.clone());
153 } else {
154 for param in self.generics.type_params() {
155 let param = ¶m.ident;
156 where_clause
157 .predicates
158 .push(parse_quote!(#param : utoipa::ToSchema))
159 }
160 }
161
162 tokens.extend(quote! {
163 impl #impl_generics utoipa::__dev::ComposeSchema for #ident #ty_generics #where_clause {
164 fn compose(
165 mut generics: Vec<utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>>
166 ) -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
167 #variant.into()
168 }
169 }
170
171 impl #impl_generics utoipa::ToSchema for #ident #ty_generics #where_clause {
172 fn name() -> std::borrow::Cow<'static, str> {
173 std::borrow::Cow::Borrowed(#name)
174 }
175
176 fn schemas(schemas: &mut Vec<(String, utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>)>) {
177 schemas.extend(#schema_refs);
178 #references;
179 #generic_references
180 }
181 }
182 });
183 Ok(())
184 }
185}
186
187#[cfg_attr(feature = "debug", derive(Debug))]
188enum SchemaVariant<'a> {
189 Named(NamedStructSchema),
190 Unnamed(UnnamedStructSchema),
191 Enum(EnumSchema<'a>),
192 Unit(UnitStructVariant),
193}
194
195impl<'a> SchemaVariant<'a> {
196 pub fn new(data: &'a Data, root: &'a Root<'a>) -> Result<SchemaVariant<'a>, Diagnostics> {
197 match data {
198 Data::Struct(content) => match &content.fields {
199 Fields::Unnamed(fields) => {
200 let FieldsUnnamed { unnamed, .. } = fields;
201 let unnamed_features = root
202 .attributes
203 .parse_features::<UnnamedFieldStructFeatures>()?
204 .into_inner()
205 .unwrap_or_default();
206
207 Ok(Self::Unnamed(UnnamedStructSchema::new(
208 root,
209 unnamed,
210 unnamed_features,
211 )?))
212 }
213 Fields::Named(fields) => {
214 let FieldsNamed { named, .. } = fields;
215 let named_features = root
216 .attributes
217 .parse_features::<NamedFieldStructFeatures>()?
218 .into_inner()
219 .unwrap_or_default();
220
221 Ok(Self::Named(NamedStructSchema::new(
222 root,
223 named,
224 named_features,
225 )?))
226 }
227 Fields::Unit => Ok(Self::Unit(UnitStructVariant::new(root)?)),
228 },
229 Data::Enum(content) => Ok(Self::Enum(EnumSchema::new(root, &content.variants)?)),
230 _ => Err(Diagnostics::with_span(
231 root.ident.span(),
232 "unexpected data type, expected syn::Data::Struct or syn::Data::Enum",
233 )),
234 }
235 }
236
237 fn get_schema_as(&self) -> &Option<As> {
238 match self {
239 Self::Enum(schema) => &schema.schema_as,
240 Self::Named(schema) => &schema.schema_as,
241 Self::Unnamed(schema) => &schema.schema_as,
242 _ => &None,
243 }
244 }
245
246 fn get_schema_references(&self) -> impl Iterator<Item = &SchemaReference> {
247 match self {
248 Self::Named(schema) => schema.fields_references.iter(),
249 Self::Unnamed(schema) => schema.schema_references.iter(),
250 Self::Enum(schema) => schema.schema_references.iter(),
251 _ => [].iter(),
252 }
253 }
254
255 fn get_schema_bound(&self) -> Option<&Bound> {
256 match self {
257 SchemaVariant::Named(schema) => schema.bound.as_ref(),
258 SchemaVariant::Unnamed(schema) => schema.bound.as_ref(),
259 SchemaVariant::Enum(schema) => schema.bound.as_ref(),
260 SchemaVariant::Unit(_) => None,
261 }
262 }
263}
264
265impl ToTokens for SchemaVariant<'_> {
266 fn to_tokens(&self, tokens: &mut TokenStream) {
267 match self {
268 Self::Enum(schema) => schema.to_tokens(tokens),
269 Self::Named(schema) => schema.to_tokens(tokens),
270 Self::Unnamed(schema) => schema.to_tokens(tokens),
271 Self::Unit(unit) => unit.to_tokens(tokens),
272 }
273 }
274}
275
276#[cfg_attr(feature = "debug", derive(Debug))]
277struct UnitStructVariant(TokenStream);
278
279impl UnitStructVariant {
280 fn new(root: &Root<'_>) -> Result<Self, Diagnostics> {
281 let mut tokens = quote! {
282 utoipa::openapi::Object::builder()
283 .schema_type(utoipa::openapi::schema::SchemaType::AnyValue)
284 .default(Some(utoipa::gen::serde_json::Value::Null))
285 };
286
287 let mut features = features::parse_schema_features_with(root.attributes, |input| {
288 Ok(parse_features!(input as Title, Description))
289 })?
290 .unwrap_or_default();
291
292 let description = pop_feature!(features => Feature::Description(_) as Option<Description>);
293
294 let comment = CommentAttributes::from_attributes(root.attributes);
295 let description = description
296 .as_ref()
297 .map(ComponentDescription::Description)
298 .or(Some(ComponentDescription::CommentAttributes(&comment)));
299
300 description.to_tokens(&mut tokens);
301 tokens.extend(features.to_token_stream());
302
303 Ok(Self(tokens))
304 }
305}
306
307impl ToTokens for UnitStructVariant {
308 fn to_tokens(&self, tokens: &mut TokenStream) {
309 self.0.to_tokens(tokens);
310 }
311}
312
313#[cfg_attr(feature = "debug", derive(Debug))]
314pub struct NamedStructSchema {
315 tokens: TokenStream,
316 pub schema_as: Option<As>,
317 fields_references: Vec<SchemaReference>,
318 bound: Option<Bound>,
319 is_all_of: bool,
320}
321
322#[cfg_attr(feature = "debug", derive(Debug))]
323struct NamedStructFieldOptions<'a> {
324 property: Property,
325 renamed_field: Option<Cow<'a, str>>,
326 required: Option<super::features::attributes::Required>,
327 is_option: bool,
328 ignore: Option<LitBoolOrExprPath>,
329}
330
331impl NamedStructSchema {
332 pub fn new(
333 root: &Root,
334 fields: &Punctuated<Field, Comma>,
335 mut features: Vec<Feature>,
336 ) -> Result<Self, Diagnostics> {
337 let mut tokens = TokenStream::new();
338
339 let rename_all = pop_feature!(features => Feature::RenameAll(_) as Option<RenameAll>);
340 let schema_as = pop_feature!(features => Feature::As(_) as Option<As>);
341 let description: Option<Description> =
342 pop_feature!(features => Feature::Description(_)).into_inner();
343 let bound = pop_feature!(features => Feature::Bound(_) as Option<Bound>);
344
345 let container_rules = serde::parse_container(root.attributes)?;
346
347 let mut fields_vec = fields
348 .iter()
349 .filter_map(|field| {
350 let mut field_name = Cow::Owned(field.ident.as_ref().unwrap().to_string());
351
352 if Borrow::<str>::borrow(&field_name).starts_with("r#") {
353 field_name = Cow::Owned(field_name[2..].to_string());
354 }
355
356 let field_rules = serde::parse_value(&field.attrs);
357 let field_rules = match field_rules {
358 Ok(field_rules) => field_rules,
359 Err(diagnostics) => return Some(Err(diagnostics)),
360 };
361 let field_options = Self::get_named_struct_field_options(
362 root,
363 field,
364 &features,
365 &field_rules,
366 &container_rules,
367 );
368
369 match field_options {
370 Ok(Some(field_options)) => {
371 Some(Ok((field_options, field_rules, field_name, field)))
372 }
373 Ok(_) => None,
374 Err(options_diagnostics) => Some(Err(options_diagnostics)),
375 }
376 })
377 .collect::<Result<Vec<_>, Diagnostics>>()?;
378
379 let fields_references = fields_vec
380 .iter_mut()
381 .filter_map(|(field_options, field_rules, ..)| {
382 match (&mut field_options.property, field_rules.skip) {
383 (Property::Schema(schema), false) => {
384 Some(std::mem::take(&mut schema.schema_references))
385 }
386 _ => None,
387 }
388 })
389 .flatten()
390 .collect::<Vec<_>>();
391
392 let mut object_tokens_empty = true;
393 let object_tokens = fields_vec
394 .iter()
395 .filter(|(_, field_rules, ..)| !field_rules.skip && !field_rules.flatten)
396 .map(|(property, field_rules, field_name, field)| {
397 Ok((
398 property,
399 field_rules,
400 field_name,
401 field,
402 as_tokens_or_diagnostics!(&property.property),
403 ))
404 })
405 .collect::<Result<Vec<_>, Diagnostics>>()?
406 .into_iter()
407 .fold(
408 quote! { let mut object = utoipa::openapi::ObjectBuilder::new(); },
409 |mut object_tokens,
410 (
411 NamedStructFieldOptions {
412 renamed_field,
413 required,
414 is_option,
415 ignore,
416 ..
417 },
418 field_rules,
419 field_name,
420 _field,
421 field_schema,
422 )| {
423 object_tokens_empty = false;
424 let rename_to = field_rules
425 .rename
426 .as_deref()
427 .map(Cow::Borrowed)
428 .or(renamed_field.as_ref().cloned());
429 let rename_all = container_rules.rename_all.as_ref().or(rename_all
430 .as_ref()
431 .map(|rename_all| rename_all.as_rename_rule()));
432
433 let name =
434 super::rename::<FieldRename>(field_name.borrow(), rename_to, rename_all)
435 .unwrap_or(Cow::Borrowed(field_name.borrow()));
436
437 let mut property_tokens = quote! {
438 object = object.property(#name, #field_schema)
439 };
440 let component_required =
441 !is_option && super::is_required(field_rules, &container_rules);
442 let required = match (required, component_required) {
443 (Some(required), _) => required.is_true(),
444 (None, component_required) => component_required,
445 };
446
447 if required {
448 property_tokens.extend(quote! {
449 .required(#name)
450 })
451 }
452
453 object_tokens.extend(match ignore {
454 Some(LitBoolOrExprPath::LitBool(bool)) => quote_spanned! {
455 bool.span() => if !#bool {
456 #property_tokens;
457 }
458 },
459 Some(LitBoolOrExprPath::ExprPath(path)) => quote_spanned! {
460 path.span() => if !#path() {
461 #property_tokens;
462 }
463 },
464 None => quote! { #property_tokens; },
465 });
466
467 object_tokens
468 },
469 );
470
471 let mut object_tokens = quote! {
472 { #object_tokens; object }
473 };
474
475 let flatten_fields = fields_vec
476 .iter()
477 .filter(|(_, field_rules, ..)| field_rules.flatten)
478 .collect::<Vec<_>>();
479
480 let all_of = if !flatten_fields.is_empty() {
481 let mut flattened_tokens = TokenStream::new();
482 let mut flattened_map_field = None;
483
484 for (options, _, _, field) in flatten_fields {
485 let NamedStructFieldOptions { property, .. } = options;
486 let property_schema = as_tokens_or_diagnostics!(property);
487
488 match property {
489 Property::Schema(_) | Property::SchemaWith(_) => {
490 flattened_tokens.extend(quote! { .item(#property_schema) })
491 }
492 Property::FlattenedMap(_) => {
493 match flattened_map_field {
494 None => {
495 object_tokens.extend(
496 quote! { .additional_properties(Some(#property_schema)) },
497 );
498 flattened_map_field = Some(field);
499 }
500 Some(flattened_map_field) => {
501 return Err(Diagnostics::with_span(
502 fields.span(),
503 format!("The structure `{}` contains multiple flattened map fields.", root.ident))
504 .note(
505 format!("first flattened map field was declared here as `{}`",
506 flattened_map_field.ident.as_ref().unwrap()))
507 .note(format!("second flattened map field was declared here as `{}`", field.ident.as_ref().unwrap()))
508 );
509 }
510 }
511 }
512 }
513 }
514
515 if flattened_tokens.is_empty() {
516 tokens.extend(object_tokens);
517 false
518 } else {
519 tokens.extend(quote! {
520 utoipa::openapi::AllOfBuilder::new()
521 #flattened_tokens
522
523 });
524 if !object_tokens_empty {
525 tokens.extend(quote! {
526 .item(#object_tokens)
527 });
528 }
529 true
530 }
531 } else {
532 tokens.extend(object_tokens);
533 false
534 };
535
536 if !all_of && container_rules.deny_unknown_fields {
537 tokens.extend(quote! {
538 .additional_properties(Some(utoipa::openapi::schema::AdditionalProperties::FreeForm(false)))
539 });
540 }
541
542 if root.attributes.has_deprecated()
543 && !features
544 .iter()
545 .any(|feature| matches!(feature, Feature::Deprecated(_)))
546 {
547 features.push(Feature::Deprecated(true.into()));
548 }
549
550 let _ = pop_feature!(features => Feature::NoRecursion(_));
551 tokens.extend(features.to_token_stream()?);
552
553 let comments = CommentAttributes::from_attributes(root.attributes);
554 let description = description
555 .as_ref()
556 .map(ComponentDescription::Description)
557 .or(Some(ComponentDescription::CommentAttributes(&comments)));
558
559 description.to_tokens(&mut tokens);
560
561 Ok(Self {
562 tokens,
563 schema_as,
564 fields_references,
565 bound,
566 is_all_of: all_of,
567 })
568 }
569
570 fn get_named_struct_field_options<'a>(
571 root: &Root,
572 field: &Field,
573 features: &[Feature],
574 field_rules: &SerdeValue,
575 container_rules: &SerdeContainer,
576 ) -> Result<Option<NamedStructFieldOptions<'a>>, Diagnostics> {
577 let type_tree = &mut TypeTree::from_type(&field.ty)?;
578
579 let mut field_features = field
580 .attrs
581 .parse_features::<NamedFieldFeatures>()?
582 .into_inner()
583 .unwrap_or_default();
584
585 if features
586 .iter()
587 .any(|feature| matches!(feature, Feature::NoRecursion(_)))
588 {
589 field_features.push(Feature::NoRecursion(NoRecursion));
590 }
591
592 let schema_default = features.iter().any(|f| matches!(f, Feature::Default(_)));
593 let serde_default = container_rules.default;
594
595 if (schema_default || serde_default)
596 && !field_features
597 .iter()
598 .any(|f| matches!(f, Feature::Default(_)))
599 {
600 let field_ident = field.ident.as_ref().unwrap().to_owned();
601
602 field_features.push(Feature::Default(
604 crate::features::attributes::Default::new_default_trait(
605 root.ident.clone(),
606 field_ident.into(),
607 ),
608 ));
609 }
610
611 if field.attrs.has_deprecated()
612 && !field_features
613 .iter()
614 .any(|feature| matches!(feature, Feature::Deprecated(_)))
615 {
616 field_features.push(Feature::Deprecated(true.into()));
617 }
618
619 let rename_field =
620 pop_feature!(field_features => Feature::Rename(_)).and_then(|feature| match feature {
621 Feature::Rename(rename) => Some(Cow::Owned(rename.into_value())),
622 _ => None,
623 });
624
625 let value_type = pop_feature!(field_features => Feature::ValueType(_) as Option<ValueType>);
626 let override_type_tree = value_type
627 .as_ref()
628 .map_try(|value_type| value_type.as_type_tree())?;
629 let comments = CommentAttributes::from_attributes(&field.attrs);
630 let description = &ComponentDescription::CommentAttributes(&comments);
631
632 let schema_with = pop_feature!(field_features => Feature::SchemaWith(_));
633 let required = pop_feature!(field_features => Feature::Required(_) as Option<crate::component::features::attributes::Required>);
634 let type_tree = override_type_tree.as_ref().unwrap_or(type_tree);
635
636 let alias_type = type_tree.get_alias_type()?;
637 let alias_type_tree = alias_type.as_ref().map_try(TypeTree::from_type)?;
638 let type_tree = alias_type_tree.as_ref().unwrap_or(type_tree);
639
640 let is_option = type_tree.is_option();
641
642 let ignore = match pop_feature!(field_features => Feature::Ignore(_)) {
643 Some(Feature::Ignore(attributes::Ignore(bool_or_exp))) => Some(bool_or_exp),
644 _ => None,
645 };
646
647 Ok(Some(NamedStructFieldOptions {
648 property: if let Some(schema_with) = schema_with {
649 Property::SchemaWith(schema_with)
650 } else {
651 let props = super::ComponentSchemaProps {
652 type_tree,
653 features: field_features,
654 description: Some(description),
655 container: &super::Container {
656 generics: root.generics,
657 },
658 };
659 if field_rules.flatten && type_tree.is_map() {
660 Property::FlattenedMap(FlattenedMapSchema::new(props)?)
661 } else {
662 let schema = ComponentSchema::new(props)?;
663 Property::Schema(schema)
664 }
665 },
666 renamed_field: rename_field,
667 required,
668 is_option,
669 ignore,
670 }))
671 }
672}
673
674impl ToTokens for NamedStructSchema {
675 fn to_tokens(&self, tokens: &mut TokenStream) {
676 self.tokens.to_tokens(tokens);
677 }
678}
679
680#[cfg_attr(feature = "debug", derive(Debug))]
681struct UnnamedStructSchema {
682 tokens: TokenStream,
683 schema_as: Option<As>,
684 schema_references: Vec<SchemaReference>,
685 bound: Option<Bound>,
686}
687
688impl UnnamedStructSchema {
689 fn new(
690 root: &Root,
691 fields: &Punctuated<Field, Comma>,
692 mut features: Vec<Feature>,
693 ) -> Result<Self, Diagnostics> {
694 let mut tokens = TokenStream::new();
695 let schema_as = pop_feature!(features => Feature::As(_) as Option<As>);
696 let description: Option<Description> =
697 pop_feature!(features => Feature::Description(_)).into_inner();
698 let bound = pop_feature!(features => Feature::Bound(_) as Option<Bound>);
699
700 let fields_len = fields.len();
701 let first_field = fields.first().unwrap();
702 let first_part = &TypeTree::from_type(&first_field.ty)?;
703
704 let all_fields_are_same = fields_len == 1
705 || fields
706 .iter()
707 .skip(1)
708 .map(|field| TypeTree::from_type(&field.ty))
709 .collect::<Result<Vec<TypeTree>, Diagnostics>>()?
710 .iter()
711 .all(|schema_part| first_part == schema_part);
712
713 if root.attributes.has_deprecated()
714 && !features
715 .iter()
716 .any(|feature| matches!(feature, Feature::Deprecated(_)))
717 {
718 features.push(Feature::Deprecated(true.into()));
719 }
720 let mut schema_references = Vec::<SchemaReference>::new();
721 if all_fields_are_same {
722 let value_type = pop_feature!(features => Feature::ValueType(_) as Option<ValueType>);
723 let override_type_tree = value_type
724 .as_ref()
725 .map_try(|value_type| value_type.as_type_tree())?;
726
727 if fields_len == 1 {
728 let inline = features::parse_schema_features_with(&first_field.attrs, |input| {
729 Ok(parse_features!(
730 input as super::features::attributes::Inline
731 ))
732 })?
733 .unwrap_or_default();
734
735 features.extend(inline);
736
737 if pop_feature!(features => Feature::Default(crate::features::attributes::Default(None)))
738 .is_some()
739 {
740 let index: syn::Index = 0.into();
741 features.push(Feature::Default(
743 crate::features::attributes::Default::new_default_trait(root.ident.clone(), index.into()),
744 ));
745 }
746 }
747 let pattern = if let Some(pattern) =
748 pop_feature!(features => Feature::Pattern(_) as Option<Pattern>)
749 {
750 if fields_len > 1 {
752 return Err(Diagnostics::with_span(
753 pattern.span(),
754 "Pattern attribute is not allowed for unnamed structs with multiple fields",
755 ));
756 }
757 Some(pattern.to_token_stream())
758 } else {
759 None
760 };
761
762 let comments = CommentAttributes::from_attributes(root.attributes);
763 let description = description
764 .as_ref()
765 .map(ComponentDescription::Description)
766 .or(Some(ComponentDescription::CommentAttributes(&comments)));
767 let type_tree = override_type_tree.as_ref().unwrap_or(first_part);
768
769 let alias_type = type_tree.get_alias_type()?;
770 let alias_type_tree = alias_type.as_ref().map_try(TypeTree::from_type)?;
771 let type_tree = alias_type_tree.as_ref().unwrap_or(type_tree);
772
773 let mut schema = ComponentSchema::new(super::ComponentSchemaProps {
774 type_tree,
775 features,
776 description: description.as_ref(),
777 container: &super::Container {
778 generics: root.generics,
779 },
780 })?;
781
782 tokens.extend(schema.to_token_stream());
783 if let Some(pattern) = pattern {
784 tokens.extend(quote! {
785 .pattern(Some(#pattern))
786 });
787 }
788 schema_references = std::mem::take(&mut schema.schema_references);
789 } else {
790 tokens.extend(quote! {
795 utoipa::openapi::ObjectBuilder::new()
796 });
797
798 tokens.extend(features.to_token_stream()?)
799 }
800
801 if fields_len > 1 {
802 let comments = CommentAttributes::from_attributes(root.attributes);
803 let description = description
804 .as_ref()
805 .map(ComponentDescription::Description)
806 .or(Some(ComponentDescription::CommentAttributes(&comments)));
807 tokens.extend(quote! {
808 .to_array_builder()
809 .max_items(Some(#fields_len))
810 .min_items(Some(#fields_len))
811 #description
812 })
813 }
814
815 Ok(UnnamedStructSchema {
816 tokens,
817 schema_as,
818 schema_references,
819 bound,
820 })
821 }
822}
823
824impl ToTokens for UnnamedStructSchema {
825 fn to_tokens(&self, tokens: &mut TokenStream) {
826 self.tokens.to_tokens(tokens);
827 }
828}
829
830#[cfg_attr(feature = "debug", derive(Debug))]
831pub struct EnumSchema<'a> {
832 schema_type: EnumSchemaType<'a>,
833 schema_as: Option<As>,
834 schema_references: Vec<SchemaReference>,
835 bound: Option<Bound>,
836}
837
838impl<'e> EnumSchema<'e> {
839 pub fn new(
840 parent: &'e Root<'e>,
841 variants: &'e Punctuated<Variant, Comma>,
842 ) -> Result<Self, Diagnostics> {
843 if variants
844 .iter()
845 .all(|variant| matches!(variant.fields, Fields::Unit))
846 {
847 #[cfg(feature = "repr")]
848 let mut features = {
849 if parent
850 .attributes
851 .iter()
852 .any(|attr| attr.path().is_ident("repr"))
853 {
854 features::parse_schema_features_with(parent.attributes, |input| {
855 Ok(parse_features!(
856 input as super::features::attributes::Example,
857 super::features::attributes::Examples,
858 super::features::attributes::Default,
859 super::features::attributes::Title,
860 crate::component::features::attributes::Deprecated,
861 As
862 ))
863 })?
864 .unwrap_or_default()
865 } else {
866 parent
867 .attributes
868 .parse_features::<EnumFeatures>()?
869 .into_inner()
870 .unwrap_or_default()
871 }
872 };
873 #[cfg(not(feature = "repr"))]
874 let mut features = {
875 parent
876 .attributes
877 .parse_features::<EnumFeatures>()?
878 .into_inner()
879 .unwrap_or_default()
880 };
881
882 let schema_as = pop_feature!(features => Feature::As(_) as Option<As>);
883 let bound = pop_feature!(features => Feature::Bound(_) as Option<Bound>);
884
885 if parent.attributes.has_deprecated() {
886 features.push(Feature::Deprecated(true.into()))
887 }
888
889 Ok(Self {
890 schema_type: EnumSchemaType::Plain(PlainEnum::new(parent, variants, features)?),
891 schema_as,
892 schema_references: Vec::new(),
893 bound,
894 })
895 } else {
896 let mut enum_features = parent
897 .attributes
898 .parse_features::<MixedEnumFeatures>()?
899 .into_inner()
900 .unwrap_or_default();
901 let schema_as = pop_feature!(enum_features => Feature::As(_) as Option<As>);
902 let bound = pop_feature!(enum_features => Feature::Bound(_) as Option<Bound>);
903
904 if parent.attributes.has_deprecated() {
905 enum_features.push(Feature::Deprecated(true.into()))
906 }
907 let mut mixed_enum = MixedEnum::new(parent, variants, enum_features)?;
908 let schema_references = std::mem::take(&mut mixed_enum.schema_references);
909 Ok(Self {
910 schema_type: EnumSchemaType::Mixed(mixed_enum),
911 schema_as,
912 schema_references,
913 bound,
914 })
915 }
916 }
917}
918
919impl ToTokens for EnumSchema<'_> {
920 fn to_tokens(&self, tokens: &mut TokenStream) {
921 self.schema_type.to_tokens(tokens)
922 }
923}
924
925#[cfg_attr(feature = "debug", derive(Debug))]
926enum EnumSchemaType<'e> {
927 Mixed(MixedEnum<'e>),
928 Plain(PlainEnum<'e>),
929}
930
931impl ToTokens for EnumSchemaType<'_> {
932 fn to_tokens(&self, tokens: &mut TokenStream) {
933 let (attributes, description) = match self {
934 Self::Mixed(mixed) => {
935 mixed.to_tokens(tokens);
936 (mixed.root.attributes, &mixed.description)
937 }
938 Self::Plain(plain) => {
939 plain.to_tokens(tokens);
940 (plain.root.attributes, &plain.description)
941 }
942 };
943
944 let comments = CommentAttributes::from_attributes(attributes);
945 let description = description
946 .as_ref()
947 .map(ComponentDescription::Description)
948 .or(Some(ComponentDescription::CommentAttributes(&comments)));
949
950 description.to_tokens(tokens);
951 }
952}
953
954fn rename_enum_variant<'s>(
955 name: &str,
956 features: &mut Vec<Feature>,
957 variant_rules: &'s SerdeValue,
958 container_rules: &'s SerdeContainer,
959 rename_all: Option<&RenameAll>,
960) -> Option<Cow<'s, str>> {
961 let rename = pop_feature!(features => Feature::Rename(_) as Option<Rename>)
962 .map(|rename| rename.into_value());
963 let rename_to = variant_rules
964 .rename
965 .as_deref()
966 .map(Cow::Borrowed)
967 .or(rename.map(Cow::Owned));
968
969 let rename_all = container_rules.rename_all.as_ref().or(rename_all
970 .as_ref()
971 .map(|rename_all| rename_all.as_rename_rule()));
972
973 super::rename::<VariantRename>(name, rename_to, rename_all)
974}
975
976#[cfg_attr(feature = "debug", derive(Debug))]
977enum Property {
978 Schema(ComponentSchema),
979 SchemaWith(Feature),
980 FlattenedMap(FlattenedMapSchema),
981}
982
983impl ToTokensDiagnostics for Property {
984 fn to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostics> {
985 match self {
986 Self::Schema(schema) => schema.to_tokens(tokens),
987 Self::FlattenedMap(schema) => schema.to_tokens(tokens)?,
988 Self::SchemaWith(schema_with) => schema_with.to_tokens(tokens)?,
989 }
990 Ok(())
991 }
992}