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}