1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
extern crate proc_macro; extern crate syn; #[macro_use] extern crate proc_macro_error; extern crate convert_case; use convert_case::{Case, Casing as ConvertCasing}; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; use std::str::FromStr; use syn::punctuated::Punctuated; use syn::{ parse_macro_input, token, AttrStyle, Attribute, AttributeArgs, Fields, ItemStruct, Meta, NestedMeta, Path, PathSegment, }; enum Casing { Pascal, Camel, Lower, Upper, Snake, ScreamingSnake, Kebab, ScreamingKebab, } impl FromStr for Casing { type Err = String; fn from_str(s: &str) -> Result<Self, Self::Err> { match s { "PascalCase" => Ok(Self::Pascal), "CamelCase" => Ok(Self::Camel), "LowerCase" => Ok(Self::Lower), "UpperCase" => Ok(Self::Upper), "SnakeCase" => Ok(Self::Snake), "ScreamingSnakeCase" => Ok(Self::ScreamingSnake), "KebabCase" => Ok(Self::Kebab), "ScreamingKebabCase" => Ok(Self::ScreamingKebab), _ => Err(format!( "unknown casing: {} try one of {:?}", s, Casing::all() )), } } } impl ToString for Casing { fn to_string(&self) -> String { let case = match self { Self::Camel => "camelCase", Self::Pascal => "PascalCase", Self::Lower => "lowercase", Self::Upper => "UPPERCASE", Self::Snake => "snake_case", Self::ScreamingSnake => "SCREAMING_SNAKE_CASE", Self::Kebab => "kebab-case", Self::ScreamingKebab => "SCREAMING-KEBAB-CASE", }; case.to_string() } } impl Casing { const fn all() -> &'static [&'static str] { return &[ "PascalCase", "CamelCase", "LowerCase", "UpperCase", "SnakeCase", "ScreamingSnakeCase", "KebabCase", "ScreamingKebabCase", ]; } } #[proc_macro_attribute] pub fn serde_alias(args: TokenStream, input: TokenStream) -> TokenStream { let mut input = parse_macro_input!(input as ItemStruct); let args = parse_macro_input!(args as AttributeArgs); let mut aliases = vec![]; for arg in args { if let NestedMeta::Meta(meta) = arg { if let Meta::Path(path) = meta { let case_ident = path.get_ident().expect("expected casing"); let case = Casing::from_str(&case_ident.to_string()).unwrap_or_else(|e| panic!("{}", e)); aliases.push(case); } } } if let Fields::Named(ref mut named) = input.fields { for field in &mut named.named { let mut punc_attr = Punctuated::new(); punc_attr.push_value(PathSegment { ident: format_ident!("serde"), arguments: Default::default(), }); let mut casings = vec![]; for case in &aliases { let convert_casing = match case { Casing::Pascal => Case::Pascal, Casing::Camel => Case::Camel, Casing::Lower => Case::Lower, Casing::Upper => Case::Upper, Casing::Snake => Case::Snake, Casing::ScreamingSnake => Case::ScreamingSnake, Casing::Kebab => Case::Kebab, Casing::ScreamingKebab => Case::Cobol, }; let converted = field .ident .as_ref() .expect("invalid field") .to_string() .to_case(convert_casing); let f = format!(r#"alias = "{}""#, converted); casings.push(f); } let res: String = casings.join(","); field.attrs.push(Attribute { pound_token: token::Pound::default(), style: AttrStyle::Outer, bracket_token: Default::default(), path: Path { leading_colon: None, segments: punc_attr.clone(), }, tokens: TokenStream2::from_str(&format!("({})", res.as_str())) .unwrap_or_else(|a| abort!(punc_attr, format!("Lex error: {}", a))), }) } let tokens = quote! {#input}; return tokens.into(); } abort!(input, "Tuple structs not supported") }