ldap_macros/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use convert_case::{Case, Casing};
4use syn::spanned::Spanned;
5
6/// Performs an LDAP search and converts the result into suitable Rust types
7#[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    //println!("Macro output:\n{}", output);
118
119    proc_macro::TokenStream::from(output)
120}