use proc_macro2::TokenStream;
use quote::quote;
use syn::{Error, Fields, Ident, ItemStruct, Result};
pub(super) fn expand_redacted_debug(
input: &ItemStruct,
secrets: &[(Ident, String)],
) -> Result<TokenStream> {
if secrets.is_empty() {
return Ok(quote! {});
}
if let Some(attribute) = derives_debug(input) {
return Err(Error::new_spanned(
attribute,
"`#[config(secret)]` generates a `Debug` that redacts the marked fields, so this \
`#[derive(Debug)]` would conflict with it; remove `Debug` from the derive",
));
}
let name = &input.ident;
let Fields::Named(fields) = &input.fields else {
unreachable!("secrets are only collected from named fields")
};
let entries = fields.named.iter().map(|field| {
let field_name = field
.ident
.as_ref()
.expect("named fields always have an identifier");
let rendered = field_name.to_string();
if secrets.iter().any(|(ident, _)| ident == field_name) {
quote!(.field(#rendered, &"***"))
} else {
quote!(.field(#rendered, &self.#field_name))
}
});
let rendered_name = name.to_string();
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
Ok(quote! {
impl #impl_generics ::core::fmt::Debug for #name #type_generics #where_clause {
fn fmt(
&self,
formatter: &mut ::core::fmt::Formatter<'_>,
) -> ::core::fmt::Result {
formatter
.debug_struct(#rendered_name)
#(#entries)*
.finish()
}
}
})
}
fn derives_debug(input: &ItemStruct) -> Option<&syn::Attribute> {
input.attrs.iter().find(|attribute| {
if !attribute.path().is_ident("derive") {
return false;
}
let mut found = false;
let _ = attribute.parse_nested_meta(|meta| {
if meta.path.is_ident("Debug") {
found = true;
}
Ok(())
});
found
})
}
pub(super) fn introspection_methods(secret_names: &[String]) -> TokenStream {
quote! {
pub fn explain(
path: &str,
) -> ::core::result::Result<::dynamic_config::Explanation, ::dynamic_config::Error> {
const SECRETS: &[&str] = &[#(#secret_names),*];
let explanation = Self::dynamic_config_builder()?.explain(path)?;
let head = path.split('.').next().unwrap_or(path);
if SECRETS.contains(&head) {
return ::core::result::Result::Ok(explanation.redacted());
}
::core::result::Result::Ok(explanation)
}
pub fn source_of(
path: &str,
) -> ::core::result::Result<
::core::option::Option<::dynamic_config::Origin>,
::dynamic_config::Error,
> {
Self::dynamic_config_builder()?.source_of(path)
}
pub fn is_set(path: &str) -> ::core::result::Result<bool, ::dynamic_config::Error> {
Self::dynamic_config_builder()?.is_set(path)
}
pub fn snapshot() -> ::core::result::Result<
::dynamic_config::Snapshot,
::dynamic_config::Error,
> {
Self::dynamic_config_builder()?.snapshot()
}
}
}
pub(super) fn check_method() -> TokenStream {
quote! {
pub fn check() -> ::core::result::Result<
::dynamic_config::Report,
::dynamic_config::Error,
> {
Self::dynamic_config_builder()?.check()
}
}
}