use syn::Data;
use syn::DeriveInput;
use syn::Error;
use syn::Fields;
use syn::LitStr;
use syn::Path;
use syn::Result;
use syn::spanned::Spanned;
use super::SerdeContainerAttributeParser;
use super::SerdeEnumRepresentation;
use super::SerdeRenameRule;
#[must_use]
pub(crate) struct SerdeContainerAttributes {
name: String,
rename_all: Option<SerdeRenameRule>,
rename_all_fields: Option<SerdeRenameRule>,
representation: SerdeEnumRepresentation,
transparent: bool,
}
impl SerdeContainerAttributes {
#[inline(always)]
pub(crate) fn parse(input: &DeriveInput, enabled: bool) -> Result<Self> {
SerdeContainerAttributeParser::parse(input, enabled)
}
#[allow(clippy::too_many_arguments)]
pub(super) fn from_parts(
input: &DeriveInput,
name: Option<String>,
rename_all: Option<SerdeRenameRule>,
rename_all_fields: Option<SerdeRenameRule>,
tag: Option<LitStr>,
content: Option<LitStr>,
untagged: Option<Path>,
transparent: bool,
) -> Result<Self> {
if transparent {
let valid = matches!(&input.data, Data::Struct(data) if match &data.fields {
Fields::Named(fields) => fields.named.len() == 1,
Fields::Unnamed(fields) => fields.unnamed.len() == 1,
Fields::Unit => false,
});
if !valid {
return Err(Error::new_spanned(
input,
format!(
"Redact serde for `{}` requires `transparent` on a single-field struct",
input.ident
),
));
}
}
let representation = representation(input, tag, content, untagged)?;
Ok(Self {
name: name.unwrap_or_else(|| input.ident.to_string()),
rename_all,
rename_all_fields,
representation,
transparent,
})
}
#[must_use]
#[inline(always)]
pub(crate) fn name(&self) -> &str {
&self.name
}
#[must_use]
#[inline]
pub(crate) fn rename_struct_field(&self, field_name: &str) -> String {
self.rename_all
.as_ref()
.map_or_else(|| field_name.to_owned(), |rule| rule.apply_to_field(field_name))
}
#[must_use]
#[inline]
pub(crate) fn rename_variant(&self, variant_name: &str) -> String {
self.rename_all
.as_ref()
.map_or_else(|| variant_name.to_owned(), |rule| rule.apply_to_variant(variant_name))
}
#[must_use]
#[inline]
pub(crate) fn rename_variant_field(&self, field_name: &str) -> String {
self.rename_all_fields
.as_ref()
.map_or_else(|| field_name.to_owned(), |rule| rule.apply_to_field(field_name))
}
#[must_use = "the selected representation determines generated serialization"]
#[inline(always)]
pub(crate) const fn representation(&self) -> &SerdeEnumRepresentation {
&self.representation
}
#[must_use]
#[inline(always)]
pub(crate) const fn transparent(&self) -> bool {
self.transparent
}
}
fn representation(
input: &DeriveInput,
tag: Option<LitStr>,
content: Option<LitStr>,
untagged: Option<Path>,
) -> Result<SerdeEnumRepresentation> {
if let Some(path) = untagged {
if tag.is_some() || content.is_some() {
return Err(Error::new(
path.span(),
format!(
"Redact serde for `{}` cannot combine `untagged` with `tag` or `content`",
input.ident,
),
));
}
return Ok(SerdeEnumRepresentation::Untagged);
}
match (tag, content) {
(None, None) => Ok(SerdeEnumRepresentation::ExternallyTagged),
(None, Some(content)) => Err(Error::new_spanned(
content,
format!(
"Redact serde for `{}` requires `tag` when `content` is present",
input.ident,
),
)),
(Some(tag), None) => Ok(SerdeEnumRepresentation::InternallyTagged { tag: tag.value() }),
(Some(tag), Some(content)) => {
if tag.value() == content.value() {
return Err(Error::new_spanned(
content,
format!(
"Redact serde for `{}` requires distinct `tag` and `content` names",
input.ident,
),
));
}
Ok(SerdeEnumRepresentation::AdjacentlyTagged {
tag: tag.value(),
content: content.value(),
})
}
}
}