1use std::collections::HashMap;
2
3pub use field::*;
4use proc_macro2::TokenTree;
5use quote::quote;
6pub use r#enum::*;
7pub use r#struct::*;
8use syn::{
9 parenthesized,
10 parse::{Parse, ParseStream},
11 punctuated::Punctuated,
12 Error, Expr, Ident, Lit, Path, Result, Token, WherePredicate,
13};
14pub use variant::*;
15mod r#enum;
16mod field;
17mod r#struct;
18mod variant;
19
20#[derive(Copy, Clone, Debug)]
21pub enum Inflection {
22 Lower,
23 Upper,
24 Camel,
25 Snake,
26 Pascal,
27 ScreamingSnake,
28 Kebab,
29 ScreamingKebab,
30}
31
32pub(super) trait Attr: Default {
33 type Item;
34
35 fn merge(self, other: Self) -> Self;
36 fn assert_validity(&self, item: &Self::Item) -> Result<()>;
37}
38
39pub(super) trait ContainerAttr: Attr {
40 fn crate_rename(&self) -> Path;
41}
42
43#[derive(Default)]
44pub(super) struct Serde<T>(pub T)
45where
46 T: Attr;
47
48impl<T> Serde<T>
49where
50 T: Attr,
51{
52 pub fn merge(self, other: Self) -> Self {
53 Self(self.0.merge(other.0))
54 }
55}
56
57impl Inflection {
58 pub fn apply(self, string: &str) -> String {
59 match self {
60 Inflection::Lower => string.to_lowercase(),
61 Inflection::Upper => string.to_uppercase(),
62 Inflection::Camel => {
63 let pascal = Inflection::apply(Inflection::Pascal, string);
64 pascal[..1].to_ascii_lowercase() + &pascal[1..]
65 }
66 Inflection::Snake => {
67 let mut s = String::new();
68
69 for (i, ch) in string.char_indices() {
70 if ch.is_uppercase() && i != 0 {
71 s.push('_');
72 }
73 s.push(ch.to_ascii_lowercase());
74 }
75
76 s
77 }
78 Inflection::Pascal => {
79 let mut s = String::with_capacity(string.len());
80
81 let mut capitalize = true;
82 for c in string.chars() {
83 if c == '_' {
84 capitalize = true;
85 continue;
86 } else if capitalize {
87 s.push(c.to_ascii_uppercase());
88 capitalize = false;
89 } else {
90 s.push(c)
91 }
92 }
93
94 s
95 }
96 Inflection::ScreamingSnake => Self::Snake.apply(string).to_ascii_uppercase(),
97 Inflection::Kebab => Self::Snake.apply(string).replace('_', "-"),
98 Inflection::ScreamingKebab => Self::Kebab.apply(string).to_ascii_uppercase(),
99 }
100 }
101}
102
103fn skip_until_next_comma(input: ParseStream) -> proc_macro2::TokenStream {
104 input
105 .step(|cursor| {
106 let mut stuff = quote!();
107 let mut rest = *cursor;
108
109 if let Some((TokenTree::Punct(ref punct), _)) = cursor.token_tree() {
110 if punct.as_char() == ',' {
111 return Ok((stuff, rest));
112 }
113 }
114
115 while let Some((tt, next)) = rest.token_tree() {
116 if let Some((TokenTree::Punct(punct), _)) = next.token_tree() {
117 if punct.as_char() == ',' {
118 return Ok((stuff, next));
119 }
120 }
121
122 rest = next;
123 stuff = quote!(#stuff #tt)
124 }
125
126 Ok((stuff, rest))
127 })
128 .unwrap()
129}
130
131fn parse_assign_expr(input: ParseStream) -> Result<Expr> {
132 input.parse::<Token![=]>()?;
133 Expr::parse(input)
134}
135
136fn parse_assign_str(input: ParseStream) -> Result<String> {
137 input.parse::<Token![=]>()?;
138 match Lit::parse(input)? {
139 Lit::Str(string) => Ok(string.value()),
140 other => Err(Error::new(other.span(), "expected string")),
141 }
142}
143
144fn parse_optional_assign_str(input: ParseStream) -> Result<Option<String>> {
145 if input.peek(Token![=]) {
146 Some(parse_assign_str(input))
147 } else {
148 None
149 }
150 .transpose()
151}
152
153fn parse_concrete(input: ParseStream) -> Result<HashMap<syn::Ident, syn::Type>> {
154 struct Concrete {
155 ident: syn::Ident,
156 _equal_token: Token![=],
157 ty: syn::Type,
158 }
159
160 impl Parse for Concrete {
161 fn parse(input: ParseStream) -> Result<Self> {
162 Ok(Self {
163 ident: input.parse()?,
164 _equal_token: input.parse()?,
165 ty: input.parse()?,
166 })
167 }
168 }
169
170 let content;
171 parenthesized!(content in input);
172
173 Ok(
174 Punctuated::<Concrete, Token![,]>::parse_terminated(&content)?
175 .into_iter()
176 .map(|concrete| (concrete.ident, concrete.ty))
177 .collect(),
178 )
179}
180
181fn parse_assign_inflection(input: ParseStream) -> Result<Inflection> {
182 input.parse::<Token![=]>()?;
183
184 match Lit::parse(input)? {
185 Lit::Str(string) => Ok(match &*string.value() {
186 "lowercase" => Inflection::Lower,
187 "UPPERCASE" => Inflection::Upper,
188 "camelCase" => Inflection::Camel,
189 "snake_case" => Inflection::Snake,
190 "PascalCase" => Inflection::Pascal,
191 "SCREAMING_SNAKE_CASE" => Inflection::ScreamingSnake,
192 "kebab-case" => Inflection::Kebab,
193 "SCREAMING-KEBAB-CASE" => Inflection::ScreamingKebab,
194 other => {
195 syn_err!(
196 string.span();
197 r#"Value "{other}" is not valid for "rename_all". Accepted values are: "lowercase", "UPPERCASE", "camelCase", "snake_case", "PascalCase", "SCREAMING_SNAKE_CASE", "kebab-case" and "SCREAMING-KEBAB-CASE""#
198 )
199 }
200 }),
201 other => Err(Error::new(other.span(), "expected string")),
202 }
203}
204
205fn parse_assign_from_str<T>(input: ParseStream) -> Result<T>
206where
207 T: Parse,
208{
209 input.parse::<Token![=]>()?;
210 match Lit::parse(input)? {
211 Lit::Str(string) => string.parse(),
212 other => Err(Error::new(other.span(), "expected string")),
213 }
214}
215
216fn parse_bound(input: ParseStream) -> Result<Vec<WherePredicate>> {
217 input.parse::<Token![=]>()?;
218 match Lit::parse(input)? {
219 Lit::Str(string) => {
220 let parser = Punctuated::<WherePredicate, Token![,]>::parse_terminated;
221
222 Ok(string.parse_with(parser)?.into_iter().collect())
223 }
224 other => Err(Error::new(other.span(), "expected string")),
225 }
226}
227
228fn parse_repr(input: ParseStream) -> Result<Repr> {
229 let content;
230 parenthesized!(content in input);
231
232 content.parse::<Token![enum]>()?;
233
234 if content.is_empty() {
235 return Ok(Repr::Int);
236 }
237
238 content.parse::<Token![=]>()?;
239
240 let span = content.span();
241 let ident = content.parse::<Ident>()?;
242 match ident.to_string().as_str() {
243 "name" => Ok(Repr::Name),
244 _ => syn_err!(span; "expected `name`"),
245 }
246}