harmony/harmony_secrets_derive/src/lib.rsglm45

101 lines
3.4 KiB
Plaintext

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::<syn::Type>(&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(...)]")
})?,
))
}