1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
use crate::util::CollectToResult;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use std::iter;
use syn::{
	parse::{Parse, ParseStream},
	punctuated::Punctuated,
	spanned::Spanned,
	DeriveInput, Error, Generics, Path, Result, Token
};

struct MimeList(Punctuated<Path, Token![,]>);

impl Parse for MimeList {
	fn parse(input: ParseStream<'_>) -> Result<Self> {
		let list = Punctuated::parse_separated_nonempty(input)?;
		Ok(Self(list))
	}
}

#[cfg(not(feature = "openapi"))]
fn impl_openapi_type(_ident: &Ident, _generics: &Generics) -> TokenStream {
	quote!()
}

#[cfg(feature = "openapi")]
fn impl_openapi_type(ident: &Ident, generics: &Generics) -> TokenStream {
	let openapi = quote!(::gotham_restful::private::openapi);
	quote! {
		impl #generics ::gotham_restful::private::OpenapiType for #ident #generics {
			fn schema() -> ::gotham_restful::private::OpenapiSchema {
				::gotham_restful::private::OpenapiSchema::new(
					#openapi::SchemaKind::Type(
						#openapi::Type::String(
							#openapi::StringType {
								format: #openapi::VariantOrUnknownOrEmpty::Item(
									#openapi::StringFormat::Binary
								),
								.. ::std::default::Default::default()
							}
						)
					)
				)
			}
		}
	}
}

pub fn expand_request_body(input: DeriveInput) -> Result<TokenStream> {
	let ident = input.ident;
	let generics = input.generics;

	let types = input
		.attrs
		.into_iter()
		.filter(|attr| {
			attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("supported_types".to_string())
		})
		.flat_map(|attr| {
			let span = attr.span();
			attr.parse_args::<MimeList>()
				.map(|list| Box::new(list.0.into_iter().map(Ok)) as Box<dyn Iterator<Item = Result<Path>>>)
				.unwrap_or_else(|mut err| {
					err.combine(Error::new(
						span,
						"Hint: Types list should look like #[supported_types(TEXT_PLAIN, APPLICATION_JSON)]"
					));
					Box::new(iter::once(Err(err)))
				})
		})
		.collect_to_result()?;

	let types = match types {
		ref types if types.is_empty() => quote!(None),
		types => quote!(Some(vec![#(#types),*]))
	};

	let impl_openapi_type = impl_openapi_type(&ident, &generics);

	Ok(quote! {
		impl #generics ::gotham_restful::RequestBody for #ident #generics
		where #ident #generics : ::gotham_restful::FromBody
		{
			fn supported_types() -> ::core::option::Option<::std::vec::Vec<::gotham_restful::Mime>> {
				#types
			}
		}

		#impl_openapi_type
	})
}