use syn::Attribute;
use syn::meta::ParseNestedMeta;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Rule {
Lower,
Upper,
Pascal,
Camel,
Snake,
ScreamingSnake,
Kebab,
ScreamingKebab,
}
impl Rule {
fn parse(name: &str) -> Option<Self> {
Some(match name {
"lowercase" => Self::Lower,
"UPPERCASE" => Self::Upper,
"PascalCase" => Self::Pascal,
"camelCase" => Self::Camel,
"snake_case" => Self::Snake,
"SCREAMING_SNAKE_CASE" => Self::ScreamingSnake,
"kebab-case" => Self::Kebab,
"SCREAMING-KEBAB-CASE" => Self::ScreamingKebab,
_ => return None,
})
}
fn variant(self, name: &str) -> String {
match self {
Self::Pascal => name.to_owned(),
Self::Lower => name.to_ascii_lowercase(),
Self::Upper => name.to_ascii_uppercase(),
Self::Camel => lower_first(name),
Self::Snake => {
let mut snake = String::new();
for (i, c) in name.char_indices() {
if i > 0 && c.is_uppercase() {
snake.push('_');
}
snake.push(c.to_ascii_lowercase());
}
snake
}
Self::ScreamingSnake => Self::Snake.variant(name).to_ascii_uppercase(),
Self::Kebab => Self::Snake.variant(name).replace('_', "-"),
Self::ScreamingKebab => Self::ScreamingSnake.variant(name).replace('_', "-"),
}
}
fn field(self, name: &str) -> String {
match self {
Self::Lower | Self::Snake => name.to_owned(),
Self::Upper | Self::ScreamingSnake => name.to_ascii_uppercase(),
Self::Pascal => {
let mut pascal = String::new();
let mut capitalize = true;
for c in name.chars() {
if c == '_' {
capitalize = true;
} else if capitalize {
pascal.push(c.to_ascii_uppercase());
capitalize = false;
} else {
pascal.push(c);
}
}
pascal
}
Self::Camel => lower_first(&Self::Pascal.field(name)),
Self::Kebab => name.replace('_', "-"),
Self::ScreamingKebab => name.to_ascii_uppercase().replace('_', "-"),
}
}
}
fn lower_first(name: &str) -> String {
let mut chars = name.chars();
chars.next().map_or_else(String::new, |first| {
first.to_lowercase().chain(chars).collect()
})
}
#[derive(Debug, Default, Clone)]
pub(crate) struct Container {
rename_all: Vec<Rule>,
rename_all_fields: Vec<Rule>,
}
impl Container {
pub(crate) fn new(attrs: &[Attribute]) -> Self {
let mut container = Self::default();
each_serde(attrs, |key, values| match key {
"rename_all" => container
.rename_all
.extend(values.iter().filter_map(|v| Rule::parse(v))),
"rename_all_fields" => container
.rename_all_fields
.extend(values.iter().filter_map(|v| Rule::parse(v))),
_ => {}
});
container
}
pub(crate) fn variant_fields(&self, attrs: &[Attribute]) -> Self {
let own = Self::new(attrs);
Self {
rename_all: if own.rename_all.is_empty() {
self.rename_all_fields.clone()
} else {
own.rename_all
},
rename_all_fields: Vec::new(),
}
}
pub(crate) fn field(&self, name: &str, attrs: &[Attribute]) -> Vec<String> {
self.spellings(name, attrs, Rule::field)
}
pub(crate) fn variant(&self, name: &str, attrs: &[Attribute]) -> Vec<String> {
self.spellings(name, attrs, Rule::variant)
}
fn spellings(
&self,
name: &str,
attrs: &[Attribute],
apply: fn(Rule, &str) -> String,
) -> Vec<String> {
let mut renamed = Vec::new();
let mut aliases = Vec::new();
let mut skipped = false;
each_serde(attrs, |key, values| match key {
"rename" => renamed.extend(values.iter().cloned()),
"alias" => aliases.extend(values.iter().cloned()),
"skip" => skipped = true,
_ => {}
});
if skipped {
return Vec::new();
}
let mut out: Vec<String> = if renamed.is_empty() {
self.rename_all
.iter()
.map(|&rule| apply(rule, name))
.collect()
} else {
renamed
};
out.extend(aliases);
out.retain(|spelling| spelling != name);
out.sort();
out.dedup();
out
}
}
fn each_serde(attrs: &[Attribute], mut visit: impl FnMut(&str, &[String])) {
for attr in attrs.iter().filter(|a| a.path().is_ident("serde")) {
let _ = attr.parse_nested_meta(|meta| {
let key = meta
.path
.get_ident()
.map(ToString::to_string)
.unwrap_or_default();
let values = values(&meta)?;
visit(&key, &values);
Ok(())
});
}
}
fn values(meta: &ParseNestedMeta<'_>) -> syn::Result<Vec<String>> {
if meta.input.peek(syn::Token![=]) {
let expr: syn::Expr = meta.value()?.parse()?;
return Ok(match expr {
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}) => vec![s.value()],
_ => Vec::new(),
});
}
let mut out = Vec::new();
if meta.input.peek(syn::token::Paren) {
meta.parse_nested_meta(|inner| {
out.extend(values(&inner)?);
Ok(())
})?;
}
Ok(out)
}