1use core::fmt;
2
3use heck::*;
4use proc_macro::TokenStream;
5use quote::ToTokens;
6use syn::{parse, parse_macro_input, parse_quote, Attribute, ExprAssign, ItemStruct, LitStr};
7
8#[proc_macro_attribute]
9pub fn serde_infer(opt: TokenStream, i: TokenStream) -> TokenStream {
10 let mut data = parse_macro_input!(i as ItemStruct);
11 let case = parse::<ExprAssign>(opt)
12 .ok()
13 .map(|f| parse::<LitStr>(f.right.to_token_stream().into()).ok())
14 .flatten();
15
16 data.fields.iter_mut().for_each(|f| {
17 f.attrs
18 .extend_from_slice(&cases(f.ident.clone().unwrap().to_string()));
19
20 case.clone().inspect(|c| {
21 f.attrs.extend_from_slice({
22 let out = outgoing(c.value(), f.ident.clone().unwrap().to_string());
23 &[parse_quote!(#[serde(rename = #out)])]
24 })
25 });
26 });
27
28 data.to_token_stream().into()
29}
30
31fn outgoing(case: String, field: String) -> String {
32 match case.as_str() {
33 "kebab" => AsKebabCase(&field).to_string(),
34 "lower_camel" => AsLowerCamelCase(&field).to_string(),
35 "pascal" => AsPascalCase(&field).to_string(),
36 "shouty_kebab" => AsShoutyKebabCase(&field).to_string(),
37 "shouty_snake" => AsShoutySnakeCase(&field).to_string(),
38 "snake" => AsSnakeCase(&field).to_string(),
39 "title" => AsTitleCase(&field).to_string(),
40 "train" => AsTrainCase(&field).to_string(),
41 "upper_camel" => AsUpperCamelCase(&field).to_string(),
42 "uppercase" | "upper" => field.to_uppercase(),
43 "lowercase" | "lower" => field.to_lowercase(),
44 _ => panic!("Invalid casing: {}", case),
45 }
46}
47
48fn cases(field: String) -> Vec<Attribute> {
49 vec![
50 AsKebabCase(&field).to_string(),
51 AsLowerCamelCase(&field).to_string(),
52 AsPascalCase(&field).to_string(),
53 AsShoutyKebabCase(&field).to_string(),
54 AsShoutySnakeCase(&field).to_string(),
55 AsShoutySnekCase(&field).to_string(),
56 AsSnakeCase(&field).to_string(),
57 AsSnekCase(&field).to_string(),
58 AsTitleCase(&field).to_string(),
59 AsTrainCase(&field).to_string(),
60 AsUpperCamelCase(&field).to_string(),
61 field.to_uppercase(),
62 field.to_lowercase(),
63 ]
64 .iter()
65 .map(|t| {
66 let field = t.to_string();
67 parse_quote!(#[serde(alias = #field)])
68 })
69 .collect::<Vec<Attribute>>()
70}