1use proc_macro::TokenStream;
5use proc_macro2::Span;
6use quote::{format_ident, quote};
7use syn::punctuated::Punctuated;
8use syn::Data;
9use syn::DataStruct;
10use syn::DeriveInput;
11use syn::GenericArgument;
12use syn::Ident;
13use syn::PathArguments;
14use syn::Token;
15use syn::Type;
16use syn::{Field, GenericParam};
17
18fn extend_decoding_params(
19 params: &Punctuated<GenericParam, Token![,]>,
20) -> proc_macro2::TokenStream {
21 let lifetimes: Vec<_> = params
22 .iter()
23 .filter_map(|p| match p {
24 GenericParam::Lifetime(l) => Some(&l.lifetime),
25 _ => None,
26 })
27 .collect();
28 if lifetimes.is_empty() {
29 quote! { 'de, #params }
30 } else {
31 quote! { 'de: #(#lifetimes)+*, #params }
32 }
33}
34
35fn is_type_container(name: &str, ty: &Type) -> bool {
36 if let Type::Path(type_path) = &ty {
37 let option = Ident::new(name, Span::call_site());
38 let first_segment = type_path.path.segments.first().unwrap();
39 return first_segment.ident == option;
40 }
41 false
42}
43
44fn is_type_option(ty: &Type) -> bool {
45 is_type_container("Option", ty)
46}
47
48fn is_type_vec(ty: &Type) -> bool {
49 is_type_container("Vec", ty)
50}
51
52fn get_field_ident(field: &Field) -> &Ident {
53 field.ident.as_ref().unwrap()
54}
55
56fn get_first_generic_type(ty: &Type) -> &Type {
57 let type_path = match ty {
58 Type::Path(type_path) => type_path,
59 _ => panic!("type doesn't have generic arguments"),
60 };
61 let last_segment = type_path.path.segments.last().unwrap();
62 let generics = match &last_segment.arguments {
63 PathArguments::AngleBracketed(generics) => &generics.args,
64 _ => panic!("type doesn't have generic arguments"),
65 };
66 let generic_type = generics
67 .iter()
68 .find_map(|g| match g {
69 GenericArgument::Type(gt) => Some(gt),
70 _ => None,
71 })
72 .expect("type doesn't have generic arguments");
73 generic_type
74}
75
76enum HeaderField<'a> {
77 RequiredSingle(&'a Ident, &'a Type),
78 RequiredRepeated(&'a Ident, &'a Type),
79 OptionalSingle(&'a Ident, &'a Type),
80 OptionalRepeated(&'a Ident, &'a Type),
81}
82
83impl<'a> HeaderField<'a> {
84 pub(crate) fn parse_all(data: &DataStruct) -> Vec<HeaderField> {
85 data.fields
86 .iter()
87 .map(|field| {
88 let ident = get_field_ident(field);
89 if is_type_option(&field.ty) {
90 let optional_type = get_first_generic_type(&field.ty);
91 if is_type_vec(optional_type) {
92 let repeated_type = get_first_generic_type(optional_type);
93 HeaderField::OptionalRepeated(ident, repeated_type)
94 } else {
95 HeaderField::OptionalSingle(ident, optional_type)
96 }
97 } else if is_type_vec(&field.ty) {
98 let repeated_type = get_first_generic_type(&field.ty);
99 HeaderField::RequiredRepeated(ident, repeated_type)
100 } else {
101 HeaderField::RequiredSingle(ident, &field.ty)
102 }
103 })
104 .collect()
105 }
106
107 pub(crate) fn make_declaration(&self) -> proc_macro2::TokenStream {
108 match self {
109 HeaderField::RequiredSingle(ident, ty) | HeaderField::OptionalSingle(ident, ty) => {
110 let maybe_ident = format_ident!("maybe_{ident}");
111 quote! {
112 let mut #maybe_ident: Option<#ty> = None;
113 }
114 }
115 HeaderField::RequiredRepeated(ident, ty) | HeaderField::OptionalRepeated(ident, ty) => {
116 let maybe_ident = format_ident!("maybe_{ident}");
117 quote! {
118 let mut #maybe_ident: Vec<#ty> = vec![];
119 }
120 }
121 }
122 }
123
124 pub(crate) fn make_extractor(&self, key: &Ident, value: &Ident) -> proc_macro2::TokenStream {
125 match self {
126 HeaderField::RequiredSingle(ident, ty) | HeaderField::OptionalSingle(ident, ty) => {
127 let maybe_ident = format_ident!("maybe_{ident}");
128 let header_key = ident.to_string().replace('_', "-");
129 quote! {
130 if #maybe_ident.is_none() && #key.eq_ignore_ascii_case(#header_key) {
131 let #ident: #ty = noggin::FromHeaderValue::parse_header_value(#value)
132 .ok_or(noggin::Error::InvalidHeaderValue(#header_key))?;
133 #maybe_ident = Some(#ident);
134 }
135 }
136 }
137 HeaderField::RequiredRepeated(ident, ty) | HeaderField::OptionalRepeated(ident, ty) => {
138 let maybe_ident = format_ident!("maybe_{ident}");
139 let header_key = ident.to_string().replace('_', "-");
140 quote! {
141 if #key.eq_ignore_ascii_case(#header_key) {
142 let #ident: Vec<#ty> = noggin::FromHeaderValue::parse_header_value(#value)
143 .ok_or(noggin::Error::InvalidHeaderValue(#header_key))?;
144 #maybe_ident.extend(#ident);
145 }
146 }
147 }
148 }
149 }
150
151 pub(crate) fn make_validator(&self) -> proc_macro2::TokenStream {
152 match self {
153 HeaderField::RequiredSingle(ident, _) => {
154 let maybe_ident = format_ident!("maybe_{ident}");
155 let header_key = ident.to_string().replace('_', "-");
156 quote! {
157 if #maybe_ident.is_none() {
158 return Err(noggin::Error::MissingHeader(#header_key));
159 }
160 }
161 }
162 HeaderField::RequiredRepeated(ident, _) => {
163 let maybe_ident = format_ident!("maybe_{ident}");
164 let header_key = ident.to_string().replace('_', "-");
165 quote! {
166 if #maybe_ident.is_empty() {
167 return Err(noggin::Error::MissingHeader(#header_key));
168 }
169 }
170 }
171 _ => quote! {},
172 }
173 }
174
175 pub(crate) fn make_builders(&self) -> proc_macro2::TokenStream {
176 match self {
177 HeaderField::RequiredSingle(ident, _) => {
178 let maybe_ident = format_ident!("maybe_{ident}");
179 quote! {
180 #ident: #maybe_ident.unwrap()
181 }
182 }
183 HeaderField::RequiredRepeated(ident, _) | HeaderField::OptionalSingle(ident, _) => {
184 let maybe_ident = format_ident!("maybe_{ident}");
185 quote! {
186 #ident: #maybe_ident
187 }
188 }
189 HeaderField::OptionalRepeated(ident, _) => {
190 let maybe_ident = format_ident!("maybe_{ident}");
191 quote! {
192 #ident: (!#maybe_ident.is_empty()).then_some(#maybe_ident)
193 }
194 }
195 }
196 }
197}
198
199#[proc_macro_derive(Noggin)]
200pub fn noggin_derive(input: TokenStream) -> TokenStream {
201 let derive_input = syn::parse_macro_input!(input as DeriveInput);
202 match &derive_input.data {
203 Data::Struct(data) => {
204 let name = &derive_input.ident;
205 let params = &derive_input.generics.params;
206 let extended_params = extend_decoding_params(params);
207 let fields = HeaderField::parse_all(data);
208 let key = Ident::new("key", Span::call_site());
209 let value = Ident::new("value", Span::call_site());
210 let declarations: Vec<_> = fields.iter().map(|f| f.make_declaration()).collect();
211 let extractors: Vec<_> = fields
212 .iter()
213 .map(|f| f.make_extractor(&key, &value))
214 .collect();
215 let validators: Vec<_> = fields.iter().map(|f| f.make_validator()).collect();
216 let builders: Vec<_> = fields.iter().map(|f| f.make_builders()).collect();
217 let result = quote! {
218 impl<#extended_params> noggin::HeadParser<'de> for #name<#params> {
219 fn parse_head_section(head: &'de str) -> Result<Self, noggin::Error> {
220 #(
221 #declarations
222 )*
223 for header in head.split("\r\n") {
224 let (key, value) = header.split_once(':')
225 .ok_or(noggin::Error::MalformedHeader)?;
226 #(
227 #extractors
228 )*
229 }
230 #(
231 #validators
232 )*
233 let result = #name {
234 #(
235 #builders
236 ),*
237 };
238 Ok(result)
239 }
240 }
241 };
242 result.into()
243 }
244 _ => panic!("Noggin derive macro only works on struct types"),
245 }
246}