1use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
2use quote::{quote, quote_spanned, ToTokens};
3use std::borrow::Cow;
4use syn::{
5 parenthesized,
6 parse::{Parse, ParseStream},
7 punctuated::Punctuated,
8 spanned::Spanned,
9 token::Comma,
10 Attribute, Error, ExprPath, LitInt, LitStr, Token, TypePath,
11};
12
13use crate::{
14 component::{features::Inline, ComponentSchema, TypeTree},
15 parse_utils, AnyValue, Array, ResultExt,
16};
17
18use super::{example::Example, status::STATUS_CODES, InlineType, PathType, PathTypeTree};
19
20pub mod derive;
21
22#[cfg_attr(feature = "debug", derive(Debug))]
23pub enum Response<'r> {
24 IntoResponses(Cow<'r, TypePath>),
26 Tuple(ResponseTuple<'r>),
28}
29
30impl Parse for Response<'_> {
31 fn parse(input: ParseStream) -> syn::Result<Self> {
32 if input.fork().parse::<ExprPath>().is_ok() {
33 Ok(Self::IntoResponses(Cow::Owned(input.parse::<TypePath>()?)))
34 } else {
35 let response;
36 parenthesized!(response in input);
37 Ok(Self::Tuple(response.parse()?))
38 }
39 }
40}
41
42#[derive(Default)]
44#[cfg_attr(feature = "debug", derive(Debug))]
45pub struct ResponseTuple<'r> {
46 status_code: ResponseStatus,
47 inner: Option<ResponseTupleInner<'r>>,
48}
49
50const RESPONSE_INCOMPATIBLE_ATTRIBUTES_MSG: &str =
51 "The `response` attribute may only be used in conjunction with the `status` attribute";
52
53impl<'r> ResponseTuple<'r> {
54 fn as_value(&mut self, span: Span) -> syn::Result<&mut ResponseValue<'r>> {
56 if self.inner.is_none() {
57 self.inner = Some(ResponseTupleInner::Value(ResponseValue::default()));
58 }
59 if let ResponseTupleInner::Value(val) = self.inner.as_mut().unwrap() {
60 Ok(val)
61 } else {
62 Err(Error::new(span, RESPONSE_INCOMPATIBLE_ATTRIBUTES_MSG))
63 }
64 }
65
66 fn set_ref_type(&mut self, span: Span, ty: InlineType<'r>) -> syn::Result<()> {
68 match &mut self.inner {
69 None => self.inner = Some(ResponseTupleInner::Ref(ty)),
70 Some(ResponseTupleInner::Ref(r)) => *r = ty,
71 Some(ResponseTupleInner::Value(_)) => {
72 return Err(Error::new(span, RESPONSE_INCOMPATIBLE_ATTRIBUTES_MSG))
73 }
74 }
75 Ok(())
76 }
77}
78
79#[cfg_attr(feature = "debug", derive(Debug))]
80enum ResponseTupleInner<'r> {
81 Value(ResponseValue<'r>),
82 Ref(InlineType<'r>),
83}
84
85impl Parse for ResponseTuple<'_> {
86 fn parse(input: ParseStream) -> syn::Result<Self> {
87 const EXPECTED_ATTRIBUTE_MESSAGE: &str = "unexpected attribute, expected any of: status, description, body, content_type, headers, example, examples, response";
88
89 let mut response = ResponseTuple::default();
90
91 while !input.is_empty() {
92 let ident = input.parse::<Ident>().map_err(|error| {
93 Error::new(
94 error.span(),
95 format!("{EXPECTED_ATTRIBUTE_MESSAGE}, {error}"),
96 )
97 })?;
98 let attribute_name = &*ident.to_string();
99
100 match attribute_name {
101 "status" => {
102 response.status_code =
103 parse_utils::parse_next(input, || input.parse::<ResponseStatus>())?;
104 }
105 "description" => {
106 response.as_value(input.span())?.description = parse::description(input)?;
107 }
108 "body" => {
109 response.as_value(input.span())?.response_type =
110 Some(parse_utils::parse_next(input, || input.parse())?);
111 }
112 "content_type" => {
113 response.as_value(input.span())?.content_type =
114 Some(parse::content_type(input)?);
115 }
116 "headers" => {
117 response.as_value(input.span())?.headers = parse::headers(input)?;
118 }
119 "example" => {
120 response.as_value(input.span())?.example = Some(parse::example(input)?);
121 }
122 "examples" => {
123 response.as_value(input.span())?.examples = Some(parse::examples(input)?);
124 }
125 "content" => {
126 response.as_value(input.span())?.content =
127 parse_utils::parse_punctuated_within_parenthesis(input)?;
128 }
129 "response" => {
130 response.set_ref_type(
131 input.span(),
132 parse_utils::parse_next(input, || input.parse())?,
133 )?;
134 }
135 _ => return Err(Error::new(ident.span(), EXPECTED_ATTRIBUTE_MESSAGE)),
136 }
137
138 if !input.is_empty() {
139 input.parse::<Token![,]>()?;
140 }
141 }
142
143 if response.inner.is_none() {
144 response.inner = Some(ResponseTupleInner::Value(ResponseValue::default()))
145 }
146
147 Ok(response)
148 }
149}
150
151impl<'r> From<ResponseValue<'r>> for ResponseTuple<'r> {
152 fn from(value: ResponseValue<'r>) -> Self {
153 ResponseTuple {
154 inner: Some(ResponseTupleInner::Value(value)),
155 ..Default::default()
156 }
157 }
158}
159
160impl<'r> From<(ResponseStatus, ResponseValue<'r>)> for ResponseTuple<'r> {
161 fn from((status_code, response_value): (ResponseStatus, ResponseValue<'r>)) -> Self {
162 ResponseTuple {
163 inner: Some(ResponseTupleInner::Value(response_value)),
164 status_code,
165 }
166 }
167}
168
169pub struct DeriveResponsesAttributes<T> {
170 derive_value: T,
171 description: String,
172}
173
174impl<'r> From<DeriveResponsesAttributes<DeriveIntoResponsesValue>> for ResponseValue<'r> {
175 fn from(value: DeriveResponsesAttributes<DeriveIntoResponsesValue>) -> Self {
176 Self::from_derive_into_responses_value(value.derive_value, value.description)
177 }
178}
179
180impl<'r> From<DeriveResponsesAttributes<Option<DeriveToResponseValue>>> for ResponseValue<'r> {
181 fn from(
182 DeriveResponsesAttributes::<Option<DeriveToResponseValue>> {
183 derive_value,
184 description,
185 }: DeriveResponsesAttributes<Option<DeriveToResponseValue>>,
186 ) -> Self {
187 if let Some(derive_value) = derive_value {
188 ResponseValue::from_derive_to_response_value(derive_value, description)
189 } else {
190 ResponseValue {
191 description,
192 ..Default::default()
193 }
194 }
195 }
196}
197
198#[derive(Default)]
199#[cfg_attr(feature = "debug", derive(Debug))]
200pub struct ResponseValue<'r> {
201 description: String,
202 response_type: Option<PathType<'r>>,
203 content_type: Option<Vec<String>>,
204 headers: Vec<Header>,
205 example: Option<AnyValue>,
206 examples: Option<Punctuated<Example, Comma>>,
207 content: Punctuated<Content<'r>, Comma>,
208}
209
210impl<'r> ResponseValue<'r> {
211 fn from_derive_to_response_value(
212 derive_value: DeriveToResponseValue,
213 description: String,
214 ) -> Self {
215 Self {
216 description: if derive_value.description.is_empty() && !description.is_empty() {
217 description
218 } else {
219 derive_value.description
220 },
221 headers: derive_value.headers,
222 example: derive_value.example.map(|(example, _)| example),
223 examples: derive_value.examples.map(|(examples, _)| examples),
224 content_type: derive_value.content_type,
225 ..Default::default()
226 }
227 }
228
229 fn from_derive_into_responses_value(
230 response_value: DeriveIntoResponsesValue,
231 description: String,
232 ) -> Self {
233 ResponseValue {
234 description: if response_value.description.is_empty() && !description.is_empty() {
235 description
236 } else {
237 response_value.description
238 },
239 headers: response_value.headers,
240 example: response_value.example.map(|(example, _)| example),
241 examples: response_value.examples.map(|(examples, _)| examples),
242 content_type: response_value.content_type,
243 ..Default::default()
244 }
245 }
246
247 fn response_type(mut self, response_type: Option<PathType<'r>>) -> Self {
248 self.response_type = response_type;
249
250 self
251 }
252}
253
254impl ToTokens for ResponseTuple<'_> {
255 fn to_tokens(&self, tokens: &mut TokenStream2) {
256 match self.inner.as_ref().unwrap() {
257 ResponseTupleInner::Ref(res) => {
258 let path = &res.ty;
259 if res.is_inline {
260 tokens.extend(quote_spanned! {path.span()=>
261 <#path as utoipa::ToResponse>::response().1
262 });
263 } else {
264 tokens.extend(quote! {
265 utoipa::openapi::Ref::from_response_name(<#path as utoipa::ToResponse>::response().0)
266 });
267 }
268 }
269 ResponseTupleInner::Value(val) => {
270 let description = &val.description;
271 tokens.extend(quote! {
272 utoipa::openapi::ResponseBuilder::new().description(#description)
273 });
274
275 let create_content = |path_type: &PathType,
276 example: &Option<AnyValue>,
277 examples: &Option<Punctuated<Example, Comma>>|
278 -> TokenStream2 {
279 let content_schema = match path_type {
280 PathType::Ref(ref_type) => quote! {
281 utoipa::openapi::schema::Ref::new(#ref_type)
282 }
283 .to_token_stream(),
284 PathType::MediaType(ref path_type) => {
285 let type_tree = path_type.as_type_tree();
286
287 ComponentSchema::new(crate::component::ComponentSchemaProps {
288 type_tree: &type_tree,
289 features: Some(vec![Inline::from(path_type.is_inline).into()]),
290 description: None,
291 deprecated: None,
292 object_name: "",
293 })
294 .to_token_stream()
295 }
296 PathType::InlineSchema(schema, _) => schema.to_token_stream(),
297 };
298
299 let mut content =
300 quote! { utoipa::openapi::ContentBuilder::new().schema(#content_schema) };
301
302 if let Some(ref example) = example {
303 content.extend(quote! {
304 .example(Some(#example))
305 })
306 }
307 if let Some(ref examples) = examples {
308 let examples = examples
309 .iter()
310 .map(|example| {
311 let name = &example.name;
312 quote!((#name, #example))
313 })
314 .collect::<Array<TokenStream2>>();
315 content.extend(quote!(
316 .examples_from_iter(#examples)
317 ))
318 }
319
320 quote! {
321 #content.build()
322 }
323 };
324
325 if let Some(response_type) = &val.response_type {
326 let content = create_content(response_type, &val.example, &val.examples);
327
328 if let Some(content_types) = val.content_type.as_ref() {
329 content_types.iter().for_each(|content_type| {
330 tokens.extend(quote! {
331 .content(#content_type, #content)
332 })
333 })
334 } else {
335 match response_type {
336 PathType::Ref(_) => {
337 tokens.extend(quote! {
338 .content("application/json", #content)
339 });
340 }
341 PathType::MediaType(path_type) => {
342 let type_tree = path_type.as_type_tree();
343 let default_type = type_tree.get_default_content_type();
344 tokens.extend(quote! {
345 .content(#default_type, #content)
346 })
347 }
348 PathType::InlineSchema(_, ty) => {
349 let type_tree = TypeTree::from_type(ty);
350 let default_type = type_tree.get_default_content_type();
351 tokens.extend(quote! {
352 .content(#default_type, #content)
353 })
354 }
355 }
356 }
357 }
358
359 val.content
360 .iter()
361 .map(|Content(content_type, body, example, examples)| {
362 let content = create_content(body, example, examples);
363 (Cow::Borrowed(&**content_type), content)
364 })
365 .for_each(|(content_type, content)| {
366 tokens.extend(quote! { .content(#content_type, #content) })
367 });
368
369 val.headers.iter().for_each(|header| {
370 let name = &header.name;
371 tokens.extend(quote! {
372 .header(#name, #header)
373 })
374 });
375
376 tokens.extend(quote! { .build() });
377 }
378 }
379 }
380}
381
382trait DeriveResponseValue: Parse {
383 fn merge_from(self, other: Self) -> Self;
384
385 fn from_attributes(attributes: &[Attribute]) -> Option<Self> {
386 attributes
387 .iter()
388 .filter(|attribute| attribute.path().get_ident().unwrap() == "response")
389 .map(|attribute| attribute.parse_args::<Self>().unwrap_or_abort())
390 .reduce(|acc, item| acc.merge_from(item))
391 }
392}
393
394#[derive(Default)]
395#[cfg_attr(feature = "debug", derive(Debug))]
396struct DeriveToResponseValue {
397 content_type: Option<Vec<String>>,
398 headers: Vec<Header>,
399 description: String,
400 example: Option<(AnyValue, Ident)>,
401 examples: Option<(Punctuated<Example, Comma>, Ident)>,
402}
403
404impl DeriveResponseValue for DeriveToResponseValue {
405 fn merge_from(mut self, other: Self) -> Self {
406 if other.content_type.is_some() {
407 self.content_type = other.content_type;
408 }
409 if !other.headers.is_empty() {
410 self.headers = other.headers;
411 }
412 if !other.description.is_empty() {
413 self.description = other.description;
414 }
415 if other.example.is_some() {
416 self.example = other.example;
417 }
418 if other.examples.is_some() {
419 self.examples = other.examples;
420 }
421
422 self
423 }
424}
425
426impl Parse for DeriveToResponseValue {
427 fn parse(input: ParseStream) -> syn::Result<Self> {
428 let mut response = DeriveToResponseValue::default();
429
430 while !input.is_empty() {
431 let ident = input.parse::<Ident>()?;
432 let attribute_name = &*ident.to_string();
433
434 match attribute_name {
435 "description" => {
436 response.description = parse::description(input)?;
437 }
438 "content_type" => {
439 response.content_type = Some(parse::content_type(input)?);
440 }
441 "headers" => {
442 response.headers = parse::headers(input)?;
443 }
444 "example" => {
445 response.example = Some((parse::example(input)?, ident));
446 }
447 "examples" => {
448 response.examples = Some((parse::examples(input)?, ident));
449 }
450 _ => {
451 return Err(Error::new(
452 ident.span(),
453 format!("unexpected attribute: {attribute_name}, expected any of: inline, description, content_type, headers, example"),
454 ));
455 }
456 }
457
458 if !input.is_empty() {
459 input.parse::<Comma>()?;
460 }
461 }
462
463 Ok(response)
464 }
465}
466
467#[derive(Default)]
468struct DeriveIntoResponsesValue {
469 status: ResponseStatus,
470 content_type: Option<Vec<String>>,
471 headers: Vec<Header>,
472 description: String,
473 example: Option<(AnyValue, Ident)>,
474 examples: Option<(Punctuated<Example, Comma>, Ident)>,
475}
476
477impl DeriveResponseValue for DeriveIntoResponsesValue {
478 fn merge_from(mut self, other: Self) -> Self {
479 self.status = other.status;
480
481 if other.content_type.is_some() {
482 self.content_type = other.content_type;
483 }
484 if !other.headers.is_empty() {
485 self.headers = other.headers;
486 }
487 if !other.description.is_empty() {
488 self.description = other.description;
489 }
490 if other.example.is_some() {
491 self.example = other.example;
492 }
493 if other.examples.is_some() {
494 self.examples = other.examples;
495 }
496
497 self
498 }
499}
500
501impl Parse for DeriveIntoResponsesValue {
502 fn parse(input: ParseStream) -> syn::Result<Self> {
503 let mut response = DeriveIntoResponsesValue::default();
504 const MISSING_STATUS_ERROR: &str = "missing expected `status` attribute";
505 let first_span = input.span();
506
507 let status_ident = input
508 .parse::<Ident>()
509 .map_err(|error| Error::new(error.span(), MISSING_STATUS_ERROR))?;
510
511 if status_ident == "status" {
512 response.status = parse_utils::parse_next(input, || input.parse::<ResponseStatus>())?;
513 } else {
514 return Err(Error::new(status_ident.span(), MISSING_STATUS_ERROR));
515 }
516
517 if response.status.to_token_stream().is_empty() {
518 return Err(Error::new(first_span, MISSING_STATUS_ERROR));
519 }
520
521 if !input.is_empty() {
522 input.parse::<Token![,]>()?;
523 }
524
525 while !input.is_empty() {
526 let ident = input.parse::<Ident>()?;
527 let attribute_name = &*ident.to_string();
528
529 match attribute_name {
530 "description" => {
531 response.description = parse::description(input)?;
532 }
533 "content_type" => {
534 response.content_type = Some(parse::content_type(input)?);
535 }
536 "headers" => {
537 response.headers = parse::headers(input)?;
538 }
539 "example" => {
540 response.example = Some((parse::example(input)?, ident));
541 }
542 "examples" => {
543 response.examples = Some((parse::examples(input)?, ident));
544 }
545 _ => {
546 return Err(Error::new(
547 ident.span(),
548 format!("unexpected attribute: {attribute_name}, expected any of: description, content_type, headers, example, examples"),
549 ));
550 }
551 }
552
553 if !input.is_empty() {
554 input.parse::<Token![,]>()?;
555 }
556 }
557
558 Ok(response)
559 }
560}
561
562#[derive(Default)]
563#[cfg_attr(feature = "debug", derive(Debug))]
564struct ResponseStatus(TokenStream2);
565
566impl Parse for ResponseStatus {
567 fn parse(input: ParseStream) -> syn::Result<Self> {
568 fn parse_lit_int(input: ParseStream) -> syn::Result<Cow<'_, str>> {
569 input.parse::<LitInt>()?.base10_parse().map(Cow::Owned)
570 }
571
572 fn parse_lit_str_status_range(input: ParseStream) -> syn::Result<Cow<'_, str>> {
573 const VALID_STATUS_RANGES: [&str; 6] = ["default", "1XX", "2XX", "3XX", "4XX", "5XX"];
574
575 input
576 .parse::<LitStr>()
577 .and_then(|lit_str| {
578 let value = lit_str.value();
579 if !VALID_STATUS_RANGES.contains(&value.as_str()) {
580 Err(Error::new(
581 value.span(),
582 format!(
583 "Invalid status range, expected one of: {}",
584 VALID_STATUS_RANGES.join(", "),
585 ),
586 ))
587 } else {
588 Ok(value)
589 }
590 })
591 .map(Cow::Owned)
592 }
593
594 fn parse_http_status_code(input: ParseStream) -> syn::Result<TokenStream2> {
595 let http_status_path = input.parse::<ExprPath>()?;
596 let last_segment = http_status_path
597 .path
598 .segments
599 .last()
600 .expect("Expected at least one segment in http StatusCode");
601
602 STATUS_CODES
603 .iter()
604 .find_map(|(code, name)| {
605 if last_segment.ident == name {
606 Some(code.to_string().to_token_stream())
607 } else {
608 None
609 }
610 })
611 .ok_or_else(|| {
612 Error::new(
613 last_segment.span(),
614 format!(
615 "No associate item `{}` found for struct `http::StatusCode`",
616 last_segment.ident
617 ),
618 )
619 })
620 }
621
622 let lookahead = input.lookahead1();
623 if lookahead.peek(LitInt) {
624 parse_lit_int(input).map(|status| Self(status.to_token_stream()))
625 } else if lookahead.peek(LitStr) {
626 parse_lit_str_status_range(input).map(|status| Self(status.to_token_stream()))
627 } else if lookahead.peek(syn::Ident) {
628 parse_http_status_code(input).map(Self)
629 } else {
630 Err(lookahead.error())
631 }
632 }
633}
634
635impl ToTokens for ResponseStatus {
636 fn to_tokens(&self, tokens: &mut TokenStream2) {
637 self.0.to_tokens(tokens);
638 }
639}
640
641#[cfg_attr(feature = "debug", derive(Debug))]
646struct Content<'c>(
647 String,
648 PathType<'c>,
649 Option<AnyValue>,
650 Option<Punctuated<Example, Comma>>,
651);
652
653impl Parse for Content<'_> {
654 fn parse(input: ParseStream) -> syn::Result<Self> {
655 let content;
656 parenthesized!(content in input);
657
658 let content_type = content.parse::<LitStr>()?;
659 content.parse::<Token![=]>()?;
660 let body = content.parse()?;
661 content.parse::<Option<Comma>>()?;
662 let mut example = None::<AnyValue>;
663 let mut examples = None::<Punctuated<Example, Comma>>;
664
665 while !content.is_empty() {
666 let ident = content.parse::<Ident>()?;
667 let attribute_name = &*ident.to_string();
668 match attribute_name {
669 "example" => {
670 example = Some(parse_utils::parse_next(&content, || {
671 AnyValue::parse_json(&content)
672 })?)
673 }
674 "examples" => {
675 examples = Some(parse_utils::parse_punctuated_within_parenthesis(&content)?)
676 }
677 _ => {
678 return Err(Error::new(
679 ident.span(),
680 format!(
681 "unexpected attribute: {ident}, expected one of: example, examples"
682 ),
683 ));
684 }
685 }
686
687 if !content.is_empty() {
688 content.parse::<Comma>()?;
689 }
690 }
691
692 Ok(Content(content_type.value(), body, example, examples))
693 }
694}
695
696pub struct Responses<'a>(pub &'a [Response<'a>]);
697
698impl ToTokens for Responses<'_> {
699 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
700 tokens.extend(self.0.iter().fold(
701 quote! { utoipa::openapi::ResponsesBuilder::new() },
702 |mut acc, response| {
703 match response {
704 Response::IntoResponses(path) => {
705 let span = path.span();
706 acc.extend(quote_spanned! {span =>
707 .responses_from_into_responses::<#path>()
708 })
709 }
710 Response::Tuple(response) => {
711 let code = &response.status_code;
712 acc.extend(quote! { .response(#code, #response) });
713 }
714 }
715
716 acc
717 },
718 ));
719
720 tokens.extend(quote! { .build() });
721 }
722}
723
724#[derive(Default)]
779#[cfg_attr(feature = "debug", derive(Debug))]
780struct Header {
781 name: String,
782 value_type: Option<InlineType<'static>>,
783 description: Option<String>,
784}
785
786impl Parse for Header {
787 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
788 let mut header = Header {
789 name: input.parse::<LitStr>()?.value(),
790 ..Default::default()
791 };
792
793 if input.peek(Token![=]) {
794 input.parse::<Token![=]>()?;
795
796 header.value_type = Some(input.parse().map_err(|error| {
797 Error::new(
798 error.span(),
799 format!("unexpected token, expected type such as String, {error}"),
800 )
801 })?);
802 }
803
804 if !input.is_empty() {
805 input.parse::<Token![,]>()?;
806 }
807
808 if input.peek(syn::Ident) {
809 input
810 .parse::<Ident>()
811 .map_err(|error| {
812 Error::new(
813 error.span(),
814 format!("unexpected attribute, expected: description, {error}"),
815 )
816 })
817 .and_then(|ident| {
818 if ident != "description" {
819 return Err(Error::new(
820 ident.span(),
821 "unexpected attribute, expected: description",
822 ));
823 }
824 Ok(ident)
825 })?;
826 input.parse::<Token![=]>()?;
827 header.description = Some(input.parse::<LitStr>()?.value());
828 }
829
830 Ok(header)
831 }
832}
833
834impl ToTokens for Header {
835 fn to_tokens(&self, tokens: &mut TokenStream2) {
836 if let Some(header_type) = &self.value_type {
837 let type_tree = header_type.as_type_tree();
839
840 let media_type_schema = ComponentSchema::new(crate::component::ComponentSchemaProps {
841 type_tree: &type_tree,
842 features: Some(vec![Inline::from(header_type.is_inline).into()]),
843 description: None,
844 deprecated: None,
845 object_name: "",
846 })
847 .to_token_stream();
848
849 tokens.extend(quote! {
850 utoipa::openapi::HeaderBuilder::new().schema(#media_type_schema)
851 })
852 } else {
853 tokens.extend(quote! {
855 Into::<utoipa::openapi::HeaderBuilder>::into(utoipa::openapi::Header::default())
856 })
857 };
858
859 if let Some(ref description) = self.description {
860 tokens.extend(quote! {
861 .description(Some(#description))
862 })
863 }
864
865 tokens.extend(quote! { .build() })
866 }
867}
868
869mod parse {
870 use syn::parse::ParseStream;
871 use syn::punctuated::Punctuated;
872 use syn::token::{Bracket, Comma};
873 use syn::{bracketed, parenthesized, LitStr, Result};
874
875 use crate::path::example::Example;
876 use crate::{parse_utils, AnyValue};
877
878 use super::Header;
879
880 #[inline]
881 pub(super) fn description(input: ParseStream) -> Result<String> {
882 parse_utils::parse_next_literal_str(input)
883 }
884
885 #[inline]
886 pub(super) fn content_type(input: ParseStream) -> Result<Vec<String>> {
887 parse_utils::parse_next(input, || {
888 let look_content_type = input.lookahead1();
889 if look_content_type.peek(LitStr) {
890 Ok(vec![input.parse::<LitStr>()?.value()])
891 } else if look_content_type.peek(Bracket) {
892 let content_types;
893 bracketed!(content_types in input);
894 Ok(
895 Punctuated::<LitStr, Comma>::parse_terminated(&content_types)?
896 .into_iter()
897 .map(|lit| lit.value())
898 .collect(),
899 )
900 } else {
901 Err(look_content_type.error())
902 }
903 })
904 }
905
906 #[inline]
907 pub(super) fn headers(input: ParseStream) -> Result<Vec<Header>> {
908 let headers;
909 parenthesized!(headers in input);
910
911 parse_utils::parse_groups(&headers)
912 }
913
914 #[inline]
915 pub(super) fn example(input: ParseStream) -> Result<AnyValue> {
916 parse_utils::parse_next(input, || AnyValue::parse_lit_str_or_json(input))
917 }
918
919 #[inline]
920 pub(super) fn examples(input: ParseStream) -> Result<Punctuated<Example, Comma>> {
921 parse_utils::parse_punctuated_within_parenthesis(input)
922 }
923}