Skip to main content

secrets_rs_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Data, DeriveInput, Fields, Type, parse_macro_input};
4
5/// Derives [`secrets_rs::Bindable`] for a struct.
6///
7/// All fields whose type is `Secret<T>` (or any path ending in `Secret`) will
8/// have `.bind(registry)` called on them. All other fields are ignored.
9///
10/// # Example
11///
12/// ```rust,ignore
13/// use secrets_rs::{Secret, bind_all, EnvSource, SourceRegistry};
14///
15/// #[derive(secrets_rs::Bindable)]
16/// struct Config {
17///     api_key:  Secret<String>,
18///     timeout:  u32,           // ignored — not a Secret
19/// }
20/// ```
21#[proc_macro_derive(Bindable)]
22pub fn derive_bindable(input: TokenStream) -> TokenStream {
23    let input = parse_macro_input!(input as DeriveInput);
24    impl_bindable(input).unwrap_or_else(|e| e.to_compile_error().into())
25}
26
27fn impl_bindable(input: DeriveInput) -> syn::Result<TokenStream> {
28    let name = &input.ident;
29
30    let named_fields = match &input.data {
31        Data::Struct(s) => match &s.fields {
32            Fields::Named(f) => &f.named,
33            _ => {
34                return Err(syn::Error::new_spanned(
35                    name,
36                    "#[derive(Bindable)] requires a struct with named fields",
37                ));
38            }
39        },
40        _ => {
41            return Err(syn::Error::new_spanned(
42                name,
43                "#[derive(Bindable)] can only be applied to structs",
44            ));
45        }
46    };
47
48    let bind_calls = named_fields
49        .iter()
50        .filter(|f| is_secret_type(&f.ty))
51        .map(|f| {
52            let ident = f.ident.as_ref().unwrap();
53            quote! {
54                if let Err(__e) = self.#ident.bind(__registry) {
55                    __errors.push(__e);
56                }
57            }
58        });
59
60    let expanded = quote! {
61        impl ::secrets_rs::Bindable for #name {
62            fn bind_secrets(
63                &mut self,
64                __registry: &::secrets_rs::SourceRegistry,
65            ) -> ::std::result::Result<(), ::std::vec::Vec<::secrets_rs::BindError>> {
66                let mut __errors: ::std::vec::Vec<::secrets_rs::BindError> =
67                    ::std::vec::Vec::new();
68                #(#bind_calls)*
69                if __errors.is_empty() {
70                    ::std::result::Result::Ok(())
71                } else {
72                    ::std::result::Result::Err(__errors)
73                }
74            }
75        }
76    };
77
78    Ok(expanded.into())
79}
80
81/// Returns `true` if the type's final path segment is `Secret`.
82fn is_secret_type(ty: &Type) -> bool {
83    match ty {
84        Type::Path(tp) => tp
85            .path
86            .segments
87            .last()
88            .map(|s| s.ident == "Secret")
89            .unwrap_or(false),
90        _ => false,
91    }
92}