utoipa_gen/
component.rs

1use std::borrow::Cow;
2
3use proc_macro2::{Ident, Span, TokenStream};
4use quote::{quote, quote_spanned, ToTokens};
5use syn::punctuated::Punctuated;
6use syn::spanned::Spanned;
7use syn::token::Comma;
8use syn::{
9    AngleBracketedGenericArguments, Attribute, GenericArgument, GenericParam, Generics, Path,
10    PathArguments, PathSegment, Type, TypePath,
11};
12
13use crate::doc_comment::CommentAttributes;
14use crate::schema_type::{KnownFormat, PrimitiveType, SchemaTypeInner};
15use crate::{
16    as_tokens_or_diagnostics, Array, AttributesExt, Diagnostics, GenericsExt, OptionExt,
17    ToTokensDiagnostics,
18};
19use crate::{schema_type::SchemaType, Deprecated};
20
21use self::features::attributes::{Description, Nullable};
22use self::features::validation::Minimum;
23use self::features::{
24    pop_feature, Feature, FeaturesExt, IntoInner, IsInline, ToTokensExt, Validatable,
25};
26use self::serde::{RenameRule, SerdeContainer, SerdeValue};
27
28pub mod into_params;
29
30pub mod features;
31pub mod schema;
32pub mod serde;
33
34/// Check whether either serde `container_rule` or `field_rule` has _`default`_ attribute set.
35#[inline]
36fn is_default(container_rules: &SerdeContainer, field_rule: &SerdeValue) -> bool {
37    container_rules.default || field_rule.default
38}
39
40/// Find `#[deprecated]` attribute from given attributes. Typically derive type attributes
41/// or field attributes of struct.
42fn get_deprecated(attributes: &[Attribute]) -> Option<Deprecated> {
43    if attributes.has_deprecated() {
44        Some(Deprecated::True)
45    } else {
46        None
47    }
48}
49
50/// Check whether field is required based on following rules.
51///
52/// * If field has not serde's `skip_serializing_if`
53/// * Field has not `serde_with` double option
54/// * Field is not default
55pub fn is_required(field_rule: &SerdeValue, container_rules: &SerdeContainer) -> bool {
56    !field_rule.skip_serializing_if
57        && !field_rule.double_option
58        && !is_default(container_rules, field_rule)
59}
60
61#[cfg_attr(feature = "debug", derive(Debug))]
62enum TypeTreeValue<'t> {
63    TypePath(&'t TypePath),
64    Path(&'t Path),
65    /// Slice and array types need to be manually defined, since they cannot be recognized from
66    /// generic arguments.
67    Array(Vec<TypeTreeValue<'t>>, Span),
68    UnitType,
69    Tuple(Vec<TypeTreeValue<'t>>, Span),
70}
71
72impl PartialEq for TypeTreeValue<'_> {
73    fn eq(&self, other: &Self) -> bool {
74        match self {
75            Self::Path(_) => self == other,
76            Self::TypePath(_) => self == other,
77            Self::Array(array, _) => matches!(other, Self::Array(other, _) if other == array),
78            Self::Tuple(tuple, _) => matches!(other, Self::Tuple(other, _) if other == tuple),
79            Self::UnitType => self == other,
80        }
81    }
82}
83
84enum TypeTreeValueIter<'a, T> {
85    Once(std::iter::Once<T>),
86    Empty,
87    Iter(Box<dyn std::iter::Iterator<Item = T> + 'a>),
88}
89
90impl<'a, T> TypeTreeValueIter<'a, T> {
91    fn once(item: T) -> Self {
92        Self::Once(std::iter::once(item))
93    }
94
95    fn empty() -> Self {
96        Self::Empty
97    }
98}
99
100impl<'a, T> Iterator for TypeTreeValueIter<'a, T> {
101    type Item = T;
102
103    fn next(&mut self) -> Option<Self::Item> {
104        match self {
105            Self::Once(iter) => iter.next(),
106            Self::Empty => None,
107            Self::Iter(iter) => iter.next(),
108        }
109    }
110
111    fn size_hint(&self) -> (usize, Option<usize>) {
112        match self {
113            Self::Once(once) => once.size_hint(),
114            Self::Empty => (0, None),
115            Self::Iter(iter) => iter.size_hint(),
116        }
117    }
118}
119
120/// [`TypeTree`] of items which represents a single parsed `type` of a
121/// `Schema`, `Parameter` or `FnArg`
122#[cfg_attr(feature = "debug", derive(Debug))]
123#[derive(Clone)]
124pub struct TypeTree<'t> {
125    pub path: Option<Cow<'t, Path>>,
126    #[allow(unused)]
127    pub span: Option<Span>,
128    pub value_type: ValueType,
129    pub generic_type: Option<GenericType>,
130    pub children: Option<Vec<TypeTree<'t>>>,
131}
132
133pub trait SynPathExt {
134    /// Rewrite path will perform conditional substitution over the current path replacing
135    /// [`PathSegment`]s and [`syn::Ident`] with aliases if found via [`TypeTree::get_alias_type`]
136    /// or by [`PrimitiveType`] if type in question is known to be a primitive type.
137    fn rewrite_path(&self) -> Result<syn::Path, Diagnostics>;
138}
139
140impl<'p> SynPathExt for &'p Path {
141    fn rewrite_path(&self) -> Result<syn::Path, Diagnostics> {
142        let last_segment = self
143            .segments
144            .last()
145            .expect("syn::Path must have at least one segment");
146
147        let mut segment = last_segment.clone();
148        if let PathArguments::AngleBracketed(anglebracketed_args) = &last_segment.arguments {
149            let args = anglebracketed_args.args.iter().try_fold(
150                Punctuated::<GenericArgument, Comma>::new(),
151                |mut args, generic_arg| {
152                    match generic_arg {
153                        GenericArgument::Type(ty) => {
154                            let type_tree = TypeTree::from_type(ty)?;
155                            let alias_type = type_tree.get_alias_type()?;
156                            let alias_type_tree =
157                                alias_type.as_ref().map(TypeTree::from_type).transpose()?;
158                            let type_tree = alias_type_tree.unwrap_or(type_tree);
159
160                            let path = type_tree
161                                .path
162                                .as_ref()
163                                .expect("TypeTree must have a path")
164                                .as_ref();
165
166                            if let Some(default_type) = PrimitiveType::new(path) {
167                                args.push(GenericArgument::Type(default_type.ty.clone()));
168                            } else {
169                                let inner = path.rewrite_path()?;
170                                args.push(GenericArgument::Type(syn::Type::Path(
171                                    syn::parse_quote!(#inner),
172                                )))
173                            }
174                        }
175                        other => args.push(other.clone()),
176                    }
177
178                    Result::<_, Diagnostics>::Ok(args)
179                },
180            )?;
181
182            let angle_bracket_args = AngleBracketedGenericArguments {
183                args,
184                lt_token: anglebracketed_args.lt_token,
185                gt_token: anglebracketed_args.gt_token,
186                colon2_token: anglebracketed_args.colon2_token,
187            };
188
189            segment.arguments = PathArguments::AngleBracketed(angle_bracket_args);
190        }
191
192        let segment_ident = &segment.ident;
193        let segment_type: Type = syn::parse_quote!(#segment_ident);
194        let type_tree = TypeTree::from_type(&segment_type)?;
195        let alias_type = type_tree.get_alias_type()?;
196        let alias_type_tree = alias_type.as_ref().map(TypeTree::from_type).transpose()?;
197        let type_tree = alias_type_tree.unwrap_or(type_tree);
198
199        let path = type_tree
200            .path
201            .as_ref()
202            .expect("TypeTree for ident must have a path")
203            .as_ref();
204
205        if let Some(default_type) = PrimitiveType::new(path) {
206            let ty = &default_type.ty;
207            let ident: Ident = syn::parse_quote!(#ty);
208
209            segment.ident = ident;
210        } else {
211            let ident = path
212                .get_ident()
213                .expect("Path of Ident must have Ident")
214                .clone();
215            segment.ident = ident;
216        }
217
218        let path = syn::Path {
219            segments: if last_segment == &segment {
220                self.segments.clone()
221            } else {
222                Punctuated::from_iter(std::iter::once(segment))
223            },
224            leading_colon: self.leading_colon,
225        };
226
227        Ok(path)
228    }
229}
230
231impl TypeTree<'_> {
232    pub fn from_type(ty: &Type) -> Result<TypeTree<'_>, Diagnostics> {
233        Self::convert_types(Self::get_type_tree_values(ty)?).map(|mut type_tree| {
234            type_tree
235                .next()
236                .expect("TypeTree from type should have one TypeTree parent")
237        })
238    }
239
240    fn get_type_tree_values(
241        ty: &Type,
242    ) -> Result<impl Iterator<Item = TypeTreeValue<'_>>, Diagnostics> {
243        let type_tree_values = match ty {
244            Type::Path(path) => {
245                TypeTreeValueIter::once(TypeTreeValue::TypePath(path))
246            },
247            // NOTE have to put this in the box to avoid compiler bug with recursive functions
248            // See here https://github.com/rust-lang/rust/pull/110844 and https://github.com/rust-lang/rust/issues/111906
249            // This bug in fixed in Rust 1.79, but in order to support Rust 1.75 these need to be
250            // boxed.
251            Type::Reference(reference) => TypeTreeValueIter::Iter(Box::new(Self::get_type_tree_values(reference.elem.as_ref())?)),
252            // Type::Reference(reference) => Self::get_type_tree_values(reference.elem.as_ref())?,
253            Type::Tuple(tuple) => {
254                // Detect unit type ()
255                if tuple.elems.is_empty() { return Ok(TypeTreeValueIter::once(TypeTreeValue::UnitType)) }
256                TypeTreeValueIter::once(TypeTreeValue::Tuple(
257                    tuple.elems.iter().map(Self::get_type_tree_values).collect::<Result<Vec<_>, Diagnostics>>()?.into_iter().flatten().collect(),
258                    tuple.span()
259                ))
260            },
261            // NOTE have to put this in the box to avoid compiler bug with recursive functions
262            // See here https://github.com/rust-lang/rust/pull/110844 and https://github.com/rust-lang/rust/issues/111906
263            // This bug in fixed in Rust 1.79, but in order to support Rust 1.75 these need to be
264            // boxed.
265            Type::Group(group) => TypeTreeValueIter::Iter(Box::new(Self::get_type_tree_values(group.elem.as_ref())?)),
266            // Type::Group(group) => Self::get_type_tree_values(group.elem.as_ref())?,
267            Type::Slice(slice) => TypeTreeValueIter::once(TypeTreeValue::Array(Self::get_type_tree_values(&slice.elem)?.collect(), slice.bracket_token.span.join())),
268            Type::Array(array) => TypeTreeValueIter::once(TypeTreeValue::Array(Self::get_type_tree_values(&array.elem)?.collect(), array.bracket_token.span.join())),
269            Type::TraitObject(trait_object) => {
270                trait_object
271                    .bounds
272                    .iter()
273                    .find_map(|bound| {
274                        match &bound {
275                            syn::TypeParamBound::Trait(trait_bound) => Some(&trait_bound.path),
276                            syn::TypeParamBound::Lifetime(_) => None,
277                            syn::TypeParamBound::Verbatim(_) => None,
278                            _ => todo!("TypeTree trait object found unrecognized TypeParamBound"),
279                        }
280                    })
281                    .map(|path| TypeTreeValueIter::once(TypeTreeValue::Path(path))).unwrap_or_else(TypeTreeValueIter::empty)
282            }
283            unexpected => return Err(Diagnostics::with_span(unexpected.span(), "unexpected type in component part get type path, expected one of: Path, Tuple, Reference, Group, Array, Slice, TraitObject")),
284        };
285
286        Ok(type_tree_values)
287    }
288
289    fn convert_types<'p, P: IntoIterator<Item = TypeTreeValue<'p>>>(
290        paths: P,
291    ) -> Result<impl Iterator<Item = TypeTree<'p>>, Diagnostics> {
292        paths
293            .into_iter()
294            .map(|value| {
295                let path = match value {
296                    TypeTreeValue::TypePath(type_path) => &type_path.path,
297                    TypeTreeValue::Path(path) => path,
298                    TypeTreeValue::Array(value, span) => {
299                        let array: Path = Ident::new("Array", span).into();
300                        return Ok(TypeTree {
301                            path: Some(Cow::Owned(array)),
302                            span: Some(span),
303                            value_type: ValueType::Object,
304                            generic_type: Some(GenericType::Vec),
305                            children: Some(match Self::convert_types(value) {
306                                Ok(converted_values) => converted_values.collect(),
307                                Err(diagnostics) => return Err(diagnostics),
308                            }),
309                        });
310                    }
311                    TypeTreeValue::Tuple(tuple, span) => {
312                        return Ok(TypeTree {
313                            path: None,
314                            span: Some(span),
315                            children: Some(match Self::convert_types(tuple) {
316                                Ok(converted_values) => converted_values.collect(),
317                                Err(diagnostics) => return Err(diagnostics),
318                            }),
319                            generic_type: None,
320                            value_type: ValueType::Tuple,
321                        })
322                    }
323                    TypeTreeValue::UnitType => {
324                        return Ok(TypeTree {
325                            path: None,
326                            span: None,
327                            value_type: ValueType::Tuple,
328                            generic_type: None,
329                            children: None,
330                        })
331                    }
332                };
333
334                // there will always be one segment at least
335                let last_segment = path
336                    .segments
337                    .last()
338                    .expect("at least one segment within path in TypeTree::convert_types");
339
340                if last_segment.arguments.is_empty() {
341                    Ok(Self::convert(path, last_segment))
342                } else {
343                    Self::resolve_schema_type(path, last_segment)
344                }
345            })
346            .collect::<Result<Vec<TypeTree<'_>>, Diagnostics>>()
347            .map(IntoIterator::into_iter)
348    }
349
350    // Only when type is a generic type we get to this function.
351    fn resolve_schema_type<'t>(
352        path: &'t Path,
353        last_segment: &'t PathSegment,
354    ) -> Result<TypeTree<'t>, Diagnostics> {
355        if last_segment.arguments.is_empty() {
356            return Err(Diagnostics::with_span(
357                last_segment.ident.span(),
358                "expected at least one angle bracket argument but was 0",
359            ));
360        };
361
362        let mut generic_schema_type = Self::convert(path, last_segment);
363
364        let mut generic_types = match &last_segment.arguments {
365            PathArguments::AngleBracketed(angle_bracketed_args) => {
366                // if all type arguments are lifetimes we ignore the generic type
367                if angle_bracketed_args.args.iter().all(|arg| {
368                    matches!(
369                        arg,
370                        GenericArgument::Lifetime(_) | GenericArgument::Const(_)
371                    )
372                }) {
373                    None
374                } else {
375                    Some(
376                        angle_bracketed_args
377                            .args
378                            .iter()
379                            .filter(|arg| {
380                                !matches!(
381                                    arg,
382                                    GenericArgument::Lifetime(_) | GenericArgument::Const(_)
383                                )
384                            })
385                            .map(|arg| match arg {
386                                GenericArgument::Type(arg) => Ok(arg),
387                                unexpected => Err(Diagnostics::with_span(
388                                    unexpected.span(),
389                                    "expected generic argument type or generic argument lifetime",
390                                )),
391                            })
392                            .collect::<Result<Vec<_>, Diagnostics>>()?
393                            .into_iter(),
394                    )
395                }
396            }
397            _ => {
398                return Err(Diagnostics::with_span(
399                    last_segment.ident.span(),
400                    "unexpected path argument, expected angle bracketed path argument",
401                ))
402            }
403        };
404
405        generic_schema_type.children = generic_types.as_mut().map_try(|generic_type| {
406            generic_type
407                .map(Self::from_type)
408                .collect::<Result<Vec<_>, Diagnostics>>()
409        })?;
410
411        Ok(generic_schema_type)
412    }
413
414    fn convert<'t>(path: &'t Path, last_segment: &'t PathSegment) -> TypeTree<'t> {
415        let generic_type = Self::get_generic_type(last_segment);
416        let schema_type = SchemaType {
417            path: Cow::Borrowed(path),
418            nullable: matches!(generic_type, Some(GenericType::Option)),
419        };
420
421        TypeTree {
422            path: Some(Cow::Borrowed(path)),
423            span: Some(path.span()),
424            value_type: if schema_type.is_primitive() {
425                ValueType::Primitive
426            } else if schema_type.is_value() {
427                ValueType::Value
428            } else {
429                ValueType::Object
430            },
431            generic_type,
432            children: None,
433        }
434    }
435
436    // TODO should we recognize unknown generic types with `GenericType::Unknown` instead of `None`?
437    fn get_generic_type(segment: &PathSegment) -> Option<GenericType> {
438        if segment.arguments.is_empty() {
439            return None;
440        }
441
442        match &*segment.ident.to_string() {
443            "HashMap" | "Map" | "BTreeMap" => Some(GenericType::Map),
444            #[cfg(feature = "indexmap")]
445            "IndexMap" => Some(GenericType::Map),
446            "Vec" => Some(GenericType::Vec),
447            "BTreeSet" | "HashSet" => Some(GenericType::Set),
448            "LinkedList" => Some(GenericType::LinkedList),
449            #[cfg(feature = "smallvec")]
450            "SmallVec" => Some(GenericType::SmallVec),
451            "Option" => Some(GenericType::Option),
452            "Cow" => Some(GenericType::Cow),
453            "Box" => Some(GenericType::Box),
454            #[cfg(feature = "rc_schema")]
455            "Arc" => Some(GenericType::Arc),
456            #[cfg(feature = "rc_schema")]
457            "Rc" => Some(GenericType::Rc),
458            "RefCell" => Some(GenericType::RefCell),
459            _ => None,
460        }
461    }
462
463    /// Check whether [`TypeTreeValue`]'s [`syn::TypePath`] or any if it's `children`s [`syn::TypePath`]
464    /// is a given type as [`str`].
465    pub fn is(&self, s: &str) -> bool {
466        let mut is = self
467            .path
468            .as_ref()
469            .map(|path| {
470                path.segments
471                    .last()
472                    .expect("expected at least one segment in TreeTypeValue path")
473                    .ident
474                    == s
475            })
476            .unwrap_or(false);
477
478        if let Some(ref children) = self.children {
479            is = is || children.iter().any(|child| child.is(s));
480        }
481
482        is
483    }
484
485    /// `Object` virtual type is used when generic object is required in OpenAPI spec. Typically used
486    /// with `value_type` attribute to hinder the actual type.
487    pub fn is_object(&self) -> bool {
488        self.is("Object")
489    }
490
491    /// `Value` virtual type is used when any JSON value is required in OpenAPI spec. Typically used
492    /// with `value_type` attribute for a member of type `serde_json::Value`.
493    pub fn is_value(&self) -> bool {
494        self.is("Value")
495    }
496
497    /// Check whether the [`TypeTree`]'s `generic_type` is [`GenericType::Option`]
498    pub fn is_option(&self) -> bool {
499        matches!(self.generic_type, Some(GenericType::Option))
500    }
501
502    /// Check whether the [`TypeTree`]'s `generic_type` is [`GenericType::Map`]
503    pub fn is_map(&self) -> bool {
504        matches!(self.generic_type, Some(GenericType::Map))
505    }
506
507    /// Get [`syn::Generics`] for current [`TypeTree`]'s [`syn::Path`].
508    pub fn get_path_generics(&self) -> syn::Result<Generics> {
509        let mut generics = Generics::default();
510        let segment = self
511            .path
512            .as_ref()
513            .ok_or_else(|| syn::Error::new(self.path.span(), "cannot get TypeTree::path, did you call this on `tuple` or `unit` type type tree?"))?
514            .segments
515            .last()
516            .expect("Path must have segments");
517
518        fn type_to_generic_params(ty: &Type) -> Vec<GenericParam> {
519            match &ty {
520                Type::Path(path) => {
521                    let mut params_vec: Vec<GenericParam> = Vec::new();
522                    let last_segment = path
523                        .path
524                        .segments
525                        .last()
526                        .expect("TypePath must have a segment");
527                    let ident = &last_segment.ident;
528                    params_vec.push(syn::parse_quote!(#ident));
529
530                    params_vec
531                }
532                Type::Reference(reference) => type_to_generic_params(reference.elem.as_ref()),
533                _ => Vec::new(),
534            }
535        }
536
537        fn angle_bracket_args_to_params(
538            args: &AngleBracketedGenericArguments,
539        ) -> impl Iterator<Item = GenericParam> + '_ {
540            args.args
541                .iter()
542                .filter_map(move |generic_argument| {
543                    match generic_argument {
544                        GenericArgument::Type(ty) => Some(type_to_generic_params(ty)),
545                        GenericArgument::Lifetime(life) => {
546                            Some(vec![GenericParam::Lifetime(syn::parse_quote!(#life))])
547                        }
548                        _ => None, // other wise ignore
549                    }
550                })
551                .flatten()
552        }
553
554        if let PathArguments::AngleBracketed(angle_bracketed_args) = &segment.arguments {
555            generics.lt_token = Some(angle_bracketed_args.lt_token);
556            generics.params = angle_bracket_args_to_params(angle_bracketed_args).collect();
557            generics.gt_token = Some(angle_bracketed_args.gt_token);
558        };
559
560        Ok(generics)
561    }
562
563    /// Get possible global alias defined in `utoipa_config::Config` for current `TypeTree`.
564    pub fn get_alias_type(&self) -> Result<Option<syn::Type>, Diagnostics> {
565        #[cfg(feature = "config")]
566        {
567            self.path
568                .as_ref()
569                .and_then(|path| path.segments.iter().last())
570                .and_then(|last_segment| {
571                    crate::CONFIG.aliases.get(&*last_segment.ident.to_string())
572                })
573                .map_try(|alias| syn::parse_str::<syn::Type>(alias.as_ref()))
574                .map_err(|error| Diagnostics::new(error.to_string()))
575        }
576
577        #[cfg(not(feature = "config"))]
578        Ok(None)
579    }
580}
581
582impl PartialEq for TypeTree<'_> {
583    #[cfg(feature = "debug")]
584    fn eq(&self, other: &Self) -> bool {
585        self.path == other.path
586            && self.value_type == other.value_type
587            && self.generic_type == other.generic_type
588            && self.children == other.children
589    }
590
591    #[cfg(not(feature = "debug"))]
592    fn eq(&self, other: &Self) -> bool {
593        let path_eg = match (self.path.as_ref(), other.path.as_ref()) {
594            (Some(Cow::Borrowed(self_path)), Some(Cow::Borrowed(other_path))) => {
595                self_path.into_token_stream().to_string()
596                    == other_path.into_token_stream().to_string()
597            }
598            (Some(Cow::Owned(self_path)), Some(Cow::Owned(other_path))) => {
599                self_path.to_token_stream().to_string()
600                    == other_path.into_token_stream().to_string()
601            }
602            (None, None) => true,
603            _ => false,
604        };
605
606        path_eg
607            && self.value_type == other.value_type
608            && self.generic_type == other.generic_type
609            && self.children == other.children
610    }
611}
612
613#[cfg_attr(feature = "debug", derive(Debug))]
614#[derive(Clone, Copy, PartialEq, Eq)]
615pub enum ValueType {
616    Primitive,
617    Object,
618    Tuple,
619    Value,
620}
621
622#[cfg_attr(feature = "debug", derive(Debug))]
623#[derive(PartialEq, Eq, Clone, Copy)]
624pub enum GenericType {
625    Vec,
626    LinkedList,
627    Set,
628    #[cfg(feature = "smallvec")]
629    SmallVec,
630    Map,
631    Option,
632    Cow,
633    Box,
634    RefCell,
635    #[cfg(feature = "rc_schema")]
636    Arc,
637    #[cfg(feature = "rc_schema")]
638    Rc,
639}
640
641trait Rename {
642    fn rename(rule: &RenameRule, value: &str) -> String;
643}
644
645/// Performs a rename for given `value` based on given rules. If no rules were
646/// provided returns [`None`]
647///
648/// Method accepts 3 arguments.
649/// * `value` to rename.
650/// * `to` Optional rename to value for fields with _`rename`_ property.
651/// * `container_rule` which is used to rename containers with _`rename_all`_ property.
652fn rename<'s, R: Rename>(
653    value: &str,
654    to: Option<Cow<'s, str>>,
655    container_rule: Option<&RenameRule>,
656) -> Option<Cow<'s, str>> {
657    let rename = to.and_then(|to| if !to.is_empty() { Some(to) } else { None });
658
659    rename.or_else(|| {
660        container_rule
661            .as_ref()
662            .map(|container_rule| Cow::Owned(R::rename(container_rule, value)))
663    })
664}
665
666/// Can be used to perform rename on container level e.g `struct`, `enum` or `enum` `variant` level.
667struct VariantRename;
668
669impl Rename for VariantRename {
670    fn rename(rule: &RenameRule, value: &str) -> String {
671        rule.rename_variant(value)
672    }
673}
674
675/// Can be used to perform rename on field level of a container e.g `struct`.
676struct FieldRename;
677
678impl Rename for FieldRename {
679    fn rename(rule: &RenameRule, value: &str) -> String {
680        rule.rename(value)
681    }
682}
683
684#[cfg_attr(feature = "debug", derive(Debug))]
685pub struct Container<'c> {
686    pub generics: &'c Generics,
687}
688
689#[cfg_attr(feature = "debug", derive(Debug))]
690pub struct ComponentSchemaProps<'c> {
691    pub container: &'c Container<'c>,
692    pub type_tree: &'c TypeTree<'c>,
693    pub features: Vec<Feature>,
694    pub description: Option<&'c ComponentDescription<'c>>,
695}
696
697impl ComponentSchemaProps<'_> {
698    fn set_nullable(&mut self) {
699        if !self
700            .features
701            .iter()
702            .any(|feature| matches!(feature, Feature::Nullable(_)))
703        {
704            self.features.push(Nullable::new().into());
705        }
706    }
707}
708
709#[cfg_attr(feature = "debug", derive(Debug))]
710pub enum ComponentDescription<'c> {
711    CommentAttributes(&'c CommentAttributes),
712    Description(&'c Description),
713}
714
715impl ToTokens for ComponentDescription<'_> {
716    fn to_tokens(&self, tokens: &mut TokenStream) {
717        let description = match self {
718            Self::CommentAttributes(attributes) => {
719                if attributes.is_empty() {
720                    TokenStream::new()
721                } else {
722                    attributes.as_formatted_string().to_token_stream()
723                }
724            }
725            Self::Description(description) => description.to_token_stream(),
726        };
727
728        if !description.is_empty() {
729            tokens.extend(quote! {
730                .description(Some(#description))
731            });
732        }
733    }
734}
735
736/// Used to store possible inner field schema name and tokens if field contains any schema
737/// references. E.g. field: Vec<Foo> should have name: Foo::name(), tokens: Foo::schema() and
738/// references: Foo::schemas()
739#[cfg_attr(feature = "debug", derive(Debug))]
740#[derive(Default)]
741pub struct SchemaReference {
742    pub name: TokenStream,
743    pub tokens: TokenStream,
744    pub references: TokenStream,
745    pub is_inline: bool,
746    pub no_recursion: bool,
747}
748
749impl SchemaReference {
750    /// Check whether `SchemaReference` is partial. Partial schema reference occurs in situation
751    /// when reference schema tokens cannot be resolved e.g. type in question is generic argument.
752    fn is_partial(&self) -> bool {
753        self.tokens.is_empty()
754    }
755}
756
757#[cfg_attr(feature = "debug", derive(Debug))]
758pub struct ComponentSchema {
759    tokens: TokenStream,
760    pub name_tokens: TokenStream,
761    pub schema_references: Vec<SchemaReference>,
762}
763
764impl ComponentSchema {
765    pub fn for_params(
766        mut schema_props: ComponentSchemaProps,
767        option_is_nullable: bool,
768    ) -> Result<Self, Diagnostics> {
769        // Add nullable feature if not already exists.
770        // Option is always nullable, except when used in query parameters.
771        if schema_props.type_tree.is_option() && option_is_nullable {
772            schema_props.set_nullable()
773        }
774
775        Self::new_inner(schema_props)
776    }
777
778    pub fn new(mut schema_props: ComponentSchemaProps) -> Result<Self, Diagnostics> {
779        // Add nullable feature if not already exists. Option is always nullable
780        if schema_props.type_tree.is_option() {
781            schema_props.set_nullable();
782        }
783
784        Self::new_inner(schema_props)
785    }
786
787    fn new_inner(
788        ComponentSchemaProps {
789            container,
790            type_tree,
791            features,
792            description,
793        }: ComponentSchemaProps,
794    ) -> Result<Self, Diagnostics> {
795        let mut tokens = TokenStream::new();
796        let mut name_tokens = TokenStream::new();
797        let mut schema_references = Vec::<SchemaReference>::new();
798
799        match type_tree.generic_type {
800            Some(GenericType::Map) => ComponentSchema::map_to_tokens(
801                &mut tokens,
802                &mut schema_references,
803                container,
804                features,
805                type_tree,
806                description,
807            )?,
808            Some(GenericType::Vec | GenericType::LinkedList | GenericType::Set) => {
809                ComponentSchema::vec_to_tokens(
810                    &mut tokens,
811                    &mut schema_references,
812                    container,
813                    features,
814                    type_tree,
815                    description,
816                )?
817            }
818            #[cfg(feature = "smallvec")]
819            Some(GenericType::SmallVec) => ComponentSchema::vec_to_tokens(
820                &mut tokens,
821                &mut schema_references,
822                container,
823                features,
824                type_tree,
825                description,
826            )?,
827            Some(GenericType::Option) => {
828                let child = type_tree
829                    .children
830                    .as_ref()
831                    .expect("ComponentSchema generic container type should have children")
832                    .iter()
833                    .next()
834                    .expect("ComponentSchema generic container type should have 1 child");
835                let alias = child.get_alias_type()?;
836                let alias = alias.as_ref().map_try(TypeTree::from_type)?;
837                let child = alias.as_ref().unwrap_or(child);
838
839                let schema = ComponentSchema::new(ComponentSchemaProps {
840                    container,
841                    type_tree: child,
842                    features,
843                    description,
844                })?;
845                schema.to_tokens(&mut tokens);
846
847                schema_references.extend(schema.schema_references);
848            }
849            Some(GenericType::Cow | GenericType::Box | GenericType::RefCell) => {
850                let child = type_tree
851                    .children
852                    .as_ref()
853                    .expect("ComponentSchema generic container type should have children")
854                    .iter()
855                    .next()
856                    .expect("ComponentSchema generic container type should have 1 child");
857                let alias = child.get_alias_type()?;
858                let alias = alias.as_ref().map_try(TypeTree::from_type)?;
859                let child = alias.as_ref().unwrap_or(child);
860
861                let schema = ComponentSchema::new(ComponentSchemaProps {
862                    container,
863                    type_tree: child,
864                    features,
865                    description,
866                })?;
867                schema.to_tokens(&mut tokens);
868
869                schema_references.extend(schema.schema_references);
870            }
871            #[cfg(feature = "rc_schema")]
872            Some(GenericType::Arc) | Some(GenericType::Rc) => {
873                let child = type_tree
874                    .children
875                    .as_ref()
876                    .expect("ComponentSchema rc generic container type should have children")
877                    .iter()
878                    .next()
879                    .expect("ComponentSchema rc generic container type should have 1 child");
880                let alias = child.get_alias_type()?;
881                let alias = alias.as_ref().map_try(TypeTree::from_type)?;
882                let child = alias.as_ref().unwrap_or(child);
883
884                let schema = ComponentSchema::new(ComponentSchemaProps {
885                    container,
886                    type_tree: child,
887                    features,
888                    description,
889                })?;
890                schema.to_tokens(&mut tokens);
891
892                schema_references.extend(schema.schema_references);
893            }
894            None => ComponentSchema::non_generic_to_tokens(
895                &mut tokens,
896                &mut name_tokens,
897                &mut schema_references,
898                container,
899                features,
900                type_tree,
901                description,
902            )?,
903        };
904
905        Ok(Self {
906            tokens,
907            name_tokens,
908            schema_references,
909        })
910    }
911
912    /// Create `.schema_type(...)` override token stream if nullable is true from given [`SchemaTypeInner`].
913    fn get_schema_type_override(
914        nullable: Option<Nullable>,
915        schema_type_inner: SchemaTypeInner,
916    ) -> Option<TokenStream> {
917        if let Some(nullable) = nullable {
918            let nullable_schema_type = nullable.into_schema_type_token_stream();
919            let schema_type = if nullable.value() && !nullable_schema_type.is_empty() {
920                Some(quote! {
921                    {
922                        use std::iter::FromIterator;
923                        utoipa::openapi::schema::SchemaType::from_iter([#schema_type_inner, #nullable_schema_type])
924                    }
925                })
926            } else {
927                None
928            };
929
930            schema_type.map(|schema_type| quote! { .schema_type(#schema_type) })
931        } else {
932            None
933        }
934    }
935
936    fn map_to_tokens(
937        tokens: &mut TokenStream,
938        schema_references: &mut Vec<SchemaReference>,
939        container: &Container,
940        mut features: Vec<Feature>,
941        type_tree: &TypeTree,
942        description_stream: Option<&ComponentDescription<'_>>,
943    ) -> Result<(), Diagnostics> {
944        let example = features.pop_by(|feature| matches!(feature, Feature::Example(_)));
945        let additional_properties = pop_feature!(features => Feature::AdditionalProperties(_));
946        let nullable: Option<Nullable> =
947            pop_feature!(features => Feature::Nullable(_)).into_inner();
948        let default = pop_feature!(features => Feature::Default(_));
949        let default_tokens = as_tokens_or_diagnostics!(&default);
950        let deprecated = pop_feature!(features => Feature::Deprecated(_)).try_to_token_stream()?;
951
952        let additional_properties = additional_properties
953            .as_ref()
954            .map_try(|feature| Ok(as_tokens_or_diagnostics!(feature)))?
955            .or_else_try(|| {
956                let children = type_tree
957                    .children
958                    .as_ref()
959                    .expect("ComponentSchema Map type should have children");
960                // Get propertyNames
961                let property_name = children
962                    .first()
963                    .expect("ComponentSchema Map type shouldu have 2 child, getting first");
964                let property_name_alias = property_name.get_alias_type()?;
965                let property_name_alias =
966                    property_name_alias.as_ref().map_try(TypeTree::from_type)?;
967                let property_name_child = property_name_alias.as_ref().unwrap_or(property_name);
968
969                let mut property_name_features = features.clone();
970                property_name_features.push(Feature::Inline(true.into()));
971                let property_name_schema = ComponentSchema::new(ComponentSchemaProps {
972                    container,
973                    type_tree: property_name_child,
974                    features: property_name_features,
975                    description: None,
976                })?;
977                let property_name_tokens = property_name_schema.to_token_stream();
978
979                // Maps are treated as generic objects with no named properties and
980                // additionalProperties denoting the type
981                // maps have 2 child schemas and we are interested the second one of them
982                // which is used to determine the additional properties
983                let child = children
984                    .get(1)
985                    .expect("ComponentSchema Map type should have 2 child");
986                let alias = child.get_alias_type()?;
987                let alias = alias.as_ref().map_try(TypeTree::from_type)?;
988                let child = alias.as_ref().unwrap_or(child);
989
990                let schema_property = ComponentSchema::new(ComponentSchemaProps {
991                    container,
992                    type_tree: child,
993                    features,
994                    description: None,
995                })?;
996                let schema_tokens = schema_property.to_token_stream();
997
998                schema_references.extend(schema_property.schema_references);
999
1000                Result::<Option<TokenStream>, Diagnostics>::Ok(Some(quote! {
1001                    .property_names(Some(#property_name_tokens))
1002                    .additional_properties(Some(#schema_tokens))
1003                }))
1004            })?;
1005
1006        let schema_type =
1007            ComponentSchema::get_schema_type_override(nullable, SchemaTypeInner::Object);
1008
1009        tokens.extend(quote! {
1010            utoipa::openapi::ObjectBuilder::new()
1011                #schema_type
1012                #additional_properties
1013                #description_stream
1014                #deprecated
1015                #default_tokens
1016        });
1017
1018        example.to_tokens(tokens)
1019    }
1020
1021    fn vec_to_tokens(
1022        tokens: &mut TokenStream,
1023        schema_references: &mut Vec<SchemaReference>,
1024        container: &Container,
1025        mut features: Vec<Feature>,
1026        type_tree: &TypeTree,
1027        description_stream: Option<&ComponentDescription<'_>>,
1028    ) -> Result<(), Diagnostics> {
1029        let example = pop_feature!(features => Feature::Example(_));
1030        let xml = features.extract_vec_xml_feature(type_tree)?;
1031        let max_items = pop_feature!(features => Feature::MaxItems(_));
1032        let min_items = pop_feature!(features => Feature::MinItems(_));
1033        let nullable: Option<Nullable> =
1034            pop_feature!(features => Feature::Nullable(_)).into_inner();
1035        let default = pop_feature!(features => Feature::Default(_));
1036        let title = pop_feature!(features => Feature::Title(_));
1037        let deprecated = pop_feature!(features => Feature::Deprecated(_)).try_to_token_stream()?;
1038        let content_encoding = pop_feature!(features => Feature::ContentEncoding(_));
1039        let content_media_type = pop_feature!(features => Feature::ContentMediaType(_));
1040
1041        let child = type_tree
1042            .children
1043            .as_ref()
1044            .expect("ComponentSchema Vec should have children")
1045            .iter()
1046            .next()
1047            .expect("ComponentSchema Vec should have 1 child");
1048
1049        #[cfg(feature = "smallvec")]
1050        let child = if type_tree.generic_type == Some(GenericType::SmallVec) {
1051            child
1052                .children
1053                .as_ref()
1054                .expect("SmallVec should have children")
1055                .iter()
1056                .next()
1057                .expect("SmallVec should have 1 child")
1058        } else {
1059            child
1060        };
1061        let alias = child.get_alias_type()?;
1062        let alias = alias.as_ref().map_try(TypeTree::from_type)?;
1063        let child = alias.as_ref().unwrap_or(child);
1064
1065        let component_schema = ComponentSchema::new(ComponentSchemaProps {
1066            container,
1067            type_tree: child,
1068            features,
1069            description: None,
1070        })?;
1071        let component_schema_tokens = component_schema.to_token_stream();
1072
1073        schema_references.extend(component_schema.schema_references);
1074
1075        let unique = match matches!(type_tree.generic_type, Some(GenericType::Set)) {
1076            true => quote! {
1077                .unique_items(true)
1078            },
1079            false => quote! {},
1080        };
1081        let schema_type =
1082            ComponentSchema::get_schema_type_override(nullable, SchemaTypeInner::Array);
1083
1084        let schema = quote! {
1085            utoipa::openapi::schema::ArrayBuilder::new()
1086                #schema_type
1087                .items(#component_schema_tokens)
1088            #unique
1089        };
1090
1091        let validate = |feature: &Feature| {
1092            let type_path = &**type_tree.path.as_ref().unwrap();
1093            let schema_type = SchemaType {
1094                path: Cow::Borrowed(type_path),
1095                nullable: nullable
1096                    .map(|nullable| nullable.value())
1097                    .unwrap_or_default(),
1098            };
1099            feature.validate(&schema_type, type_tree);
1100        };
1101
1102        tokens.extend(quote! {
1103            #schema
1104            #deprecated
1105            #description_stream
1106        });
1107
1108        if let Some(max_items) = max_items {
1109            validate(&max_items);
1110            tokens.extend(max_items.to_token_stream())
1111        }
1112
1113        if let Some(min_items) = min_items {
1114            validate(&min_items);
1115            tokens.extend(min_items.to_token_stream())
1116        }
1117
1118        content_encoding.to_tokens(tokens)?;
1119        content_media_type.to_tokens(tokens)?;
1120        default.to_tokens(tokens)?;
1121        title.to_tokens(tokens)?;
1122        example.to_tokens(tokens)?;
1123        xml.to_tokens(tokens)?;
1124
1125        Ok(())
1126    }
1127
1128    fn non_generic_to_tokens(
1129        tokens: &mut TokenStream,
1130        name_tokens: &mut TokenStream,
1131        schema_references: &mut Vec<SchemaReference>,
1132        container: &Container,
1133        mut features: Vec<Feature>,
1134        type_tree: &TypeTree,
1135        description_stream: Option<&ComponentDescription<'_>>,
1136    ) -> Result<(), Diagnostics> {
1137        let nullable_feat: Option<Nullable> =
1138            pop_feature!(features => Feature::Nullable(_)).into_inner();
1139        let nullable = nullable_feat
1140            .map(|nullable| nullable.value())
1141            .unwrap_or_default();
1142        let deprecated = pop_feature!(features => Feature::Deprecated(_)).try_to_token_stream()?;
1143
1144        match type_tree.value_type {
1145            ValueType::Primitive => {
1146                let type_path = &**type_tree.path.as_ref().unwrap();
1147                let schema_type = SchemaType {
1148                    path: Cow::Borrowed(type_path),
1149                    nullable,
1150                };
1151                if schema_type.is_unsigned_integer() {
1152                    // add default minimum feature only when there is no explicit minimum
1153                    // provided
1154                    if !features
1155                        .iter()
1156                        .any(|feature| matches!(&feature, Feature::Minimum(_)))
1157                    {
1158                        features.push(Minimum::new(0f64, type_path.span()).into());
1159                    }
1160                }
1161
1162                let schema_type_tokens = as_tokens_or_diagnostics!(&schema_type);
1163                tokens.extend(quote! {
1164                    utoipa::openapi::ObjectBuilder::new().schema_type(#schema_type_tokens)
1165                });
1166
1167                let format = KnownFormat::from_path(type_path)?;
1168                if format.is_known_format() {
1169                    tokens.extend(quote! {
1170                        .format(Some(#format))
1171                    })
1172                }
1173
1174                description_stream.to_tokens(tokens);
1175                tokens.extend(deprecated);
1176                for feature in features.iter().filter(|feature| feature.is_validatable()) {
1177                    feature.validate(&schema_type, type_tree);
1178                }
1179                let _ = pop_feature!(features => Feature::NoRecursion(_)); // primitive types are not recursive
1180                tokens.extend(features.to_token_stream()?);
1181            }
1182            ValueType::Value => {
1183                // since OpenAPI 3.1 the type is an array, thus nullable should not be necessary
1184                // for value type that is going to allow all types of content.
1185                if type_tree.is_value() {
1186                    tokens.extend(quote! {
1187                        utoipa::openapi::ObjectBuilder::new()
1188                            .schema_type(utoipa::openapi::schema::SchemaType::AnyValue)
1189                            #description_stream #deprecated
1190                    })
1191                }
1192            }
1193            ValueType::Object => {
1194                let is_inline = features.is_inline();
1195
1196                if type_tree.is_object() {
1197                    let nullable_schema_type = ComponentSchema::get_schema_type_override(
1198                        nullable_feat,
1199                        SchemaTypeInner::Object,
1200                    );
1201                    tokens.extend(quote! {
1202                        utoipa::openapi::ObjectBuilder::new()
1203                            #nullable_schema_type
1204                            #description_stream #deprecated
1205                    })
1206                } else {
1207                    fn nullable_one_of_item(nullable: bool) -> Option<TokenStream> {
1208                        if nullable {
1209                            Some(
1210                                quote! { .item(utoipa::openapi::schema::ObjectBuilder::new().schema_type(utoipa::openapi::schema::Type::Null)) },
1211                            )
1212                        } else {
1213                            None
1214                        }
1215                    }
1216                    let type_path = &**type_tree.path.as_ref().unwrap();
1217                    let rewritten_path = type_path.rewrite_path()?;
1218                    let nullable_item = nullable_one_of_item(nullable);
1219                    let mut object_schema_reference = SchemaReference {
1220                        no_recursion: features
1221                            .iter()
1222                            .any(|feature| matches!(feature, Feature::NoRecursion(_))),
1223                        ..SchemaReference::default()
1224                    };
1225
1226                    if let Some(children) = &type_tree.children {
1227                        let children_name = Self::compose_name(
1228                            Self::filter_const_generics(children, container.generics),
1229                            container.generics,
1230                        )?;
1231                        name_tokens.extend(quote! { std::borrow::Cow::Owned(format!("{}_{}", < #rewritten_path as utoipa::ToSchema >::name(), #children_name)) });
1232                    } else {
1233                        name_tokens.extend(
1234                            quote! { format!("{}", < #rewritten_path as utoipa::ToSchema >::name()) },
1235                        );
1236                    }
1237
1238                    object_schema_reference.name = quote! { String::from(#name_tokens) };
1239
1240                    let default = pop_feature!(features => Feature::Default(_));
1241                    let default_tokens = as_tokens_or_diagnostics!(&default);
1242                    let title = pop_feature!(features => Feature::Title(_));
1243                    let title_tokens = as_tokens_or_diagnostics!(&title);
1244
1245                    if is_inline {
1246                        let schema_type = SchemaType {
1247                            path: Cow::Borrowed(&rewritten_path),
1248                            nullable,
1249                        };
1250                        let index =
1251                            if !schema_type.is_primitive() || type_tree.generic_type.is_none() {
1252                                container.generics.get_generic_type_param_index(type_tree)
1253                            } else {
1254                                None
1255                            };
1256
1257                        object_schema_reference.is_inline = true;
1258                        let items_tokens = if let Some(children) = &type_tree.children {
1259                            schema_references.extend(Self::compose_child_references(children)?);
1260
1261                            let composed_generics =
1262                                Self::compose_generics(children, container.generics)?
1263                                    .collect::<Array<_>>();
1264
1265                            if index.is_some() {
1266                                quote_spanned! {type_path.span()=>
1267                                    let _ = <#rewritten_path as utoipa::PartialSchema>::schema;
1268
1269                                    if let Some(composed) = generics.get_mut(#index) {
1270                                        composed.clone()
1271                                    } else {
1272                                        <#rewritten_path as utoipa::PartialSchema>::schema()
1273                                    }
1274                                }
1275                            } else {
1276                                quote_spanned! {type_path.span()=>
1277                                    <#rewritten_path as utoipa::__dev::ComposeSchema>::compose(#composed_generics.to_vec())
1278                                }
1279                            }
1280                        } else {
1281                            quote_spanned! {type_path.span()=>
1282                                <#rewritten_path as utoipa::PartialSchema>::schema()
1283                            }
1284                        };
1285                        object_schema_reference.tokens = items_tokens.clone();
1286                        object_schema_reference.references =
1287                            quote! { <#rewritten_path as utoipa::ToSchema>::schemas(schemas) };
1288
1289                        let description_tokens = description_stream.to_token_stream();
1290                        let schema = if default.is_some()
1291                            || nullable
1292                            || title.is_some()
1293                            || !description_tokens.is_empty()
1294                        {
1295                            quote_spanned! {type_path.span()=>
1296                                utoipa::openapi::schema::OneOfBuilder::new()
1297                                    #nullable_item
1298                                    .item(#items_tokens)
1299                                #title_tokens
1300                                #default_tokens
1301                                #description_stream
1302                            }
1303                        } else {
1304                            items_tokens
1305                        };
1306
1307                        schema.to_tokens(tokens);
1308                    } else {
1309                        let schema_type = SchemaType {
1310                            path: Cow::Borrowed(&rewritten_path),
1311                            nullable,
1312                        };
1313                        let index =
1314                            if !schema_type.is_primitive() || type_tree.generic_type.is_none() {
1315                                container.generics.get_generic_type_param_index(type_tree)
1316                            } else {
1317                                None
1318                            };
1319
1320                        // forcibly inline primitive type parameters, otherwise use references
1321                        if index.is_none() {
1322                            let reference_tokens = if let Some(children) = &type_tree.children {
1323                                let composed_generics = Self::compose_generics(
1324                                    Self::filter_const_generics(children, container.generics),
1325                                    container.generics,
1326                                )?
1327                                .collect::<Array<_>>();
1328                                quote! { <#rewritten_path as utoipa::__dev::ComposeSchema>::compose(#composed_generics.to_vec()) }
1329                            } else {
1330                                quote! { <#rewritten_path as utoipa::PartialSchema>::schema() }
1331                            };
1332                            object_schema_reference.tokens = reference_tokens;
1333                        }
1334                        // any case the references call should be passed down in generic and non
1335                        // non generic likewise.
1336                        object_schema_reference.references =
1337                            quote! { <#rewritten_path as utoipa::ToSchema>::schemas(schemas) };
1338                        let composed_or_ref = |item_tokens: TokenStream| -> TokenStream {
1339                            if let Some(index) = &index {
1340                                quote_spanned! {type_path.span()=>
1341                                    {
1342                                        let _ = <#rewritten_path as utoipa::PartialSchema>::schema;
1343
1344                                        if let Some(composed) = generics.get_mut(#index) {
1345                                            composed.clone()
1346                                        } else {
1347                                            #item_tokens.into()
1348                                        }
1349                                    }
1350                                }
1351                            } else {
1352                                quote_spanned! {type_path.span()=>
1353                                    #item_tokens
1354                                }
1355                            }
1356                        };
1357
1358                        // TODO: refs support `summary` field but currently there is no such field
1359                        // on schemas more over there is no way to distinct the `summary` from
1360                        // `description` of the ref. Should we consider supporting the summary?
1361                        let schema = if default.is_some() || nullable || title.is_some() {
1362                            composed_or_ref(quote_spanned! {type_path.span()=>
1363                                utoipa::openapi::schema::OneOfBuilder::new()
1364                                    #nullable_item
1365                                    .item(utoipa::openapi::schema::RefBuilder::new()
1366                                        #description_stream
1367                                        .ref_location_from_schema_name(#name_tokens)
1368                                    )
1369                                    #title_tokens
1370                                    #default_tokens
1371                            })
1372                        } else {
1373                            composed_or_ref(quote_spanned! {type_path.span()=>
1374                                utoipa::openapi::schema::RefBuilder::new()
1375                                    #description_stream
1376                                    .ref_location_from_schema_name(#name_tokens)
1377                            })
1378                        };
1379
1380                        schema.to_tokens(tokens);
1381                    }
1382
1383                    schema_references.push(object_schema_reference);
1384                }
1385            }
1386            ValueType::Tuple => {
1387                type_tree
1388                    .children
1389                    .as_ref()
1390                    .map_try(|children| {
1391                        let prefix_items = children
1392                            .iter()
1393                            .map(|child| {
1394                                let mut features = if child.is_option() {
1395                                    vec![Feature::Nullable(Nullable::new())]
1396                                } else {
1397                                    Vec::new()
1398                                };
1399                                // Prefix item is always inlined
1400                                features.push(Feature::Inline(true.into()));
1401
1402                                match ComponentSchema::new(ComponentSchemaProps {
1403                                    container,
1404                                    type_tree: child,
1405                                    features,
1406                                    description: None,
1407                                }) {
1408                                    Ok(child) => Ok(quote! {
1409                                        Into::<utoipa::openapi::schema::Schema>::into(#child)
1410                                    }),
1411                                    Err(diagnostics) => Err(diagnostics),
1412                                }
1413                            })
1414                            .collect::<Result<Vec<_>, Diagnostics>>()?
1415                            .into_iter()
1416                            .collect::<Array<_>>();
1417
1418                        let nullable_schema_type = ComponentSchema::get_schema_type_override(
1419                            nullable_feat,
1420                            SchemaTypeInner::Array,
1421                        );
1422                        Result::<TokenStream, Diagnostics>::Ok(quote! {
1423                            utoipa::openapi::schema::ArrayBuilder::new()
1424                                #nullable_schema_type
1425                                .items(utoipa::openapi::schema::ArrayItems::False)
1426                                .prefix_items(#prefix_items)
1427                                #description_stream
1428                                #deprecated
1429                        })
1430                    })?
1431                    .unwrap_or_else(|| quote!(utoipa::openapi::schema::empty())) // TODO should
1432                    // this bee type "null"?
1433                    .to_tokens(tokens);
1434                tokens.extend(features.to_token_stream());
1435            }
1436        }
1437        Ok(())
1438    }
1439
1440    fn compose_name<'tr, I>(
1441        children: I,
1442        generics: &'tr Generics,
1443    ) -> Result<TokenStream, Diagnostics>
1444    where
1445        I: IntoIterator<Item = &'tr TypeTree<'tr>>,
1446    {
1447        let children = children
1448            .into_iter()
1449            .map(|type_tree| {
1450                let name = type_tree
1451                    .path
1452                    .as_deref()
1453                    .expect("Generic ValueType::Object must have path");
1454                let rewritten_name = name.rewrite_path()?;
1455
1456                if let Some(children) = &type_tree.children {
1457                    let children_name = Self::compose_name(Self::filter_const_generics(children, generics), generics)?;
1458
1459                    Ok(quote! { std::borrow::Cow::Owned(format!("{}_{}", <#rewritten_name as utoipa::ToSchema>::name(), #children_name)) })
1460                } else {
1461                    Ok(quote! { <#rewritten_name as utoipa::ToSchema>::name() })
1462                }
1463            })
1464            .collect::<Result<Array<_>, Diagnostics>>()?;
1465
1466        Ok(quote! { std::borrow::Cow::<String>::Owned(#children.to_vec().join("_")) })
1467    }
1468
1469    fn compose_generics<'v, I: IntoIterator<Item = &'v TypeTree<'v>>>(
1470        children: I,
1471        generics: &'v Generics,
1472    ) -> Result<impl Iterator<Item = TokenStream> + 'v, Diagnostics>
1473    where
1474        <I as std::iter::IntoIterator>::IntoIter: 'v,
1475    {
1476        let iter = children.into_iter().map(|child| {
1477            let path = child
1478                .path
1479                .as_deref()
1480                .expect("inline TypeTree ValueType::Object must have child path if generic");
1481            let rewritten_path = path.rewrite_path()?;
1482            if let Some(children) = &child.children {
1483                let items = Self::compose_generics(Self::filter_const_generics(children, generics), generics)?.collect::<Array<_>>();
1484                Ok(quote! { <#rewritten_path as utoipa::__dev::ComposeSchema>::compose(#items.to_vec()) })
1485            } else {
1486                Ok(quote! { <#rewritten_path as utoipa::PartialSchema>::schema() })
1487            }
1488        }).collect::<Result<Vec<_>, Diagnostics>>()?
1489        .into_iter();
1490
1491        Ok(iter)
1492    }
1493
1494    fn filter_const_generics<'v, I: IntoIterator<Item = &'v TypeTree<'v>>>(
1495        children: I,
1496        generics: &'v Generics,
1497    ) -> impl IntoIterator<Item = &'v TypeTree<'v>> + 'v
1498    where
1499        <I as std::iter::IntoIterator>::IntoIter: 'v,
1500    {
1501        children.into_iter().filter(|type_tree| {
1502            let path = type_tree
1503                .path
1504                .as_deref()
1505                .expect("child TypeTree must have a Path, did you call this on array or tuple?");
1506            let is_const = path
1507                .get_ident()
1508                .map(|path_ident| {
1509                    generics.params.iter().any(
1510                        |param| matches!(param, GenericParam::Const(ty) if ty.ident == *path_ident),
1511                    )
1512                })
1513                .unwrap_or(false);
1514
1515            !is_const
1516        })
1517    }
1518
1519    fn compose_child_references<'a, I: IntoIterator<Item = &'a TypeTree<'a>> + 'a>(
1520        children: I,
1521    ) -> Result<impl Iterator<Item = SchemaReference> + 'a, Diagnostics> {
1522        let iter = children.into_iter().map(|type_tree| {
1523            if let Some(children) = &type_tree.children {
1524                let iter = Self::compose_child_references(children)?;
1525                Ok(ChildRefIter::Iter(Box::new(iter)))
1526            } else if type_tree.value_type == ValueType::Object {
1527                let type_path = type_tree
1528                    .path
1529                    .as_deref()
1530                    .expect("Object TypePath must have type path, compose child references");
1531
1532                let rewritten_path = type_path.rewrite_path()?;
1533
1534                Ok(ChildRefIter::Once(std::iter::once(SchemaReference {
1535                    name: quote! { String::from(< #rewritten_path as utoipa::ToSchema >::name().as_ref()) },
1536                    tokens: quote! { <#rewritten_path as utoipa::PartialSchema>::schema() },
1537                    references: quote !{ <#rewritten_path as utoipa::ToSchema>::schemas(schemas) },
1538                    is_inline: false,
1539                    no_recursion: false,
1540                }))
1541                )
1542            } else {
1543                Ok(ChildRefIter::Empty)
1544            }
1545        }).collect::<Result<Vec<_>, Diagnostics>>()?
1546        .into_iter()
1547        .flatten();
1548
1549        Ok(iter)
1550    }
1551}
1552
1553impl ToTokens for ComponentSchema {
1554    fn to_tokens(&self, tokens: &mut TokenStream) {
1555        self.tokens.to_tokens(tokens);
1556    }
1557}
1558
1559enum ChildRefIter<'c, T> {
1560    Iter(Box<dyn std::iter::Iterator<Item = T> + 'c>),
1561    Once(std::iter::Once<T>),
1562    Empty,
1563}
1564
1565impl<'a, T> Iterator for ChildRefIter<'a, T> {
1566    type Item = T;
1567
1568    fn next(&mut self) -> Option<Self::Item> {
1569        match self {
1570            Self::Iter(iter) => iter.next(),
1571            Self::Once(once) => once.next(),
1572            Self::Empty => None,
1573        }
1574    }
1575
1576    fn size_hint(&self) -> (usize, Option<usize>) {
1577        match self {
1578            Self::Iter(iter) => iter.size_hint(),
1579            Self::Once(once) => once.size_hint(),
1580            Self::Empty => (0, None),
1581        }
1582    }
1583}
1584
1585#[cfg_attr(feature = "debug", derive(Debug))]
1586pub struct FlattenedMapSchema {
1587    tokens: TokenStream,
1588}
1589
1590impl FlattenedMapSchema {
1591    pub fn new(
1592        ComponentSchemaProps {
1593            container,
1594            type_tree,
1595            mut features,
1596            description,
1597        }: ComponentSchemaProps,
1598    ) -> Result<Self, Diagnostics> {
1599        let mut tokens = TokenStream::new();
1600        let deprecated = pop_feature!(features => Feature::Deprecated(_)).try_to_token_stream()?;
1601
1602        let example = features.pop_by(|feature| matches!(feature, Feature::Example(_)));
1603        let nullable = pop_feature!(features => Feature::Nullable(_));
1604        let default = pop_feature!(features => Feature::Default(_));
1605        let default_tokens = as_tokens_or_diagnostics!(&default);
1606
1607        // Maps are treated as generic objects with no named properties and
1608        // additionalProperties denoting the type
1609        // maps have 2 child schemas and we are interested the second one of them
1610        // which is used to determine the additional properties
1611        let schema_property = ComponentSchema::new(ComponentSchemaProps {
1612            container,
1613            type_tree: type_tree
1614                .children
1615                .as_ref()
1616                .expect("ComponentSchema Map type should have children")
1617                .get(1)
1618                .expect("ComponentSchema Map type should have 2 child"),
1619            features,
1620            description: None,
1621        })?;
1622        let schema_tokens = schema_property.to_token_stream();
1623
1624        tokens.extend(quote! {
1625            #schema_tokens
1626                #description
1627                #deprecated
1628                #default_tokens
1629        });
1630
1631        example.to_tokens(&mut tokens)?;
1632        nullable.to_tokens(&mut tokens)?;
1633
1634        Ok(Self { tokens })
1635    }
1636}
1637
1638impl ToTokensDiagnostics for FlattenedMapSchema {
1639    fn to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostics> {
1640        self.tokens.to_tokens(tokens);
1641        Ok(())
1642    }
1643}