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#[inline]
36fn is_default(container_rules: &SerdeContainer, field_rule: &SerdeValue) -> bool {
37 container_rules.default || field_rule.default
38}
39
40fn get_deprecated(attributes: &[Attribute]) -> Option<Deprecated> {
43 if attributes.has_deprecated() {
44 Some(Deprecated::True)
45 } else {
46 None
47 }
48}
49
50pub 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 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#[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 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 Type::Reference(reference) => TypeTreeValueIter::Iter(Box::new(Self::get_type_tree_values(reference.elem.as_ref())?)),
252 Type::Tuple(tuple) => {
254 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 Type::Group(group) => TypeTreeValueIter::Iter(Box::new(Self::get_type_tree_values(group.elem.as_ref())?)),
266 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 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 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 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 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 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 pub fn is_object(&self) -> bool {
488 self.is("Object")
489 }
490
491 pub fn is_value(&self) -> bool {
494 self.is("Value")
495 }
496
497 pub fn is_option(&self) -> bool {
499 matches!(self.generic_type, Some(GenericType::Option))
500 }
501
502 pub fn is_map(&self) -> bool {
504 matches!(self.generic_type, Some(GenericType::Map))
505 }
506
507 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, }
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 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
645fn 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
666struct VariantRename;
668
669impl Rename for VariantRename {
670 fn rename(rule: &RenameRule, value: &str) -> String {
671 rule.rename_variant(value)
672 }
673}
674
675struct 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#[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 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 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 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 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 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 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 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(_)); tokens.extend(features.to_token_stream()?);
1181 }
1182 ValueType::Value => {
1183 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 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 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 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 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())) .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 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}