1#![doc = include_str!("../README.md")]
2
3use convert_case::{Case, Casing};
4use syn::spanned::Spanned;
5
6#[proc_macro]
8pub fn ldap_search(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
9 let args = syn::parse_macro_input!(input with syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>::parse_terminated);
10
11 if args.len() != 7 {
12 return quote::quote! { compile_error!("Expected 7 arguments (ldap client handle, base dn, scope, filter, attributes, return type, body)") }.into();
13 }
14
15 let ldap_client_handle = &args[0];
16 let base_dn = &args[1];
17 let scope = &args[2];
18 let filter = &args[3];
19 let attributes = &args[4];
20 let return_type = &args[5];
21 let body = &args[6];
22
23 let syn::Expr::Array(attributes) = attributes else {
24 return quote::quote! { compile_error!("Expected fifth argument to be an array of attribute specifiers (attribute name as Rust type)") }.into();
25 };
26
27 let syn::Expr::Lit(syn::ExprLit {
28 attrs: _,
29 lit: syn::Lit::Str(return_type),
30 }) = return_type
31 else {
32 return quote::quote! { compile_error!("Expected sixth argument to be a literal String containing a Rust type") }.into();
33 };
34
35 let Ok(return_type): Result<syn::Type, syn::Error> = syn::parse_str(&return_type.value())
36 else {
37 return quote::quote! { compile_error!("Expected sixth argument to be a literal String containing a Rust type") }.into();
38 };
39
40 let mut attribute_names = Vec::new();
41 let mut attribute_handlers = Vec::new();
42 let mut attribute_definition_parameters = Vec::new();
43 let mut attribute_call_parameters = Vec::new();
44 attribute_definition_parameters.push(quote::quote! {
45 dn: ldap_types::basic::DistinguishedName
46 });
47 attribute_call_parameters.push(quote::quote! {
48 dn
49 });
50 for elem in &attributes.elems {
51 let span = elem.span();
52 let syn::Expr::Lit(syn::ExprLit {
53 attrs: _,
54 lit: syn::Lit::Str(attribute_specifier),
55 }) = elem
56 else {
57 return quote::quote! { compile_error!("Expected attribute specifier to be literal string") }.into();
58 };
59 let Ok(attribute_cast): Result<syn::ExprCast, syn::Error> =
60 syn::parse_str(&attribute_specifier.value())
61 else {
62 return quote::quote! { compile_error!("Expected attribute specifier to be cast expression") }.into();
63 };
64 let syn::Expr::Path(syn::ExprPath {
65 attrs: _,
66 qself: _,
67 path:
68 syn::Path {
69 leading_colon: None,
70 segments,
71 },
72 }) = *attribute_cast.expr
73 else {
74 return quote::quote! { compile_error!("Expected attribute name to be identifier (within the literal string for the cast expression)") }.into();
75 };
76 if segments.len() != 1 {
77 return quote::quote! { compile_error!("Expected attribute name to be identifier with a path length of 1") }.into();
78 }
79 let Some(attribute_name) = segments.first().map(|s| s.ident.to_string()) else {
80 return quote::quote! { compile_error!("Expected attribute name to be identifier with a path length of 1") }.into();
81 };
82 let attribute_rust_type = *attribute_cast.ty;
83 let attribute_rust_variable = syn::Ident::new(&attribute_name.to_case(Case::Snake), span);
84 attribute_names.push(quote::quote! {
85 #attribute_name
86 });
87 attribute_handlers.push(quote::quote! {
88 let #attribute_rust_variable: #attribute_rust_type =
89 <#attribute_rust_type as ldap_types::conversion::FromLdapType>::parse(<ldap3::SearchEntry as ldap_types::conversion::SearchEntryExt>::attribute_results(&entry, #attribute_name))?;
90 });
91 attribute_definition_parameters.push(quote::quote! {
92 #attribute_rust_variable: #attribute_rust_type
93 });
94 attribute_call_parameters.push(quote::quote! {
95 #attribute_rust_variable
96 });
97 }
98
99 let output = quote::quote! {
100 let it = ldap_utils::ldap_search(
101 #ldap_client_handle,
102 #base_dn,
103 #scope,
104 #filter,
105 vec![#(#attribute_names),*],
106 ).await?;
107
108 let mut generated_ldap_search_entry_handler = async |#(#attribute_definition_parameters),*| -> #return_type #body;
109
110 for entry in it {
111 let dn : ldap_types::basic::DistinguishedName = entry.dn.clone().try_into()?;
112 #(#attribute_handlers)*
113 generated_ldap_search_entry_handler(#(#attribute_call_parameters),*).await?;
114 }
115 };
116
117 proc_macro::TokenStream::from(output)
120}