utoipa_gen/component/
schema.rs

1use std::borrow::Cow;
2
3use proc_macro2::{Ident, Span, TokenStream};
4use proc_macro_error::abort;
5use quote::{format_ident, quote, ToTokens};
6use syn::{
7    parse::Parse, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute,
8    Data, Field, Fields, FieldsNamed, FieldsUnnamed, GenericArgument, GenericParam, Generics,
9    Lifetime, LifetimeParam, Path, PathArguments, Token, Type, Variant, Visibility,
10};
11
12use crate::{
13    component::features::{Example, Rename},
14    doc_comment::CommentAttributes,
15    Array, Deprecated, ResultExt,
16};
17
18use self::{
19    enum_variant::{
20        AdjacentlyTaggedEnum, CustomEnum, Enum, ObjectVariant, SimpleEnumVariant, TaggedEnum,
21        UntaggedEnum,
22    },
23    features::{
24        ComplexEnumFeatures, EnumFeatures, EnumNamedFieldVariantFeatures,
25        EnumUnnamedFieldVariantFeatures, FromAttributes, NamedFieldFeatures,
26        NamedFieldStructFeatures, UnnamedFieldStructFeatures,
27    },
28};
29
30use super::{
31    features::{
32        parse_features, pop_feature, pop_feature_as_inner, As, Feature, FeaturesExt, IntoInner,
33        RenameAll, ToTokensExt,
34    },
35    serde::{self, SerdeContainer, SerdeEnumRepr, SerdeValue},
36    ComponentSchema, FieldRename, TypeTree, ValueType, VariantRename,
37};
38
39mod enum_variant;
40mod features;
41pub mod xml;
42
43pub struct Schema<'a> {
44    ident: &'a Ident,
45    attributes: &'a [Attribute],
46    generics: &'a Generics,
47    aliases: Option<Punctuated<AliasSchema, Comma>>,
48    data: &'a Data,
49    vis: &'a Visibility,
50}
51
52impl<'a> Schema<'a> {
53    const TO_SCHEMA_LIFETIME: &'static str = "'__s";
54    pub fn new(
55        data: &'a Data,
56        attributes: &'a [Attribute],
57        ident: &'a Ident,
58        generics: &'a Generics,
59        vis: &'a Visibility,
60    ) -> Self {
61        let aliases = if generics.type_params().count() > 0 {
62            parse_aliases(attributes)
63        } else {
64            None
65        };
66
67        Self {
68            data,
69            ident,
70            attributes,
71            generics,
72            aliases,
73            vis,
74        }
75    }
76}
77
78impl ToTokens for Schema<'_> {
79    fn to_tokens(&self, tokens: &mut TokenStream) {
80        let ident = self.ident;
81        let variant = SchemaVariant::new(
82            self.data,
83            self.attributes,
84            ident,
85            self.generics,
86            None::<Vec<(TypeTree, &TypeTree)>>,
87        );
88
89        let (_, ty_generics, where_clause) = self.generics.split_for_impl();
90
91        let life = &Lifetime::new(Schema::TO_SCHEMA_LIFETIME, Span::call_site());
92
93        let schema_ty: Type = parse_quote!(#ident #ty_generics);
94        let schema_children = &*TypeTree::from_type(&schema_ty).children.unwrap_or_default();
95
96        let aliases = self.aliases.as_ref().map(|aliases| {
97            let alias_schemas = aliases
98                .iter()
99                .map(|alias| {
100                    let name = &*alias.name;
101                    let alias_type_tree = TypeTree::from_type(&alias.ty);
102
103                    let variant = SchemaVariant::new(
104                        self.data,
105                        self.attributes,
106                        ident,
107                        self.generics,
108                        alias_type_tree
109                            .children
110                            .map(|children| children.into_iter().zip(schema_children)),
111                    );
112                    quote! { (#name, #variant.into()) }
113                })
114                .collect::<Array<TokenStream>>();
115
116            quote! {
117                fn aliases() -> Vec<(& #life str, utoipa::openapi::schema::Schema)> {
118                    #alias_schemas.to_vec()
119                }
120            }
121        });
122
123        let type_aliases = self.aliases.as_ref().map(|aliases| {
124            aliases
125                .iter()
126                .map(|alias| {
127                    let name = quote::format_ident!("{}", alias.name);
128                    let ty = &alias.ty;
129                    let vis = self.vis;
130                    let name_generics = alias.get_lifetimes().fold(
131                        Punctuated::<&GenericArgument, Comma>::new(),
132                        |mut acc, lifetime| {
133                            acc.push(lifetime);
134                            acc
135                        },
136                    );
137
138                    quote! {
139                        #vis type #name < #name_generics > = #ty;
140                    }
141                })
142                .collect::<TokenStream>()
143        });
144
145        let name = if let Some(schema_as) = variant.get_schema_as() {
146            format_path_ref(&schema_as.0.path)
147        } else {
148            ident.to_string()
149        };
150
151        let schema_lifetime: GenericParam = LifetimeParam::new(life.clone()).into();
152        let schema_generics = Generics {
153            params: [schema_lifetime.clone()].into_iter().collect(),
154            ..Default::default()
155        };
156
157        let mut impl_generics = self.generics.clone();
158        impl_generics.params.push(schema_lifetime);
159        let (impl_generics, _, _) = impl_generics.split_for_impl();
160
161        tokens.extend(quote! {
162            impl #impl_generics utoipa::ToSchema #schema_generics for #ident #ty_generics #where_clause {
163                fn schema() -> (& #life str, utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>) {
164                    (#name, #variant.into())
165                }
166
167                #aliases
168            }
169
170            #type_aliases
171        })
172    }
173}
174
175#[cfg_attr(feature = "debug", derive(Debug))]
176enum SchemaVariant<'a> {
177    Named(NamedStructSchema<'a>),
178    Unnamed(UnnamedStructSchema<'a>),
179    Enum(EnumSchema<'a>),
180    Unit(UnitStructVariant),
181}
182
183impl<'a> SchemaVariant<'a> {
184    pub fn new<I: IntoIterator<Item = (TypeTree<'a>, &'a TypeTree<'a>)>>(
185        data: &'a Data,
186        attributes: &'a [Attribute],
187        ident: &'a Ident,
188        generics: &'a Generics,
189        aliases: Option<I>,
190    ) -> SchemaVariant<'a> {
191        match data {
192            Data::Struct(content) => match &content.fields {
193                Fields::Unnamed(fields) => {
194                    let FieldsUnnamed { unnamed, .. } = fields;
195                    let mut unnamed_features = attributes
196                        .parse_features::<UnnamedFieldStructFeatures>()
197                        .into_inner();
198
199                    let schema_as = pop_feature_as_inner!(unnamed_features => Feature::As(_v));
200                    Self::Unnamed(UnnamedStructSchema {
201                        struct_name: Cow::Owned(ident.to_string()),
202                        attributes,
203                        features: unnamed_features,
204                        fields: unnamed,
205                        schema_as,
206                    })
207                }
208                Fields::Named(fields) => {
209                    let FieldsNamed { named, .. } = fields;
210                    let mut named_features = attributes
211                        .parse_features::<NamedFieldStructFeatures>()
212                        .into_inner();
213                    let schema_as = pop_feature_as_inner!(named_features => Feature::As(_v));
214
215                    Self::Named(NamedStructSchema {
216                        struct_name: Cow::Owned(ident.to_string()),
217                        attributes,
218                        rename_all: named_features.pop_rename_all_feature(),
219                        features: named_features,
220                        fields: named,
221                        generics: Some(generics),
222                        schema_as,
223                        aliases: aliases.map(|aliases| aliases.into_iter().collect()),
224                    })
225                }
226                Fields::Unit => Self::Unit(UnitStructVariant),
227            },
228            Data::Enum(content) => Self::Enum(EnumSchema::new(
229                Cow::Owned(ident.to_string()),
230                &content.variants,
231                attributes,
232            )),
233            _ => abort!(
234                ident.span(),
235                "unexpected data type, expected syn::Data::Struct or syn::Data::Enum"
236            ),
237        }
238    }
239
240    fn get_schema_as(&self) -> &Option<As> {
241        match self {
242            Self::Enum(schema) => &schema.schema_as,
243            Self::Named(schema) => &schema.schema_as,
244            Self::Unnamed(schema) => &schema.schema_as,
245            _ => &None,
246        }
247    }
248}
249
250impl ToTokens for SchemaVariant<'_> {
251    fn to_tokens(&self, tokens: &mut TokenStream) {
252        match self {
253            Self::Enum(schema) => schema.to_tokens(tokens),
254            Self::Named(schema) => schema.to_tokens(tokens),
255            Self::Unnamed(schema) => schema.to_tokens(tokens),
256            Self::Unit(unit) => unit.to_tokens(tokens),
257        }
258    }
259}
260
261#[cfg_attr(feature = "debug", derive(Debug))]
262struct UnitStructVariant;
263
264impl ToTokens for UnitStructVariant {
265    fn to_tokens(&self, tokens: &mut TokenStream) {
266        tokens.extend(quote! {
267            utoipa::openapi::schema::empty()
268        });
269    }
270}
271
272#[cfg_attr(feature = "debug", derive(Debug))]
273pub struct NamedStructSchema<'a> {
274    pub struct_name: Cow<'a, str>,
275    pub fields: &'a Punctuated<Field, Comma>,
276    pub attributes: &'a [Attribute],
277    pub features: Option<Vec<Feature>>,
278    pub rename_all: Option<RenameAll>,
279    pub generics: Option<&'a Generics>,
280    pub aliases: Option<Vec<(TypeTree<'a>, &'a TypeTree<'a>)>>,
281    pub schema_as: Option<As>,
282}
283
284struct NamedStructFieldOptions<'a> {
285    property: Property,
286    rename_field_value: Option<Cow<'a, str>>,
287    required: Option<super::features::Required>,
288    is_option: bool,
289}
290
291impl NamedStructSchema<'_> {
292    fn field_as_schema_property<R>(
293        &self,
294        field: &Field,
295        container_rules: &Option<SerdeContainer>,
296        yield_: impl FnOnce(NamedStructFieldOptions<'_>) -> R,
297    ) -> R {
298        let type_tree = &mut TypeTree::from_type(&field.ty);
299        if let Some(aliases) = &self.aliases {
300            for (new_generic, old_generic_matcher) in aliases.iter() {
301                if let Some(generic_match) = type_tree.find_mut(old_generic_matcher) {
302                    *generic_match = new_generic.clone();
303                }
304            }
305        }
306
307        let mut field_features = field
308            .attrs
309            .parse_features::<NamedFieldFeatures>()
310            .into_inner();
311
312        let schema_default = self
313            .features
314            .as_ref()
315            .map(|features| features.iter().any(|f| matches!(f, Feature::Default(_))))
316            .unwrap_or(false);
317        let serde_default = container_rules
318            .as_ref()
319            .map(|rules| rules.default)
320            .unwrap_or(false);
321
322        if schema_default || serde_default {
323            let features_inner = field_features.get_or_insert(vec![]);
324            if !features_inner
325                .iter()
326                .any(|f| matches!(f, Feature::Default(_)))
327            {
328                let field_ident = field.ident.as_ref().unwrap().to_owned();
329                let struct_ident = format_ident!("{}", &self.struct_name);
330                features_inner.push(Feature::Default(
331                    crate::features::Default::new_default_trait(struct_ident, field_ident.into()),
332                ));
333            }
334        }
335
336        // check for Rust's `#[deprecated]` attribute first, then check for `deprecated` feature
337        let deprecated = super::get_deprecated(&field.attrs).or_else(|| {
338            pop_feature!(field_features => Feature::Deprecated(_)).and_then(|feature| match feature
339            {
340                Feature::Deprecated(_) => Some(Deprecated::True),
341                _ => None,
342            })
343        });
344
345        let rename_field =
346            pop_feature!(field_features => Feature::Rename(_)).and_then(|feature| match feature {
347                Feature::Rename(rename) => Some(Cow::Owned(rename.into_value())),
348                _ => None,
349            });
350
351        let value_type = field_features
352            .as_mut()
353            .and_then(|features| features.pop_value_type_feature());
354        let override_type_tree = value_type
355            .as_ref()
356            .map(|value_type| value_type.as_type_tree());
357        let comments = CommentAttributes::from_attributes(&field.attrs);
358        let schema_with = pop_feature!(field_features => Feature::SchemaWith(_));
359        let required = pop_feature_as_inner!(field_features => Feature::Required(_v));
360        let type_tree = override_type_tree.as_ref().unwrap_or(type_tree);
361        let is_option = type_tree.is_option();
362
363        yield_(NamedStructFieldOptions {
364            property: if let Some(schema_with) = schema_with {
365                Property::SchemaWith(schema_with)
366            } else {
367                Property::Schema(ComponentSchema::new(super::ComponentSchemaProps {
368                    type_tree,
369                    features: field_features,
370                    description: Some(&comments),
371                    deprecated: deprecated.as_ref(),
372                    object_name: self.struct_name.as_ref(),
373                }))
374            },
375            rename_field_value: rename_field,
376            required,
377            is_option,
378        })
379    }
380}
381
382impl ToTokens for NamedStructSchema<'_> {
383    fn to_tokens(&self, tokens: &mut TokenStream) {
384        let container_rules = serde::parse_container(self.attributes);
385
386        let object_tokens = self
387            .fields
388            .iter()
389            .filter_map(|field| {
390                let field_rule = serde::parse_value(&field.attrs);
391
392                if is_not_skipped(&field_rule) && !is_flatten(&field_rule) {
393                    Some((field, field_rule))
394                } else {
395                    None
396                }
397            })
398            .fold(
399                quote! { utoipa::openapi::ObjectBuilder::new() },
400                |mut object_tokens, (field, field_rule)| {
401                    let mut field_name = &*field.ident.as_ref().unwrap().to_string();
402
403                    if field_name.starts_with("r#") {
404                        field_name = &field_name[2..];
405                    }
406
407                    self.field_as_schema_property(
408                        field,
409                        &container_rules,
410                        |NamedStructFieldOptions {
411                             property,
412                             rename_field_value,
413                             required,
414                             is_option,
415                         }| {
416                            let rename_to = field_rule
417                                .as_ref()
418                                .and_then(|field_rule| {
419                                    field_rule.rename.as_deref().map(Cow::Borrowed)
420                                })
421                                .or(rename_field_value);
422                            let rename_all = container_rules
423                                .as_ref()
424                                .and_then(|container_rule| container_rule.rename_all.as_ref())
425                                .or_else(|| {
426                                    self.rename_all
427                                        .as_ref()
428                                        .map(|rename_all| rename_all.as_rename_rule())
429                                });
430
431                            let name =
432                                super::rename::<FieldRename>(field_name, rename_to, rename_all)
433                                    .unwrap_or(Cow::Borrowed(field_name));
434
435                            object_tokens.extend(quote! {
436                                .property(#name, #property)
437                            });
438
439                            if let Property::Schema(_) = property {
440                                if (!is_option
441                                    && super::is_required(
442                                        field_rule.as_ref(),
443                                        container_rules.as_ref(),
444                                    ))
445                                    || required
446                                        .as_ref()
447                                        .map(super::features::Required::is_true)
448                                        .unwrap_or(false)
449                                {
450                                    object_tokens.extend(quote! {
451                                        .required(#name)
452                                    })
453                                }
454                            }
455
456                            object_tokens
457                        },
458                    )
459                },
460            );
461
462        let flatten_fields: Vec<&Field> = self
463            .fields
464            .iter()
465            .filter(|field| {
466                let field_rule = serde::parse_value(&field.attrs);
467                is_flatten(&field_rule)
468            })
469            .collect();
470
471        if !flatten_fields.is_empty() {
472            tokens.extend(quote! {
473                utoipa::openapi::AllOfBuilder::new()
474            });
475
476            for field in flatten_fields {
477                self.field_as_schema_property(
478                    field,
479                    &container_rules,
480                    |NamedStructFieldOptions { property, .. }| {
481                        tokens.extend(quote! { .item(#property) });
482                    },
483                )
484            }
485
486            tokens.extend(quote! {
487                .item(#object_tokens)
488            })
489        } else {
490            tokens.extend(object_tokens)
491        }
492
493        if let Some(deprecated) = super::get_deprecated(self.attributes) {
494            tokens.extend(quote! { .deprecated(Some(#deprecated)) });
495        }
496
497        if let Some(struct_features) = self.features.as_ref() {
498            tokens.extend(struct_features.to_token_stream())
499        }
500
501        let description = CommentAttributes::from_attributes(self.attributes).as_formatted_string();
502        if !description.is_empty() {
503            tokens.extend(quote! {
504                .description(Some(#description))
505            })
506        }
507    }
508}
509
510#[cfg_attr(feature = "debug", derive(Debug))]
511struct UnnamedStructSchema<'a> {
512    struct_name: Cow<'a, str>,
513    fields: &'a Punctuated<Field, Comma>,
514    attributes: &'a [Attribute],
515    features: Option<Vec<Feature>>,
516    schema_as: Option<As>,
517}
518
519impl ToTokens for UnnamedStructSchema<'_> {
520    fn to_tokens(&self, tokens: &mut TokenStream) {
521        let fields_len = self.fields.len();
522        let first_field = self.fields.first().unwrap();
523        let first_part = &TypeTree::from_type(&first_field.ty);
524
525        let all_fields_are_same = fields_len == 1
526            || self.fields.iter().skip(1).all(|field| {
527                let schema_part = &TypeTree::from_type(&field.ty);
528
529                first_part == schema_part
530            });
531
532        let deprecated = super::get_deprecated(self.attributes);
533        if all_fields_are_same {
534            let mut unnamed_struct_features = self.features.clone();
535            let value_type = unnamed_struct_features
536                .as_mut()
537                .and_then(|features| features.pop_value_type_feature());
538            let override_type_tree = value_type
539                .as_ref()
540                .map(|value_type| value_type.as_type_tree());
541
542            if fields_len == 1 {
543                if let Some(ref mut features) = unnamed_struct_features {
544                    if pop_feature!(features => Feature::Default(crate::features::Default(None)))
545                        .is_some()
546                    {
547                        let struct_ident = format_ident!("{}", &self.struct_name);
548                        let index: syn::Index = 0.into();
549                        features.push(Feature::Default(
550                            crate::features::Default::new_default_trait(struct_ident, index.into()),
551                        ));
552                    }
553                }
554            }
555
556            tokens.extend(
557                ComponentSchema::new(super::ComponentSchemaProps {
558                    type_tree: override_type_tree.as_ref().unwrap_or(first_part),
559                    features: unnamed_struct_features,
560                    description: Some(&CommentAttributes::from_attributes(self.attributes)),
561                    deprecated: deprecated.as_ref(),
562                    object_name: self.struct_name.as_ref(),
563                })
564                .to_token_stream(),
565            );
566        } else {
567            // Struct that has multiple unnamed fields is serialized to array by default with serde.
568            // See: https://serde.rs/json.html
569            // Typically OpenAPI does not support multi type arrays thus we simply consider the case
570            // as generic object array
571            tokens.extend(quote! {
572                utoipa::openapi::ObjectBuilder::new()
573            });
574
575            if let Some(deprecated) = deprecated {
576                tokens.extend(quote! { .deprecated(Some(#deprecated)) });
577            }
578
579            if let Some(ref attrs) = self.features {
580                tokens.extend(attrs.to_token_stream())
581            }
582        }
583
584        if fields_len > 1 {
585            let description =
586                CommentAttributes::from_attributes(self.attributes).as_formatted_string();
587            tokens.extend(
588                quote! { .to_array_builder().description(Some(#description)).max_items(Some(#fields_len)).min_items(Some(#fields_len)) },
589            )
590        }
591    }
592}
593
594#[cfg_attr(feature = "debug", derive(Debug))]
595pub struct EnumSchema<'a> {
596    schema_type: EnumSchemaType<'a>,
597    schema_as: Option<As>,
598}
599
600impl<'e> EnumSchema<'e> {
601    pub fn new(
602        enum_name: Cow<'e, str>,
603        variants: &'e Punctuated<Variant, Comma>,
604        attributes: &'e [Attribute],
605    ) -> Self {
606        if variants
607            .iter()
608            .all(|variant| matches!(variant.fields, Fields::Unit))
609        {
610            #[cfg(feature = "repr")]
611            {
612                attributes
613                    .iter()
614                    .find_map(|attribute| {
615                        if attribute.path().is_ident("repr") {
616                            attribute.parse_args::<syn::TypePath>().ok()
617                        } else {
618                            None
619                        }
620                    })
621                    .map(|enum_type| {
622                        let mut repr_enum_features =
623                            features::parse_schema_features_with(attributes, |input| {
624                                Ok(parse_features!(
625                                    input as super::features::Example,
626                                    super::features::Default,
627                                    super::features::Title,
628                                    As
629                                ))
630                            })
631                            .unwrap_or_default();
632
633                        let schema_as =
634                            pop_feature_as_inner!(repr_enum_features => Feature::As(_v));
635                        Self {
636                            schema_type: EnumSchemaType::Repr(ReprEnum {
637                                variants,
638                                attributes,
639                                enum_type,
640                                enum_features: repr_enum_features,
641                            }),
642                            schema_as,
643                        }
644                    })
645                    .unwrap_or_else(|| {
646                        let mut simple_enum_features = attributes
647                            .parse_features::<EnumFeatures>()
648                            .into_inner()
649                            .unwrap_or_default();
650                        let schema_as =
651                            pop_feature_as_inner!(simple_enum_features => Feature::As(_v));
652                        let rename_all = simple_enum_features.pop_rename_all_feature();
653
654                        Self {
655                            schema_type: EnumSchemaType::Simple(SimpleEnum {
656                                attributes,
657                                variants,
658                                enum_features: simple_enum_features,
659                                rename_all,
660                            }),
661                            schema_as,
662                        }
663                    })
664            }
665
666            #[cfg(not(feature = "repr"))]
667            {
668                let mut simple_enum_features = attributes
669                    .parse_features::<EnumFeatures>()
670                    .into_inner()
671                    .unwrap_or_default();
672                let schema_as = pop_feature_as_inner!(simple_enum_features => Feature::As(_v));
673                let rename_all = simple_enum_features.pop_rename_all_feature();
674
675                Self {
676                    schema_type: EnumSchemaType::Simple(SimpleEnum {
677                        attributes,
678                        variants,
679                        enum_features: simple_enum_features,
680                        rename_all,
681                    }),
682                    schema_as,
683                }
684            }
685        } else {
686            let mut enum_features = attributes
687                .parse_features::<ComplexEnumFeatures>()
688                .into_inner()
689                .unwrap_or_default();
690            let schema_as = pop_feature_as_inner!(enum_features => Feature::As(_v));
691            let rename_all = enum_features.pop_rename_all_feature();
692
693            Self {
694                schema_type: EnumSchemaType::Complex(ComplexEnum {
695                    enum_name,
696                    attributes,
697                    variants,
698                    rename_all,
699                    enum_features,
700                }),
701                schema_as,
702            }
703        }
704    }
705}
706
707impl ToTokens for EnumSchema<'_> {
708    fn to_tokens(&self, tokens: &mut TokenStream) {
709        self.schema_type.to_tokens(tokens);
710    }
711}
712
713#[cfg_attr(feature = "debug", derive(Debug))]
714enum EnumSchemaType<'e> {
715    Simple(SimpleEnum<'e>),
716    #[cfg(feature = "repr")]
717    Repr(ReprEnum<'e>),
718    Complex(ComplexEnum<'e>),
719}
720
721impl ToTokens for EnumSchemaType<'_> {
722    fn to_tokens(&self, tokens: &mut TokenStream) {
723        let attributes = match self {
724            Self::Simple(simple) => {
725                simple.to_tokens(tokens);
726                simple.attributes
727            }
728            #[cfg(feature = "repr")]
729            Self::Repr(repr) => {
730                repr.to_tokens(tokens);
731                repr.attributes
732            }
733            Self::Complex(complex) => {
734                complex.to_tokens(tokens);
735                complex.attributes
736            }
737        };
738
739        if let Some(deprecated) = super::get_deprecated(attributes) {
740            tokens.extend(quote! { .deprecated(Some(#deprecated)) });
741        }
742
743        let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
744        if !description.is_empty() {
745            tokens.extend(quote! {
746                .description(Some(#description))
747            })
748        }
749    }
750}
751
752#[cfg(feature = "repr")]
753#[cfg_attr(feature = "debug", derive(Debug))]
754struct ReprEnum<'a> {
755    variants: &'a Punctuated<Variant, Comma>,
756    attributes: &'a [Attribute],
757    enum_type: syn::TypePath,
758    enum_features: Vec<Feature>,
759}
760
761#[cfg(feature = "repr")]
762impl ToTokens for ReprEnum<'_> {
763    fn to_tokens(&self, tokens: &mut TokenStream) {
764        let container_rules = serde::parse_container(self.attributes);
765
766        regular_enum_to_tokens(tokens, &container_rules, &self.enum_features, || {
767            self.variants
768                .iter()
769                .filter_map(|variant| {
770                    let variant_type = &variant.ident;
771                    let variant_rules = serde::parse_value(&variant.attrs);
772
773                    if is_not_skipped(&variant_rules) {
774                        let repr_type = &self.enum_type;
775                        Some(enum_variant::ReprVariant {
776                            value: quote! { Self::#variant_type as #repr_type },
777                            type_path: repr_type,
778                        })
779                    } else {
780                        None
781                    }
782                })
783                .collect::<Vec<enum_variant::ReprVariant<TokenStream>>>()
784        });
785    }
786}
787
788fn rename_enum_variant<'a>(
789    name: &'a str,
790    features: &mut Vec<Feature>,
791    variant_rules: &'a Option<SerdeValue>,
792    container_rules: &'a Option<SerdeContainer>,
793    rename_all: &'a Option<RenameAll>,
794) -> Option<Cow<'a, str>> {
795    let rename = features
796        .pop_rename_feature()
797        .map(|rename| rename.into_value());
798    let rename_to = variant_rules
799        .as_ref()
800        .and_then(|variant_rules| variant_rules.rename.as_deref().map(Cow::Borrowed))
801        .or_else(|| rename.map(Cow::Owned));
802
803    let rename_all = container_rules
804        .as_ref()
805        .and_then(|container_rules| container_rules.rename_all.as_ref())
806        .or_else(|| {
807            rename_all
808                .as_ref()
809                .map(|rename_all| rename_all.as_rename_rule())
810        });
811
812    super::rename::<VariantRename>(name, rename_to, rename_all)
813}
814
815#[cfg_attr(feature = "debug", derive(Debug))]
816struct SimpleEnum<'a> {
817    variants: &'a Punctuated<Variant, Comma>,
818    attributes: &'a [Attribute],
819    enum_features: Vec<Feature>,
820    rename_all: Option<RenameAll>,
821}
822
823impl ToTokens for SimpleEnum<'_> {
824    fn to_tokens(&self, tokens: &mut TokenStream) {
825        let container_rules = serde::parse_container(self.attributes);
826
827        regular_enum_to_tokens(tokens, &container_rules, &self.enum_features, || {
828            self.variants
829                .iter()
830                .filter_map(|variant| {
831                    let variant_rules = serde::parse_value(&variant.attrs);
832
833                    if is_not_skipped(&variant_rules) {
834                        Some((variant, variant_rules))
835                    } else {
836                        None
837                    }
838                })
839                .flat_map(|(variant, variant_rules)| {
840                    let name = &*variant.ident.to_string();
841                    let mut variant_features =
842                        features::parse_schema_features_with(&variant.attrs, |input| {
843                            Ok(parse_features!(input as Rename))
844                        })
845                        .unwrap_or_default();
846                    let variant_name = rename_enum_variant(
847                        name,
848                        &mut variant_features,
849                        &variant_rules,
850                        &container_rules,
851                        &self.rename_all,
852                    );
853
854                    variant_name
855                        .map(|name| SimpleEnumVariant {
856                            value: name.to_token_stream(),
857                        })
858                        .or_else(|| {
859                            Some(SimpleEnumVariant {
860                                value: name.to_token_stream(),
861                            })
862                        })
863                })
864                .collect::<Vec<SimpleEnumVariant<TokenStream>>>()
865        });
866    }
867}
868
869fn regular_enum_to_tokens<T: self::enum_variant::Variant>(
870    tokens: &mut TokenStream,
871    container_rules: &Option<SerdeContainer>,
872    enum_variant_features: &Vec<Feature>,
873    get_variants_tokens_vec: impl FnOnce() -> Vec<T>,
874) {
875    let enum_values = get_variants_tokens_vec();
876
877    tokens.extend(match container_rules {
878        Some(serde_container) => match &serde_container.enum_repr {
879            SerdeEnumRepr::ExternallyTagged => Enum::new(enum_values).to_token_stream(),
880            SerdeEnumRepr::InternallyTagged { tag } => TaggedEnum::new(
881                enum_values
882                    .into_iter()
883                    .map(|variant| (Cow::Borrowed(tag.as_str()), variant)),
884            )
885            .to_token_stream(),
886            SerdeEnumRepr::Untagged => UntaggedEnum::new().to_token_stream(),
887            SerdeEnumRepr::AdjacentlyTagged { tag, content } => {
888                AdjacentlyTaggedEnum::new(enum_values.into_iter().map(|variant| {
889                    (
890                        Cow::Borrowed(tag.as_str()),
891                        Cow::Borrowed(content.as_str()),
892                        variant,
893                    )
894                }))
895                .to_token_stream()
896            }
897            // This should not be possible as serde should not let that happen
898            SerdeEnumRepr::UnfinishedAdjacentlyTagged { .. } => panic!("Invalid serde enum repr"),
899        },
900        _ => Enum::new(enum_values).to_token_stream(),
901    });
902
903    tokens.extend(enum_variant_features.to_token_stream());
904}
905
906#[cfg_attr(feature = "debug", derive(Debug))]
907struct ComplexEnum<'a> {
908    variants: &'a Punctuated<Variant, Comma>,
909    attributes: &'a [Attribute],
910    enum_name: Cow<'a, str>,
911    enum_features: Vec<Feature>,
912    rename_all: Option<RenameAll>,
913}
914
915impl ComplexEnum<'_> {
916    /// Produce tokens that represent a variant of a [`ComplexEnum`].
917    fn variant_tokens(
918        &self,
919        name: Cow<'_, str>,
920        variant: &Variant,
921        variant_rules: &Option<SerdeValue>,
922        container_rules: &Option<SerdeContainer>,
923        rename_all: &Option<RenameAll>,
924    ) -> TokenStream {
925        // TODO need to be able to split variant.attrs for variant and the struct representation!
926        match &variant.fields {
927            Fields::Named(named_fields) => {
928                let (title_features, mut named_struct_features) = variant
929                    .attrs
930                    .parse_features::<EnumNamedFieldVariantFeatures>()
931                    .into_inner()
932                    .map(|features| features.split_for_title())
933                    .unwrap_or_default();
934                let variant_name = rename_enum_variant(
935                    name.as_ref(),
936                    &mut named_struct_features,
937                    variant_rules,
938                    container_rules,
939                    rename_all,
940                );
941
942                let example = pop_feature!(named_struct_features => Feature::Example(_));
943
944                self::enum_variant::Variant::to_tokens(&ObjectVariant {
945                    name: variant_name.unwrap_or(Cow::Borrowed(&name)),
946                    title: title_features.first().map(ToTokens::to_token_stream),
947                    example: example.as_ref().map(ToTokens::to_token_stream),
948                    item: NamedStructSchema {
949                        struct_name: Cow::Borrowed(&*self.enum_name),
950                        attributes: &variant.attrs,
951                        rename_all: named_struct_features.pop_rename_all_feature(),
952                        features: Some(named_struct_features),
953                        fields: &named_fields.named,
954                        generics: None,
955                        aliases: None,
956                        schema_as: None,
957                    },
958                })
959            }
960            Fields::Unnamed(unnamed_fields) => {
961                let (title_features, mut unnamed_struct_features) = variant
962                    .attrs
963                    .parse_features::<EnumUnnamedFieldVariantFeatures>()
964                    .into_inner()
965                    .map(|features| features.split_for_title())
966                    .unwrap_or_default();
967                let variant_name = rename_enum_variant(
968                    name.as_ref(),
969                    &mut unnamed_struct_features,
970                    variant_rules,
971                    container_rules,
972                    rename_all,
973                );
974
975                let example = pop_feature!(unnamed_struct_features => Feature::Example(_));
976
977                self::enum_variant::Variant::to_tokens(&ObjectVariant {
978                    name: variant_name.unwrap_or(Cow::Borrowed(&name)),
979                    title: title_features.first().map(ToTokens::to_token_stream),
980                    example: example.as_ref().map(ToTokens::to_token_stream),
981                    item: UnnamedStructSchema {
982                        struct_name: Cow::Borrowed(&*self.enum_name),
983                        attributes: &variant.attrs,
984                        features: Some(unnamed_struct_features),
985                        fields: &unnamed_fields.unnamed,
986                        schema_as: None,
987                    },
988                })
989            }
990            Fields::Unit => {
991                let mut unit_features =
992                    features::parse_schema_features_with(&variant.attrs, |input| {
993                        Ok(parse_features!(
994                            input as super::features::Title,
995                            RenameAll,
996                            Rename,
997                            Example
998                        ))
999                    })
1000                    .unwrap_or_default();
1001                let title = pop_feature!(unit_features => Feature::Title(_));
1002                let variant_name = rename_enum_variant(
1003                    name.as_ref(),
1004                    &mut unit_features,
1005                    variant_rules,
1006                    container_rules,
1007                    rename_all,
1008                );
1009
1010                let example: Option<Feature> = pop_feature!(unit_features => Feature::Example(_));
1011
1012                let description =
1013                    CommentAttributes::from_attributes(&variant.attrs).as_formatted_string();
1014                let description =
1015                    (!description.is_empty()).then(|| Feature::Description(description.into()));
1016
1017                // Unit variant is just simple enum with single variant.
1018                Enum::new([SimpleEnumVariant {
1019                    value: variant_name
1020                        .unwrap_or(Cow::Borrowed(&name))
1021                        .to_token_stream(),
1022                }])
1023                .with_title(title.as_ref().map(ToTokens::to_token_stream))
1024                .with_example(example.as_ref().map(ToTokens::to_token_stream))
1025                .with_description(description.as_ref().map(ToTokens::to_token_stream))
1026                .to_token_stream()
1027            }
1028        }
1029    }
1030
1031    /// Produce tokens that represent a variant of a [`ComplexEnum`] where serde enum attribute
1032    /// `untagged` applies.
1033    fn untagged_variant_tokens(&self, variant: &Variant) -> TokenStream {
1034        match &variant.fields {
1035            Fields::Named(named_fields) => {
1036                let mut named_struct_features = variant
1037                    .attrs
1038                    .parse_features::<EnumNamedFieldVariantFeatures>()
1039                    .into_inner()
1040                    .unwrap_or_default();
1041
1042                NamedStructSchema {
1043                    struct_name: Cow::Borrowed(&*self.enum_name),
1044                    attributes: &variant.attrs,
1045                    rename_all: named_struct_features.pop_rename_all_feature(),
1046                    features: Some(named_struct_features),
1047                    fields: &named_fields.named,
1048                    generics: None,
1049                    aliases: None,
1050                    schema_as: None,
1051                }
1052                .to_token_stream()
1053            }
1054            Fields::Unnamed(unnamed_fields) => {
1055                let unnamed_struct_features = variant
1056                    .attrs
1057                    .parse_features::<EnumUnnamedFieldVariantFeatures>()
1058                    .into_inner()
1059                    .unwrap_or_default();
1060
1061                UnnamedStructSchema {
1062                    struct_name: Cow::Borrowed(&*self.enum_name),
1063                    attributes: &variant.attrs,
1064                    features: Some(unnamed_struct_features),
1065                    fields: &unnamed_fields.unnamed,
1066                    schema_as: None,
1067                }
1068                .to_token_stream()
1069            }
1070            Fields::Unit => {
1071                let mut unit_features =
1072                    features::parse_schema_features_with(&variant.attrs, |input| {
1073                        Ok(parse_features!(input as super::features::Title))
1074                    })
1075                    .unwrap_or_default();
1076                let title = pop_feature!(unit_features => Feature::Title(_));
1077
1078                UntaggedEnum::with_title(title).to_token_stream()
1079            }
1080        }
1081    }
1082
1083    /// Produce tokens that represent a variant of a [`ComplexEnum`] where serde enum attribute
1084    /// `tag = ` applies.
1085    fn tagged_variant_tokens(
1086        &self,
1087        tag: &str,
1088        name: Cow<'_, str>,
1089        variant: &Variant,
1090        variant_rules: &Option<SerdeValue>,
1091        container_rules: &Option<SerdeContainer>,
1092        rename_all: &Option<RenameAll>,
1093    ) -> TokenStream {
1094        match &variant.fields {
1095            Fields::Named(named_fields) => {
1096                let (title_features, mut named_struct_features) = variant
1097                    .attrs
1098                    .parse_features::<EnumNamedFieldVariantFeatures>()
1099                    .into_inner()
1100                    .map(|features| features.split_for_title())
1101                    .unwrap_or_default();
1102                let variant_name = rename_enum_variant(
1103                    name.as_ref(),
1104                    &mut named_struct_features,
1105                    variant_rules,
1106                    container_rules,
1107                    rename_all,
1108                );
1109
1110                let named_enum = NamedStructSchema {
1111                    struct_name: Cow::Borrowed(&*self.enum_name),
1112                    attributes: &variant.attrs,
1113                    rename_all: named_struct_features.pop_rename_all_feature(),
1114                    features: Some(named_struct_features),
1115                    fields: &named_fields.named,
1116                    generics: None,
1117                    aliases: None,
1118                    schema_as: None,
1119                };
1120                let title = title_features.first().map(ToTokens::to_token_stream);
1121
1122                let variant_name_tokens = Enum::new([SimpleEnumVariant {
1123                    value: variant_name
1124                        .unwrap_or(Cow::Borrowed(&name))
1125                        .to_token_stream(),
1126                }]);
1127                quote! {
1128                    #named_enum
1129                        #title
1130                        .property(#tag, #variant_name_tokens)
1131                        .required(#tag)
1132                }
1133            }
1134            Fields::Unnamed(unnamed_fields) => {
1135                if unnamed_fields.unnamed.len() == 1 {
1136                    let (title_features, mut unnamed_struct_features) = variant
1137                        .attrs
1138                        .parse_features::<EnumUnnamedFieldVariantFeatures>()
1139                        .into_inner()
1140                        .map(|features| features.split_for_title())
1141                        .unwrap_or_default();
1142                    let variant_name = rename_enum_variant(
1143                        name.as_ref(),
1144                        &mut unnamed_struct_features,
1145                        variant_rules,
1146                        container_rules,
1147                        rename_all,
1148                    );
1149
1150                    let unnamed_enum = UnnamedStructSchema {
1151                        struct_name: Cow::Borrowed(&*self.enum_name),
1152                        attributes: &variant.attrs,
1153                        features: Some(unnamed_struct_features),
1154                        fields: &unnamed_fields.unnamed,
1155                        schema_as: None,
1156                    };
1157
1158                    let title = title_features.first().map(ToTokens::to_token_stream);
1159                    let variant_name_tokens = Enum::new([SimpleEnumVariant {
1160                        value: variant_name
1161                            .unwrap_or(Cow::Borrowed(&name))
1162                            .to_token_stream(),
1163                    }]);
1164
1165                    let is_reference = unnamed_fields.unnamed.iter().any(|field| {
1166                        let ty = TypeTree::from_type(&field.ty);
1167
1168                        ty.value_type == ValueType::Object
1169                    });
1170
1171                    if is_reference {
1172                        quote! {
1173                            utoipa::openapi::schema::AllOfBuilder::new()
1174                                #title
1175                                .item(#unnamed_enum)
1176                                .item(utoipa::openapi::schema::ObjectBuilder::new()
1177                                    .schema_type(utoipa::openapi::schema::SchemaType::Object)
1178                                    .property(#tag, #variant_name_tokens)
1179                                    .required(#tag)
1180                                )
1181                        }
1182                    } else {
1183                        quote! {
1184                            #unnamed_enum
1185                                #title
1186                                .schema_type(utoipa::openapi::schema::SchemaType::Object)
1187                                .property(#tag, #variant_name_tokens)
1188                                .required(#tag)
1189                        }
1190                    }
1191                } else {
1192                    abort!(
1193                        variant,
1194                        "Unnamed (tuple) enum variants are unsupported for internally tagged enums using the `tag = ` serde attribute";
1195
1196                        help = "Try using a different serde enum representation";
1197                        note = "See more about enum limitations here: `https://serde.rs/enum-representations.html#internally-tagged`"
1198                    );
1199                }
1200            }
1201            Fields::Unit => {
1202                let mut unit_features =
1203                    features::parse_schema_features_with(&variant.attrs, |input| {
1204                        Ok(parse_features!(input as super::features::Title, Rename))
1205                    })
1206                    .unwrap_or_default();
1207                let title = pop_feature!(unit_features => Feature::Title(_));
1208
1209                let variant_name = rename_enum_variant(
1210                    name.as_ref(),
1211                    &mut unit_features,
1212                    variant_rules,
1213                    container_rules,
1214                    rename_all,
1215                );
1216
1217                // Unit variant is just simple enum with single variant.
1218                let variant_tokens = Enum::new([SimpleEnumVariant {
1219                    value: variant_name
1220                        .unwrap_or(Cow::Borrowed(&name))
1221                        .to_token_stream(),
1222                }]);
1223
1224                quote! {
1225                    utoipa::openapi::schema::ObjectBuilder::new()
1226                        #title
1227                        .property(#tag, #variant_tokens)
1228                        .required(#tag)
1229                }
1230            }
1231        }
1232    }
1233
1234    // FIXME perhaps design this better to lessen the amount of args.
1235    #[allow(clippy::too_many_arguments)]
1236    fn adjacently_tagged_variant_tokens(
1237        &self,
1238        tag: &str,
1239        content: &str,
1240        name: Cow<'_, str>,
1241        variant: &Variant,
1242        variant_rules: &Option<SerdeValue>,
1243        container_rules: &Option<SerdeContainer>,
1244        rename_all: &Option<RenameAll>,
1245    ) -> TokenStream {
1246        match &variant.fields {
1247            Fields::Named(named_fields) => {
1248                let (title_features, mut named_struct_features) = variant
1249                    .attrs
1250                    .parse_features::<EnumNamedFieldVariantFeatures>()
1251                    .into_inner()
1252                    .map(|features| features.split_for_title())
1253                    .unwrap_or_default();
1254                let variant_name = rename_enum_variant(
1255                    name.as_ref(),
1256                    &mut named_struct_features,
1257                    variant_rules,
1258                    container_rules,
1259                    rename_all,
1260                );
1261
1262                let named_enum = NamedStructSchema {
1263                    struct_name: Cow::Borrowed(&*self.enum_name),
1264                    attributes: &variant.attrs,
1265                    rename_all: named_struct_features.pop_rename_all_feature(),
1266                    features: Some(named_struct_features),
1267                    fields: &named_fields.named,
1268                    generics: None,
1269                    aliases: None,
1270                    schema_as: None,
1271                };
1272                let title = title_features.first().map(ToTokens::to_token_stream);
1273
1274                let variant_name_tokens = Enum::new([SimpleEnumVariant {
1275                    value: variant_name
1276                        .unwrap_or(Cow::Borrowed(&name))
1277                        .to_token_stream(),
1278                }]);
1279                quote! {
1280                    utoipa::openapi::schema::ObjectBuilder::new()
1281                        #title
1282                        .schema_type(utoipa::openapi::schema::SchemaType::Object)
1283                        .property(#tag, #variant_name_tokens)
1284                        .required(#tag)
1285                        .property(#content, #named_enum)
1286                        .required(#content)
1287                }
1288            }
1289            Fields::Unnamed(unnamed_fields) => {
1290                if unnamed_fields.unnamed.len() == 1 {
1291                    let (title_features, mut unnamed_struct_features) = variant
1292                        .attrs
1293                        .parse_features::<EnumUnnamedFieldVariantFeatures>()
1294                        .into_inner()
1295                        .map(|features| features.split_for_title())
1296                        .unwrap_or_default();
1297                    let variant_name = rename_enum_variant(
1298                        name.as_ref(),
1299                        &mut unnamed_struct_features,
1300                        variant_rules,
1301                        container_rules,
1302                        rename_all,
1303                    );
1304
1305                    let unnamed_enum = UnnamedStructSchema {
1306                        struct_name: Cow::Borrowed(&*self.enum_name),
1307                        attributes: &variant.attrs,
1308                        features: Some(unnamed_struct_features),
1309                        fields: &unnamed_fields.unnamed,
1310                        schema_as: None,
1311                    };
1312
1313                    let title = title_features.first().map(ToTokens::to_token_stream);
1314                    let variant_name_tokens = Enum::new([SimpleEnumVariant {
1315                        value: variant_name
1316                            .unwrap_or(Cow::Borrowed(&name))
1317                            .to_token_stream(),
1318                    }]);
1319
1320                    quote! {
1321                        utoipa::openapi::schema::ObjectBuilder::new()
1322                            #title
1323                            .schema_type(utoipa::openapi::schema::SchemaType::Object)
1324                            .property(#tag, #variant_name_tokens)
1325                            .required(#tag)
1326                            .property(#content, #unnamed_enum)
1327                            .required(#content)
1328                    }
1329                } else {
1330                    abort!(
1331                        variant,
1332                        "Unnamed (tuple) enum variants are unsupported for adjacently tagged enums using the `tag = <tag>, content = <content>` serde attribute";
1333
1334                        help = "Try using a different serde enum representation";
1335                        note = "See more about enum limitations here: `https://serde.rs/enum-representations.html#adjacently-tagged`"
1336                    );
1337                }
1338            }
1339            Fields::Unit => {
1340                // In this case `content` is simply ignored - there is nothing to put in it.
1341
1342                let mut unit_features =
1343                    features::parse_schema_features_with(&variant.attrs, |input| {
1344                        Ok(parse_features!(input as super::features::Title, Rename))
1345                    })
1346                    .unwrap_or_default();
1347                let title = pop_feature!(unit_features => Feature::Title(_));
1348
1349                let variant_name = rename_enum_variant(
1350                    name.as_ref(),
1351                    &mut unit_features,
1352                    variant_rules,
1353                    container_rules,
1354                    rename_all,
1355                );
1356
1357                // Unit variant is just simple enum with single variant.
1358                let variant_tokens = Enum::new([SimpleEnumVariant {
1359                    value: variant_name
1360                        .unwrap_or(Cow::Borrowed(&name))
1361                        .to_token_stream(),
1362                }]);
1363
1364                quote! {
1365                    utoipa::openapi::schema::ObjectBuilder::new()
1366                        #title
1367                        .property(#tag, #variant_tokens)
1368                        .required(#tag)
1369                }
1370            }
1371        }
1372    }
1373}
1374
1375impl ToTokens for ComplexEnum<'_> {
1376    fn to_tokens(&self, tokens: &mut TokenStream) {
1377        let attributes = &self.attributes;
1378        let container_rules = serde::parse_container(attributes);
1379
1380        let enum_repr = container_rules
1381            .as_ref()
1382            .map(|rules| rules.enum_repr.clone())
1383            .unwrap_or_default();
1384        let tag = match &enum_repr {
1385            SerdeEnumRepr::AdjacentlyTagged { tag, .. }
1386            | SerdeEnumRepr::InternallyTagged { tag } => Some(tag),
1387            SerdeEnumRepr::ExternallyTagged
1388            | SerdeEnumRepr::Untagged
1389            | SerdeEnumRepr::UnfinishedAdjacentlyTagged { .. } => None,
1390        };
1391
1392        self.variants
1393            .iter()
1394            .filter_map(|variant: &Variant| {
1395                let variant_serde_rules = serde::parse_value(&variant.attrs);
1396                if is_not_skipped(&variant_serde_rules) {
1397                    Some((variant, variant_serde_rules))
1398                } else {
1399                    None
1400                }
1401            })
1402            .map(|(variant, variant_serde_rules)| {
1403                let variant_name = &*variant.ident.to_string();
1404
1405                match &enum_repr {
1406                    SerdeEnumRepr::ExternallyTagged => self.variant_tokens(
1407                        Cow::Borrowed(variant_name),
1408                        variant,
1409                        &variant_serde_rules,
1410                        &container_rules,
1411                        &self.rename_all,
1412                    ),
1413                    SerdeEnumRepr::InternallyTagged { tag } => self.tagged_variant_tokens(
1414                        tag,
1415                        Cow::Borrowed(variant_name),
1416                        variant,
1417                        &variant_serde_rules,
1418                        &container_rules,
1419                        &self.rename_all,
1420                    ),
1421                    SerdeEnumRepr::Untagged => self.untagged_variant_tokens(variant),
1422                    SerdeEnumRepr::AdjacentlyTagged { tag, content } => self
1423                        .adjacently_tagged_variant_tokens(
1424                            tag,
1425                            content,
1426                            Cow::Borrowed(variant_name),
1427                            variant,
1428                            &variant_serde_rules,
1429                            &container_rules,
1430                            &self.rename_all,
1431                        ),
1432                    SerdeEnumRepr::UnfinishedAdjacentlyTagged { .. } => {
1433                        unreachable!("Serde should not have parsed an UnfinishedAdjacentlyTagged")
1434                    }
1435                }
1436            })
1437            .collect::<CustomEnum<'_, TokenStream>>()
1438            .with_discriminator(tag.map(|t| Cow::Borrowed(t.as_str())))
1439            .to_tokens(tokens);
1440
1441        tokens.extend(self.enum_features.to_token_stream());
1442    }
1443}
1444
1445#[cfg_attr(feature = "debug", derive(Debug))]
1446#[derive(PartialEq)]
1447struct TypeTuple<'a, T>(T, &'a Ident);
1448
1449#[cfg_attr(feature = "debug", derive(Debug))]
1450enum Property {
1451    Schema(ComponentSchema),
1452    SchemaWith(Feature),
1453}
1454
1455impl ToTokens for Property {
1456    fn to_tokens(&self, tokens: &mut TokenStream) {
1457        match self {
1458            Self::Schema(schema) => schema.to_tokens(tokens),
1459            Self::SchemaWith(schema_with) => schema_with.to_tokens(tokens),
1460        }
1461    }
1462}
1463
1464trait SchemaFeatureExt {
1465    fn split_for_title(self) -> (Vec<Feature>, Vec<Feature>);
1466}
1467
1468impl SchemaFeatureExt for Vec<Feature> {
1469    fn split_for_title(self) -> (Vec<Feature>, Vec<Feature>) {
1470        self.into_iter()
1471            .partition(|feature| matches!(feature, Feature::Title(_)))
1472    }
1473}
1474
1475/// Reformat a path reference string that was generated using [`quote`] to be used as a nice compact schema reference,
1476/// by removing spaces between colon punctuation and `::` and the path segments.
1477pub(crate) fn format_path_ref(path: &Path) -> String {
1478    let mut path = path.clone();
1479
1480    // Generics and path arguments are unsupported
1481    if let Some(last_segment) = path.segments.last_mut() {
1482        last_segment.arguments = PathArguments::None;
1483    }
1484    // :: are not officially supported in the spec
1485    // See: https://github.com/juhaku/utoipa/pull/187#issuecomment-1173101405
1486    path.to_token_stream().to_string().replace(" :: ", ".")
1487}
1488
1489#[inline]
1490fn is_not_skipped(rule: &Option<SerdeValue>) -> bool {
1491    rule.as_ref().map(|value| !value.skip).unwrap_or(true)
1492}
1493
1494#[inline]
1495fn is_flatten(rule: &Option<SerdeValue>) -> bool {
1496    rule.as_ref().map(|value| value.flatten).unwrap_or(false)
1497}
1498
1499#[cfg_attr(feature = "debug", derive(Debug))]
1500pub struct AliasSchema {
1501    pub name: String,
1502    pub ty: Type,
1503}
1504
1505impl AliasSchema {
1506    fn get_lifetimes(&self) -> impl Iterator<Item = &GenericArgument> {
1507        fn lifetimes_from_type(ty: &Type) -> impl Iterator<Item = &GenericArgument> {
1508            match ty {
1509                Type::Path(type_path) => type_path
1510                    .path
1511                    .segments
1512                    .iter()
1513                    .flat_map(|segment| match &segment.arguments {
1514                        PathArguments::AngleBracketed(angle_bracketed_args) => {
1515                            Some(angle_bracketed_args.args.iter())
1516                        }
1517                        _ => None,
1518                    })
1519                    .flatten()
1520                    .flat_map(|arg| match arg {
1521                        GenericArgument::Type(type_argument) => {
1522                            lifetimes_from_type(type_argument).collect::<Vec<_>>()
1523                        }
1524                        _ => vec![arg],
1525                    })
1526                    .filter(|generic_arg| matches!(generic_arg, syn::GenericArgument::Lifetime(lifetime) if lifetime.ident != "'static")),
1527                _ => abort!(
1528                    &ty.span(),
1529                    "AliasSchema `get_lifetimes` only supports syn::TypePath types"
1530                ),
1531            }
1532        }
1533
1534        lifetimes_from_type(&self.ty)
1535    }
1536}
1537
1538impl Parse for AliasSchema {
1539    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
1540        let name = input.parse::<Ident>()?;
1541        input.parse::<Token![=]>()?;
1542
1543        Ok(Self {
1544            name: name.to_string(),
1545            ty: input.parse::<Type>()?,
1546        })
1547    }
1548}
1549
1550fn parse_aliases(attributes: &[Attribute]) -> Option<Punctuated<AliasSchema, Comma>> {
1551    attributes
1552        .iter()
1553        .find(|attribute| attribute.path().is_ident("aliases"))
1554        .map(|aliases| {
1555            aliases
1556                .parse_args_with(Punctuated::<AliasSchema, Comma>::parse_terminated)
1557                .unwrap_or_abort()
1558        })
1559}