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#[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
38fn 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
55pub 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 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#[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 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 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 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 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 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 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 pub fn is_object(&self) -> bool {
335 self.is("Object")
336 }
337
338 pub fn is_value(&self) -> bool {
341 self.is("Value")
342 }
343
344 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
411fn 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
432struct VariantRename;
434
435impl Rename for VariantRename {
436 fn rename(rule: &RenameRule, value: &str) -> String {
437 rule.rename_variant(value)
438 }
439}
440
441struct 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 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 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 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 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}