1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Data, DeriveInput, Fields, Type, parse_macro_input};
4
5#[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
81fn 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}