use syn::{
Attribute,
Data,
DeriveInput,
Ident,
LitStr,
Meta,
Token,
};
use crate::{
serde_container_attributes::SerdeContainerAttributes,
serde_rename_rule::SerdeRenameRule,
};
pub(crate) struct SerdeContainerAttributeParser<'input> {
input: &'input DeriveInput,
name: Option<String>,
rename_all: Option<SerdeRenameRule>,
rename_all_fields: Option<SerdeRenameRule>,
tag: Option<LitStr>,
content: Option<LitStr>,
untagged: Option<syn::Path>,
}
impl<'input> SerdeContainerAttributeParser<'input> {
pub(crate) fn parse(
input: &'input DeriveInput,
enabled: bool,
) -> syn::Result<SerdeContainerAttributes> {
let mut parser = Self::new(input);
parser.parse_attributes(enabled)?;
parser.finish()
}
#[must_use]
#[inline(always)]
fn new(input: &'input DeriveInput) -> Self {
Self {
input,
name: None,
rename_all: None,
rename_all_fields: None,
tag: None,
content: None,
untagged: None,
}
}
fn parse_attributes(&mut self, enabled: bool) -> syn::Result<()> {
if !enabled {
return Ok(());
}
for attribute in &self.input.attrs {
if attribute.path().is_ident("serde") {
self.parse_attribute(attribute)?;
}
}
Ok(())
}
fn parse_attribute(&mut self, attribute: &Attribute) -> syn::Result<()> {
let Meta::List(_) = &attribute.meta else {
return Err(syn::Error::new_spanned(
attribute,
format!(
"Redact serde for `{}` expects `#[serde(...)]`",
self.input.ident,
),
));
};
attribute.parse_nested_meta(|meta| self.parse_nested_attribute(meta))
}
fn parse_nested_attribute(
&mut self,
meta: syn::meta::ParseNestedMeta<'_>,
) -> syn::Result<()> {
if meta.path.is_ident("rename") {
parse_name(&meta, &self.input.ident, "rename", &mut self.name)
} else if meta.path.is_ident("rename_all") {
parse_rule(
&meta,
&self.input.ident,
"rename_all",
&mut self.rename_all,
)
} else if meta.path.is_ident("rename_all_fields") {
self.parse_rename_all_fields(meta)
} else if meta.path.is_ident("tag") {
self.parse_tag(meta)
} else if meta.path.is_ident("content") {
self.parse_content(meta)
} else if meta.path.is_ident("untagged") {
self.parse_untagged(meta)
} else {
Err(self.unsupported_control_error(meta))
}
}
fn parse_rename_all_fields(
&mut self,
meta: syn::meta::ParseNestedMeta<'_>,
) -> syn::Result<()> {
require_enum(&meta, self.input, "rename_all_fields")?;
parse_rule(
&meta,
&self.input.ident,
"rename_all_fields",
&mut self.rename_all_fields,
)
}
fn parse_tag(
&mut self,
meta: syn::meta::ParseNestedMeta<'_>,
) -> syn::Result<()> {
require_enum(&meta, self.input, "tag")?;
parse_literal(&meta, &self.input.ident, "tag", &mut self.tag)
}
fn parse_content(
&mut self,
meta: syn::meta::ParseNestedMeta<'_>,
) -> syn::Result<()> {
require_enum(&meta, self.input, "content")?;
parse_literal(&meta, &self.input.ident, "content", &mut self.content)
}
fn parse_untagged(
&mut self,
meta: syn::meta::ParseNestedMeta<'_>,
) -> syn::Result<()> {
require_enum(&meta, self.input, "untagged")?;
if meta.input.peek(Token![=]) || meta.input.peek(syn::token::Paren) {
return Err(meta.error(format!(
"Redact serde for `{}` requires bare `untagged`",
self.input.ident,
)));
}
if self.untagged.is_some() {
return Err(meta.error(format!(
"Redact serde for `{}` repeats `untagged`",
self.input.ident,
)));
}
self.untagged = Some(meta.path);
Ok(())
}
fn finish(self) -> syn::Result<SerdeContainerAttributes> {
SerdeContainerAttributes::from_parts(
self.input,
self.name,
self.rename_all,
self.rename_all_fields,
self.tag,
self.content,
self.untagged,
)
}
fn unsupported_control_error(
&self,
meta: syn::meta::ParseNestedMeta<'_>,
) -> syn::Error {
let key = meta
.path
.segments
.last()
.expect("syn nested meta paths always contain a segment")
.ident
.to_string();
meta.error(format!(
"Redact serde for `{}` does not support container `{key}` because it can change value paths or bypass redaction; use only `rename`, `rename_all`, `rename_all_fields`, `tag`, `content`, or `untagged`",
self.input.ident,
))
}
}
fn require_enum(
meta: &syn::meta::ParseNestedMeta<'_>,
input: &DeriveInput,
name: &str,
) -> syn::Result<()> {
if matches!(input.data, Data::Enum(_)) {
Ok(())
} else {
Err(meta.error(format!(
"Redact serde for `{}` allows `{name}` only on enums",
input.ident,
)))
}
}
fn parse_name(
meta: &syn::meta::ParseNestedMeta<'_>,
type_name: &Ident,
name: &str,
output: &mut Option<String>,
) -> syn::Result<()> {
if output.is_some() {
return Err(meta.error(format!(
"Redact serde for `{type_name}` repeats `{name}`",
)));
}
let mut literal = None;
parse_literal(meta, type_name, name, &mut literal)?;
*output = literal.map(|literal| literal.value());
Ok(())
}
fn parse_rule(
meta: &syn::meta::ParseNestedMeta<'_>,
type_name: &Ident,
name: &str,
output: &mut Option<SerdeRenameRule>,
) -> syn::Result<()> {
if output.is_some() {
return Err(meta.error(format!(
"Redact serde for `{type_name}` repeats `{name}`",
)));
}
let literal: LitStr = meta.value()?.parse()?;
*output = Some(SerdeRenameRule::parse(&literal)?);
Ok(())
}
fn parse_literal(
meta: &syn::meta::ParseNestedMeta<'_>,
type_name: &Ident,
name: &str,
output: &mut Option<LitStr>,
) -> syn::Result<()> {
if output.is_some() {
return Err(meta.error(format!(
"Redact serde for `{type_name}` repeats `{name}`",
)));
}
*output = Some(meta.value()?.parse()?);
Ok(())
}