noggin_derive/
lib.rs

1//! Do not import or use this crate directly, import and use `noggin` instead.
2//! See: [noggin](https://docs.rs/noggin/latest/noggin/)
3
4use 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}