lapdog_derive/
lib.rs

1use proc_macro2::TokenStream;
2use quote::{format_ident, quote};
3use syn::{DataStruct, DeriveInput, Field, Fields, Ident};
4
5#[proc_macro_derive(Entry, attributes(lapdog))]
6pub fn implement_from_entry(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
7    let input = syn::parse_macro_input!(item as DeriveInput);
8    let name = input.ident;
9    let (fields, object_name_field) = match parse_fields(
10        match input.data {
11            syn::Data::Struct(DataStruct { fields, .. }) => match fields {
12                Fields::Named(f) => f,
13                _ => panic!("Structs fields/attributes must be named to be derivable"),
14            },
15            _ => unimplemented!("non-struct derives are not supported"),
16        }
17        .named,
18    ) {
19        Ok(f) => f,
20        Err(e) => return e.into_compile_error().into(),
21    };
22
23    let insert_object_name = object_name_field.as_ref().map(insert_object_name);
24    let field_quotes = fields.iter().map(field_line);
25    let field_names = fields.iter().map(|x| x.ident());
26    let attribute_names = fields.iter().map(|x| x.attribute_name.clone());
27    quote!(
28        impl lapdog::search::FromEntry for #name {
29            fn from_entry(entry: lapdog::search::RawEntry) -> Result<#name, lapdog::search::FailedToGetFromEntry> {
30                #( #field_quotes )*
31                Ok(#name { #(#field_names,)* #insert_object_name })
32            }
33
34            fn attributes() -> Option<impl Iterator<Item = &'static str>> {
35                Some(vec![#(#attribute_names,)*].into_iter())
36            }
37        }
38    )
39    .into()
40}
41
42fn insert_object_name(field: &Field) -> TokenStream {
43    let field_name = field.ident.as_ref().expect("checked to be named field");
44    let ty = &field.ty;
45    quote! {
46        #field_name: <#ty as From<String>>::from(entry.object_name)
47    }
48}
49
50struct AttributeField {
51    attribute_name: String,
52    multiple: bool,
53    field: Field,
54}
55impl AttributeField {
56    fn ident(&self) -> Ident {
57        self.field.ident.clone().expect("checked to be named field")
58    }
59}
60fn parse_fields(
61    raw_fields: impl IntoIterator<Item = Field>,
62) -> Result<(Vec<AttributeField>, Option<Field>), syn::Error> {
63    let mut fields: Vec<AttributeField> = Vec::new();
64    let mut object_name_field = None;
65    'fields: for field in raw_fields {
66        let mut multiple = false;
67        let mut replaced_attribute_name = None;
68        for attr in &field.attrs {
69            let mut has_set_object_name_field = false;
70            attr.parse_nested_meta(|meta| {
71                if meta.path.is_ident("object_name") {
72                    if object_name_field.replace(field.clone()).is_some() {
73                        return Err(meta.error("\"object_name\" can only be declared on one field"));
74                    };
75                    has_set_object_name_field = true;
76                    return Ok(());
77                }
78                if meta.path.require_ident()? == "rename" {
79                    let lookahead = meta.input.lookahead1();
80                    if lookahead.peek(syn::Token![=]) {
81                        let expr = meta
82                            .value()
83                            .expect("Meta has no value")
84                            .parse()
85                            .expect("Meta is no expression");
86                        let mut value = &expr;
87                        while let syn::Expr::Group(e) = value {
88                            value = &e.expr;
89                        }
90                        if let syn::Expr::Lit(syn::ExprLit {
91                            lit: syn::Lit::Str(lit),
92                            ..
93                        }) = value
94                        {
95                            replaced_attribute_name = Some(lit.value());
96                        } else {
97                            return Err(meta.error("rename argument must be a string literal"));
98                        }
99                    } else {
100                        return Err(meta.error("rename must be used like \"rename = <LDAP NAME>\""));
101                    }
102                }
103                if meta.path.require_ident()? == "multiple" {
104                    multiple = true;
105                }
106                Ok(())
107            })?;
108            if has_set_object_name_field {
109                continue 'fields;
110            }
111        }
112        let attribute_name = replaced_attribute_name
113            .unwrap_or_else(|| field.ident.as_ref().expect("checked as named field").to_string());
114        fields.push(AttributeField {
115            attribute_name,
116            multiple,
117            field,
118        })
119    }
120    Ok((fields, object_name_field))
121}
122
123fn field_line(data: &AttributeField) -> TokenStream {
124    let lookup_name = &data.attribute_name;
125    let field_type = &data.field.ty;
126    let varname = format_ident!("{}", data.ident());
127    if data.multiple {
128        quote! {
129            let #varname = <#field_type as lapdog::search::FromMultipleOctetStrings>::from_multiple_octet_strings(
130                entry.attributes
131                    .iter()
132                    .find(|x| x.r#type == #lookup_name)
133                    .ok_or(lapdog::search::FailedToGetFromEntry::MissingField(#lookup_name))?
134                    .values
135                    .iter()
136                    .map(|x| x.as_ref())
137                ).map_err(|b| lapdog::search::FailedToGetFromEntry::FailedToParseField(#lookup_name, Box::new(b)))?;
138        }
139    } else {
140        quote! {
141            let #varname = <#field_type as lapdog::search::FromOctetString>::from_octet_string(match
142                entry.attributes
143                    .iter()
144                    .find(|x| x.r#type == #lookup_name)
145                    .ok_or(lapdog::search::FailedToGetFromEntry::MissingField(#lookup_name))?
146                    .values
147                    .as_slice() {
148                    [] => {return Err(lapdog::search::FailedToGetFromEntry::MissingField(#lookup_name));},
149                    [value] => value,
150                    _ => {return Err(lapdog::search::FailedToGetFromEntry::TooManyValues(#lookup_name));}
151                }
152            ).map_err(|b| lapdog::search::FailedToGetFromEntry::FailedToParseField(#lookup_name, Box::new(b)))?;
153        }
154    }
155}