use syn::Attribute;
use syn::Data;
use syn::DeriveInput;
use syn::Error;
use syn::Ident;
use syn::LitStr;
use syn::Meta;
use syn::Path;
use syn::Result;
use syn::Token;
use syn::meta::ParseNestedMeta;
use syn::token::Paren;
use super::parse_serialize_name;
use crate::attributes::SerdeContainerAttributes;
use crate::attributes::SerdeRenameRule;
pub(crate) struct SerdeContainerAttributeParser<'input> {
input: &'input DeriveInput,
name: Option<String>,
name_seen: bool,
rename_all: Option<SerdeRenameRule>,
rename_all_seen: bool,
rename_all_fields: Option<SerdeRenameRule>,
rename_all_fields_seen: bool,
tag: Option<LitStr>,
content: Option<LitStr>,
untagged: Option<Path>,
transparent: bool,
}
impl<'input> SerdeContainerAttributeParser<'input> {
#[inline]
pub(crate) fn parse(input: &'input DeriveInput, enabled: bool) -> 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,
name_seen: false,
rename_all: None,
rename_all_seen: false,
rename_all_fields: None,
rename_all_fields_seen: false,
tag: None,
content: None,
untagged: None,
transparent: false,
}
}
fn parse_attributes(&mut self, enabled: bool) -> 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) -> Result<()> {
let Meta::List(_) = &attribute.meta else {
return Err(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: ParseNestedMeta<'_>) -> Result<()> {
if meta.path.is_ident("rename") {
parse_name(&meta, &self.input.ident, "rename", &mut self.name_seen, &mut self.name)
} else if meta.path.is_ident("rename_all") {
parse_rule(
&meta,
&self.input.ident,
"rename_all",
&mut self.rename_all_seen,
&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 if meta.path.is_ident("crate") {
let _: LitStr = meta.value()?.parse()?;
Ok(())
} else if meta.path.is_ident("transparent") {
if meta.input.peek(Token![=]) || meta.input.peek(Paren) {
return Err(meta.error(format!(
"Redact serde for `{}` requires bare `transparent`",
self.input.ident,
)));
}
if self.transparent {
return Err(meta.error(format!("Redact serde for `{}` repeats `transparent`", self.input.ident,)));
}
self.transparent = true;
Ok(())
} else if meta.path.is_ident("default") {
parse_deserialize_only_default(&meta, &self.input.ident)
} else if meta.path.is_ident("deny_unknown_fields") {
require_bare_deserialize_only(&meta, &self.input.ident, "deny_unknown_fields")
} else {
Err(self.unsupported_control_error(meta))
}
}
fn parse_rename_all_fields(&mut self, meta: ParseNestedMeta<'_>) -> Result<()> {
require_enum(&meta, self.input, "rename_all_fields")?;
parse_rule(
&meta,
&self.input.ident,
"rename_all_fields",
&mut self.rename_all_fields_seen,
&mut self.rename_all_fields,
)
}
fn parse_tag(&mut self, meta: ParseNestedMeta<'_>) -> Result<()> {
require_enum(&meta, self.input, "tag")?;
parse_literal(&meta, &self.input.ident, "tag", &mut self.tag)
}
fn parse_content(&mut self, meta: ParseNestedMeta<'_>) -> Result<()> {
require_enum(&meta, self.input, "content")?;
parse_literal(&meta, &self.input.ident, "content", &mut self.content)
}
fn parse_untagged(&mut self, meta: ParseNestedMeta<'_>) -> Result<()> {
require_enum(&meta, self.input, "untagged")?;
if meta.input.peek(Token![=]) || meta.input.peek(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(())
}
#[inline]
fn finish(self) -> Result<SerdeContainerAttributes> {
SerdeContainerAttributes::from_parts(
self.input,
self.name,
self.rename_all,
self.rename_all_fields,
self.tag,
self.content,
self.untagged,
self.transparent,
)
}
#[must_use]
fn unsupported_control_error(&self, meta: ParseNestedMeta<'_>) -> 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`, `untagged`, `transparent`, `crate`, or deserialization-only controls such as `default` and `deny_unknown_fields`",
self.input.ident,
))
}
}
fn parse_deserialize_only_default(meta: &ParseNestedMeta<'_>, type_name: &Ident) -> Result<()> {
if meta.input.peek(Token![=]) {
let _: LitStr = meta.value()?.parse()?;
return Ok(());
}
if meta.input.peek(Paren) {
return Err(meta.error(format!(
"Redact serde for `{type_name}` requires bare `default` or `default = \"...\"`"
)));
}
Ok(())
}
fn require_bare_deserialize_only(meta: &ParseNestedMeta<'_>, type_name: &Ident, name: &str) -> Result<()> {
if meta.input.peek(Token![=]) || meta.input.peek(Paren) {
return Err(meta.error(format!("Redact serde for `{type_name}` requires bare `{name}`")));
}
Ok(())
}
fn require_enum(meta: &ParseNestedMeta<'_>, input: &DeriveInput, name: &str) -> 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: &ParseNestedMeta<'_>,
type_name: &Ident,
name: &str,
seen: &mut bool,
output: &mut Option<String>,
) -> Result<()> {
if *seen {
return Err(meta.error(format!("Redact serde for `{type_name}` repeats `{name}`",)));
}
*output = parse_serialize_name(meta, name)?.map(|literal| literal.value());
*seen = true;
Ok(())
}
fn parse_rule(
meta: &ParseNestedMeta<'_>,
type_name: &Ident,
name: &str,
seen: &mut bool,
output: &mut Option<SerdeRenameRule>,
) -> Result<()> {
if *seen {
return Err(meta.error(format!("Redact serde for `{type_name}` repeats `{name}`",)));
}
*output = parse_serialize_name(meta, name)?
.map(|literal| SerdeRenameRule::parse(&literal))
.transpose()?;
*seen = true;
Ok(())
}
fn parse_literal(meta: &ParseNestedMeta<'_>, type_name: &Ident, name: &str, output: &mut Option<LitStr>) -> Result<()> {
if output.is_some() {
return Err(meta.error(format!("Redact serde for `{type_name}` repeats `{name}`",)));
}
*output = Some(meta.value()?.parse()?);
Ok(())
}