1#![doc = include_str!("../README.md")]
2
3use convert_case::{Case, Casing as _};
4use syn::spanned::Spanned as _;
5
6#[proc_macro]
49pub fn ldap_search(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
50 let args = syn::parse_macro_input!(input with syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>::parse_terminated);
51
52 let Some(ldap_client_handle) = args.get(0) else {
53 return quote::quote_spanned! { args.span() =>
54 compile_error!("Missing first argument: ldap client handle");
55 }
56 .into();
57 };
58 let Some(base_dn) = args.get(1) else {
59 return quote::quote_spanned! { args.span() =>
60 compile_error!("Missing second argument: base dn");
61 }
62 .into();
63 };
64 let Some(scope) = args.get(2) else {
65 return quote::quote_spanned! { args.span() =>
66 compile_error!("Missing third argument: scope");
67 }
68 .into();
69 };
70 let Some(filter) = args.get(3) else {
71 return quote::quote_spanned! { args.span() =>
72 compile_error!("Missing fourth argument: filter");
73 }
74 .into();
75 };
76 let Some(attributes) = args.get(4) else {
77 return quote::quote_spanned! { args.span() =>
78 compile_error!("Missing fifth argument: attributes (array of attribute specifiers)");
79 }
80 .into();
81 };
82 let Some(return_type) = args.get(5) else {
83 return quote::quote_spanned! { args.span() =>
84 compile_error!("Missing sixth argument: return type (literal String)");
85 }
86 .into();
87 };
88 let Some(body) = args.get(6) else {
89 return quote::quote_spanned! { args.span() =>
90 compile_error!("Missing seventh argument: body (code block)");
91 }
92 .into();
93 };
94
95 let syn::Expr::Array(attributes) = attributes else {
96 return quote::quote! { compile_error!("Expected fifth argument to be an array of attribute specifiers (attribute name as Rust type)") }.into();
97 };
98
99 let syn::Expr::Lit(syn::ExprLit {
100 attrs: _,
101 lit: syn::Lit::Str(return_type),
102 }) = return_type
103 else {
104 return quote::quote! { compile_error!("Expected sixth argument to be a literal String containing a Rust type") }.into();
105 };
106
107 let Ok(return_type): Result<syn::Type, syn::Error> = syn::parse_str(&return_type.value())
108 else {
109 return quote::quote! { compile_error!("Expected sixth argument to be a literal String containing a Rust type") }.into();
110 };
111
112 let mut attribute_names = Vec::new();
113 let mut attribute_handlers = Vec::new();
114 let mut attribute_definition_parameters = Vec::new();
115 let mut attribute_call_parameters = Vec::new();
116 attribute_definition_parameters.push(quote::quote! {
117 dn: ldap_types::basic::DistinguishedName
118 });
119 attribute_call_parameters.push(quote::quote! {
120 dn
121 });
122 for elem in &attributes.elems {
123 let span = elem.span();
124 let syn::Expr::Lit(syn::ExprLit {
125 attrs: _,
126 lit: syn::Lit::Str(attribute_specifier),
127 }) = elem
128 else {
129 return quote::quote! { compile_error!("Expected attribute specifier to be literal string") }.into();
130 };
131 let Ok(attribute_cast): Result<syn::ExprCast, syn::Error> =
132 syn::parse_str(&attribute_specifier.value())
133 else {
134 return quote::quote! { compile_error!("Expected attribute specifier to be cast expression") }.into();
135 };
136 let syn::Expr::Path(syn::ExprPath {
137 attrs: _,
138 qself: _,
139 path:
140 syn::Path {
141 leading_colon: None,
142 segments,
143 },
144 }) = *attribute_cast.expr
145 else {
146 return quote::quote! { compile_error!("Expected attribute name to be identifier (within the literal string for the cast expression)") }.into();
147 };
148 if segments.len() != 1 {
149 return quote::quote! { compile_error!("Expected attribute name to be identifier with a path length of 1") }.into();
150 }
151 let Some(attribute_name) = segments.first().map(|s| s.ident.to_string()) else {
152 return quote::quote! { compile_error!("Expected attribute name to be identifier with a path length of 1") }.into();
153 };
154 let attribute_rust_type = *attribute_cast.ty;
155 let attribute_rust_variable = syn::Ident::new(&attribute_name.to_case(Case::Snake), span);
156 attribute_names.push(quote::quote! {
157 #attribute_name
158 });
159 attribute_handlers.push(quote::quote! {
160 let #attribute_rust_variable: #attribute_rust_type =
161 <#attribute_rust_type as ldap_types::conversion::FromLdapType>::parse(<ldap3::SearchEntry as ldap_types::conversion::SearchEntryExt>::attribute_results(&entry, #attribute_name))?;
162 });
163 attribute_definition_parameters.push(quote::quote! {
164 #attribute_rust_variable: #attribute_rust_type
165 });
166 attribute_call_parameters.push(quote::quote! {
167 #attribute_rust_variable
168 });
169 }
170
171 let output = quote::quote! {
172 let it = ldap_utils::ldap_search(
173 #ldap_client_handle,
174 #base_dn,
175 #scope,
176 #filter,
177 vec![#(#attribute_names),*],
178 ).await?;
179
180 let entries: Vec<ldap3::SearchEntry> = it.collect();
182
183 let mut generated_ldap_search_entry_handler = async |#(#attribute_definition_parameters),*| -> #return_type #body;
184
185 for entry in entries { let dn : ldap_types::basic::DistinguishedName = entry.dn.clone().try_into()?;
187 #(#attribute_handlers)*
188 generated_ldap_search_entry_handler(#(#attribute_call_parameters),*).await?;
189 }
190 };
191
192 proc_macro::TokenStream::from(output)
195}