]> git.proxmox.com Git - proxmox.git/blame - proxmox-api-macro/src/serde.rs
api-macro: add VariantAttrib
[proxmox.git] / proxmox-api-macro / src / serde.rs
CommitLineData
799c993d
WB
1//! Serde support module.
2//!
3//! The `#![api]` macro needs to be able to cope with some `#[serde(...)]` attributes such as
4//! `rename` and `rename_all`.
5
6use std::convert::TryFrom;
7
b232b580
WB
8use syn::punctuated::Punctuated;
9use syn::Token;
799c993d
WB
10
11/// Serde name types.
e461be1c 12#[allow(clippy::enum_variant_names)]
799c993d
WB
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum RenameAll {
15 LowerCase,
16 UpperCase,
17 PascalCase,
18 CamelCase,
19 SnakeCase,
20 ScreamingSnakeCase,
21 KebabCase,
22 ScreamingKebabCase,
23}
24
25impl TryFrom<&syn::Lit> for RenameAll {
26 type Error = syn::Error;
27 fn try_from(s: &syn::Lit) -> Result<Self, syn::Error> {
28 match s {
29 syn::Lit::Str(s) => Self::try_from(s),
30 _ => bail!(s => "expected rename type as string"),
31 }
32 }
33}
34
35impl TryFrom<&syn::LitStr> for RenameAll {
36 type Error = syn::Error;
37 fn try_from(s: &syn::LitStr) -> Result<Self, syn::Error> {
38 let s = s.value();
39 if s == "lowercase" {
40 Ok(RenameAll::LowerCase)
41 } else if s == "UPPERCASE" {
42 Ok(RenameAll::UpperCase)
43 } else if s == "PascalCase" {
44 Ok(RenameAll::PascalCase)
45 } else if s == "camelCase" {
46 Ok(RenameAll::CamelCase)
47 } else if s == "snake_case" {
48 Ok(RenameAll::SnakeCase)
49 } else if s == "SCREAMING_SNAKE_CASE" {
50 Ok(RenameAll::ScreamingSnakeCase)
51 } else if s == "kebab-case" {
52 Ok(RenameAll::KebabCase)
53 } else if s == "SCREAMING-KEBAB-CASE" {
54 Ok(RenameAll::ScreamingKebabCase)
55 } else {
56 bail!(&s => "unhandled `rename_all` type: {}", s.to_string())
57 }
58 }
59}
60
61impl RenameAll {
62 /// Like in serde, we assume that fields are in `snake_case` and enum variants are in
63 /// `PascalCase`, so we only perform the changes required for fields here!
64 pub fn apply_to_field(&self, s: &str) -> String {
65 match self {
66 RenameAll::SnakeCase => s.to_owned(), // this is our source type
67 RenameAll::ScreamingSnakeCase => s.to_uppercase(), // capitalized source type
68 RenameAll::LowerCase => s.to_lowercase(),
69 RenameAll::UpperCase => s.to_uppercase(),
70 RenameAll::PascalCase => {
71 // Strip underscores and capitalize instead:
72 let mut out = String::new();
73 let mut cap = true;
74 for c in s.chars() {
75 if c == '_' {
76 cap = true;
77 } else if cap {
78 cap = false;
79 out.push(c.to_ascii_uppercase());
80 } else {
81 out.push(c.to_ascii_lowercase());
82 }
83 }
84 out
85 }
86 RenameAll::CamelCase => {
87 let s = RenameAll::PascalCase.apply_to_field(s);
88 s[..1].to_ascii_lowercase() + &s[1..]
89 }
90 RenameAll::KebabCase => s.replace('_', "-"),
91 RenameAll::ScreamingKebabCase => s.replace('_', "-").to_ascii_uppercase(),
92 }
93 }
94
95 /// Like in serde, we assume that fields are in `snake_case` and enum variants are in
96 /// `PascalCase`, so we only perform the changes required for enum variants here!
97 pub fn apply_to_variant(&self, s: &str) -> String {
98 match self {
99 RenameAll::PascalCase => s.to_owned(), // this is our source type
100 RenameAll::CamelCase => s[..1].to_ascii_lowercase() + &s[1..],
101 RenameAll::LowerCase => s.to_lowercase(),
102 RenameAll::UpperCase => s.to_uppercase(),
103 RenameAll::SnakeCase => {
104 // Relatively simple: all lower-case, and new words get split by underscores:
105 let mut out = String::new();
106 for (i, c) in s.char_indices() {
107 if i > 0 && c.is_uppercase() {
108 out.push('_');
109 }
110 out.push(c.to_ascii_lowercase());
111 }
112 out
113 }
114 RenameAll::KebabCase => RenameAll::SnakeCase.apply_to_variant(s).replace('_', "-"),
115 RenameAll::ScreamingSnakeCase => RenameAll::SnakeCase
116 .apply_to_variant(s)
117 .to_ascii_uppercase(),
118 RenameAll::ScreamingKebabCase => RenameAll::KebabCase
119 .apply_to_variant(s)
120 .to_ascii_uppercase(),
121 }
122 }
123}
124
125/// `serde` container attributes we support
126#[derive(Default)]
127pub struct ContainerAttrib {
dd5cd3f3 128 pub rename_all: Option<RenameAll>,
799c993d
WB
129}
130
131impl TryFrom<&[syn::Attribute]> for ContainerAttrib {
132 type Error = syn::Error;
133
134 fn try_from(attributes: &[syn::Attribute]) -> Result<Self, syn::Error> {
135 let mut this: Self = Default::default();
136
137 for attrib in attributes {
b232b580
WB
138 let list = match &attrib.meta {
139 syn::Meta::List(list) if list.path.is_ident("serde") => list,
140 _ => continue,
141 };
142
143 let args =
144 list.parse_args_with(Punctuated::<syn::Meta, Token![,]>::parse_terminated)?;
799c993d 145
b232b580
WB
146 for arg in args {
147 if let syn::Meta::NameValue(var) = arg {
148 if !var.path.is_ident("rename_all") {
149 continue;
150 }
151 match &var.value {
152 syn::Expr::Lit(lit) => {
153 let rename_all = RenameAll::try_from(&lit.lit)?;
154 if this.rename_all.is_some() && this.rename_all != Some(rename_all) {
155 error!(var.value => "multiple conflicting 'rename_all' attributes");
156 }
157 this.rename_all = Some(rename_all);
799c993d 158 }
b232b580 159 _ => error!(var.value => "invalid 'rename_all' value type"),
799c993d
WB
160 }
161 }
162 }
163 }
164
165 Ok(this)
166 }
167}
168
89b29415 169/// `serde` field attributes we support
799c993d 170#[derive(Default)]
fa9a50a0 171pub struct FieldAttrib {
12674a37 172 pub rename: Option<syn::LitStr>,
fbc9be47 173 pub flatten: bool,
799c993d
WB
174}
175
fa9a50a0 176impl FieldAttrib {
8ebcd68a 177 pub fn parse_attribute(&mut self, attrib: &syn::Attribute) -> Result<(), syn::Error> {
b232b580
WB
178 let list = match &attrib.meta {
179 syn::Meta::List(list) if list.path.is_ident("serde") => list,
180 _ => return Ok(()),
181 };
182
183 let args = list.parse_args_with(Punctuated::<syn::Meta, Token![,]>::parse_terminated)?;
184
185 for arg in args {
186 let path = arg.path();
187 if path.is_ident("rename") {
188 match &arg.require_name_value()?.value {
189 syn::Expr::Lit(syn::ExprLit {
190 lit: syn::Lit::Str(rename),
191 ..
192 }) => {
193 if self.rename.is_some() && self.rename.as_ref() != Some(rename) {
194 error!(&rename => "multiple conflicting 'rename' attributes");
799c993d 195 }
b232b580 196 self.rename = Some(rename.clone());
799c993d 197 }
b232b580 198 value => error!(value => "'rename' value must be a string literal"),
799c993d 199 }
b232b580
WB
200 } else if path.is_ident("flatten") {
201 arg.require_path_only()?;
202 self.flatten = true;
799c993d
WB
203 }
204 }
205
8ebcd68a
WB
206 Ok(())
207 }
208}
209
fa9a50a0 210impl TryFrom<&[syn::Attribute]> for FieldAttrib {
8ebcd68a
WB
211 type Error = syn::Error;
212
213 fn try_from(attributes: &[syn::Attribute]) -> Result<Self, syn::Error> {
214 let mut this: Self = Default::default();
215
216 for attrib in attributes {
217 this.parse_attribute(attrib)?;
218 }
219
799c993d
WB
220 Ok(this)
221 }
222}
89b29415
WB
223
224/// `serde` variant attributes we support
225#[derive(Default)]
226pub struct VariantAttrib {
227 pub rename: Option<syn::LitStr>,
228}
229
230impl VariantAttrib {
231 pub fn parse_attribute(&mut self, attrib: &syn::Attribute) -> Result<(), syn::Error> {
232 let list = match &attrib.meta {
233 syn::Meta::List(list) if list.path.is_ident("serde") => list,
234 _ => return Ok(()),
235 };
236
237 let args = list.parse_args_with(Punctuated::<syn::Meta, Token![,]>::parse_terminated)?;
238
239 for arg in args {
240 let path = arg.path();
241 if path.is_ident("rename") {
242 match &arg.require_name_value()?.value {
243 syn::Expr::Lit(syn::ExprLit {
244 lit: syn::Lit::Str(rename),
245 ..
246 }) => {
247 if self.rename.is_some() && self.rename.as_ref() != Some(rename) {
248 error!(&rename => "multiple conflicting 'rename' attributes");
249 }
250 self.rename = Some(rename.clone());
251 }
252 value => error!(value => "'rename' value must be a string literal"),
253 }
254 }
255 }
256
257 Ok(())
258 }
259}
260
261impl TryFrom<&[syn::Attribute]> for VariantAttrib {
262 type Error = syn::Error;
263
264 fn try_from(attributes: &[syn::Attribute]) -> Result<Self, syn::Error> {
265 let mut this: Self = Default::default();
266
267 for attrib in attributes {
268 this.parse_attribute(attrib)?;
269 }
270
271 Ok(this)
272 }
273}