1use std::borrow::Cow;
2use std::{iter, mem};
3
4use proc_macro2::{Ident, Span, TokenStream};
5use proc_macro_error::{abort, emit_error};
6use quote::{quote, ToTokens};
7use syn::parse::ParseStream;
8use syn::punctuated::Punctuated;
9use syn::spanned::Spanned;
10use syn::token::Comma;
11use syn::{
12 Attribute, Data, Field, Fields, Generics, Lifetime, LifetimeParam, LitStr, Path, Type,
13 TypePath, Variant,
14};
15
16use crate::component::schema::{EnumSchema, NamedStructSchema};
17use crate::doc_comment::CommentAttributes;
18use crate::path::{InlineType, PathType};
19use crate::{Array, ResultExt};
20
21use super::{
22 Content, DeriveIntoResponsesValue, DeriveResponseValue, DeriveResponsesAttributes,
23 DeriveToResponseValue, ResponseTuple, ResponseTupleInner, ResponseValue,
24};
25
26pub struct ToResponse<'r> {
27 ident: Ident,
28 lifetime: Lifetime,
29 generics: Generics,
30 response: ResponseTuple<'r>,
31}
32
33impl<'r> ToResponse<'r> {
34 const LIFETIME: &'static str = "'__r";
35
36 pub fn new(
37 attributes: Vec<Attribute>,
38 data: &'r Data,
39 generics: Generics,
40 ident: Ident,
41 ) -> ToResponse<'r> {
42 let response = match &data {
43 Data::Struct(struct_value) => match &struct_value.fields {
44 Fields::Named(fields) => {
45 ToResponseNamedStructResponse::new(&attributes, &ident, &fields.named).0
46 }
47 Fields::Unnamed(fields) => {
48 let field = fields
49 .unnamed
50 .iter()
51 .next()
52 .expect("Unnamed struct must have 1 field");
53
54 ToResponseUnnamedStructResponse::new(&attributes, &field.ty, &field.attrs).0
55 }
56 Fields::Unit => ToResponseUnitStructResponse::new(&attributes).0,
57 },
58 Data::Enum(enum_value) => {
59 EnumResponse::new(&ident, &enum_value.variants, &attributes).0
60 }
61 Data::Union(_) => abort!(ident, "`ToResponse` does not support `Union` type"),
62 };
63
64 let lifetime = Lifetime::new(ToResponse::LIFETIME, Span::call_site());
65
66 Self {
67 ident,
68 lifetime,
69 generics,
70 response,
71 }
72 }
73}
74
75impl ToTokens for ToResponse<'_> {
76 fn to_tokens(&self, tokens: &mut TokenStream) {
77 let (_, ty_generics, where_clause) = self.generics.split_for_impl();
78
79 let lifetime = &self.lifetime;
80 let ident = &self.ident;
81 let name = ident.to_string();
82 let response = &self.response;
83
84 let mut to_reponse_generics = self.generics.clone();
85 to_reponse_generics
86 .params
87 .push(syn::GenericParam::Lifetime(LifetimeParam::new(
88 lifetime.clone(),
89 )));
90 let (to_response_impl_generics, _, _) = to_reponse_generics.split_for_impl();
91
92 tokens.extend(quote! {
93 impl #to_response_impl_generics utoipa::ToResponse <#lifetime> for #ident #ty_generics #where_clause {
94 fn response() -> (& #lifetime str, utoipa::openapi::RefOr<utoipa::openapi::response::Response>) {
95 (#name, #response.into())
96 }
97 }
98 });
99 }
100}
101
102pub struct IntoResponses {
103 pub attributes: Vec<Attribute>,
104 pub data: Data,
105 pub generics: Generics,
106 pub ident: Ident,
107}
108
109impl ToTokens for IntoResponses {
110 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
111 let responses = match &self.data {
112 Data::Struct(struct_value) => match &struct_value.fields {
113 Fields::Named(fields) => {
114 let response =
115 NamedStructResponse::new(&self.attributes, &self.ident, &fields.named).0;
116 let status = &response.status_code;
117
118 Array::from_iter(iter::once(quote!((#status, #response))))
119 }
120 Fields::Unnamed(fields) => {
121 let field = fields
122 .unnamed
123 .iter()
124 .next()
125 .expect("Unnamed struct must have 1 field");
126
127 let response =
128 UnnamedStructResponse::new(&self.attributes, &field.ty, &field.attrs).0;
129 let status = &response.status_code;
130
131 Array::from_iter(iter::once(quote!((#status, #response))))
132 }
133 Fields::Unit => {
134 let response = UnitStructResponse::new(&self.attributes).0;
135 let status = &response.status_code;
136
137 Array::from_iter(iter::once(quote!((#status, #response))))
138 }
139 },
140 Data::Enum(enum_value) => enum_value
141 .variants
142 .iter()
143 .map(|variant| match &variant.fields {
144 Fields::Named(fields) => {
145 NamedStructResponse::new(&variant.attrs, &variant.ident, &fields.named).0
146 }
147 Fields::Unnamed(fields) => {
148 let field = fields
149 .unnamed
150 .iter()
151 .next()
152 .expect("Unnamed enum variant must have 1 field");
153 UnnamedStructResponse::new(&variant.attrs, &field.ty, &field.attrs).0
154 }
155 Fields::Unit => UnitStructResponse::new(&variant.attrs).0,
156 })
157 .map(|response| {
158 let status = &response.status_code;
159 quote!((#status, utoipa::openapi::RefOr::from(#response)))
160 })
161 .collect::<Array<TokenStream>>(),
162 Data::Union(_) => abort!(self.ident, "`IntoResponses` does not support `Union` type"),
163 };
164
165 let ident = &self.ident;
166 let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
167
168 let responses = if responses.len() > 0 {
169 Some(quote!( .responses_from_iter(#responses)))
170 } else {
171 None
172 };
173 tokens.extend(quote!{
174 impl #impl_generics utoipa::IntoResponses for #ident #ty_generics #where_clause {
175 fn responses() -> std::collections::BTreeMap<String, utoipa::openapi::RefOr<utoipa::openapi::response::Response>> {
176 utoipa::openapi::response::ResponsesBuilder::new()
177 #responses
178 .build()
179 .into()
180 }
181 }
182 })
183 }
184}
185
186trait Response {
187 fn to_type(ident: &Ident) -> Type {
188 let path = Path::from(ident.clone());
189 let type_path = TypePath { path, qself: None };
190 Type::Path(type_path)
191 }
192
193 fn has_no_field_attributes(attribute: &Attribute) -> (bool, &'static str) {
194 const ERROR: &str =
195 "Unexpected field attribute, field attributes are only supported at unnamed fields";
196
197 let ident = attribute.path().get_ident().unwrap();
198 match &*ident.to_string() {
199 "to_schema" => (false, ERROR),
200 "ref_response" => (false, ERROR),
201 "content" => (false, ERROR),
202 "to_response" => (false, ERROR),
203 _ => (true, ERROR),
204 }
205 }
206
207 fn validate_attributes<'a, I: IntoIterator<Item = &'a Attribute>>(
208 attributes: I,
209 validate: impl Fn(&Attribute) -> (bool, &'static str),
210 ) {
211 for attribute in attributes {
212 let (valid, message) = validate(attribute);
213 if !valid {
214 emit_error!(attribute, message)
215 }
216 }
217 }
218}
219
220struct UnnamedStructResponse<'u>(ResponseTuple<'u>);
221
222impl Response for UnnamedStructResponse<'_> {}
223
224impl<'u> UnnamedStructResponse<'u> {
225 fn new(attributes: &[Attribute], ty: &'u Type, inner_attributes: &[Attribute]) -> Self {
226 let is_inline = inner_attributes
227 .iter()
228 .any(|attribute| attribute.path().get_ident().unwrap() == "to_schema");
229 let ref_response = inner_attributes
230 .iter()
231 .any(|attribute| attribute.path().get_ident().unwrap() == "ref_response");
232 let to_response = inner_attributes
233 .iter()
234 .any(|attribute| attribute.path().get_ident().unwrap() == "to_response");
235
236 if is_inline && (ref_response || to_response) {
237 abort!(
238 ty.span(),
239 "Attribute `to_schema` cannot be used with `ref_response` and `to_response` attribute"
240 )
241 }
242 let mut derive_value = DeriveIntoResponsesValue::from_attributes(attributes)
243 .expect("`IntoResponses` must have `#[response(...)]` attribute");
244 let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
245 let status_code = mem::take(&mut derive_value.status);
246
247 match (ref_response, to_response) {
248 (false, false) => Self(
249 (
250 status_code,
251 ResponseValue::from_derive_into_responses_value(derive_value, description)
252 .response_type(Some(PathType::MediaType(InlineType {
253 ty: Cow::Borrowed(ty),
254 is_inline,
255 }))),
256 )
257 .into(),
258 ),
259 (true, false) => Self(ResponseTuple {
260 inner: Some(ResponseTupleInner::Ref(InlineType {
261 ty: Cow::Borrowed(ty),
262 is_inline: false,
263 })),
264 status_code,
265 }),
266 (false, true) => Self(ResponseTuple {
267 inner: Some(ResponseTupleInner::Ref(InlineType {
268 ty: Cow::Borrowed(ty),
269 is_inline: true,
270 })),
271 status_code,
272 }),
273 (true, true) => {
274 abort!(
275 ty.span(),
276 "Cannot define `ref_response` and `to_response` attribute simultaneously"
277 );
278 }
279 }
280 }
281}
282
283struct NamedStructResponse<'n>(ResponseTuple<'n>);
284
285impl Response for NamedStructResponse<'_> {}
286
287impl NamedStructResponse<'_> {
288 fn new(attributes: &[Attribute], ident: &Ident, fields: &Punctuated<Field, Comma>) -> Self {
289 Self::validate_attributes(attributes, Self::has_no_field_attributes);
290 Self::validate_attributes(
291 fields.iter().flat_map(|field| &field.attrs),
292 Self::has_no_field_attributes,
293 );
294
295 let mut derive_value = DeriveIntoResponsesValue::from_attributes(attributes)
296 .expect("`IntoResponses` must have `#[response(...)]` attribute");
297 let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
298 let status_code = mem::take(&mut derive_value.status);
299
300 let inline_schema = NamedStructSchema {
301 attributes,
302 fields,
303 aliases: None,
304 features: None,
305 generics: None,
306 rename_all: None,
307 struct_name: Cow::Owned(ident.to_string()),
308 schema_as: None,
309 };
310
311 let ty = Self::to_type(ident);
312
313 Self(
314 (
315 status_code,
316 ResponseValue::from_derive_into_responses_value(derive_value, description)
317 .response_type(Some(PathType::InlineSchema(
318 inline_schema.to_token_stream(),
319 ty,
320 ))),
321 )
322 .into(),
323 )
324 }
325}
326
327struct UnitStructResponse<'u>(ResponseTuple<'u>);
328
329impl Response for UnitStructResponse<'_> {}
330
331impl UnitStructResponse<'_> {
332 fn new(attributes: &[Attribute]) -> Self {
333 Self::validate_attributes(attributes, Self::has_no_field_attributes);
334
335 let mut derive_value = DeriveIntoResponsesValue::from_attributes(attributes)
336 .expect("`IntoResponses` must have `#[response(...)]` attribute");
337 let status_code = mem::take(&mut derive_value.status);
338 let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
339
340 Self(
341 (
342 status_code,
343 ResponseValue::from_derive_into_responses_value(derive_value, description),
344 )
345 .into(),
346 )
347 }
348}
349
350struct ToResponseNamedStructResponse<'p>(ResponseTuple<'p>);
351
352impl Response for ToResponseNamedStructResponse<'_> {}
353
354impl<'p> ToResponseNamedStructResponse<'p> {
355 fn new(attributes: &[Attribute], ident: &Ident, fields: &Punctuated<Field, Comma>) -> Self {
356 Self::validate_attributes(attributes, Self::has_no_field_attributes);
357 Self::validate_attributes(
358 fields.iter().flat_map(|field| &field.attrs),
359 Self::has_no_field_attributes,
360 );
361
362 let derive_value = DeriveToResponseValue::from_attributes(attributes);
363 let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
364 let ty = Self::to_type(ident);
365
366 let inline_schema = NamedStructSchema {
367 aliases: None,
368 fields,
369 features: None,
370 generics: None,
371 attributes,
372 struct_name: Cow::Owned(ident.to_string()),
373 rename_all: None,
374 schema_as: None,
375 };
376 let response_type = PathType::InlineSchema(inline_schema.to_token_stream(), ty);
377
378 let mut response_value: ResponseValue = ResponseValue::from(DeriveResponsesAttributes {
379 derive_value,
380 description,
381 });
382 response_value.response_type = Some(response_type);
383
384 Self(response_value.into())
385 }
386}
387
388struct ToResponseUnnamedStructResponse<'c>(ResponseTuple<'c>);
389
390impl Response for ToResponseUnnamedStructResponse<'_> {}
391
392impl<'u> ToResponseUnnamedStructResponse<'u> {
393 fn new(attributes: &[Attribute], ty: &'u Type, inner_attributes: &[Attribute]) -> Self {
394 Self::validate_attributes(attributes, Self::has_no_field_attributes);
395 Self::validate_attributes(inner_attributes, |attribute| {
396 const ERROR: &str =
397 "Unexpected attribute, `content` is only supported on unnamed field enum variant";
398 if attribute.path().get_ident().unwrap() == "content" {
399 (false, ERROR)
400 } else {
401 (true, ERROR)
402 }
403 });
404 let derive_value = DeriveToResponseValue::from_attributes(attributes);
405 let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
406
407 let is_inline = inner_attributes
408 .iter()
409 .any(|attribute| attribute.path().get_ident().unwrap() == "to_schema");
410 let mut response_value: ResponseValue = ResponseValue::from(DeriveResponsesAttributes {
411 description,
412 derive_value,
413 });
414
415 response_value.response_type = Some(PathType::MediaType(InlineType {
416 ty: Cow::Borrowed(ty),
417 is_inline,
418 }));
419
420 Self(response_value.into())
421 }
422}
423
424struct VariantAttributes<'r> {
425 type_and_content: Option<(&'r Type, String)>,
426 derive_value: Option<DeriveToResponseValue>,
427 is_inline: bool,
428}
429
430struct EnumResponse<'r>(ResponseTuple<'r>);
431
432impl Response for EnumResponse<'_> {}
433
434impl<'r> EnumResponse<'r> {
435 fn new(
436 ident: &Ident,
437 variants: &'r Punctuated<Variant, Comma>,
438 attributes: &[Attribute],
439 ) -> Self {
440 Self::validate_attributes(attributes, Self::has_no_field_attributes);
441 Self::validate_attributes(
442 variants.iter().flat_map(|variant| &variant.attrs),
443 Self::has_no_field_attributes,
444 );
445
446 let ty = Self::to_type(ident);
447 let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
448
449 let variants_content = variants
450 .into_iter()
451 .map(Self::parse_variant_attributes)
452 .filter_map(Self::to_content);
453 let content: Punctuated<Content, Comma> = Punctuated::from_iter(variants_content);
454
455 let derive_value = DeriveToResponseValue::from_attributes(attributes);
456 if let Some(derive_value) = &derive_value {
457 if (!content.is_empty() && derive_value.example.is_some())
458 || (!content.is_empty() && derive_value.examples.is_some())
459 {
460 let ident = derive_value
461 .example
462 .as_ref()
463 .map(|(_, ident)| ident)
464 .or_else(|| derive_value.examples.as_ref().map(|(_, ident)| ident))
465 .expect("Expected `example` or `examples` to be present");
466 abort! {
467 ident,
468 "Enum with `#[content]` attribute in variant cannot have enum level `example` or `examples` defined";
469 help = "Try defining `{}` on the enum variant", ident.to_string(),
470 }
471 }
472 }
473
474 let mut response_value: ResponseValue = From::from(DeriveResponsesAttributes {
475 derive_value,
476 description,
477 });
478 response_value.response_type = if content.is_empty() {
479 let inline_schema =
480 EnumSchema::new(Cow::Owned(ident.to_string()), variants, attributes);
481
482 Some(PathType::InlineSchema(
483 inline_schema.into_token_stream(),
484 ty,
485 ))
486 } else {
487 None
488 };
489 response_value.content = content;
490
491 Self(response_value.into())
492 }
493
494 fn parse_variant_attributes(variant: &Variant) -> VariantAttributes {
495 let variant_derive_response_value =
496 DeriveToResponseValue::from_attributes(variant.attrs.as_slice());
497 if let Fields::Named(named_fields) = &variant.fields {
499 Self::validate_attributes(
500 named_fields.named.iter().flat_map(|field| &field.attrs),
501 Self::has_no_field_attributes,
502 )
503 };
504
505 let field = variant.fields.iter().next();
506
507 let content_type = field.and_then(|field| {
508 field
509 .attrs
510 .iter()
511 .find(|attribute| attribute.path().get_ident().unwrap() == "content")
512 .map(|attribute| {
513 attribute
514 .parse_args_with(|input: ParseStream| input.parse::<LitStr>())
515 .unwrap_or_abort()
516 })
517 .map(|content| content.value())
518 });
519
520 let is_inline = field
521 .map(|field| {
522 field
523 .attrs
524 .iter()
525 .any(|attribute| attribute.path().get_ident().unwrap() == "to_schema")
526 })
527 .unwrap_or(false);
528
529 VariantAttributes {
530 type_and_content: field.map(|field| &field.ty).zip(content_type),
531 derive_value: variant_derive_response_value,
532 is_inline,
533 }
534 }
535
536 fn to_content(
537 VariantAttributes {
538 type_and_content: field_and_content,
539 mut derive_value,
540 is_inline,
541 }: VariantAttributes,
542 ) -> Option<Content<'_>> {
543 let (example, examples) = if let Some(variant_derive) = &mut derive_value {
544 (
545 mem::take(&mut variant_derive.example),
546 mem::take(&mut variant_derive.examples),
547 )
548 } else {
549 (None, None)
550 };
551
552 field_and_content.map(|(ty, content_type)| {
553 Content(
554 content_type,
555 PathType::MediaType(InlineType {
556 ty: Cow::Borrowed(ty),
557 is_inline,
558 }),
559 example.map(|(example, _)| example),
560 examples.map(|(examples, _)| examples),
561 )
562 })
563 }
564}
565
566struct ToResponseUnitStructResponse<'u>(ResponseTuple<'u>);
567
568impl Response for ToResponseUnitStructResponse<'_> {}
569
570impl ToResponseUnitStructResponse<'_> {
571 fn new(attributes: &[Attribute]) -> Self {
572 Self::validate_attributes(attributes, Self::has_no_field_attributes);
573
574 let derive_value = DeriveToResponseValue::from_attributes(attributes);
575 let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
576 let response_value: ResponseValue = ResponseValue::from(DeriveResponsesAttributes {
577 derive_value,
578 description,
579 });
580
581 Self(response_value.into())
582 }
583}