utoipa_gen/component/schema/
enums.rs

1use std::{borrow::Cow, ops::Deref};
2
3use proc_macro2::TokenStream;
4use quote::{quote, ToTokens};
5use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Fields, TypePath, Variant};
6
7use crate::{
8    component::{
9        features::{
10            attributes::{
11                Deprecated, Description, Discriminator, Example, Examples, NoRecursion, Rename,
12                RenameAll, Title,
13            },
14            parse_features, pop_feature, Feature, IntoInner, IsInline, ToTokensExt,
15        },
16        schema::features::{
17            EnumNamedFieldVariantFeatures, EnumUnnamedFieldVariantFeatures, FromAttributes,
18        },
19        serde::{SerdeContainer, SerdeEnumRepr, SerdeValue},
20        FeaturesExt, SchemaReference, TypeTree, ValueType,
21    },
22    doc_comment::CommentAttributes,
23    schema_type::SchemaType,
24    Array, AttributesExt, Diagnostics, ToTokensDiagnostics,
25};
26
27use super::{features, serde, NamedStructSchema, Root, UnnamedStructSchema};
28
29#[cfg_attr(feature = "debug", derive(Debug))]
30enum PlainEnumRepr<'p> {
31    Plain(Array<'p, TokenStream>),
32    Repr(Array<'p, TokenStream>, syn::TypePath),
33}
34
35#[cfg_attr(feature = "debug", derive(Debug))]
36pub struct PlainEnum<'e> {
37    pub root: &'e Root<'e>,
38    enum_variant: PlainEnumRepr<'e>,
39    serde_enum_repr: SerdeEnumRepr,
40    features: Vec<Feature>,
41    pub description: Option<Description>,
42}
43
44impl<'e> PlainEnum<'e> {
45    pub fn new(
46        root: &'e Root,
47        variants: &Punctuated<Variant, Comma>,
48        mut features: Vec<Feature>,
49    ) -> Result<Self, Diagnostics> {
50        #[cfg(feature = "repr")]
51        let repr_type_path = PlainEnum::get_repr_type(root.attributes)?;
52
53        #[cfg(not(feature = "repr"))]
54        let repr_type_path = None;
55
56        let rename_all = pop_feature!(features => Feature::RenameAll(_) as Option<RenameAll>);
57        let description = pop_feature!(features => Feature::Description(_) as Option<Description>);
58
59        let container_rules = serde::parse_container(root.attributes)?;
60        let variants_iter = variants
61            .iter()
62            .map(|variant| match serde::parse_value(&variant.attrs) {
63                Ok(variant_rules) => Ok((variant, variant_rules)),
64                Err(diagnostics) => Err(diagnostics),
65            })
66            .collect::<Result<Vec<_>, Diagnostics>>()?
67            .into_iter()
68            .filter_map(|(variant, variant_rules)| {
69                if variant_rules.skip {
70                    None
71                } else {
72                    Some((variant, variant_rules))
73                }
74            });
75
76        let enum_variant = match repr_type_path {
77            Some(repr_type_path) => PlainEnumRepr::Repr(
78                variants_iter
79                    .map(|(variant, _)| {
80                        let ty = &variant.ident;
81                        quote! {
82                            Self::#ty as #repr_type_path
83                        }
84                    })
85                    .collect::<Array<TokenStream>>(),
86                repr_type_path,
87            ),
88            None => PlainEnumRepr::Plain(
89                variants_iter
90                    .map(|(variant, variant_rules)| {
91                        let parsed_features_result =
92                            features::parse_schema_features_with(&variant.attrs, |input| {
93                                Ok(parse_features!(input as Rename))
94                            });
95
96                        match parsed_features_result {
97                            Ok(variant_features) => {
98                                Ok((variant, variant_rules, variant_features.unwrap_or_default()))
99                            }
100                            Err(diagnostics) => Err(diagnostics),
101                        }
102                    })
103                    .collect::<Result<Vec<_>, Diagnostics>>()?
104                    .into_iter()
105                    .map(|(variant, variant_rules, mut variant_features)| {
106                        let name = &*variant.ident.to_string();
107                        let renamed = super::rename_enum_variant(
108                            name,
109                            &mut variant_features,
110                            &variant_rules,
111                            &container_rules,
112                            rename_all.as_ref(),
113                        );
114
115                        renamed.unwrap_or(Cow::Borrowed(name)).to_token_stream()
116                    })
117                    .collect::<Array<TokenStream>>(),
118            ),
119        };
120
121        Ok(Self {
122            root,
123            enum_variant,
124            features,
125            serde_enum_repr: container_rules.enum_repr,
126            description,
127        })
128    }
129
130    #[cfg(feature = "repr")]
131    fn get_repr_type(attributes: &[syn::Attribute]) -> Result<Option<syn::TypePath>, syn::Error> {
132        attributes
133            .iter()
134            .find_map(|attr| {
135                if attr.path().is_ident("repr") {
136                    Some(attr.parse_args::<syn::TypePath>())
137                } else {
138                    None
139                }
140            })
141            .transpose()
142    }
143}
144
145impl ToTokens for PlainEnum<'_> {
146    fn to_tokens(&self, tokens: &mut TokenStream) {
147        let (variants, schema_type, enum_type) = match &self.enum_variant {
148            PlainEnumRepr::Plain(items) => (
149                Roo::Ref(items),
150                Roo::Owned(SchemaType {
151                    nullable: false,
152                    path: Cow::Owned(syn::parse_quote!(str)),
153                }),
154                Roo::Owned(quote! { &str }),
155            ),
156            PlainEnumRepr::Repr(repr, repr_type) => (
157                Roo::Ref(repr),
158                Roo::Owned(SchemaType {
159                    nullable: false,
160                    path: Cow::Borrowed(&repr_type.path),
161                }),
162                Roo::Owned(repr_type.path.to_token_stream()),
163            ),
164        };
165
166        match &self.serde_enum_repr {
167            SerdeEnumRepr::ExternallyTagged => {
168                EnumSchema::<PlainSchema>::with_types(variants, schema_type, enum_type)
169                    .to_tokens(tokens);
170            }
171            SerdeEnumRepr::InternallyTagged { tag } => {
172                let items = variants
173                    .iter()
174                    .map(|item| Array::Owned(vec![item]))
175                    .collect::<Array<_>>();
176                let schema_type = schema_type.as_ref();
177                let enum_type = enum_type.as_ref();
178
179                OneOf {
180                    items: &items
181                        .iter()
182                        .map(|item| {
183                            EnumSchema::<PlainSchema>::with_types(
184                                Roo::Ref(item),
185                                Roo::Ref(schema_type),
186                                Roo::Ref(enum_type),
187                            )
188                            .tagged(tag)
189                        })
190                        .collect(),
191                    discriminator: None,
192                }
193                .to_tokens(tokens)
194            }
195            SerdeEnumRepr::Untagged => {
196                // Even though untagged enum might have multiple variants, but unit type variants
197                // all will result `null` empty schema thus returning one empty schema is
198                // sufficient instead of returning one of N * `null` schema.
199                EnumSchema::<TokenStream>::untagged().to_tokens(tokens);
200            }
201            SerdeEnumRepr::AdjacentlyTagged { tag, content } => {
202                let items = variants
203                    .iter()
204                    .map(|item| Array::Owned(vec![item]))
205                    .collect::<Array<_>>();
206                let schema_type = schema_type.as_ref();
207                let enum_type = enum_type.as_ref();
208
209                OneOf {
210                    items: &items
211                        .iter()
212                        .map(|item| {
213                            EnumSchema::<ObjectSchema>::adjacently_tagged(
214                                PlainSchema::new(
215                                    item.deref(),
216                                    Roo::Ref(schema_type),
217                                    Roo::Ref(enum_type),
218                                ),
219                                content,
220                            )
221                            .tag(tag, PlainSchema::for_name(content))
222                        })
223                        .collect(),
224                    discriminator: None,
225                }
226                .to_tokens(tokens)
227            }
228            // This should not be possible as serde should not let that happen
229            SerdeEnumRepr::UnfinishedAdjacentlyTagged { .. } => {
230                unreachable!("Invalid serde enum repr, serde should have panicked and not reach here, plain enum")
231            }
232        };
233
234        tokens.extend(self.features.to_token_stream());
235    }
236}
237
238#[cfg_attr(feature = "debug", derive(Debug))]
239pub struct MixedEnum<'p> {
240    pub root: &'p Root<'p>,
241    pub tokens: TokenStream,
242    pub description: Option<Description>,
243    pub schema_references: Vec<SchemaReference>,
244}
245
246impl<'p> MixedEnum<'p> {
247    pub fn new(
248        root: &'p Root,
249        variants: &Punctuated<Variant, Comma>,
250        mut features: Vec<Feature>,
251    ) -> Result<Self, Diagnostics> {
252        let attributes = root.attributes;
253        let container_rules = serde::parse_container(attributes)?;
254
255        let rename_all = pop_feature!(features => Feature::RenameAll(_) as Option<RenameAll>);
256        let description = pop_feature!(features => Feature::Description(_) as Option<Description>);
257        let discriminator = pop_feature!(features => Feature::Discriminator(_));
258
259        let variants = variants
260            .iter()
261            .map(|variant| match serde::parse_value(&variant.attrs) {
262                Ok(variant_rules) => Ok((variant, variant_rules)),
263                Err(diagnostics) => Err(diagnostics),
264            })
265            .collect::<Result<Vec<_>, Diagnostics>>()?
266            .into_iter()
267            .filter_map(|(variant, variant_rules)| {
268                if variant_rules.skip {
269                    None
270                } else {
271                    let variant_features = match &variant.fields {
272                        Fields::Named(_) => {
273                            match variant
274                                .attrs
275                                .parse_features::<EnumNamedFieldVariantFeatures>()
276                            {
277                                Ok(features) => features.into_inner().unwrap_or_default(),
278                                Err(diagnostics) => return Some(Err(diagnostics)),
279                            }
280                        }
281                        Fields::Unnamed(_) => {
282                            match variant
283                                .attrs
284                                .parse_features::<EnumUnnamedFieldVariantFeatures>()
285                            {
286                                Ok(features) => features.into_inner().unwrap_or_default(),
287                                Err(diagnostics) => return Some(Err(diagnostics)),
288                            }
289                        }
290                        Fields::Unit => {
291                            let parse_unit_features =
292                                features::parse_schema_features_with(&variant.attrs, |input| {
293                                    Ok(parse_features!(
294                                        input as Title,
295                                        Rename,
296                                        Example,
297                                        Examples,
298                                        Deprecated
299                                    ))
300                                });
301
302                            match parse_unit_features {
303                                Ok(features) => features.unwrap_or_default(),
304                                Err(diagnostics) => return Some(Err(diagnostics)),
305                            }
306                        }
307                    };
308
309                    Some(Ok((variant, variant_rules, variant_features)))
310                }
311            })
312            .collect::<Result<Vec<_>, Diagnostics>>()?;
313
314        // discriminator is only supported when all variants are unnamed with single non primitive
315        // field
316        let discriminator_supported = variants
317            .iter()
318            .all(|(variant, _, features)|
319                matches!(&variant.fields, Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1
320                && TypeTree::from_type(&unnamed.unnamed.first().unwrap().ty).expect("unnamed field should be valid TypeTree").value_type == ValueType::Object
321                && !features.is_inline())
322            )
323            && matches!(container_rules.enum_repr, SerdeEnumRepr::Untagged);
324
325        if discriminator.is_some() && !discriminator_supported {
326            let discriminator: Discriminator =
327                IntoInner::<Option<Discriminator>>::into_inner(discriminator).unwrap();
328            return Err(Diagnostics::with_span(
329                discriminator.get_attribute().span(),
330                "Found discriminator in not discriminator supported context",
331            ).help("`discriminator` is only supported on enums with `#[serde(untagged)]` having unnamed field variants with single reference field.")
332            .note("Unnamed field variants with inlined or primitive schemas does not support discriminator.")
333            .note("Read more about discriminators from the specs <https://spec.openapis.org/oas/latest.html#discriminator-object>"));
334        }
335
336        let mut items = variants
337            .into_iter()
338            .map(|(variant, variant_serde_rules, mut variant_features)| {
339                if features
340                    .iter()
341                    .any(|feature| matches!(feature, Feature::NoRecursion(_)))
342                {
343                    variant_features.push(Feature::NoRecursion(NoRecursion));
344                }
345                MixedEnumContent::new(
346                    variant,
347                    root,
348                    &container_rules,
349                    rename_all.as_ref(),
350                    variant_serde_rules,
351                    variant_features,
352                )
353            })
354            .collect::<Result<Vec<MixedEnumContent>, Diagnostics>>()?;
355
356        let schema_references = items
357            .iter_mut()
358            .flat_map(|item| std::mem::take(&mut item.schema_references))
359            .collect::<Vec<_>>();
360
361        let one_of_enum = OneOf {
362            items: &Array::Owned(items),
363            discriminator,
364        };
365
366        let _ = pop_feature!(features => Feature::NoRecursion(_));
367        let mut tokens = one_of_enum.to_token_stream();
368        tokens.extend(features.to_token_stream());
369
370        Ok(Self {
371            root,
372            tokens,
373            description,
374            schema_references,
375        })
376    }
377}
378
379impl ToTokens for MixedEnum<'_> {
380    fn to_tokens(&self, tokens: &mut TokenStream) {
381        self.tokens.to_tokens(tokens);
382    }
383}
384
385#[cfg_attr(feature = "debug", derive(Debug))]
386struct MixedEnumContent {
387    tokens: TokenStream,
388    schema_references: Vec<SchemaReference>,
389}
390
391impl MixedEnumContent {
392    fn new(
393        variant: &Variant,
394        root: &Root,
395        serde_container: &SerdeContainer,
396        rename_all: Option<&RenameAll>,
397        variant_serde_rules: SerdeValue,
398        mut variant_features: Vec<Feature>,
399    ) -> Result<Self, Diagnostics> {
400        let mut tokens = TokenStream::new();
401        let name = variant.ident.to_string();
402        // TODO support `description = ...` attribute via Feature::Description
403        // let description =
404        //     pop_feature!(variant_features => Feature::Description(_) as Option<Description>);
405        let variant_description =
406            CommentAttributes::from_attributes(&variant.attrs).as_formatted_string();
407        let description: Option<Description> =
408            (!variant_description.is_empty()).then(|| variant_description.into());
409        if let Some(description) = description {
410            variant_features.push(Feature::Description(description))
411        }
412
413        if variant.attrs.has_deprecated() {
414            variant_features.push(Feature::Deprecated(true.into()))
415        }
416
417        let mut schema_references: Vec<SchemaReference> = Vec::new();
418        match &variant.fields {
419            Fields::Named(named) => {
420                let (variant_tokens, references) =
421                    MixedEnumContent::get_named_tokens_with_schema_references(
422                        root,
423                        MixedEnumVariant {
424                            variant,
425                            fields: &named.named,
426                            name,
427                        },
428                        variant_features,
429                        serde_container,
430                        variant_serde_rules,
431                        rename_all,
432                    )?;
433                schema_references.extend(references);
434                variant_tokens.to_tokens(&mut tokens);
435            }
436            Fields::Unnamed(unnamed) => {
437                let (variant_tokens, references) =
438                    MixedEnumContent::get_unnamed_tokens_with_schema_reference(
439                        root,
440                        MixedEnumVariant {
441                            variant,
442                            fields: &unnamed.unnamed,
443                            name,
444                        },
445                        variant_features,
446                        serde_container,
447                        variant_serde_rules,
448                        rename_all,
449                    )?;
450
451                schema_references.extend(references);
452                variant_tokens.to_tokens(&mut tokens);
453            }
454            Fields::Unit => {
455                let variant_tokens = MixedEnumContent::get_unit_tokens(
456                    name,
457                    variant_features,
458                    serde_container,
459                    variant_serde_rules,
460                    rename_all,
461                );
462                variant_tokens.to_tokens(&mut tokens);
463            }
464        }
465
466        Ok(Self {
467            tokens,
468            schema_references,
469        })
470    }
471
472    fn get_named_tokens_with_schema_references(
473        root: &Root,
474        variant: MixedEnumVariant,
475        mut variant_features: Vec<Feature>,
476        serde_container: &SerdeContainer,
477        variant_serde_rules: SerdeValue,
478        rename_all: Option<&RenameAll>,
479    ) -> Result<(TokenStream, Vec<SchemaReference>), Diagnostics> {
480        let MixedEnumVariant {
481            variant,
482            fields,
483            name,
484        } = variant;
485
486        let renamed = super::rename_enum_variant(
487            &name,
488            &mut variant_features,
489            &variant_serde_rules,
490            serde_container,
491            rename_all,
492        );
493        let name = renamed.unwrap_or(Cow::Owned(name));
494
495        let root = &Root {
496            ident: &variant.ident,
497            attributes: &variant.attrs,
498            generics: root.generics,
499        };
500
501        let tokens_with_schema_references = match &serde_container.enum_repr {
502            SerdeEnumRepr::ExternallyTagged => {
503                let (enum_features, variant_features) =
504                    MixedEnumContent::split_enum_features(variant_features);
505                let schema = NamedStructSchema::new(root, fields, variant_features)?;
506                let schema_tokens = schema.to_token_stream();
507
508                (
509                    EnumSchema::<ObjectSchema>::new(name.as_ref(), schema_tokens)
510                        .features(enum_features)
511                        .to_token_stream(),
512                    schema.fields_references,
513                )
514            }
515            SerdeEnumRepr::InternallyTagged { tag } => {
516                let (enum_features, variant_features) =
517                    MixedEnumContent::split_enum_features(variant_features);
518                let schema = NamedStructSchema::new(root, fields, variant_features)?;
519
520                let mut schema_tokens = schema.to_token_stream();
521                (
522                    if schema.is_all_of {
523                        let object_builder_tokens =
524                            quote! { utoipa::openapi::schema::Object::builder() };
525                        let enum_schema_tokens =
526                            EnumSchema::<ObjectSchema>::tagged(object_builder_tokens)
527                                .tag(tag, PlainSchema::for_name(name.as_ref()))
528                                .features(enum_features)
529                                .to_token_stream();
530                        schema_tokens.extend(quote! {
531                            .item(#enum_schema_tokens)
532                        });
533                        schema_tokens
534                    } else {
535                        EnumSchema::<ObjectSchema>::tagged(schema_tokens)
536                            .tag(tag, PlainSchema::for_name(name.as_ref()))
537                            .features(enum_features)
538                            .to_token_stream()
539                    },
540                    schema.fields_references,
541                )
542            }
543            SerdeEnumRepr::Untagged => {
544                let schema = NamedStructSchema::new(root, fields, variant_features)?;
545                (schema.to_token_stream(), schema.fields_references)
546            }
547            SerdeEnumRepr::AdjacentlyTagged { tag, content } => {
548                let (enum_features, variant_features) =
549                    MixedEnumContent::split_enum_features(variant_features);
550                let schema = NamedStructSchema::new(root, fields, variant_features)?;
551
552                let schema_tokens = schema.to_token_stream();
553                (
554                    EnumSchema::<ObjectSchema>::adjacently_tagged(schema_tokens, content)
555                        .tag(tag, PlainSchema::for_name(name.as_ref()))
556                        .features(enum_features)
557                        .to_token_stream(),
558                    schema.fields_references,
559                )
560            }
561            SerdeEnumRepr::UnfinishedAdjacentlyTagged { .. } => unreachable!(
562                "Invalid serde enum repr, serde should have panicked before reaching here"
563            ),
564        };
565
566        Ok(tokens_with_schema_references)
567    }
568
569    fn get_unnamed_tokens_with_schema_reference(
570        root: &Root,
571        variant: MixedEnumVariant,
572        mut variant_features: Vec<Feature>,
573        serde_container: &SerdeContainer,
574        variant_serde_rules: SerdeValue,
575        rename_all: Option<&RenameAll>,
576    ) -> Result<(TokenStream, Vec<SchemaReference>), Diagnostics> {
577        let MixedEnumVariant {
578            variant,
579            fields,
580            name,
581        } = variant;
582
583        let renamed = super::rename_enum_variant(
584            &name,
585            &mut variant_features,
586            &variant_serde_rules,
587            serde_container,
588            rename_all,
589        );
590        let name = renamed.unwrap_or(Cow::Owned(name));
591
592        let root = &Root {
593            ident: &variant.ident,
594            attributes: &variant.attrs,
595            generics: root.generics,
596        };
597
598        let tokens_with_schema_reference = match &serde_container.enum_repr {
599            SerdeEnumRepr::ExternallyTagged => {
600                let (enum_features, variant_features) =
601                    MixedEnumContent::split_enum_features(variant_features);
602                let schema = UnnamedStructSchema::new(root, fields, variant_features)?;
603
604                let schema_tokens = schema.to_token_stream();
605                (
606                    EnumSchema::<ObjectSchema>::new(name.as_ref(), schema_tokens)
607                        .features(enum_features)
608                        .to_token_stream(),
609                    schema.schema_references,
610                )
611            }
612            SerdeEnumRepr::InternallyTagged { tag } => {
613                let (enum_features, variant_features) =
614                    MixedEnumContent::split_enum_features(variant_features);
615                let schema = UnnamedStructSchema::new(root, fields, variant_features)?;
616
617                let schema_tokens = schema.to_token_stream();
618
619                let is_reference = fields
620                    .iter()
621                    .map(|field| TypeTree::from_type(&field.ty))
622                    .collect::<Result<Vec<TypeTree>, Diagnostics>>()?
623                    .iter()
624                    .any(|type_tree| type_tree.value_type == ValueType::Object);
625
626                (
627                    EnumSchema::<InternallyTaggedUnnamedSchema>::new(schema_tokens, is_reference)
628                        .tag(tag, PlainSchema::for_name(name.as_ref()))
629                        .features(enum_features)
630                        .to_token_stream(),
631                    schema.schema_references,
632                )
633            }
634            SerdeEnumRepr::Untagged => {
635                let schema = UnnamedStructSchema::new(root, fields, variant_features)?;
636                (schema.to_token_stream(), schema.schema_references)
637            }
638            SerdeEnumRepr::AdjacentlyTagged { tag, content } => {
639                if fields.len() > 1 {
640                    return Err(Diagnostics::with_span(variant.span(),
641                        "Unnamed (tuple) enum variants are unsupported for internally tagged enums using the `tag = ` serde attribute")
642                        .help("Try using a different serde enum representation")
643                        .note("See more about enum limitations here: `https://serde.rs/enum-representations.html#internally-tagged`")
644                    );
645                }
646
647                let (enum_features, variant_features) =
648                    MixedEnumContent::split_enum_features(variant_features);
649                let schema = UnnamedStructSchema::new(root, fields, variant_features)?;
650
651                let schema_tokens = schema.to_token_stream();
652                (
653                    EnumSchema::<ObjectSchema>::adjacently_tagged(schema_tokens, content)
654                        .tag(tag, PlainSchema::for_name(name.as_ref()))
655                        .features(enum_features)
656                        .to_token_stream(),
657                    schema.schema_references,
658                )
659            }
660            SerdeEnumRepr::UnfinishedAdjacentlyTagged { .. } => unreachable!(
661                "Invalid serde enum repr, serde should have panicked before reaching here"
662            ),
663        };
664
665        Ok(tokens_with_schema_reference)
666    }
667
668    fn get_unit_tokens(
669        name: String,
670        mut variant_features: Vec<Feature>,
671        serde_container: &SerdeContainer,
672        variant_serde_rules: SerdeValue,
673        rename_all: Option<&RenameAll>,
674    ) -> TokenStream {
675        let renamed = super::rename_enum_variant(
676            &name,
677            &mut variant_features,
678            &variant_serde_rules,
679            serde_container,
680            rename_all,
681        );
682        let name = renamed.unwrap_or(Cow::Owned(name));
683
684        match &serde_container.enum_repr {
685            SerdeEnumRepr::ExternallyTagged => EnumSchema::<PlainSchema>::new(name.as_ref())
686                .features(variant_features)
687                .to_token_stream(),
688            SerdeEnumRepr::InternallyTagged { tag } => {
689                EnumSchema::<PlainSchema>::new(name.as_ref())
690                    .tagged(tag)
691                    .features(variant_features)
692                    .to_token_stream()
693            }
694            SerdeEnumRepr::Untagged => {
695                let v: EnumSchema = EnumSchema::untagged().features(variant_features);
696                v.to_token_stream()
697            }
698            SerdeEnumRepr::AdjacentlyTagged { tag, .. } => {
699                EnumSchema::<PlainSchema>::new(name.as_ref())
700                    .tagged(tag)
701                    .features(variant_features)
702                    .to_token_stream()
703            }
704            SerdeEnumRepr::UnfinishedAdjacentlyTagged { .. } => unreachable!(
705                "Invalid serde enum repr, serde should have panicked before reaching here"
706            ),
707        }
708    }
709
710    fn split_enum_features(variant_features: Vec<Feature>) -> (Vec<Feature>, Vec<Feature>) {
711        let (enum_features, variant_features): (Vec<_>, Vec<_>) =
712            variant_features.into_iter().partition(|feature| {
713                matches!(
714                    feature,
715                    Feature::Title(_)
716                        | Feature::Example(_)
717                        | Feature::Examples(_)
718                        | Feature::Default(_)
719                        | Feature::Description(_)
720                        | Feature::Deprecated(_)
721                )
722            });
723
724        (enum_features, variant_features)
725    }
726}
727
728impl ToTokens for MixedEnumContent {
729    fn to_tokens(&self, tokens: &mut TokenStream) {
730        self.tokens.to_tokens(tokens);
731    }
732}
733
734#[cfg_attr(feature = "debug", derive(Debug))]
735pub struct MixedEnumVariant<'v> {
736    variant: &'v syn::Variant,
737    fields: &'v Punctuated<syn::Field, Comma>,
738    name: String,
739}
740
741#[cfg_attr(feature = "debug", derive(Debug))]
742pub struct EnumSchema<T = TokenStream> {
743    features: Vec<Feature>,
744    untagged: bool,
745    content: Option<T>,
746}
747
748impl<T> EnumSchema<T> {
749    fn untagged() -> EnumSchema<T> {
750        Self {
751            untagged: true,
752            features: Vec::new(),
753            content: None,
754        }
755    }
756
757    fn features(mut self, features: Vec<Feature>) -> Self {
758        self.features = features;
759
760        self
761    }
762}
763
764impl<T> ToTokens for EnumSchema<T>
765where
766    T: ToTokens,
767{
768    fn to_tokens(&self, tokens: &mut TokenStream) {
769        if let Some(content) = &self.content {
770            tokens.extend(content.to_token_stream());
771        }
772
773        if self.untagged {
774            tokens.extend(quote! {
775                utoipa::openapi::schema::Object::builder()
776                    .schema_type(utoipa::openapi::schema::Type::Null)
777                    .default(Some(utoipa::gen::serde_json::Value::Null))
778            })
779        }
780
781        tokens.extend(self.features.to_token_stream());
782    }
783}
784
785impl<'a> EnumSchema<ObjectSchema> {
786    fn new<T: ToTokens>(name: &'a str, item: T) -> Self {
787        let content = quote! {
788            utoipa::openapi::schema::Object::builder()
789                .property(#name, #item)
790                .required(#name)
791        };
792
793        Self {
794            content: Some(ObjectSchema(content)),
795            features: Vec::new(),
796            untagged: false,
797        }
798    }
799
800    fn tagged<T: ToTokens>(item: T) -> Self {
801        let content = item.to_token_stream();
802
803        Self {
804            content: Some(ObjectSchema(content)),
805            features: Vec::new(),
806            untagged: false,
807        }
808    }
809
810    fn tag(mut self, tag: &'a str, tag_schema: PlainSchema) -> Self {
811        let content = self.content.get_or_insert(ObjectSchema::default());
812
813        content.0.extend(quote! {
814            .property(#tag, utoipa::openapi::schema::Object::builder() #tag_schema)
815            .required(#tag)
816        });
817
818        self
819    }
820
821    fn adjacently_tagged<T: ToTokens>(item: T, content: &str) -> Self {
822        let content = quote! {
823            utoipa::openapi::schema::Object::builder()
824                .property(#content, #item)
825                .required(#content)
826        };
827
828        Self {
829            content: Some(ObjectSchema(content)),
830            features: Vec::new(),
831            untagged: false,
832        }
833    }
834}
835
836impl EnumSchema<InternallyTaggedUnnamedSchema> {
837    fn new<T: ToTokens>(item: T, is_reference: bool) -> Self {
838        let schema = item.to_token_stream();
839
840        let tokens = if is_reference {
841            quote! {
842                utoipa::openapi::schema::AllOfBuilder::new()
843                    .item(#schema)
844            }
845        } else {
846            quote! {
847                #schema
848                .schema_type(utoipa::openapi::schema::Type::Object)
849            }
850        };
851
852        Self {
853            content: Some(InternallyTaggedUnnamedSchema(tokens, is_reference)),
854            untagged: false,
855            features: Vec::new(),
856        }
857    }
858
859    fn tag(mut self, tag: &str, tag_schema: PlainSchema) -> Self {
860        let content = self
861            .content
862            .get_or_insert(InternallyTaggedUnnamedSchema::default());
863        let is_reference = content.1;
864
865        if is_reference {
866            content.0.extend(quote! {
867                .item(
868                    utoipa::openapi::schema::Object::builder()
869                        .property(#tag, utoipa::openapi::schema::Object::builder() #tag_schema)
870                        .required(#tag)
871                )
872            });
873        } else {
874            content.0.extend(quote! {
875                .property(#tag, utoipa::openapi::schema::Object::builder() #tag_schema)
876                .required(#tag)
877            });
878        }
879
880        self
881    }
882}
883
884impl<'a> EnumSchema<PlainSchema> {
885    fn new<N: ToTokens>(name: N) -> Self {
886        let plain_schema = PlainSchema::for_name(name);
887
888        Self {
889            content: Some(PlainSchema(quote! {
890                utoipa::openapi::schema::Object::builder() #plain_schema
891            })),
892            untagged: false,
893            features: Vec::new(),
894        }
895    }
896
897    fn with_types<T: ToTokens>(
898        items: Roo<'a, Array<'a, T>>,
899        schema_type: Roo<'a, SchemaType<'a>>,
900        enum_type: Roo<'a, TokenStream>,
901    ) -> Self {
902        let plain_schema = PlainSchema::new(&items, schema_type, enum_type);
903
904        Self {
905            content: Some(PlainSchema(quote! {
906                utoipa::openapi::schema::Object::builder() #plain_schema
907            })),
908            untagged: false,
909            features: Vec::new(),
910        }
911    }
912
913    fn tagged(mut self, tag: &str) -> Self {
914        if let Some(content) = self.content {
915            let plain_schema = content.0;
916            self.content = Some(PlainSchema(
917                quote! {
918                    utoipa::openapi::schema::Object::builder()
919                        .property(#tag, #plain_schema )
920                        .required(#tag)
921                }
922                .to_token_stream(),
923            ));
924        }
925
926        self
927    }
928}
929
930#[derive(Default)]
931struct ObjectSchema(TokenStream);
932
933impl ToTokens for ObjectSchema {
934    fn to_tokens(&self, tokens: &mut TokenStream) {
935        self.0.to_tokens(tokens);
936    }
937}
938
939#[derive(Default)]
940struct InternallyTaggedUnnamedSchema(TokenStream, bool);
941
942impl ToTokens for InternallyTaggedUnnamedSchema {
943    fn to_tokens(&self, tokens: &mut TokenStream) {
944        self.0.to_tokens(tokens);
945    }
946}
947
948#[derive(Default)]
949struct PlainSchema(TokenStream);
950
951impl PlainSchema {
952    fn get_default_types() -> (Roo<'static, SchemaType<'static>>, Roo<'static, TokenStream>) {
953        let type_path: TypePath = syn::parse_quote!(str);
954        let schema_type = SchemaType {
955            path: Cow::Owned(type_path.path),
956            nullable: false,
957        };
958        let enum_type = quote! { &str };
959
960        (Roo::Owned(schema_type), Roo::Owned(enum_type))
961    }
962
963    fn new<'a, T: ToTokens>(
964        items: &[T],
965        schema_type: Roo<'a, SchemaType<'a>>,
966        enum_type: Roo<'a, TokenStream>,
967    ) -> Self {
968        let schema_type = schema_type.to_token_stream();
969        let enum_type = enum_type.as_ref();
970        let items = Array::Borrowed(items);
971        let len = items.len();
972
973        let plain_enum = quote! {
974                .schema_type(#schema_type)
975                .enum_values::<[#enum_type; #len], #enum_type>(Some(#items))
976        };
977
978        Self(plain_enum.to_token_stream())
979    }
980
981    fn for_name<N: ToTokens>(name: N) -> Self {
982        let (schema_type, enum_type) = Self::get_default_types();
983        let name = &[name.to_token_stream()];
984        Self::new(name, schema_type, enum_type)
985    }
986}
987
988impl ToTokens for PlainSchema {
989    fn to_tokens(&self, tokens: &mut TokenStream) {
990        self.0.to_tokens(tokens);
991    }
992}
993
994#[cfg_attr(feature = "debug", derive(Debug))]
995pub struct OneOf<'a, T: ToTokens> {
996    items: &'a Array<'a, T>,
997    discriminator: Option<Feature>,
998}
999
1000impl<'a, T> ToTokens for OneOf<'a, T>
1001where
1002    T: ToTokens,
1003{
1004    fn to_tokens(&self, tokens: &mut TokenStream) {
1005        let items = self.items;
1006        let len = items.len();
1007
1008        // concat items
1009        let items_as_tokens = items.iter().fold(TokenStream::new(), |mut items, item| {
1010            items.extend(quote! {
1011                .item(#item)
1012            });
1013
1014            items
1015        });
1016
1017        // discriminator tokens will not fail
1018        let discriminator = self.discriminator.to_token_stream();
1019
1020        tokens.extend(quote! {
1021            Into::<utoipa::openapi::schema::OneOfBuilder>::into(utoipa::openapi::OneOf::with_capacity(#len))
1022                #items_as_tokens
1023                #discriminator
1024        });
1025    }
1026}
1027
1028/// `RefOrOwned` is simple `Cow` like type to wrap either `ref` or owned value. This allows passing
1029/// either owned or referenced values as if they were owned like the `Cow` does but this works with
1030/// non cloneable types. Thus values cannot be modified but they can be passed down as re-referenced
1031/// values by dereffing the original value. `Roo::Ref(original.deref())`.
1032#[cfg_attr(feature = "debug", derive(Debug))]
1033pub enum Roo<'t, T> {
1034    Ref(&'t T),
1035    Owned(T),
1036}
1037
1038impl<'t, T> Deref for Roo<'t, T> {
1039    type Target = T;
1040
1041    fn deref(&self) -> &Self::Target {
1042        match self {
1043            Self::Ref(t) => t,
1044            Self::Owned(t) => t,
1045        }
1046    }
1047}
1048
1049impl<'t, T> AsRef<T> for Roo<'t, T> {
1050    fn as_ref(&self) -> &T {
1051        self.deref()
1052    }
1053}