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 #[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(¶ms)
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#[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 #[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 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 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 fn get_default_content_type(&self) -> Cow<'static, str>;
735
736 fn is_array(&self) -> bool;
738}
739
740impl<'p> PathTypeTree for TypeTree<'p> {
741 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 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}