1use std::borrow::Cow;
2use std::{iter, mem};
3
4use proc_macro2::{Ident, Span, TokenStream};
5use quote::{quote, ToTokens};
6use syn::parse::ParseStream;
7use syn::punctuated::Punctuated;
8use syn::spanned::Spanned;
9use syn::token::Comma;
10use syn::{
11 Attribute, Data, Field, Fields, Generics, Lifetime, LifetimeParam, LitStr, Path, Type,
12 TypePath, Variant,
13};
14
15use crate::component::schema::{EnumSchema, NamedStructSchema, Root};
16use crate::doc_comment::CommentAttributes;
17use crate::path::media_type::{DefaultSchema, MediaTypeAttr, ParsedType, Schema};
18use crate::{
19 as_tokens_or_diagnostics, parse_utils, Array, Diagnostics, OptionExt, ToTokensDiagnostics,
20};
21
22use super::{
23 DeriveIntoResponsesValue, DeriveResponseValue, DeriveToResponseValue, ResponseTuple,
24 ResponseTupleInner, ResponseValue,
25};
26
27pub struct ToResponse<'r> {
28 ident: Ident,
29 lifetime: Lifetime,
30 generics: Generics,
31 response: ResponseTuple<'r>,
32}
33
34impl<'r> ToResponse<'r> {
35 const LIFETIME: &'static str = "'__r";
36
37 pub fn new(
38 attributes: Vec<Attribute>,
39 data: &'r Data,
40 generics: Generics,
41 ident: Ident,
42 ) -> Result<ToResponse<'r>, Diagnostics> {
43 let response = match &data {
44 Data::Struct(struct_value) => match &struct_value.fields {
45 Fields::Named(fields) => {
46 ToResponseNamedStructResponse::new(&attributes, &ident, &fields.named)?.0
47 }
48 Fields::Unnamed(fields) => {
49 let field = fields
50 .unnamed
51 .iter()
52 .next()
53 .expect("Unnamed struct must have 1 field");
54
55 ToResponseUnnamedStructResponse::new(&attributes, &field.ty, &field.attrs)?.0
56 }
57 Fields::Unit => ToResponseUnitStructResponse::new(&attributes)?.0,
58 },
59 Data::Enum(enum_value) => {
60 EnumResponse::new(&ident, &enum_value.variants, &attributes)?.0
61 }
62 Data::Union(_) => {
63 return Err(Diagnostics::with_span(
64 ident.span(),
65 "`ToResponse` does not support `Union` type",
66 ))
67 }
68 };
69
70 let lifetime = Lifetime::new(ToResponse::LIFETIME, Span::call_site());
71
72 Ok(Self {
73 ident,
74 lifetime,
75 generics,
76 response,
77 })
78 }
79}
80
81impl ToTokensDiagnostics for ToResponse<'_> {
82 fn to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostics> {
83 let (_, ty_generics, where_clause) = self.generics.split_for_impl();
84
85 let lifetime = &self.lifetime;
86 let ident = &self.ident;
87 let name = ident.to_string();
88 let response = as_tokens_or_diagnostics!(&self.response);
89
90 let mut to_response_generics = self.generics.clone();
91 to_response_generics
92 .params
93 .push(syn::GenericParam::Lifetime(LifetimeParam::new(
94 lifetime.clone(),
95 )));
96 let (to_response_impl_generics, _, _) = to_response_generics.split_for_impl();
97
98 tokens.extend(quote! {
99 impl #to_response_impl_generics utoipa::ToResponse <#lifetime> for #ident #ty_generics #where_clause {
100 fn response() -> (& #lifetime str, utoipa::openapi::RefOr<utoipa::openapi::response::Response>) {
101 (#name, #response.into())
102 }
103 }
104 });
105
106 Ok(())
107 }
108}
109
110pub struct IntoResponses {
111 pub attributes: Vec<Attribute>,
112 pub data: Data,
113 pub generics: Generics,
114 pub ident: Ident,
115}
116
117impl ToTokensDiagnostics for IntoResponses {
118 fn to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostics> {
119 let responses = match &self.data {
120 Data::Struct(struct_value) => match &struct_value.fields {
121 Fields::Named(fields) => {
122 let response =
123 NamedStructResponse::new(&self.attributes, &self.ident, &fields.named)?.0;
124 let status = &response.status_code;
125 let response_tokens = as_tokens_or_diagnostics!(&response);
126
127 Array::from_iter(iter::once(quote!((#status, #response_tokens))))
128 }
129 Fields::Unnamed(fields) => {
130 let field = fields
131 .unnamed
132 .iter()
133 .next()
134 .expect("Unnamed struct must have 1 field");
135
136 let response =
137 UnnamedStructResponse::new(&self.attributes, &field.ty, &field.attrs)?.0;
138 let status = &response.status_code;
139 let response_tokens = as_tokens_or_diagnostics!(&response);
140
141 Array::from_iter(iter::once(quote!((#status, #response_tokens))))
142 }
143 Fields::Unit => {
144 let response = UnitStructResponse::new(&self.attributes)?.0;
145 let status = &response.status_code;
146 let response_tokens = as_tokens_or_diagnostics!(&response);
147
148 Array::from_iter(iter::once(quote!((#status, #response_tokens))))
149 }
150 },
151 Data::Enum(enum_value) => enum_value
152 .variants
153 .iter()
154 .map(|variant| match &variant.fields {
155 Fields::Named(fields) => Ok(NamedStructResponse::new(
156 &variant.attrs,
157 &variant.ident,
158 &fields.named,
159 )?
160 .0),
161 Fields::Unnamed(fields) => {
162 let field = fields
163 .unnamed
164 .iter()
165 .next()
166 .expect("Unnamed enum variant must have 1 field");
167 match UnnamedStructResponse::new(&variant.attrs, &field.ty, &field.attrs) {
168 Ok(response) => Ok(response.0),
169 Err(diagnostics) => Err(diagnostics),
170 }
171 }
172 Fields::Unit => Ok(UnitStructResponse::new(&variant.attrs)?.0),
173 })
174 .collect::<Result<Vec<ResponseTuple>, Diagnostics>>()?
175 .iter()
176 .map(|response| {
177 let status = &response.status_code;
178 let response_tokens = as_tokens_or_diagnostics!(response);
179 Ok(quote!((#status, utoipa::openapi::RefOr::from(#response_tokens))))
180 })
181 .collect::<Result<Array<TokenStream>, Diagnostics>>()?,
182 Data::Union(_) => {
183 return Err(Diagnostics::with_span(
184 self.ident.span(),
185 "`IntoResponses` does not support `Union` type",
186 ))
187 }
188 };
189
190 let ident = &self.ident;
191 let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
192
193 let responses = if responses.len() > 0 {
194 Some(quote!( .responses_from_iter(#responses)))
195 } else {
196 None
197 };
198 tokens.extend(quote!{
199 impl #impl_generics utoipa::IntoResponses for #ident #ty_generics #where_clause {
200 fn responses() -> std::collections::BTreeMap<String, utoipa::openapi::RefOr<utoipa::openapi::response::Response>> {
201 utoipa::openapi::response::ResponsesBuilder::new()
202 #responses
203 .build()
204 .into()
205 }
206 }
207 });
208
209 Ok(())
210 }
211}
212
213trait Response {
214 fn to_type(ident: &Ident) -> Type {
215 let path = Path::from(ident.clone());
216 let type_path = TypePath { path, qself: None };
217 Type::Path(type_path)
218 }
219
220 fn has_no_field_attributes(attribute: &Attribute) -> (bool, &'static str) {
221 const ERROR: &str =
222 "Unexpected field attribute, field attributes are only supported at unnamed fields";
223
224 let ident = attribute.path().get_ident().unwrap();
225 match &*ident.to_string() {
226 "to_schema" => (false, ERROR),
227 "ref_response" => (false, ERROR),
228 "content" => (false, ERROR),
229 "to_response" => (false, ERROR),
230 _ => (true, ERROR),
231 }
232 }
233
234 fn validate_attributes<'a, I: IntoIterator<Item = &'a Attribute>>(
235 attributes: I,
236 validate: impl Fn(&Attribute) -> (bool, &'static str) + 'a,
237 ) -> impl Iterator<Item = Diagnostics> {
238 attributes.into_iter().filter_map(move |attribute| {
239 let (valid, error_message) = validate(attribute);
240 if !valid {
241 Some(Diagnostics::with_span(attribute.span(), error_message))
242 } else {
243 None
244 }
245 })
246 }
247}
248
249struct UnnamedStructResponse<'u>(ResponseTuple<'u>);
250
251impl Response for UnnamedStructResponse<'_> {}
252
253impl<'u> UnnamedStructResponse<'u> {
254 fn new(
255 attributes: &[Attribute],
256 ty: &'u Type,
257 inner_attributes: &[Attribute],
258 ) -> Result<Self, Diagnostics> {
259 let is_inline = inner_attributes
260 .iter()
261 .any(|attribute| attribute.path().get_ident().unwrap() == "to_schema");
262 let ref_response = inner_attributes
263 .iter()
264 .any(|attribute| attribute.path().get_ident().unwrap() == "ref_response");
265 let to_response = inner_attributes
266 .iter()
267 .any(|attribute| attribute.path().get_ident().unwrap() == "to_response");
268
269 if is_inline && (ref_response || to_response) {
270 return Err(Diagnostics::with_span(ty.span(), "Attribute `to_schema` cannot be used with `ref_response` and `to_response` attribute"));
271 }
272 let mut derive_value = DeriveIntoResponsesValue::from_attributes(attributes)?
273 .expect("`IntoResponses` must have `#[response(...)]` attribute");
274 let description = {
275 let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
276 parse_utils::LitStrOrExpr::LitStr(LitStr::new(&s, Span::call_site()))
277 };
278 let status_code = mem::take(&mut derive_value.status);
279
280 let response = match (ref_response, to_response) {
281 (false, false) => Self(
282 (
283 status_code,
284 ResponseValue::from_derive_into_responses_value(
285 derive_value,
286 ParsedType {
287 ty: Cow::Borrowed(ty),
288 is_inline,
289 },
290 description,
291 ),
292 )
293 .into(),
294 ),
295 (true, false) => Self(ResponseTuple {
296 inner: Some(ResponseTupleInner::Ref(ParsedType {
297 ty: Cow::Borrowed(ty),
298 is_inline: false,
299 })),
300 status_code,
301 }),
302 (false, true) => Self(ResponseTuple {
303 inner: Some(ResponseTupleInner::Ref(ParsedType {
304 ty: Cow::Borrowed(ty),
305 is_inline: true,
306 })),
307 status_code,
308 }),
309 (true, true) => {
310 return Err(Diagnostics::with_span(
311 ty.span(),
312 "Cannot define `ref_response` and `to_response` attribute simultaneously",
313 ))
314 }
315 };
316
317 Ok(response)
318 }
319}
320
321struct NamedStructResponse<'n>(ResponseTuple<'n>);
322
323impl Response for NamedStructResponse<'_> {}
324
325impl NamedStructResponse<'_> {
326 fn new(
327 attributes: &[Attribute],
328 ident: &Ident,
329 fields: &Punctuated<Field, Comma>,
330 ) -> Result<Self, Diagnostics> {
331 if let Some(diagnostics) =
332 Self::validate_attributes(attributes, Self::has_no_field_attributes)
333 .chain(Self::validate_attributes(
334 fields.iter().flat_map(|field| &field.attrs),
335 Self::has_no_field_attributes,
336 ))
337 .collect::<Option<Diagnostics>>()
338 {
339 return Err(diagnostics);
340 }
341
342 let mut derive_value = DeriveIntoResponsesValue::from_attributes(attributes)?
343 .expect("`IntoResponses` must have `#[response(...)]` attribute");
344 let description = {
345 let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
346 parse_utils::LitStrOrExpr::LitStr(LitStr::new(&s, Span::call_site()))
347 };
348 let status_code = mem::take(&mut derive_value.status);
349 let inline_schema = NamedStructSchema::new(
350 &Root {
351 ident,
352 attributes,
353 generics: &Generics::default(),
354 },
355 fields,
356 Vec::new(),
357 )?;
358
359 let ty = Self::to_type(ident);
360
361 Ok(Self(
362 (
363 status_code,
364 ResponseValue::from_derive_into_responses_value(
365 derive_value,
366 Schema::Default(DefaultSchema::Raw {
367 tokens: inline_schema.to_token_stream(),
368 ty: Cow::Owned(ty),
369 }),
370 description,
371 ),
372 )
373 .into(),
374 ))
375 }
376}
377
378struct UnitStructResponse<'u>(ResponseTuple<'u>);
379
380impl Response for UnitStructResponse<'_> {}
381
382impl UnitStructResponse<'_> {
383 fn new(attributes: &[Attribute]) -> Result<Self, Diagnostics> {
384 if let Some(diagnostics) =
385 Self::validate_attributes(attributes, Self::has_no_field_attributes)
386 .collect::<Option<Diagnostics>>()
387 {
388 return Err(diagnostics);
389 }
390
391 let mut derive_value = DeriveIntoResponsesValue::from_attributes(attributes)?
392 .expect("`IntoResponses` must have `#[response(...)]` attribute");
393 let status_code = mem::take(&mut derive_value.status);
394 let description = {
395 let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
396 parse_utils::LitStrOrExpr::LitStr(LitStr::new(&s, Span::call_site()))
397 };
398
399 Ok(Self(
400 (
401 status_code,
402 ResponseValue::from_derive_into_responses_value(
403 derive_value,
404 Schema::Default(DefaultSchema::None),
405 description,
406 ),
407 )
408 .into(),
409 ))
410 }
411}
412
413struct ToResponseNamedStructResponse<'p>(ResponseTuple<'p>);
414
415impl Response for ToResponseNamedStructResponse<'_> {}
416
417impl<'p> ToResponseNamedStructResponse<'p> {
418 fn new(
419 attributes: &[Attribute],
420 ident: &Ident,
421 fields: &Punctuated<Field, Comma>,
422 ) -> Result<Self, Diagnostics> {
423 if let Some(diagnostics) =
424 Self::validate_attributes(attributes, Self::has_no_field_attributes)
425 .chain(Self::validate_attributes(
426 fields.iter().flat_map(|field| &field.attrs),
427 Self::has_no_field_attributes,
428 ))
429 .collect::<Option<Diagnostics>>()
430 {
431 return Err(diagnostics);
432 }
433
434 let derive_value = DeriveToResponseValue::from_attributes(attributes)?;
435 let description = {
436 let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
437 parse_utils::LitStrOrExpr::LitStr(LitStr::new(&s, Span::call_site()))
438 };
439 let ty = Self::to_type(ident);
440
441 let inline_schema = NamedStructSchema::new(
442 &Root {
443 ident,
444 attributes,
445 generics: &Generics::default(),
446 },
447 fields,
448 Vec::new(),
449 )?;
450
451 let response_value = if let Some(derive_value) = derive_value {
452 ResponseValue::from_derive_to_response_value(
453 derive_value,
454 Schema::Default(DefaultSchema::Raw {
455 tokens: inline_schema.to_token_stream(),
456 ty: Cow::Owned(ty),
457 }),
458 description,
459 )
460 } else {
461 ResponseValue::from_schema(
462 Schema::Default(DefaultSchema::Raw {
463 tokens: inline_schema.to_token_stream(),
464 ty: Cow::Owned(ty),
465 }),
466 description,
467 )
468 };
469 Ok(Self(response_value.into()))
472 }
473}
474
475struct ToResponseUnnamedStructResponse<'c>(ResponseTuple<'c>);
476
477impl Response for ToResponseUnnamedStructResponse<'_> {}
478
479impl<'u> ToResponseUnnamedStructResponse<'u> {
480 fn new(
481 attributes: &[Attribute],
482 ty: &'u Type,
483 inner_attributes: &[Attribute],
484 ) -> Result<Self, Diagnostics> {
485 if let Some(diagnostics) =
486 Self::validate_attributes(attributes, Self::has_no_field_attributes)
487 .chain(Self::validate_attributes(inner_attributes, |attribute| {
488 const ERROR: &str =
489 "Unexpected attribute, `content` is only supported on unnamed field enum variant";
490 if attribute.path().get_ident().unwrap() == "content" {
491 (false, ERROR)
492 } else {
493 (true, ERROR)
494 }
495 }))
496 .collect::<Option<Diagnostics>>()
497 {
498 return Err(diagnostics);
499 }
500 let derive_value = DeriveToResponseValue::from_attributes(attributes)?;
501 let description = {
502 let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
503 parse_utils::LitStrOrExpr::LitStr(LitStr::new(&s, Span::call_site()))
504 };
505
506 let is_inline = inner_attributes
507 .iter()
508 .any(|attribute| attribute.path().get_ident().unwrap() == "to_schema");
509
510 let response_value = if let Some(derive_value) = derive_value {
511 ResponseValue::from_derive_to_response_value(
512 derive_value,
513 ParsedType {
514 ty: Cow::Borrowed(ty),
515 is_inline,
516 },
517 description,
518 )
519 } else {
520 ResponseValue::from_schema(
521 ParsedType {
522 ty: Cow::Borrowed(ty),
523 is_inline,
524 },
525 description,
526 )
527 };
528
529 Ok(Self(response_value.into()))
530 }
531}
532
533#[cfg_attr(feature = "debug", derive(Debug))]
534struct VariantAttributes<'r> {
535 type_and_content: Option<(&'r Type, String)>,
536 derive_value: Option<DeriveToResponseValue>,
537 is_inline: bool,
538}
539
540struct EnumResponse<'r>(ResponseTuple<'r>);
541
542impl Response for EnumResponse<'_> {}
543
544impl<'r> EnumResponse<'r> {
545 fn new(
546 ident: &Ident,
547 variants: &'r Punctuated<Variant, Comma>,
548 attributes: &[Attribute],
549 ) -> Result<Self, Diagnostics> {
550 if let Some(diagnostics) =
551 Self::validate_attributes(attributes, Self::has_no_field_attributes)
552 .chain(Self::validate_attributes(
553 variants.iter().flat_map(|variant| &variant.attrs),
554 Self::has_no_field_attributes,
555 ))
556 .collect::<Option<Diagnostics>>()
557 {
558 return Err(diagnostics);
559 }
560
561 let ty = Self::to_type(ident);
562 let description = {
563 let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
564 parse_utils::LitStrOrExpr::LitStr(LitStr::new(&s, Span::call_site()))
565 };
566
567 let content = variants
568 .into_iter()
569 .map(Self::parse_variant_attributes)
570 .collect::<Result<Vec<VariantAttributes>, Diagnostics>>()?
571 .into_iter()
572 .filter(|variant| variant.type_and_content.is_some())
573 .collect::<Vec<_>>();
574
575 let derive_value = DeriveToResponseValue::from_attributes(attributes)?;
576 if let Some(derive_value) = &derive_value {
577 if (!content.is_empty() && derive_value.example.is_some())
578 || (!content.is_empty() && derive_value.examples.is_some())
579 {
580 let ident = derive_value
581 .example
582 .as_ref()
583 .map(|(_, ident)| ident)
584 .or_else(|| derive_value.examples.as_ref().map(|(_, ident)| ident))
585 .expect("Expected `example` or `examples` to be present");
586 return Err(
587 Diagnostics::with_span(ident.span(),
588 "Enum with `#[content]` attribute in variant cannot have enum level `example` or `examples` defined")
589 .help(format!("Try defining `{}` on the enum variant", ident))
590 );
591 }
592 }
593
594 let generics = Generics::default();
595 let root = &Root {
596 ident,
597 attributes,
598 generics: &generics,
599 };
600 let inline_schema = EnumSchema::new(root, variants)?;
601
602 let response_value = if content.is_empty() {
603 if let Some(derive_value) = derive_value {
604 ResponseValue::from_derive_to_response_value(
605 derive_value,
606 Schema::Default(DefaultSchema::None),
607 description,
608 )
609 } else {
610 ResponseValue::from_schema(
611 Schema::Default(DefaultSchema::Raw {
612 tokens: inline_schema.to_token_stream(),
613 ty: Cow::Owned(ty),
614 }),
615 description,
616 )
617 }
618 } else {
619 let content = content
620 .into_iter()
621 .map(
622 |VariantAttributes {
623 type_and_content,
624 derive_value,
625 is_inline,
626 }| {
627 let (content_type, schema) = if let Some((ty, content)) = type_and_content {
628 (
629 Some(content.into()),
630 Some(Schema::Default(DefaultSchema::TypePath(ParsedType {
631 ty: Cow::Borrowed(ty),
632 is_inline,
633 }))),
634 )
635 } else {
636 (None, None)
637 };
638 let (example, examples) = if let Some(derive_value) = derive_value {
639 (
640 derive_value.example.map(|(example, _)| example),
641 derive_value.examples.map(|(examples, _)| examples),
642 )
643 } else {
644 (None, None)
645 };
646
647 MediaTypeAttr {
648 content_type,
649 schema: schema.unwrap_or_else(|| Schema::Default(DefaultSchema::None)),
650 example,
651 examples: examples.unwrap_or_default(),
652 ..MediaTypeAttr::default()
653 }
654 },
655 )
656 .collect::<Vec<_>>();
657
658 let mut response = if let Some(derive_value) = derive_value {
659 ResponseValue::from_derive_to_response_value(
660 derive_value,
661 Schema::Default(DefaultSchema::None),
662 description,
663 )
664 } else {
665 ResponseValue::from_schema(
666 Schema::Default(DefaultSchema::Raw {
667 tokens: inline_schema.to_token_stream(),
668 ty: Cow::Owned(ty),
669 }),
670 description,
671 )
672 };
673 response.content = content;
674
675 response
676 };
677
678 Ok(Self(response_value.into()))
679 }
680
681 fn parse_variant_attributes(variant: &Variant) -> Result<VariantAttributes, Diagnostics> {
682 let variant_derive_response_value =
683 DeriveToResponseValue::from_attributes(variant.attrs.as_slice())?;
684 if let Fields::Named(named_fields) = &variant.fields {
686 if let Some(diagnostics) = Self::validate_attributes(
687 named_fields.named.iter().flat_map(|field| &field.attrs),
688 Self::has_no_field_attributes,
689 )
690 .collect::<Option<Diagnostics>>()
691 {
692 return Err(diagnostics);
693 }
694 };
695
696 let field = variant.fields.iter().next();
697
698 let content_type = field.and_then_try(|field| {
699 field
700 .attrs
701 .iter()
702 .find(|attribute| attribute.path().get_ident().unwrap() == "content")
703 .map_try(|attribute| {
704 attribute
705 .parse_args_with(|input: ParseStream| input.parse::<LitStr>())
706 .map(|content| content.value())
707 .map_err(Diagnostics::from)
708 })
709 })?;
710
711 let is_inline = field
712 .map(|field| {
713 field
714 .attrs
715 .iter()
716 .any(|attribute| attribute.path().get_ident().unwrap() == "to_schema")
717 })
718 .unwrap_or(false);
719
720 Ok(VariantAttributes {
721 type_and_content: field.map(|field| &field.ty).zip(content_type),
722 derive_value: variant_derive_response_value,
723 is_inline,
724 })
725 }
726}
727
728struct ToResponseUnitStructResponse<'u>(ResponseTuple<'u>);
729
730impl Response for ToResponseUnitStructResponse<'_> {}
731
732impl ToResponseUnitStructResponse<'_> {
733 fn new(attributes: &[Attribute]) -> Result<Self, Diagnostics> {
734 if let Some(diagnostics) =
735 Self::validate_attributes(attributes, Self::has_no_field_attributes)
736 .collect::<Option<Diagnostics>>()
737 {
738 return Err(diagnostics);
739 }
740
741 let derive_value = DeriveToResponseValue::from_attributes(attributes)?;
742 let description = {
743 let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
744 parse_utils::LitStrOrExpr::LitStr(LitStr::new(&s, Span::call_site()))
745 };
746
747 let response_value = if let Some(derive_value) = derive_value {
748 ResponseValue::from_derive_to_response_value(
749 derive_value,
750 Schema::Default(DefaultSchema::None),
751 description,
752 )
753 } else {
754 ResponseValue {
755 description,
756 ..Default::default()
757 }
758 };
759
760 Ok(Self(response_value.into()))
761 }
762}