headers_derive/
lib.rs

1#![recursion_limit="128"]
2
3extern crate proc_macro;
4extern crate proc_macro2;
5#[macro_use]
6extern crate quote;
7extern crate syn;
8
9use proc_macro::TokenStream;
10use proc_macro2::Span;
11use syn::{Data, Fields, Ident, Lit, Meta, NestedMeta};
12
13#[proc_macro_derive(Header, attributes(header))]
14pub fn derive_header(input: TokenStream) -> TokenStream {
15    let ast = syn::parse(input).unwrap();
16    impl_header(&ast).into()
17}
18
19fn impl_header(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
20    let fns = match impl_fns(ast) {
21        Ok(fns) => fns,
22        Err(msg) => {
23            return quote! {
24                compile_error!(#msg);
25            }.into();
26        }
27    };
28
29    let decode = fns.decode;
30    let encode = fns.encode;
31
32    let ty = &ast.ident;
33    let hname = fns.name.unwrap_or_else(|| {
34        to_header_name(&ty.to_string())
35    });
36    let hname_ident = Ident::new(&hname, Span::call_site());
37    let dummy_const = Ident::new(&format!("_IMPL_HEADER_FOR_{}", hname), Span::call_site());
38    let impl_block = quote! {
39        impl __hc::Header for #ty {
40            fn name() -> &'static __hc::HeaderName {
41                &__hc::header::#hname_ident
42            }
43            fn decode<'i, I>(values: &mut I) -> Result<Self, __hc::Error>
44            where
45                I: Iterator<Item = &'i __hc::HeaderValue>,
46            {
47                #decode
48            }
49            fn encode<E: Extend<__hc::HeaderValue>>(&self, values: &mut E) {
50                #encode
51            }
52        }
53    };
54
55    quote! {
56        const #dummy_const: () = {
57            extern crate headers_core as __hc;
58            #impl_block
59        };
60    }
61}
62
63struct Fns {
64    encode: proc_macro2::TokenStream,
65    decode: proc_macro2::TokenStream,
66    name: Option<String>,
67}
68
69fn impl_fns(ast: &syn::DeriveInput) -> Result<Fns, String> {
70    let ty = &ast.ident;
71
72    // Only structs are allowed...
73    let st = match ast.data {
74        Data::Struct(ref st) => st,
75        _ => {
76            return Err("derive(Header) only works on structs".into())
77        }
78    };
79
80    // Check attributes for `#[header(...)]` that may influence the code
81    // that is generated...
82    //let mut is_csv = false;
83    let mut name = None;
84    for attr in &ast.attrs {
85        if attr.path.segments.len() != 1 {
86            continue;
87        }
88        if attr.path.segments[0].ident != "header" {
89            continue;
90        }
91
92        match attr.parse_meta() {
93            Ok(Meta::List(list)) => {
94                for meta in &list.nested {
95                    match meta {
96                        /*
97                        To be conservative, this attribute is disabled...
98                        NestedMeta::Meta(Meta::Word(ref word)) if word == "csv" => {
99                            is_csv = true;
100                        },
101                        */
102
103                        NestedMeta::Meta(Meta::NameValue(ref kv)) if kv.path.is_ident("name_const") => {
104                            if name.is_some() {
105                                return Err("repeated 'name_const' option in #[header] attribute".into());
106                            }
107                            name = match kv.lit {
108                                Lit::Str(ref s) => Some(s.value()),
109                                _ => {
110                                    return Err("illegal literal in #[header(name_const = ..)] attribute".into());
111                                }
112                            };
113                        }
114                        _ => {
115                            return Err("illegal option in #[header(..)] attribute".into())
116                        }
117
118                    }
119                }
120
121            },
122            Ok(Meta::NameValue(_)) => {
123                return Err("illegal #[header = ..] attribute".into())
124            },
125            Ok(Meta::Path(_)) => {
126                return Err("empty #[header] attributes do nothing".into())
127            },
128            Err(e) => {
129                // TODO stringify attribute to return better error
130                return Err(format!("illegal #[header ??] attribute: {:?}", e))
131            }
132        }
133    }
134
135    let decode_res = quote! {
136        ::util::TryFromValues::try_from_values(values)
137    };
138
139    let (decode, encode_name) = match st.fields {
140        Fields::Named(ref fields) => {
141            if fields.named.len() != 1 {
142                return Err("derive(Header) doesn't support multiple fields".into());
143            }
144
145            let field = fields
146                .named
147                .iter()
148                .next()
149                .expect("just checked for len() == 1");
150            let field_name = field.ident.as_ref().unwrap();
151
152            let decode = quote! {
153                #decode_res
154                    .map(|inner| #ty {
155                        #field_name: inner,
156                    })
157            };
158
159            let encode_name = Ident::new(&field_name.to_string(), Span::call_site());
160            (decode, Value::Named(encode_name))
161        },
162        Fields::Unnamed(ref fields) => {
163            if fields.unnamed.len() != 1 {
164                return Err("derive(Header) doesn't support multiple fields".into());
165            }
166
167            let decode = quote! {
168                #decode_res
169                    .map(#ty)
170            };
171
172            (decode, Value::Unnamed)
173        },
174        Fields::Unit => {
175            return Err("derive(Header) doesn't support unit structs".into())
176        }
177    };
178
179    // csv attr disabled for now
180    let encode = /*if is_csv {
181        let field = if let Value::Named(field) = encode_name {
182            quote! {
183                (&(self.0).#field)
184            }
185        } else {
186            quote! {
187                (&(self.0).0)
188            }
189        };
190        quote! {
191            struct __HeaderFmt<'hfmt>(&'hfmt #ty);
192            impl<'hfmt> ::std::fmt::Display for __HeaderFmt<'hfmt> {
193                fn fmt(&self, hfmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
194                    __hc::encode::comma_delimited(hfmt, (#field).into_iter())
195                }
196            }
197            values.append_fmt(&__HeaderFmt(self));
198        }
199    } else*/ {
200        let field = if let Value::Named(field) = encode_name {
201            quote! {
202                (&self.#field)
203            }
204        } else {
205            quote! {
206                (&self.0)
207            }
208        };
209        quote! {
210            values.extend(::std::iter::once((#field).into()));
211        }
212    };
213
214    Ok(Fns {
215        decode,
216        encode,
217        name,
218    })
219}
220
221fn to_header_name(ty_name: &str) -> String {
222    let mut out = String::new();
223    let mut first = true;
224    for c in ty_name.chars() {
225        if first {
226            out.push(c.to_ascii_uppercase());
227            first = false;
228        } else {
229            if c.is_uppercase() {
230                out.push('_');
231            }
232            out.push(c.to_ascii_uppercase());
233        }
234    }
235    out
236}
237
238enum Value {
239    Named(Ident),
240    Unnamed,
241}
242