]> git.proxmox.com Git - proxmox.git/blob - proxmox-api-macro/src/serde.rs
api-macro: rename SerdeAttrib to FieldAttrib
[proxmox.git] / proxmox-api-macro / src / serde.rs
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
6 use std::convert::TryFrom;
7
8 use syn::punctuated::Punctuated;
9 use syn::Token;
10
11 /// Serde name types.
12 #[allow(clippy::enum_variant_names)]
13 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
14 pub enum RenameAll {
15 LowerCase,
16 UpperCase,
17 PascalCase,
18 CamelCase,
19 SnakeCase,
20 ScreamingSnakeCase,
21 KebabCase,
22 ScreamingKebabCase,
23 }
24
25 impl 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
35 impl 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
61 impl 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)]
127 pub struct ContainerAttrib {
128 pub rename_all: Option<RenameAll>,
129 }
130
131 impl 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 {
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)?;
145
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);
158 }
159 _ => error!(var.value => "invalid 'rename_all' value type"),
160 }
161 }
162 }
163 }
164
165 Ok(this)
166 }
167 }
168
169 /// `serde` field/variant attributes we support
170 #[derive(Default)]
171 pub struct FieldAttrib {
172 pub rename: Option<syn::LitStr>,
173 pub flatten: bool,
174 }
175
176 impl FieldAttrib {
177 pub fn parse_attribute(&mut self, attrib: &syn::Attribute) -> Result<(), syn::Error> {
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");
195 }
196 self.rename = Some(rename.clone());
197 }
198 value => error!(value => "'rename' value must be a string literal"),
199 }
200 } else if path.is_ident("flatten") {
201 arg.require_path_only()?;
202 self.flatten = true;
203 }
204 }
205
206 Ok(())
207 }
208 }
209
210 impl TryFrom<&[syn::Attribute]> for FieldAttrib {
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
220 Ok(this)
221 }
222 }