utoipa_gen/component/
schema.rs

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        // TODO refactor this to avoid clone
151        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 = &param.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            // TODO refactor the clone away
603            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                    // TODO refactor the clone away
742                    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                // Pattern Attribute is only allowed for unnamed structs with single field
751                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            // Struct that has multiple unnamed fields is serialized to array by default with serde.
791            // See: https://serde.rs/json.html
792            // Typically OpenAPI does not support multi type arrays thus we simply consider the case
793            // as generic object array
794            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}