utoipa_gen/
path.rs

1use std::borrow::Cow;
2use std::ops::Deref;
3use std::{io::Error, str::FromStr};
4
5use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
6use quote::{quote, quote_spanned, ToTokens};
7use syn::punctuated::Punctuated;
8use syn::spanned::Spanned;
9use syn::token::Comma;
10use syn::{parenthesized, parse::Parse, Token};
11use syn::{Expr, ExprLit, Lit, LitStr};
12
13use crate::component::{features::attributes::Extensions, ComponentSchema, GenericType, TypeTree};
14use crate::{
15    as_tokens_or_diagnostics, parse_utils, Deprecated, Diagnostics, OptionExt, ToTokensDiagnostics,
16};
17use crate::{schema_type::SchemaType, security_requirement::SecurityRequirementsAttr, Array};
18
19use self::response::Response;
20use self::{parameter::Parameter, request_body::RequestBodyAttr, response::Responses};
21
22pub mod example;
23pub mod handler;
24pub mod media_type;
25pub mod parameter;
26mod request_body;
27pub mod response;
28mod status;
29
30const PATH_STRUCT_PREFIX: &str = "__path_";
31
32#[inline]
33pub fn format_path_ident(fn_name: Cow<'_, Ident>) -> Cow<'_, Ident> {
34    Cow::Owned(quote::format_ident!(
35        "{PATH_STRUCT_PREFIX}{}",
36        fn_name.as_ref()
37    ))
38}
39
40#[derive(Default)]
41#[cfg_attr(feature = "debug", derive(Debug))]
42pub struct PathAttr<'p> {
43    methods: Vec<HttpMethod>,
44    request_body: Option<RequestBodyAttr<'p>>,
45    responses: Vec<Response<'p>>,
46    pub(super) path: Option<parse_utils::LitStrOrExpr>,
47    operation_id: Option<Expr>,
48    tag: Option<parse_utils::LitStrOrExpr>,
49    tags: Vec<parse_utils::LitStrOrExpr>,
50    params: Vec<Parameter<'p>>,
51    security: Option<Array<'p, SecurityRequirementsAttr>>,
52    context_path: Option<parse_utils::LitStrOrExpr>,
53    impl_for: Option<Ident>,
54    description: Option<parse_utils::LitStrOrExpr>,
55    summary: Option<parse_utils::LitStrOrExpr>,
56    extensions: Option<Extensions>,
57}
58
59impl<'p> PathAttr<'p> {
60    #[cfg(feature = "auto_into_responses")]
61    pub fn responses_from_into_responses(&mut self, ty: &'p syn::TypePath) {
62        self.responses
63            .push(Response::IntoResponses(Cow::Borrowed(ty)))
64    }
65
66    #[cfg(any(
67        feature = "actix_extras",
68        feature = "rocket_extras",
69        feature = "axum_extras"
70    ))]
71    pub fn update_request_body(&mut self, schema: Option<crate::ext::ExtSchema<'p>>) {
72        use self::media_type::Schema;
73        if self.request_body.is_none() {
74            if let Some(schema) = schema {
75                self.request_body = Some(RequestBodyAttr::from_schema(Schema::Ext(schema)));
76            }
77        }
78    }
79
80    /// Update path with external parameters from extensions.
81    #[cfg(any(
82        feature = "actix_extras",
83        feature = "rocket_extras",
84        feature = "axum_extras"
85    ))]
86    pub fn update_parameters_ext<I: IntoIterator<Item = Parameter<'p>>>(
87        &mut self,
88        ext_parameters: I,
89    ) {
90        let ext_params = ext_parameters.into_iter();
91
92        let (existing_incoming_params, new_params): (Vec<Parameter>, Vec<Parameter>) =
93            ext_params.partition(|param| self.params.iter().any(|p| p == param));
94
95        for existing_incoming in existing_incoming_params {
96            if let Some(param) = self.params.iter_mut().find(|p| **p == existing_incoming) {
97                param.merge(existing_incoming);
98            }
99        }
100
101        self.params.extend(
102            new_params
103                .into_iter()
104                .filter(|param| !matches!(param, Parameter::IntoParamsIdent(_))),
105        );
106    }
107}
108
109impl Parse for PathAttr<'_> {
110    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
111        const EXPECTED_ATTRIBUTE_MESSAGE: &str = "unexpected identifier, expected any of: method, get, post, put, delete, options, head, patch, trace, operation_id, path, request_body, responses, params, tag, security, context_path, description, summary";
112        let mut path_attr = PathAttr::default();
113
114        while !input.is_empty() {
115            let ident = input.parse::<Ident>().map_err(|error| {
116                syn::Error::new(
117                    error.span(),
118                    format!("{EXPECTED_ATTRIBUTE_MESSAGE}, {error}"),
119                )
120            })?;
121            let attribute_name = &*ident.to_string();
122
123            match attribute_name {
124                "method" => {
125                    path_attr.methods =
126                        parse_utils::parse_parethesized_terminated::<HttpMethod, Comma>(input)?
127                            .into_iter()
128                            .collect()
129                }
130                "operation_id" => {
131                    path_attr.operation_id =
132                        Some(parse_utils::parse_next(input, || Expr::parse(input))?);
133                }
134                "path" => {
135                    path_attr.path = Some(parse_utils::parse_next_literal_str_or_expr(input)?);
136                }
137                "request_body" => {
138                    path_attr.request_body = Some(input.parse::<RequestBodyAttr>()?);
139                }
140                "responses" => {
141                    let responses;
142                    parenthesized!(responses in input);
143                    path_attr.responses =
144                        Punctuated::<Response, Token![,]>::parse_terminated(&responses)
145                            .map(|punctuated| punctuated.into_iter().collect::<Vec<Response>>())?;
146                }
147                "params" => {
148                    let params;
149                    parenthesized!(params in input);
150                    path_attr.params =
151                        Punctuated::<Parameter, Token![,]>::parse_terminated(&params)
152                            .map(|punctuated| punctuated.into_iter().collect::<Vec<Parameter>>())?;
153                }
154                "tag" => {
155                    path_attr.tag = Some(parse_utils::parse_next_literal_str_or_expr(input)?);
156                }
157                "tags" => {
158                    path_attr.tags = parse_utils::parse_next(input, || {
159                        let tags;
160                        syn::bracketed!(tags in input);
161                        Punctuated::<parse_utils::LitStrOrExpr, Token![,]>::parse_terminated(&tags)
162                    })?
163                    .into_iter()
164                    .collect::<Vec<_>>();
165                }
166                "security" => {
167                    let security;
168                    parenthesized!(security in input);
169                    path_attr.security = Some(parse_utils::parse_groups_collect(&security)?)
170                }
171                "context_path" => {
172                    path_attr.context_path =
173                        Some(parse_utils::parse_next_literal_str_or_expr(input)?)
174                }
175                "impl_for" => {
176                    path_attr.impl_for =
177                        Some(parse_utils::parse_next(input, || input.parse::<Ident>())?);
178                }
179                "description" => {
180                    path_attr.description =
181                        Some(parse_utils::parse_next_literal_str_or_expr(input)?)
182                }
183                "summary" => {
184                    path_attr.summary = Some(parse_utils::parse_next_literal_str_or_expr(input)?)
185                }
186                "extensions" => {
187                    path_attr.extensions = Some(input.parse::<Extensions>()?);
188                }
189                _ => {
190                    if let Some(path_operation) =
191                        attribute_name.parse::<HttpMethod>().into_iter().next()
192                    {
193                        path_attr.methods = vec![path_operation]
194                    } else {
195                        return Err(syn::Error::new(ident.span(), EXPECTED_ATTRIBUTE_MESSAGE));
196                    }
197                }
198            }
199
200            if !input.is_empty() {
201                input.parse::<Token![,]>()?;
202            }
203        }
204
205        Ok(path_attr)
206    }
207}
208
209/// Path operation HTTP method
210#[cfg_attr(feature = "debug", derive(Debug))]
211pub enum HttpMethod {
212    Get,
213    Post,
214    Put,
215    Delete,
216    Options,
217    Head,
218    Patch,
219    Trace,
220}
221
222impl Parse for HttpMethod {
223    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
224        let method = input
225            .parse::<Ident>()
226            .map_err(|error| syn::Error::new(error.span(), HttpMethod::ERROR_MESSAGE))?;
227
228        method
229            .to_string()
230            .parse::<HttpMethod>()
231            .map_err(|_| syn::Error::new(method.span(), HttpMethod::ERROR_MESSAGE))
232    }
233}
234
235impl HttpMethod {
236    const ERROR_MESSAGE: &'static str = "unexpected http method, expected one of: get, post, put, delete, options, head, patch, trace";
237    /// Create path operation from ident
238    ///
239    /// Ident must have value of http request type as lower case string such as `get`.
240    #[cfg(any(feature = "actix_extras", feature = "rocket_extras"))]
241    pub fn from_ident(ident: &Ident) -> Result<Self, Diagnostics> {
242        let name = &*ident.to_string();
243        name
244            .parse::<HttpMethod>()
245            .map_err(|error| {
246                let mut diagnostics = Diagnostics::with_span(ident.span(), error.to_string());
247                if name == "connect" {
248                    diagnostics = diagnostics.note("HTTP method `CONNET` is not supported by OpenAPI spec <https://spec.openapis.org/oas/latest.html#path-item-object>");
249                }
250
251                diagnostics
252            })
253    }
254}
255
256impl FromStr for HttpMethod {
257    type Err = Error;
258
259    fn from_str(s: &str) -> Result<Self, Self::Err> {
260        match s {
261            "get" => Ok(Self::Get),
262            "post" => Ok(Self::Post),
263            "put" => Ok(Self::Put),
264            "delete" => Ok(Self::Delete),
265            "options" => Ok(Self::Options),
266            "head" => Ok(Self::Head),
267            "patch" => Ok(Self::Patch),
268            "trace" => Ok(Self::Trace),
269            _ => Err(Error::new(
270                std::io::ErrorKind::Other,
271                HttpMethod::ERROR_MESSAGE,
272            )),
273        }
274    }
275}
276
277impl ToTokens for HttpMethod {
278    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
279        let path_item_type = match self {
280            Self::Get => quote! { utoipa::openapi::HttpMethod::Get },
281            Self::Post => quote! { utoipa::openapi::HttpMethod::Post },
282            Self::Put => quote! { utoipa::openapi::HttpMethod::Put },
283            Self::Delete => quote! { utoipa::openapi::HttpMethod::Delete },
284            Self::Options => quote! { utoipa::openapi::HttpMethod::Options },
285            Self::Head => quote! { utoipa::openapi::HttpMethod::Head },
286            Self::Patch => quote! { utoipa::openapi::HttpMethod::Patch },
287            Self::Trace => quote! { utoipa::openapi::HttpMethod::Trace },
288        };
289
290        tokens.extend(path_item_type);
291    }
292}
293pub struct Path<'p> {
294    path_attr: PathAttr<'p>,
295    fn_ident: &'p Ident,
296    ext_methods: Vec<HttpMethod>,
297    path: Option<String>,
298    doc_comments: Option<Vec<String>>,
299    deprecated: bool,
300}
301
302impl<'p> Path<'p> {
303    pub fn new(path_attr: PathAttr<'p>, fn_ident: &'p Ident) -> Self {
304        Self {
305            path_attr,
306            fn_ident,
307            ext_methods: Vec::new(),
308            path: None,
309            doc_comments: None,
310            deprecated: false,
311        }
312    }
313
314    pub fn ext_methods(mut self, methods: Option<Vec<HttpMethod>>) -> Self {
315        self.ext_methods = methods.unwrap_or_default();
316
317        self
318    }
319
320    pub fn path(mut self, path: Option<String>) -> Self {
321        self.path = path;
322
323        self
324    }
325
326    pub fn doc_comments(mut self, doc_comments: Vec<String>) -> Self {
327        self.doc_comments = Some(doc_comments);
328
329        self
330    }
331
332    pub fn deprecated(mut self, deprecated: bool) -> Self {
333        self.deprecated = deprecated;
334
335        self
336    }
337}
338
339impl<'p> ToTokensDiagnostics for Path<'p> {
340    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) -> Result<(), Diagnostics> {
341        let fn_name = &*self.fn_ident.to_string();
342        let operation_id = self
343            .path_attr
344            .operation_id
345            .clone()
346            .or(Some(
347                ExprLit {
348                    attrs: vec![],
349                    lit: Lit::Str(LitStr::new(fn_name, Span::call_site())),
350                }
351                .into(),
352            ))
353            .ok_or_else(|| {
354                Diagnostics::new("operation id is not defined for path")
355                    .help(format!(
356                        "Try to define it in #[utoipa::path(operation_id = {})]",
357                        &fn_name
358                    ))
359                    .help("Did you define the #[utoipa::path(...)] over function?")
360            })?;
361
362        let methods = if !self.path_attr.methods.is_empty() {
363            &self.path_attr.methods
364        } else {
365            &self.ext_methods
366        };
367        if methods.is_empty() {
368            let diagnostics = || {
369                Diagnostics::new("path operation(s) is not defined for path")
370                    .help("Did you forget to define it, e.g. #[utoipa::path(get, ...)]")
371                    .help("Or perhaps #[utoipa::path(method(head, get), ...)]")
372            };
373
374            #[cfg(any(feature = "actix_extras", feature = "rocket_extras"))]
375            {
376                return Err(diagnostics().help(
377                    "Did you forget to define operation path attribute macro e.g #[get(...)]",
378                ));
379            }
380
381            #[cfg(not(any(feature = "actix_extras", feature = "rocket_extras")))]
382            return Err(diagnostics());
383        }
384
385        let method_operations = methods.iter().collect::<Array<_>>();
386
387        let path = self
388            .path_attr
389            .path
390            .as_ref()
391            .map(|path| path.to_token_stream())
392            .or(self.path.as_ref().map(|path| path.to_token_stream()))
393            .ok_or_else(|| {
394                let diagnostics = || {
395                    Diagnostics::new("path is not defined for #[utoipa::path(...)]").help(
396                        r#"Did you forget to define it in #[utoipa::path(..., path = "...")]"#,
397                    )
398                };
399
400                #[cfg(any(feature = "actix_extras", feature = "rocket_extras"))]
401                {
402                    diagnostics().help(
403                        "Did you forget to define operation path attribute macro e.g #[get(...)]",
404                    )
405                }
406
407                #[cfg(not(any(feature = "actix_extras", feature = "rocket_extras")))]
408                diagnostics()
409            })?;
410
411        let path_with_context_path = self
412            .path_attr
413            .context_path
414            .as_ref()
415            .map(|context_path| {
416                let context_path = context_path.to_token_stream();
417                let context_path_tokens = quote! {
418                    format!("{}{}",
419                        #context_path,
420                        #path
421                    )
422                };
423                context_path_tokens
424            })
425            .unwrap_or_else(|| {
426                quote! {
427                    String::from(#path)
428                }
429            });
430
431        let split_comment = self.doc_comments.as_ref().map(|comments| {
432            let mut split = comments.split(|comment| comment.trim().is_empty());
433            let summary = split
434                .by_ref()
435                .next()
436                .map(|summary| summary.join("\n"))
437                .unwrap_or_default();
438            let description = split.map(|lines| lines.join("\n")).collect::<Vec<_>>();
439
440            (summary, description)
441        });
442
443        let summary = self
444            .path_attr
445            .summary
446            .as_ref()
447            .map(Summary::Value)
448            .or_else(|| {
449                split_comment
450                    .as_ref()
451                    .map(|(summary, _)| Summary::Str(summary))
452            });
453
454        let description = self
455            .path_attr
456            .description
457            .as_ref()
458            .map(Description::Value)
459            .or_else(|| {
460                split_comment
461                    .as_ref()
462                    .map(|(_, description)| Description::Vec(description))
463            });
464
465        let operation: Operation = Operation {
466            deprecated: self.deprecated,
467            operation_id,
468            summary,
469            description,
470            parameters: self.path_attr.params.as_ref(),
471            request_body: self.path_attr.request_body.as_ref(),
472            responses: self.path_attr.responses.as_ref(),
473            security: self.path_attr.security.as_ref(),
474            extensions: self.path_attr.extensions.as_ref(),
475        };
476        let operation = as_tokens_or_diagnostics!(&operation);
477
478        fn to_schema_references(
479            mut schemas: TokenStream2,
480            (is_inline, component_schema): (bool, ComponentSchema),
481        ) -> TokenStream2 {
482            for reference in component_schema.schema_references {
483                let name = &reference.name;
484                let tokens = &reference.tokens;
485                let references = &reference.references;
486
487                #[cfg(feature = "config")]
488                let should_collect_schema = (matches!(
489                    crate::CONFIG.schema_collect,
490                    utoipa_config::SchemaCollect::NonInlined
491                ) && !is_inline)
492                    || matches!(
493                        crate::CONFIG.schema_collect,
494                        utoipa_config::SchemaCollect::All
495                    );
496                #[cfg(not(feature = "config"))]
497                let should_collect_schema = !is_inline;
498                if should_collect_schema {
499                    schemas.extend(quote!( schemas.push((#name, #tokens)); ));
500                }
501                schemas.extend(quote!( #references; ));
502            }
503
504            schemas
505        }
506
507        let response_schemas = self
508            .path_attr
509            .responses
510            .iter()
511            .map(|response| response.get_component_schemas())
512            .collect::<Result<Vec<_>, Diagnostics>>()?
513            .into_iter()
514            .flatten()
515            .fold(TokenStream2::new(), to_schema_references);
516
517        let schemas = self
518            .path_attr
519            .request_body
520            .as_ref()
521            .map_try(|request_body| request_body.get_component_schemas())?
522            .into_iter()
523            .flatten()
524            .fold(TokenStream2::new(), to_schema_references);
525
526        let mut tags = self.path_attr.tags.clone();
527        if let Some(tag) = self.path_attr.tag.as_ref() {
528            // if defined tag is the first before the additional tags
529            tags.insert(0, tag.clone());
530        }
531        let tags_list = tags.into_iter().collect::<Array<_>>();
532
533        let impl_for = if let Some(impl_for) = &self.path_attr.impl_for {
534            Cow::Borrowed(impl_for)
535        } else {
536            let path_struct = format_path_ident(Cow::Borrowed(self.fn_ident));
537
538            tokens.extend(quote! {
539                #[allow(non_camel_case_types)]
540                #[doc(hidden)]
541                #[derive(Clone)]
542                pub struct #path_struct;
543            });
544
545            #[cfg(feature = "actix_extras")]
546            {
547                // Add supporting passthrough implementations only if actix-web service config
548                // is implemented and no impl_for has been defined
549                if self.path_attr.impl_for.is_none() && !self.ext_methods.is_empty() {
550                    let fn_ident = self.fn_ident;
551                    tokens.extend(quote! {
552                        impl ::actix_web::dev::HttpServiceFactory for #path_struct {
553                            fn register(self, __config: &mut actix_web::dev::AppService) {
554                                ::actix_web::dev::HttpServiceFactory::register(#fn_ident, __config);
555                            }
556                        }
557                        impl<'t> utoipa::__dev::Tags<'t> for #fn_ident {
558                            fn tags() -> Vec<&'t str> {
559                                #path_struct::tags()
560                            }
561                        }
562                        impl utoipa::Path for #fn_ident {
563                            fn path() -> String {
564                                #path_struct::path()
565                            }
566
567                            fn methods() -> Vec<utoipa::openapi::path::HttpMethod> {
568                                #path_struct::methods()
569                            }
570
571                            fn operation() -> utoipa::openapi::path::Operation {
572                                #path_struct::operation()
573                            }
574                        }
575
576                        impl utoipa::__dev::SchemaReferences for #fn_ident {
577                            fn schemas(schemas: &mut Vec<(String, utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>)>) {
578                                <#path_struct as utoipa::__dev::SchemaReferences>::schemas(schemas);
579                            }
580                        }
581                    })
582                }
583            }
584
585            path_struct
586        };
587
588        tokens.extend(quote! {
589            impl<'t> utoipa::__dev::Tags<'t> for #impl_for {
590                fn tags() -> Vec<&'t str> {
591                    #tags_list.into()
592                }
593            }
594            impl utoipa::Path for #impl_for {
595                fn path() -> String {
596                    #path_with_context_path
597                }
598
599                fn methods() -> Vec<utoipa::openapi::path::HttpMethod> {
600                    #method_operations.into()
601                }
602
603                fn operation() -> utoipa::openapi::path::Operation {
604                    use utoipa::openapi::ToArray;
605                    use std::iter::FromIterator;
606                    #operation.into()
607                }
608            }
609
610            impl utoipa::__dev::SchemaReferences for #impl_for {
611                fn schemas(schemas: &mut Vec<(String, utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>)>) {
612                    #schemas
613                    #response_schemas
614                }
615            }
616
617        });
618
619        Ok(())
620    }
621}
622
623#[cfg_attr(feature = "debug", derive(Debug))]
624struct Operation<'a> {
625    operation_id: Expr,
626    summary: Option<Summary<'a>>,
627    description: Option<Description<'a>>,
628    deprecated: bool,
629    parameters: &'a Vec<Parameter<'a>>,
630    request_body: Option<&'a RequestBodyAttr<'a>>,
631    responses: &'a Vec<Response<'a>>,
632    security: Option<&'a Array<'a, SecurityRequirementsAttr>>,
633    extensions: Option<&'a Extensions>,
634}
635
636impl ToTokensDiagnostics for Operation<'_> {
637    fn to_tokens(&self, tokens: &mut TokenStream2) -> Result<(), Diagnostics> {
638        tokens.extend(quote! { utoipa::openapi::path::OperationBuilder::new() });
639
640        if let Some(request_body) = self.request_body {
641            let request_body = as_tokens_or_diagnostics!(request_body);
642            tokens.extend(quote! {
643                .request_body(Some(#request_body))
644            })
645        }
646
647        let responses = Responses(self.responses);
648        let responses = as_tokens_or_diagnostics!(&responses);
649        tokens.extend(quote! {
650            .responses(#responses)
651        });
652        if let Some(security_requirements) = self.security {
653            tokens.extend(quote! {
654                .securities(Some(#security_requirements))
655            })
656        }
657        let operation_id = &self.operation_id;
658        tokens.extend(quote_spanned! { operation_id.span() =>
659            .operation_id(Some(#operation_id))
660        });
661
662        if self.deprecated {
663            let deprecated: Deprecated = self.deprecated.into();
664            tokens.extend(quote!( .deprecated(Some(#deprecated))))
665        }
666
667        if let Some(summary) = &self.summary {
668            summary.to_tokens(tokens);
669        }
670
671        if let Some(description) = &self.description {
672            description.to_tokens(tokens);
673        }
674
675        for parameter in self.parameters {
676            parameter.to_tokens(tokens)?;
677        }
678
679        if let Some(extensions) = self.extensions {
680            tokens.extend(quote! { .extensions(Some(#extensions)) })
681        }
682
683        Ok(())
684    }
685}
686
687#[cfg_attr(feature = "debug", derive(Debug))]
688enum Description<'a> {
689    Value(&'a parse_utils::LitStrOrExpr),
690    Vec(&'a [String]),
691}
692
693impl ToTokens for Description<'_> {
694    fn to_tokens(&self, tokens: &mut TokenStream2) {
695        match self {
696            Self::Value(value) => tokens.extend(quote! {
697                .description(Some(#value))
698            }),
699            Self::Vec(vec) => {
700                let description = vec.join("\n\n");
701
702                if !description.is_empty() {
703                    tokens.extend(quote! {
704                        .description(Some(#description))
705                    })
706                }
707            }
708        }
709    }
710}
711
712#[cfg_attr(feature = "debug", derive(Debug))]
713enum Summary<'a> {
714    Value(&'a parse_utils::LitStrOrExpr),
715    Str(&'a str),
716}
717
718impl ToTokens for Summary<'_> {
719    fn to_tokens(&self, tokens: &mut TokenStream2) {
720        match self {
721            Self::Value(value) => tokens.extend(quote! {
722                .summary(Some(#value))
723            }),
724            Self::Str(str) if !str.is_empty() => tokens.extend(quote! {
725                .summary(Some(#str))
726            }),
727            _ => (),
728        }
729    }
730}
731
732pub trait PathTypeTree {
733    /// Resolve default content type based on current [`Type`].
734    fn get_default_content_type(&self) -> Cow<'static, str>;
735
736    /// Check whether [`TypeTree`] is a Vec, slice, array or other supported array type
737    fn is_array(&self) -> bool;
738}
739
740impl<'p> PathTypeTree for TypeTree<'p> {
741    /// Resolve default content type based on current [`Type`].
742    fn get_default_content_type(&self) -> Cow<'static, str> {
743        if self.is_array()
744            && self
745                .children
746                .as_ref()
747                .map(|children| {
748                    children
749                        .iter()
750                        .flat_map(|child| child.path.as_ref().zip(Some(child.is_option())))
751                        .any(|(path, nullable)| {
752                            SchemaType {
753                                path: Cow::Borrowed(path),
754                                nullable,
755                            }
756                            .is_byte()
757                        })
758                })
759                .unwrap_or(false)
760        {
761            Cow::Borrowed("application/octet-stream")
762        } else if self
763            .path
764            .as_ref()
765            .map(|path| SchemaType {
766                path: Cow::Borrowed(path.deref()),
767                nullable: self.is_option(),
768            })
769            .map(|schema_type| schema_type.is_primitive())
770            .unwrap_or(false)
771        {
772            Cow::Borrowed("text/plain")
773        } else {
774            Cow::Borrowed("application/json")
775        }
776    }
777
778    /// Check whether [`TypeTree`] is a Vec, slice, array or other supported array type
779    fn is_array(&self) -> bool {
780        match self.generic_type {
781            Some(GenericType::Vec | GenericType::Set) => true,
782            Some(_) => self
783                .children
784                .as_ref()
785                .unwrap()
786                .iter()
787                .any(|child| child.is_array()),
788            None => false,
789        }
790    }
791}
792
793mod parse {
794    use syn::parse::ParseStream;
795    use syn::punctuated::Punctuated;
796    use syn::token::Comma;
797    use syn::Result;
798
799    use crate::path::example::Example;
800    use crate::{parse_utils, AnyValue};
801
802    #[inline]
803    pub(super) fn description(input: ParseStream) -> Result<parse_utils::LitStrOrExpr> {
804        parse_utils::parse_next_literal_str_or_expr(input)
805    }
806
807    #[inline]
808    pub(super) fn example(input: ParseStream) -> Result<AnyValue> {
809        parse_utils::parse_next(input, || AnyValue::parse_lit_str_or_json(input))
810    }
811
812    #[inline]
813    pub(super) fn examples(input: ParseStream) -> Result<Punctuated<Example, Comma>> {
814        parse_utils::parse_comma_separated_within_parenthesis(input)
815    }
816}