#![doc = include_str!("../README.md")]
use convert_case::{Case, Casing as _};
use syn::spanned::Spanned as _;
#[proc_macro]
pub fn ldap_search(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let args = syn::parse_macro_input!(input with syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>::parse_terminated);
let Some(ldap_client_handle) = args.get(0) else {
return quote::quote_spanned! { args.span() =>
compile_error!("Missing first argument: ldap client handle");
}
.into();
};
let Some(base_dn) = args.get(1) else {
return quote::quote_spanned! { args.span() =>
compile_error!("Missing second argument: base dn");
}
.into();
};
let Some(scope) = args.get(2) else {
return quote::quote_spanned! { args.span() =>
compile_error!("Missing third argument: scope");
}
.into();
};
let Some(filter) = args.get(3) else {
return quote::quote_spanned! { args.span() =>
compile_error!("Missing fourth argument: filter");
}
.into();
};
let Some(attributes) = args.get(4) else {
return quote::quote_spanned! { args.span() =>
compile_error!("Missing fifth argument: attributes (array of attribute specifiers)");
}
.into();
};
let Some(return_type) = args.get(5) else {
return quote::quote_spanned! { args.span() =>
compile_error!("Missing sixth argument: return type (literal String)");
}
.into();
};
let Some(body) = args.get(6) else {
return quote::quote_spanned! { args.span() =>
compile_error!("Missing seventh argument: body (code block)");
}
.into();
};
let syn::Expr::Array(attributes) = attributes else {
return quote::quote! { compile_error!("Expected fifth argument to be an array of attribute specifiers (attribute name as Rust type)") }.into();
};
let syn::Expr::Lit(syn::ExprLit {
attrs: _,
lit: syn::Lit::Str(return_type),
}) = return_type
else {
return quote::quote! { compile_error!("Expected sixth argument to be a literal String containing a Rust type") }.into();
};
let Ok(return_type): Result<syn::Type, syn::Error> = syn::parse_str(&return_type.value())
else {
return quote::quote! { compile_error!("Expected sixth argument to be a literal String containing a Rust type") }.into();
};
let mut attribute_names = Vec::new();
let mut attribute_handlers = Vec::new();
let mut attribute_definition_parameters = Vec::new();
let mut attribute_call_parameters = Vec::new();
attribute_definition_parameters.push(quote::quote! {
dn: ldap_types::basic::DistinguishedName
});
attribute_call_parameters.push(quote::quote! {
dn
});
for elem in &attributes.elems {
let span = elem.span();
let syn::Expr::Lit(syn::ExprLit {
attrs: _,
lit: syn::Lit::Str(attribute_specifier),
}) = elem
else {
return quote::quote! { compile_error!("Expected attribute specifier to be literal string") }.into();
};
let Ok(attribute_cast): Result<syn::ExprCast, syn::Error> =
syn::parse_str(&attribute_specifier.value())
else {
return quote::quote! { compile_error!("Expected attribute specifier to be cast expression") }.into();
};
let syn::Expr::Path(syn::ExprPath {
attrs: _,
qself: _,
path:
syn::Path {
leading_colon: None,
segments,
},
}) = *attribute_cast.expr
else {
return quote::quote! { compile_error!("Expected attribute name to be identifier (within the literal string for the cast expression)") }.into();
};
if segments.len() != 1 {
return quote::quote! { compile_error!("Expected attribute name to be identifier with a path length of 1") }.into();
}
let Some(attribute_name) = segments.first().map(|s| s.ident.to_string()) else {
return quote::quote! { compile_error!("Expected attribute name to be identifier with a path length of 1") }.into();
};
let attribute_rust_type = *attribute_cast.ty;
let attribute_rust_variable = syn::Ident::new(&attribute_name.to_case(Case::Snake), span);
attribute_names.push(quote::quote! {
#attribute_name
});
attribute_handlers.push(quote::quote! {
let #attribute_rust_variable: #attribute_rust_type =
<#attribute_rust_type as ldap_types::conversion::FromLdapType>::parse(<ldap3::SearchEntry as ldap_types::conversion::SearchEntryExt>::attribute_results(&entry, #attribute_name))?;
});
attribute_definition_parameters.push(quote::quote! {
#attribute_rust_variable: #attribute_rust_type
});
attribute_call_parameters.push(quote::quote! {
#attribute_rust_variable
});
}
let output = quote::quote! {
let it = ldap_utils::ldap_search(
#ldap_client_handle,
#base_dn,
#scope,
#filter,
vec![#(#attribute_names),*],
).await?;
let entries: Vec<ldap3::SearchEntry> = it.collect();
let mut generated_ldap_search_entry_handler = async |#(#attribute_definition_parameters),*| -> #return_type #body;
for entry in entries { let dn : ldap_types::basic::DistinguishedName = entry.dn.clone().try_into()?;
#(#attribute_handlers)*
generated_ldap_search_entry_handler(#(#attribute_call_parameters),*).await?;
}
};
proc_macro::TokenStream::from(output)
}