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 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 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 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 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 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 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#[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}