101 lines
3.4 KiB
Plaintext
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(...)]")
|
|
})?,
|
|
))
|
|
}
|