Skip to main content

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}