use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput, Attribute, Meta}; use quote::quote; use proc_macro_crate::crate_name; #[proc_macro_derive(Secret, attributes(secret))] pub fn derive_secret(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); // Verify this is a unit struct if !matches!(&input.data, syn::Data::Struct(data) if data.fields.is_empty()) { return syn::Error::new_spanned( input.ident, "#[derive(Secret)] only supports unit structs (e.g., `struct MySecret;`)", ) .to_compile_error() .into(); } // Parse the #[secret(...)] attribute let (namespace, key, value_type) = match parse_secret_attributes(&input.attrs) { Ok(attrs) => attrs, Err(e) => return e.into_compile_error().into(), }; // Get the path to the harmony_secrets crate let secret_crate_path = match crate_name("harmony-secrets") { Ok(proc_macro_crate::FoundCrate::Itself) => quote!(crate), Ok(proc_macro_crate::FoundCrate::Name(name)) => { let ident = quote::format_ident!("{}", name); quote!(::#ident) } Err(_) => { return syn::Error::new_spanned( &input.ident, "harmony-secrets crate not found in dependencies", ) .to_compile_error() .into(); } }; let struct_ident = input.ident; TokenStream::from(quote! { impl #secret_crate_path::Secret for #struct_ident { type Value = #value_type; const NAMESPACE: &'static str = #namespace; const KEY: &'static str = #key; } }) } fn parse_secret_attributes(attrs: &[Attribute]) -> syn::Result<(String, String, syn::Type)> { let secret_attr = attrs .iter() .find(|attr| attr.path().is_ident("secret")) .ok_or_else(|| { syn::Error::new_spanned( attrs.first().unwrap_or_else(|| &attrs[0]), "missing #[secret(...)] attribute", ) })?; let mut namespace = None; let mut key = None; let mut value_type = None; if let Meta::List(meta_list) = &secret_attr.parse_meta()? { for nested in &meta_list.nested { if let syn::NestedMeta::Meta(Meta::NameValue(nv)) = nested { if nv.path.is_ident("namespace") { if let syn::Lit::Str(lit) = &nv.lit { namespace = Some(lit.value()); } } else if nv.path.is_ident("key") { if let syn::Lit::Str(lit) = &nv.lit { key = Some(lit.value()); } } else if nv.path.is_ident("value_type") { if let syn::Lit::Str(lit) = &nv.lit { value_type = Some(syn::parse_str::(&lit.value())?); } } } } } Ok(( namespace.ok_or_else(|| { syn::Error::new_spanned(secret_attr, "missing `namespace` in #[secret(...)]") })?, key.ok_or_else(|| { syn::Error::new_spanned(secret_attr, "missing `key` in #[secret(...)]") })?, value_type.ok_or_else(|| { syn::Error::new_spanned(secret_attr, "missing `value_type` in #[secret(...)]") })?, )) }