utoipa_gen/
component.rs

1use std::borrow::Cow;
2
3use proc_macro2::{Ident, Span, TokenStream};
4use proc_macro_error::{abort, abort_call_site};
5use quote::{quote, quote_spanned, ToTokens};
6use syn::spanned::Spanned;
7use syn::{Attribute, GenericArgument, Path, PathArguments, PathSegment, Type, TypePath};
8
9use crate::doc_comment::CommentAttributes;
10use crate::schema_type::SchemaFormat;
11use crate::{schema_type::SchemaType, Deprecated};
12
13use self::features::{
14    pop_feature, Feature, FeaturesExt, IsInline, Minimum, Nullable, ToTokensExt, Validatable,
15};
16use self::schema::format_path_ref;
17use self::serde::{RenameRule, SerdeContainer, SerdeValue};
18
19pub mod into_params;
20
21pub mod features;
22pub mod schema;
23pub mod serde;
24
25/// Check whether either serde `container_rule` or `field_rule` has _`default`_ attribute set.
26#[inline]
27fn is_default(container_rules: &Option<&SerdeContainer>, field_rule: &Option<&SerdeValue>) -> bool {
28    container_rules
29        .as_ref()
30        .map(|rule| rule.default)
31        .unwrap_or(false)
32        || field_rule
33            .as_ref()
34            .map(|rule| rule.default)
35            .unwrap_or(false)
36}
37
38/// Find `#[deprecated]` attribute from given attributes. Typically derive type attributes
39/// or field attributes of struct.
40fn get_deprecated(attributes: &[Attribute]) -> Option<Deprecated> {
41    attributes.iter().find_map(|attribute| {
42        if attribute
43            .path()
44            .get_ident()
45            .map(|ident| *ident == "deprecated")
46            .unwrap_or(false)
47        {
48            Some(Deprecated::True)
49        } else {
50            None
51        }
52    })
53}
54
55/// Check whether field is required based on following rules.
56///
57/// * If field has not serde's `skip_serializing_if`
58/// * Field has not `serde_with` double option
59/// * Field is not default
60pub fn is_required(
61    field_rule: Option<&SerdeValue>,
62    container_rules: Option<&SerdeContainer>,
63) -> bool {
64    !field_rule
65        .map(|rule| rule.skip_serializing_if)
66        .unwrap_or(false)
67        && !field_rule.map(|rule| rule.double_option).unwrap_or(false)
68        && !is_default(&container_rules, &field_rule)
69}
70
71#[cfg_attr(feature = "debug", derive(Debug))]
72enum TypeTreeValue<'t> {
73    TypePath(&'t TypePath),
74    Path(&'t Path),
75    /// Slice and array types need to be manually defined, since they cannot be recognized from
76    /// generic arguments.
77    Array(Vec<TypeTreeValue<'t>>, Span),
78    UnitType,
79    Tuple(Vec<TypeTreeValue<'t>>, Span),
80}
81
82impl PartialEq for TypeTreeValue<'_> {
83    fn eq(&self, other: &Self) -> bool {
84        match self {
85            Self::Path(_) => self == other,
86            Self::TypePath(_) => self == other,
87            Self::Array(array, _) => matches!(other, Self::Array(other, _) if other == array),
88            Self::Tuple(tuple, _) => matches!(other, Self::Tuple(other, _) if other == tuple),
89            Self::UnitType => self == other,
90        }
91    }
92}
93
94/// [`TypeTree`] of items which represents a single parsed `type` of a
95/// `Schema`, `Parameter` or `FnArg`
96#[cfg_attr(feature = "debug", derive(Debug))]
97#[derive(Clone)]
98pub struct TypeTree<'t> {
99    pub path: Option<Cow<'t, Path>>,
100    pub span: Option<Span>,
101    pub value_type: ValueType,
102    pub generic_type: Option<GenericType>,
103    pub children: Option<Vec<TypeTree<'t>>>,
104}
105
106impl<'t> TypeTree<'t> {
107    pub fn from_type(ty: &'t Type) -> TypeTree<'t> {
108        Self::convert_types(Self::get_type_tree_values(ty))
109            .next()
110            .expect("TypeTree from type should have one TypeTree parent")
111    }
112
113    fn get_type_tree_values(ty: &'t Type) -> Vec<TypeTreeValue> {
114        match ty {
115            Type::Path(path) => {
116                vec![TypeTreeValue::TypePath(path)]
117            },
118            Type::Reference(reference) => Self::get_type_tree_values(reference.elem.as_ref()),
119            Type::Tuple(tuple) => {
120                // Detect unit type ()
121                if tuple.elems.is_empty() { return vec![TypeTreeValue::UnitType] }
122
123                vec![TypeTreeValue::Tuple(tuple.elems.iter().flat_map(Self::get_type_tree_values).collect(), tuple.span())]
124            },
125            Type::Group(group) => Self::get_type_tree_values(group.elem.as_ref()),
126            Type::Slice(slice) => vec![TypeTreeValue::Array(Self::get_type_tree_values(&slice.elem), slice.bracket_token.span.join())],
127            Type::Array(array) => vec![TypeTreeValue::Array(Self::get_type_tree_values(&array.elem), array.bracket_token.span.join())],
128            Type::TraitObject(trait_object) => {
129                trait_object
130                    .bounds
131                    .iter()
132                    .find_map(|bound| {
133                        match &bound {
134                            syn::TypeParamBound::Trait(trait_bound) => Some(&trait_bound.path),
135                            syn::TypeParamBound::Lifetime(_) => None,
136                            syn::TypeParamBound::Verbatim(_) => None,
137                            _ => todo!("TypeTree trait object found unrecognized TypeParamBound"),
138                        }
139                    })
140                    .map(|path| vec![TypeTreeValue::Path(path)]).unwrap_or_else(Vec::new)
141            }
142            _ => abort_call_site!(
143                "unexpected type in component part get type path, expected one of: Path, Tuple, Reference, Group, Array, Slice, TraitObject"
144            ),
145        }
146    }
147
148    fn convert_types(paths: Vec<TypeTreeValue<'t>>) -> impl Iterator<Item = TypeTree<'t>> {
149        paths.into_iter().map(|value| {
150            let path = match value {
151                TypeTreeValue::TypePath(type_path) => &type_path.path,
152                TypeTreeValue::Path(path) => path,
153                TypeTreeValue::Array(value, span) => {
154                    let array: Path = Ident::new("Array", span).into();
155                    return TypeTree {
156                        path: Some(Cow::Owned(array)),
157                        span: Some(span),
158                        value_type: ValueType::Object,
159                        generic_type: Some(GenericType::Vec),
160                        children: Some(Self::convert_types(value).collect()),
161                    };
162                }
163                TypeTreeValue::Tuple(tuple, span) => {
164                    return TypeTree {
165                        path: None,
166                        span: Some(span),
167                        children: Some(Self::convert_types(tuple).collect()),
168                        generic_type: None,
169                        value_type: ValueType::Tuple,
170                    }
171                }
172                TypeTreeValue::UnitType => {
173                    return TypeTree {
174                        path: None,
175                        span: None,
176                        value_type: ValueType::Tuple,
177                        generic_type: None,
178                        children: None,
179                    }
180                }
181            };
182
183            // there will always be one segment at least
184            let last_segment = path
185                .segments
186                .last()
187                .expect("at least one segment within path in TypeTree::convert_types");
188
189            if last_segment.arguments.is_empty() {
190                Self::convert(path, last_segment)
191            } else {
192                Self::resolve_schema_type(path, last_segment)
193            }
194        })
195    }
196
197    // Only when type is a generic type we get to this function.
198    fn resolve_schema_type(path: &'t Path, last_segment: &'t PathSegment) -> TypeTree<'t> {
199        if last_segment.arguments.is_empty() {
200            abort!(
201                last_segment.ident,
202                "expected at least one angle bracket argument but was 0"
203            );
204        };
205
206        let mut generic_schema_type = Self::convert(path, last_segment);
207
208        let mut generic_types = match &last_segment.arguments {
209            PathArguments::AngleBracketed(angle_bracketed_args) => {
210                // if all type arguments are lifetimes we ignore the generic type
211                if angle_bracketed_args.args.iter().all(|arg| {
212                    matches!(
213                        arg,
214                        GenericArgument::Lifetime(_) | GenericArgument::Const(_)
215                    )
216                }) {
217                    None
218                } else {
219                    Some(
220                        angle_bracketed_args
221                            .args
222                            .iter()
223                            .filter(|arg| {
224                                !matches!(
225                                    arg,
226                                    GenericArgument::Lifetime(_) | GenericArgument::Const(_)
227                                )
228                            })
229                            .map(|arg| match arg {
230                                GenericArgument::Type(arg) => arg,
231                                _ => abort!(
232                                    arg,
233                                    "expected generic argument type or generic argument lifetime"
234                                ),
235                            }),
236                    )
237                }
238            }
239            _ => abort!(
240                last_segment.ident,
241                "unexpected path argument, expected angle bracketed path argument"
242            ),
243        };
244
245        generic_schema_type.children = generic_types
246            .as_mut()
247            .map(|generic_type| generic_type.map(Self::from_type).collect());
248
249        generic_schema_type
250    }
251
252    fn convert(path: &'t Path, last_segment: &'t PathSegment) -> TypeTree<'t> {
253        let generic_type = Self::get_generic_type(last_segment);
254        let schema_type = SchemaType(path);
255
256        Self {
257            path: Some(Cow::Borrowed(path)),
258            span: Some(path.span()),
259            value_type: if schema_type.is_primitive() {
260                ValueType::Primitive
261            } else if schema_type.is_value() {
262                ValueType::Value
263            } else {
264                ValueType::Object
265            },
266            generic_type,
267            children: None,
268        }
269    }
270
271    // TODO should we recognize unknown generic types with `GenericType::Unknown` instead of `None`?
272    fn get_generic_type(segment: &PathSegment) -> Option<GenericType> {
273        match &*segment.ident.to_string() {
274            "HashMap" | "Map" | "BTreeMap" => Some(GenericType::Map),
275            #[cfg(feature = "indexmap")]
276            "IndexMap" => Some(GenericType::Map),
277            "Vec" => Some(GenericType::Vec),
278            #[cfg(feature = "smallvec")]
279            "SmallVec" => Some(GenericType::SmallVec),
280            "Option" => Some(GenericType::Option),
281            "Cow" => Some(GenericType::Cow),
282            "Box" => Some(GenericType::Box),
283            #[cfg(feature = "rc_schema")]
284            "Arc" => Some(GenericType::Arc),
285            #[cfg(feature = "rc_schema")]
286            "Rc" => Some(GenericType::Rc),
287            "RefCell" => Some(GenericType::RefCell),
288            _ => None,
289        }
290    }
291
292    /// Check whether [`TypeTreeValue`]'s [`syn::TypePath`] or any if it's `children`s [`syn::TypePath`]
293    /// is a given type as [`str`].
294    pub fn is(&self, s: &str) -> bool {
295        let mut is = self
296            .path
297            .as_ref()
298            .map(|path| {
299                path.segments
300                    .last()
301                    .expect("expected at least one segment in TreeTypeValue path")
302                    .ident
303                    == s
304            })
305            .unwrap_or(false);
306
307        if let Some(ref children) = self.children {
308            is = is || children.iter().any(|child| child.is(s));
309        }
310
311        is
312    }
313
314    fn find_mut(&mut self, type_tree: &TypeTree) -> Option<&mut Self> {
315        let is = self
316            .path
317            .as_mut()
318            .map(|p| matches!(&type_tree.path, Some(path) if path.as_ref() == p.as_ref()))
319            .unwrap_or(false);
320
321        if is {
322            Some(self)
323        } else {
324            self.children.as_mut().and_then(|children| {
325                children
326                    .iter_mut()
327                    .find_map(|child| Self::find_mut(child, type_tree))
328            })
329        }
330    }
331
332    /// `Object` virtual type is used when generic object is required in OpenAPI spec. Typically used
333    /// with `value_type` attribute to hinder the actual type.
334    pub fn is_object(&self) -> bool {
335        self.is("Object")
336    }
337
338    /// `Value` virtual type is used when any JSON value is required in OpenAPI spec. Typically used
339    /// with `value_type` attribute for a member of type `serde_json::Value`.
340    pub fn is_value(&self) -> bool {
341        self.is("Value")
342    }
343
344    /// Check whether the [`TypeTree`]'s `generic_type` is [`GenericType::Option`]
345    pub fn is_option(&self) -> bool {
346        matches!(self.generic_type, Some(GenericType::Option))
347    }
348}
349
350impl PartialEq for TypeTree<'_> {
351    #[cfg(feature = "debug")]
352    fn eq(&self, other: &Self) -> bool {
353        self.path == other.path
354            && self.value_type == other.value_type
355            && self.generic_type == other.generic_type
356            && self.children == other.children
357    }
358
359    #[cfg(not(feature = "debug"))]
360    fn eq(&self, other: &Self) -> bool {
361        let path_eg = match (self.path.as_ref(), other.path.as_ref()) {
362            (Some(Cow::Borrowed(self_path)), Some(Cow::Borrowed(other_path))) => {
363                self_path.into_token_stream().to_string()
364                    == other_path.into_token_stream().to_string()
365            }
366            (Some(Cow::Owned(self_path)), Some(Cow::Owned(other_path))) => {
367                self_path.to_token_stream().to_string()
368                    == other_path.into_token_stream().to_string()
369            }
370            (None, None) => true,
371            _ => false,
372        };
373
374        path_eg
375            && self.value_type == other.value_type
376            && self.generic_type == other.generic_type
377            && self.children == other.children
378    }
379}
380
381#[cfg_attr(feature = "debug", derive(Debug))]
382#[derive(Clone, Copy, PartialEq, Eq)]
383pub enum ValueType {
384    Primitive,
385    Object,
386    Tuple,
387    Value,
388}
389
390#[cfg_attr(feature = "debug", derive(Debug))]
391#[derive(PartialEq, Eq, Clone, Copy)]
392pub enum GenericType {
393    Vec,
394    #[cfg(feature = "smallvec")]
395    SmallVec,
396    Map,
397    Option,
398    Cow,
399    Box,
400    RefCell,
401    #[cfg(feature = "rc_schema")]
402    Arc,
403    #[cfg(feature = "rc_schema")]
404    Rc,
405}
406
407trait Rename {
408    fn rename(rule: &RenameRule, value: &str) -> String;
409}
410
411/// Performs a rename for given `value` based on given rules. If no rules were
412/// provided returns [`None`]
413///
414/// Method accepts 3 arguments.
415/// * `value` to rename.
416/// * `to` Optional rename to value for fields with _`rename`_ property.
417/// * `container_rule` which is used to rename containers with _`rename_all`_ property.
418fn rename<'r, R: Rename>(
419    value: &'r str,
420    to: Option<Cow<'r, str>>,
421    container_rule: Option<&'r RenameRule>,
422) -> Option<Cow<'r, str>> {
423    let rename = to.and_then(|to| if !to.is_empty() { Some(to) } else { None });
424
425    rename.or_else(|| {
426        container_rule
427            .as_ref()
428            .map(|container_rule| Cow::Owned(R::rename(container_rule, value)))
429    })
430}
431
432/// Can be used to perform rename on container level e.g `struct`, `enum` or `enum` `variant` level.
433struct VariantRename;
434
435impl Rename for VariantRename {
436    fn rename(rule: &RenameRule, value: &str) -> String {
437        rule.rename_variant(value)
438    }
439}
440
441/// Can be used to perform rename on field level of a container e.g `struct`.
442struct FieldRename;
443
444impl Rename for FieldRename {
445    fn rename(rule: &RenameRule, value: &str) -> String {
446        rule.rename(value)
447    }
448}
449
450#[cfg_attr(feature = "debug", derive(Debug))]
451pub struct ComponentSchemaProps<'c> {
452    pub type_tree: &'c TypeTree<'c>,
453    pub features: Option<Vec<Feature>>,
454    pub(crate) description: Option<&'c CommentAttributes>,
455    pub(crate) deprecated: Option<&'c Deprecated>,
456    pub object_name: &'c str,
457}
458
459#[cfg_attr(feature = "debug", derive(Debug))]
460pub struct ComponentSchema {
461    tokens: TokenStream,
462}
463
464impl<'c> ComponentSchema {
465    pub fn new(
466        ComponentSchemaProps {
467            type_tree,
468            features,
469            description,
470            deprecated,
471            object_name,
472        }: ComponentSchemaProps,
473    ) -> Self {
474        let mut tokens = TokenStream::new();
475        let mut features = features.unwrap_or(Vec::new());
476        let deprecated_stream = ComponentSchema::get_deprecated(deprecated);
477        let description_stream = ComponentSchema::get_description(description);
478
479        match type_tree.generic_type {
480            Some(GenericType::Map) => ComponentSchema::map_to_tokens(
481                &mut tokens,
482                features,
483                type_tree,
484                object_name,
485                description_stream,
486                deprecated_stream,
487            ),
488            Some(GenericType::Vec) => ComponentSchema::vec_to_tokens(
489                &mut tokens,
490                features,
491                type_tree,
492                object_name,
493                description_stream,
494                deprecated_stream,
495            ),
496            #[cfg(feature = "smallvec")]
497            Some(GenericType::SmallVec) => ComponentSchema::vec_to_tokens(
498                &mut tokens,
499                features,
500                type_tree,
501                object_name,
502                description_stream,
503                deprecated_stream,
504            ),
505            Some(GenericType::Option) => {
506                // Add nullable feature if not already exists. Option is always nullable
507                if !features
508                    .iter()
509                    .any(|feature| matches!(feature, Feature::Nullable(_)))
510                {
511                    features.push(Nullable::new().into());
512                }
513
514                ComponentSchema::new(ComponentSchemaProps {
515                    type_tree: type_tree
516                        .children
517                        .as_ref()
518                        .expect("CompnentSchema generic container type should have children")
519                        .iter()
520                        .next()
521                        .expect("CompnentSchema generic container type should have 1 child"),
522                    features: Some(features),
523                    description,
524                    deprecated,
525                    object_name,
526                })
527                .to_tokens(&mut tokens);
528            }
529            Some(GenericType::Cow) | Some(GenericType::Box) | Some(GenericType::RefCell) => {
530                ComponentSchema::new(ComponentSchemaProps {
531                    type_tree: type_tree
532                        .children
533                        .as_ref()
534                        .expect("ComponentSchema generic container type should have children")
535                        .iter()
536                        .next()
537                        .expect("ComponentSchema generic container type should have 1 child"),
538                    features: Some(features),
539                    description,
540                    deprecated,
541                    object_name,
542                })
543                .to_tokens(&mut tokens);
544            }
545            #[cfg(feature = "rc_schema")]
546            Some(GenericType::Arc) | Some(GenericType::Rc) => {
547                ComponentSchema::new(ComponentSchemaProps {
548                    type_tree: type_tree
549                        .children
550                        .as_ref()
551                        .expect("ComponentSchema rc generic container type should have children")
552                        .iter()
553                        .next()
554                        .expect("ComponentSchema rc generic container type should have 1 child"),
555                    features: Some(features),
556                    description,
557                    deprecated,
558                    object_name,
559                })
560                .to_tokens(&mut tokens);
561            }
562            None => ComponentSchema::non_generic_to_tokens(
563                &mut tokens,
564                features,
565                type_tree,
566                object_name,
567                description_stream,
568                deprecated_stream,
569            ),
570        }
571
572        Self { tokens }
573    }
574
575    fn map_to_tokens(
576        tokens: &mut TokenStream,
577        mut features: Vec<Feature>,
578        type_tree: &TypeTree,
579        object_name: &str,
580        description_stream: Option<TokenStream>,
581        deprecated_stream: Option<TokenStream>,
582    ) {
583        let example = features.pop_by(|feature| matches!(feature, Feature::Example(_)));
584        let additional_properties = pop_feature!(features => Feature::AdditionalProperties(_));
585        let nullable = pop_feature!(features => Feature::Nullable(_));
586        let default = pop_feature!(features => Feature::Default(_));
587
588        let additional_properties = additional_properties
589            .as_ref()
590            .map(ToTokens::to_token_stream)
591            .unwrap_or_else(|| {
592                // Maps are treated as generic objects with no named properties and
593                // additionalProperties denoting the type
594                // maps have 2 child schemas and we are interested the second one of them
595                // which is used to determine the additional properties
596                let schema_property = ComponentSchema::new(ComponentSchemaProps {
597                    type_tree: type_tree
598                        .children
599                        .as_ref()
600                        .expect("ComponentSchema Map type should have children")
601                        .iter()
602                        .nth(1)
603                        .expect("ComponentSchema Map type should have 2 child"),
604                    features: Some(features),
605                    description: None,
606                    deprecated: None,
607                    object_name,
608                });
609
610                quote! { .additional_properties(Some(#schema_property)) }
611            });
612
613        tokens.extend(quote! {
614            utoipa::openapi::ObjectBuilder::new()
615                #additional_properties
616                #description_stream
617                #deprecated_stream
618                #default
619        });
620
621        example.to_tokens(tokens);
622        nullable.to_tokens(tokens)
623    }
624
625    fn vec_to_tokens(
626        tokens: &mut TokenStream,
627        mut features: Vec<Feature>,
628        type_tree: &TypeTree,
629        object_name: &str,
630        description_stream: Option<TokenStream>,
631        deprecated_stream: Option<TokenStream>,
632    ) {
633        let example = pop_feature!(features => Feature::Example(_));
634        let xml = features.extract_vec_xml_feature(type_tree);
635        let max_items = pop_feature!(features => Feature::MaxItems(_));
636        let min_items = pop_feature!(features => Feature::MinItems(_));
637        let nullable = pop_feature!(features => Feature::Nullable(_));
638        let default = pop_feature!(features => Feature::Default(_));
639
640        let child = type_tree
641            .children
642            .as_ref()
643            .expect("CompnentSchema Vec should have children")
644            .iter()
645            .next()
646            .expect("CompnentSchema Vec should have 1 child");
647
648        #[cfg(feature = "smallvec")]
649        let child = if type_tree.generic_type == Some(GenericType::SmallVec) {
650            child
651                .children
652                .as_ref()
653                .expect("SmallVec should have children")
654                .iter()
655                .next()
656                .expect("SmallVec should have 1 child")
657        } else {
658            child
659        };
660
661        // is octet-stream
662        let schema = if child
663            .path
664            .as_ref()
665            .map(|path| SchemaType(path).is_byte())
666            .unwrap_or(false)
667        {
668            quote! {
669                utoipa::openapi::ObjectBuilder::new()
670                    .schema_type(utoipa::openapi::schema::SchemaType::String)
671                    .format(Some(utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Binary)))
672            }
673        } else {
674            let component_schema = ComponentSchema::new(ComponentSchemaProps {
675                type_tree: child,
676                features: Some(features),
677                description: None,
678                deprecated: None,
679                object_name,
680            });
681
682            quote! {
683                utoipa::openapi::schema::ArrayBuilder::new()
684                    .items(#component_schema)
685            }
686        };
687
688        let validate = |feature: &Feature| {
689            let type_path = &**type_tree.path.as_ref().unwrap();
690            let schema_type = SchemaType(type_path);
691            feature.validate(&schema_type, type_tree);
692        };
693
694        tokens.extend(quote! {
695            #schema
696            #deprecated_stream
697            #description_stream
698        });
699
700        if let Some(max_items) = max_items {
701            validate(&max_items);
702            tokens.extend(max_items.to_token_stream())
703        }
704
705        if let Some(min_items) = min_items {
706            validate(&min_items);
707            tokens.extend(min_items.to_token_stream())
708        }
709
710        if let Some(default) = default {
711            tokens.extend(default.to_token_stream())
712        }
713
714        example.to_tokens(tokens);
715        xml.to_tokens(tokens);
716        nullable.to_tokens(tokens);
717    }
718
719    fn non_generic_to_tokens(
720        tokens: &mut TokenStream,
721        mut features: Vec<Feature>,
722        type_tree: &TypeTree,
723        object_name: &str,
724        description_stream: Option<TokenStream>,
725        deprecated_stream: Option<TokenStream>,
726    ) {
727        let nullable = pop_feature!(features => Feature::Nullable(_));
728
729        match type_tree.value_type {
730            ValueType::Primitive => {
731                let type_path = &**type_tree.path.as_ref().unwrap();
732                let schema_type = SchemaType(type_path);
733                if schema_type.is_unsigned_integer() {
734                    // add default minimum feature only when there is no explicit minimum
735                    // provided
736                    if !features
737                        .iter()
738                        .any(|feature| matches!(&feature, Feature::Minimum(_)))
739                    {
740                        features.push(Minimum::new(0f64, type_path.span()).into());
741                    }
742                }
743
744                tokens.extend(quote! {
745                    utoipa::openapi::ObjectBuilder::new().schema_type(#schema_type)
746                });
747
748                let format: SchemaFormat = (type_path).into();
749                if format.is_known_format() {
750                    tokens.extend(quote! {
751                        .format(Some(#format))
752                    })
753                }
754
755                tokens.extend(description_stream);
756                tokens.extend(deprecated_stream);
757                for feature in features.iter().filter(|feature| feature.is_validatable()) {
758                    feature.validate(&schema_type, type_tree);
759                }
760                tokens.extend(features.to_token_stream());
761                nullable.to_tokens(tokens);
762            }
763            ValueType::Value => {
764                if type_tree.is_value() {
765                    tokens.extend(quote! {
766                        utoipa::openapi::ObjectBuilder::new()
767                            .schema_type(utoipa::openapi::schema::SchemaType::Value)
768                            #description_stream #deprecated_stream #nullable
769                    })
770                }
771            }
772            ValueType::Object => {
773                let is_inline = features.is_inline();
774
775                if type_tree.is_object() {
776                    tokens.extend(quote! {
777                        utoipa::openapi::ObjectBuilder::new()
778                            #description_stream #deprecated_stream #nullable
779                    })
780                } else {
781                    let type_path = &**type_tree.path.as_ref().unwrap();
782                    if is_inline {
783                        let default = pop_feature!(features => Feature::Default(_));
784                        let schema = if default.is_some() || nullable.is_some() {
785                            quote_spanned! {type_path.span()=>
786                                utoipa::openapi::schema::AllOfBuilder::new()
787                                    #nullable
788                                    .item(<#type_path as utoipa::ToSchema>::schema().1)
789                                    #default
790                            }
791                        } else {
792                            quote_spanned! {type_path.span() =>
793                                <#type_path as utoipa::ToSchema>::schema().1
794                            }
795                        };
796
797                        schema.to_tokens(tokens);
798                    } else {
799                        let mut name = Cow::Owned(format_path_ref(type_path));
800                        if name == "Self" && !object_name.is_empty() {
801                            name = Cow::Borrowed(object_name);
802                        }
803
804                        let default = pop_feature!(features => Feature::Default(_));
805
806                        let schema = if default.is_some() || nullable.is_some() {
807                            quote! {
808                                utoipa::openapi::schema::AllOfBuilder::new()
809                                    #nullable
810                                    .item(utoipa::openapi::Ref::from_schema_name(#name))
811                                    #default
812                            }
813                        } else {
814                            quote! {
815                                utoipa::openapi::Ref::from_schema_name(#name)
816                            }
817                        };
818
819                        schema.to_tokens(tokens);
820                    }
821                }
822            }
823            ValueType::Tuple => {
824                type_tree
825                    .children
826                    .as_ref()
827                    .map(|children| {
828                        let all_of = children.iter().fold(
829                            quote! { utoipa::openapi::schema::AllOfBuilder::new() },
830                            |mut all_of, child| {
831                                let features = if child.is_option() {
832                                    Some(vec![Feature::Nullable(Nullable::new())])
833                                } else {
834                                    None
835                                };
836
837                                let item = ComponentSchema::new(ComponentSchemaProps {
838                                    type_tree: child,
839                                    features,
840                                    description: None,
841                                    deprecated: None,
842                                    object_name,
843                                });
844                                all_of.extend(quote!( .item(#item) ));
845
846                                all_of
847                            },
848                        );
849                        quote! {
850                            utoipa::openapi::schema::ArrayBuilder::new()
851                                .items(#all_of)
852                                #nullable
853                                #description_stream
854                                #deprecated_stream
855                        }
856                    })
857                    .unwrap_or_else(|| quote!(utoipa::openapi::schema::empty()))
858                    .to_tokens(tokens);
859                tokens.extend(features.to_token_stream());
860            }
861        }
862    }
863
864    fn get_description(comments: Option<&'c CommentAttributes>) -> Option<TokenStream> {
865        comments
866            .and_then(|comments| {
867                let comment = CommentAttributes::as_formatted_string(comments);
868                if comment.is_empty() {
869                    None
870                } else {
871                    Some(comment)
872                }
873            })
874            .map(|description| quote! { .description(Some(#description)) })
875    }
876
877    fn get_deprecated(deprecated: Option<&'c Deprecated>) -> Option<TokenStream> {
878        deprecated.map(|deprecated| quote! { .deprecated(Some(#deprecated)) })
879    }
880}
881
882impl ToTokens for ComponentSchema {
883    fn to_tokens(&self, tokens: &mut TokenStream) {
884        self.tokens.to_tokens(tokens)
885    }
886}