use syn::Data;
use syn::DeriveInput;
use syn::Error;
use syn::Meta;
use syn::Path;
use syn::Result;
use syn::Token;
use syn::token::Paren;
#[must_use]
pub(crate) struct ContainerAttributes {
debug: bool,
display: bool,
serde: bool,
transparent: bool,
}
impl ContainerAttributes {
pub(crate) fn parse(input: &DeriveInput) -> Result<Self> {
let mut debug = false;
let mut display = false;
let mut serde = false;
let mut transparent = false;
for attribute in &input.attrs {
if !attribute.path().is_ident("redact") {
continue;
}
let Meta::List(list) = &attribute.meta else {
return Err(Error::new_spanned(
attribute,
format!(
"Redact derive for `{}` expects `#[redact(debug, display, serde)]` on the container",
input.ident,
),
));
};
if list.tokens.is_empty() {
return Err(Error::new_spanned(
attribute,
format!(
"Redact derive for `{}` does not allow an empty container attribute; use \
`#[redact(debug)]`, `#[redact(display)]`, or `#[redact(serde)]`",
input.ident,
),
));
}
attribute.parse_nested_meta(|meta| {
if meta.path.is_ident("crate") {
let _: Path = meta.value()?.parse()?;
return Ok(());
}
let option = if meta.path.is_ident("debug") {
&mut debug
} else if meta.path.is_ident("display") {
&mut display
} else if meta.path.is_ident("serde") {
&mut serde
} else if meta.path.is_ident("transparent") {
&mut transparent
} else {
return Err(meta.error(format!(
"Redact derive for `{}` has unknown container attribute; use \
`debug`, `display`, `serde`, `transparent`, or `crate = path`",
input.ident,
)));
};
if meta.input.peek(Token![=]) || meta.input.peek(Paren) {
let name = meta
.path
.segments
.last()
.map_or("option".to_owned(), |segment| segment.ident.to_string());
return Err(meta.error(format!(
"Redact derive for `{}` requires bare `{name}` without arguments",
input.ident
)));
}
if *option {
let name = meta
.path
.segments
.last()
.map_or("option".to_owned(), |segment| segment.ident.to_string());
return Err(meta.error(format!(
"Redact derive for `{}` repeats the `{name}` container option",
input.ident
)));
}
*option = true;
Ok(())
})?;
}
if transparent {
let valid = matches!(&input.data, Data::Struct(data) if data.fields.iter().count() == 1);
if !valid {
return Err(Error::new_spanned(
input,
format!(
"Redact derive for `{}` requires `transparent` on a single-field struct",
input.ident
),
));
}
}
Ok(Self {
debug,
display,
serde,
transparent,
})
}
#[must_use]
#[inline(always)]
pub(crate) const fn debug_enabled(&self) -> bool {
self.debug
}
#[must_use]
#[inline(always)]
pub(crate) const fn display_enabled(&self) -> bool {
self.display
}
#[must_use]
#[inline(always)]
pub(crate) const fn serde_enabled(&self) -> bool {
self.serde
}
#[must_use]
#[inline(always)]
pub(crate) const fn transparent(&self) -> bool {
self.transparent
}
}