bitwarden_core_macro/lib.rs
1//! Proc macros for the Bitwarden SDK.
2//!
3//! Provides:
4//! - `#[derive(FromClient)]` derive macro for implementing the `FromClient` trait on client
5//! structs.
6
7use proc_macro::TokenStream;
8use quote::quote;
9use syn::{DeriveInput, parse_macro_input};
10
11/// Derive macro for implementing the `FromClient` trait on client structs.
12///
13/// This macro generates an implementation of the `FromClient` trait that extracts
14/// all struct fields from a `Client` using the `FromClientPart` trait.
15///
16/// # Example
17///
18/// ```ignore
19/// use bitwarden_core::client::FromClient;
20/// use bitwarden_core_macro::FromClient;
21///
22/// #[derive(FromClient)]
23/// pub struct FoldersClient {
24/// key_store: KeyStore<KeyIds>,
25/// api_configurations: Arc<ApiConfigurations>,
26/// repository: Arc<dyn Repository<Folder>>,
27/// }
28/// ```
29///
30/// The macro generates:
31///
32/// ```ignore
33/// impl FromClient for FoldersClient {
34/// fn from_client(client: &Client) -> Result<Self, String> {
35/// Ok(Self {
36/// key_store: FromClientPart::<KeyStore<KeyIds>>::get_part(client).map_err(|e| e.to_string())?,
37/// api_configs: FromClientPart::<Arc<ApiConfigurations>>::get_part(client).map_err(|e| e.to_string())?,
38/// repository: FromClientPart::<Arc<dyn Repository<Folder>>>::get_part(client).map_err(|e| e.to_string())?,
39/// })
40/// }
41/// }
42/// ```
43#[proc_macro_derive(FromClient)]
44pub fn derive_from_client(item: TokenStream) -> TokenStream {
45 let input = parse_macro_input!(item as DeriveInput);
46
47 let struct_name = &input.ident;
48 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
49
50 let syn::Data::Struct(syn::DataStruct {
51 fields: syn::Fields::Named(fields),
52 ..
53 }) = &input.data
54 else {
55 return syn::Error::new_spanned(
56 &input,
57 "FromClient can only be derived for structs with named fields",
58 )
59 .to_compile_error()
60 .into();
61 };
62
63 let field_inits = fields.named.iter().filter_map(|f| {
64 let field_name = f.ident.as_ref()?;
65 let field_type = &f.ty;
66 Some(quote! {
67 #field_name: ::bitwarden_core::client::FromClientPart::<#field_type>::get_part(client).map_err(|e| e.to_string())?
68 })
69 });
70
71 let expanded = quote! {
72 impl #impl_generics ::bitwarden_core::client::FromClient for #struct_name #ty_generics #where_clause {
73 fn from_client(client: &::bitwarden_core::Client) -> Result<Self, String> {
74 Ok(Self {
75 #(#field_inits),*
76 })
77 }
78 }
79 };
80
81 TokenStream::from(expanded)
82}