]>
git.proxmox.com Git - proxmox.git/blob - proxmox-api-macro/src/serde.rs
1 //! Serde support module.
3 //! The `#![api]` macro needs to be able to cope with some `#[serde(...)]` attributes such as
4 //! `rename` and `rename_all`.
6 use std
::convert
::TryFrom
;
8 use syn
::punctuated
::Punctuated
;
12 #[allow(clippy::enum_variant_names)]
13 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
25 impl TryFrom
<&syn
::Lit
> for RenameAll
{
26 type Error
= syn
::Error
;
27 fn try_from(s
: &syn
::Lit
) -> Result
<Self, syn
::Error
> {
29 syn
::Lit
::Str(s
) => Self::try_from(s
),
30 _
=> bail
!(s
=> "expected rename type as string"),
35 impl TryFrom
<&syn
::LitStr
> for RenameAll
{
36 type Error
= syn
::Error
;
37 fn try_from(s
: &syn
::LitStr
) -> Result
<Self, syn
::Error
> {
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
)
56 bail
!(&s
=> "unhandled `rename_all` type: {}", s
.to_string())
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
{
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();
79 out
.push(c
.to_ascii_uppercase());
81 out
.push(c
.to_ascii_lowercase());
86 RenameAll
::CamelCase
=> {
87 let s
= RenameAll
::PascalCase
.apply_to_field(s
);
88 s
[..1].to_ascii_lowercase() + &s
[1..]
90 RenameAll
::KebabCase
=> s
.replace('_'
, "-"),
91 RenameAll
::ScreamingKebabCase
=> s
.replace('_'
, "-").to_ascii_uppercase(),
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
{
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() {
110 out
.push(c
.to_ascii_lowercase());
114 RenameAll
::KebabCase
=> RenameAll
::SnakeCase
.apply_to_variant(s
).replace('_'
, "-"),
115 RenameAll
::ScreamingSnakeCase
=> RenameAll
::SnakeCase
117 .to_ascii_uppercase(),
118 RenameAll
::ScreamingKebabCase
=> RenameAll
::KebabCase
120 .to_ascii_uppercase(),
125 /// `serde` container attributes we support
127 pub struct ContainerAttrib
{
128 pub rename_all
: Option
<RenameAll
>,
131 impl TryFrom
<&[syn
::Attribute
]> for ContainerAttrib
{
132 type Error
= syn
::Error
;
134 fn try_from(attributes
: &[syn
::Attribute
]) -> Result
<Self, syn
::Error
> {
135 let mut this
: Self = Default
::default();
137 for attrib
in attributes
{
138 let list
= match &attrib
.meta
{
139 syn
::Meta
::List(list
) if list
.path
.is_ident("serde") => list
,
144 list
.parse_args_with(Punctuated
::<syn
::Meta
, Token
![,]>::parse_terminated
)?
;
147 if let syn
::Meta
::NameValue(var
) = arg
{
148 if !var
.path
.is_ident("rename_all") {
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");
157 this
.rename_all
= Some(rename_all
);
159 _
=> error
!(var
.value
=> "invalid 'rename_all' value type"),
169 /// `serde` field/variant attributes we support
171 pub struct FieldAttrib
{
172 pub rename
: Option
<syn
::LitStr
>,
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
,
183 let args
= list
.parse_args_with(Punctuated
::<syn
::Meta
, Token
![,]>::parse_terminated
)?
;
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
),
193 if self.rename
.is_some() && self.rename
.as_ref() != Some(rename
) {
194 error
!(&rename
=> "multiple conflicting 'rename' attributes");
196 self.rename
= Some(rename
.clone());
198 value
=> error
!(value
=> "'rename' value must be a string literal"),
200 } else if path
.is_ident("flatten") {
201 arg
.require_path_only()?
;
210 impl TryFrom
<&[syn
::Attribute
]> for FieldAttrib
{
211 type Error
= syn
::Error
;
213 fn try_from(attributes
: &[syn
::Attribute
]) -> Result
<Self, syn
::Error
> {
214 let mut this
: Self = Default
::default();
216 for attrib
in attributes
{
217 this
.parse_attribute(attrib
)?
;