use syn::{Attribute, Lit, Meta, MetaNameValue};
#[derive(Debug, Default, Clone)]
pub(crate) struct SerdeEnumAttrs {
pub tag: Option<String>,
pub content: Option<String>,
pub untagged: bool,
pub rename: Option<String>,
pub rename_all: Option<String>,
}
impl SerdeEnumAttrs {
pub(crate) fn tagging_strategy(&self) -> TaggingStrategy {
if self.untagged {
TaggingStrategy::Untagged
} else if let Some(ref tag) = self.tag {
if let Some(ref content) = self.content {
TaggingStrategy::Adjacent {
tag: tag.clone(),
content: content.clone(),
}
} else {
TaggingStrategy::Internal { tag: tag.clone() }
}
} else {
TaggingStrategy::External
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum TaggingStrategy {
External,
Internal { tag: String },
Adjacent { tag: String, content: String },
Untagged,
}
pub(crate) fn extract_serde_enum_attrs(attrs: &[Attribute]) -> SerdeEnumAttrs {
let mut result = SerdeEnumAttrs::default();
for attr in attrs {
if !attr.path().is_ident("serde") {
continue;
}
let Ok(meta_list) = attr.meta.require_list() else {
continue;
};
let Ok(nested_metas) = meta_list
.parse_args_with(syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated)
else {
continue;
};
for nested_meta in nested_metas {
match nested_meta {
Meta::Path(path) => {
if path.is_ident("untagged") {
result.untagged = true;
}
}
Meta::NameValue(MetaNameValue {
path,
value:
syn::Expr::Lit(syn::ExprLit {
lit: Lit::Str(lit_str),
..
}),
..
}) => {
let value = lit_str.value();
if path.is_ident("tag") {
result.tag = Some(value);
} else if path.is_ident("content") {
result.content = Some(value);
} else if path.is_ident("rename") {
result.rename = Some(value);
} else if path.is_ident("rename_all") {
result.rename_all = Some(value);
}
}
_ => {}
}
}
}
result
}
pub(crate) fn extract_serde_rename_all(attrs: &[Attribute]) -> Option<String> {
for attr in attrs {
if !attr.path().is_ident("serde") {
continue;
}
let Ok(meta_list) = attr.meta.require_list() else {
continue;
};
let Ok(nested_metas) = meta_list
.parse_args_with(syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated)
else {
continue;
};
for nested_meta in nested_metas {
if let Meta::NameValue(MetaNameValue {
path,
value: syn::Expr::Lit(syn::ExprLit {
lit: Lit::Str(lit_str),
..
}),
..
}) = nested_meta
&& path.is_ident("rename_all")
{
return Some(lit_str.value());
}
}
}
None
}
#[derive(Debug, Default, Clone)]
pub(crate) struct SerdeVariantAttrs {
pub rename: Option<String>,
pub skip: bool,
pub aliases: Vec<String>,
}
pub(crate) fn extract_serde_variant_attrs(attrs: &[Attribute]) -> SerdeVariantAttrs {
let mut result = SerdeVariantAttrs::default();
for attr in attrs {
if !attr.path().is_ident("serde") {
continue;
}
let Ok(meta_list) = attr.meta.require_list() else {
continue;
};
let Ok(nested_metas) = meta_list
.parse_args_with(syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated)
else {
continue;
};
for nested_meta in nested_metas {
match nested_meta {
Meta::Path(path) => {
if path.is_ident("skip") {
result.skip = true;
}
}
Meta::NameValue(MetaNameValue {
path,
value:
syn::Expr::Lit(syn::ExprLit {
lit: Lit::Str(lit_str),
..
}),
..
}) => {
let value = lit_str.value();
if path.is_ident("rename") {
result.rename = Some(value);
} else if path.is_ident("alias") {
result.aliases.push(value);
}
}
_ => {}
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_is_external() {
let attrs = SerdeEnumAttrs::default();
assert_eq!(attrs.tagging_strategy(), TaggingStrategy::External);
}
#[test]
fn test_untagged_strategy() {
let attrs = SerdeEnumAttrs {
untagged: true,
..Default::default()
};
assert_eq!(attrs.tagging_strategy(), TaggingStrategy::Untagged);
}
#[test]
fn test_internal_tagging() {
let attrs = SerdeEnumAttrs {
tag: Some("type".to_string()),
..Default::default()
};
assert_eq!(
attrs.tagging_strategy(),
TaggingStrategy::Internal {
tag: "type".to_string()
}
);
}
#[test]
fn test_adjacent_tagging() {
let attrs = SerdeEnumAttrs {
tag: Some("t".to_string()),
content: Some("c".to_string()),
..Default::default()
};
assert_eq!(
attrs.tagging_strategy(),
TaggingStrategy::Adjacent {
tag: "t".to_string(),
content: "c".to_string()
}
);
}
}