diff --git a/proxmox-api-macro/src/api_def.rs b/proxmox-api-macro/src/api_def.rs index f63d80ed..f756d45d 100644 --- a/proxmox-api-macro/src/api_def.rs +++ b/proxmox-api-macro/src/api_def.rs @@ -1,6 +1,6 @@ use std::convert::TryFrom; -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use derive_builder::Builder; use failure::{bail, Error}; @@ -107,6 +107,11 @@ pub struct ParameterDefinition { /// the format name. #[builder(default)] pub format: Option, + + #[builder(default)] + pub serialize_with: Option, + #[builder(default)] + pub deserialize_with: Option, } impl ParameterDefinition { @@ -144,6 +149,26 @@ impl ParameterDefinition { "format" => { def.format(Some(value.expect_path()?)); } + "serialize_with" => { + def.serialize_with(Some(value.expect_path()?)); + } + "deserialize_with" => { + def.deserialize_with(Some(value.expect_path()?)); + } + "serialization" => { + let mut de = value.expect_path()?; + let mut ser = de.clone(); + ser.segments.push(syn::PathSegment { + ident: Ident::new("serialize", obj_span), + arguments: syn::PathArguments::None, + }); + de.segments.push(syn::PathSegment { + ident: Ident::new("deserialize", obj_span), + arguments: syn::PathArguments::None, + }); + def.deserialize_with(Some(de)); + def.serialize_with(Some(ser)); + } other => c_bail!(key.span(), "invalid key in type definition: {}", other), } } diff --git a/proxmox-api-macro/src/api_macro.rs b/proxmox-api-macro/src/api_macro.rs index b95be250..6f7014e1 100644 --- a/proxmox-api-macro/src/api_macro.rs +++ b/proxmox-api-macro/src/api_macro.rs @@ -666,6 +666,31 @@ fn named_struct_impl_verify(span: Span, fields: &[StructField]) -> Result (TokenStream, Ident) { + let helper_name = Ident::new( + &format!("SerializeWith{}", crate::util::to_camel_case(&name.to_string())), + name.span(), + ); + + (quote_spanned! { span => + struct #helper_name<'a>(&'a #ty); + + impl<'a> ::serde::ser::Serialize for #helper_name<'a> { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: ::serde::ser::Serializer, + { + #with(self.0, serializer) + } + } + }, helper_name) +} + fn named_struct_derive_serialize( span: Span, type_ident: &Ident, @@ -679,11 +704,27 @@ fn named_struct_derive_serialize( let field_ident = field.ident; let field_span = field.ident.span(); let field_str = &field.strlit; - entries.extend(quote_spanned! { field_span => - if !::proxmox::api::ApiType::should_skip_serialization(&self.#field_ident) { - state.serialize_field(#field_str, &self.#field_ident)?; + match field.def.serialize_with.as_ref() { + Some(path) => { + let (serializer, serializer_name) = + wrap_serialize_with(field_span, field_ident, &field.ty, path); + + entries.extend(quote_spanned! { field_span => + if !::proxmox::api::ApiType::should_skip_serialization(&self.#field_ident) { + #serializer + + state.serialize_field(#field_str, &#serializer_name(&self.#field_ident))?; + } + }); } - }); + None => { + entries.extend(quote_spanned! { field_span => + if !::proxmox::api::ApiType::should_skip_serialization(&self.#field_ident) { + state.serialize_field(#field_str, &self.#field_ident)?; + } + }); + } + } } Ok(quote_spanned! { span => @@ -701,6 +742,37 @@ fn named_struct_derive_serialize( }) } +fn wrap_deserialize_with( + span: Span, + name: &Ident, + ty: &syn::Type, + with: &syn::Path, +) -> (TokenStream, Ident) { + let helper_name = Ident::new( + &format!("DeserializeWith{}", crate::util::to_camel_case(&name.to_string())), + name.span(), + ); + + (quote_spanned! { span => + struct #helper_name<'de> { + value: #ty, + _lifetime: ::std::marker::PhantomData<&'de ()>, + } + + impl<'de> ::serde::de::Deserialize<'de> for #helper_name<'de> { + fn deserialize(deserializer: D) -> std::result::Result + where + D: ::serde::de::Deserializer<'de>, + { + Ok(Self { + value: #with(deserializer)?, + _lifetime: ::std::marker::PhantomData, + }) + } + } + }, helper_name) +} + fn named_struct_derive_deserialize( span: Span, type_ident: &Ident, @@ -740,18 +812,42 @@ fn named_struct_derive_deserialize( )?; }); - field_option_init_list.extend(quote_spanned! { field_span => - let mut #field_ident = None; - }); + match field.def.deserialize_with.as_ref() { + Some(path) => { + let (deserializer, deserializer_name) = + wrap_deserialize_with(field_span, field_ident, &field.ty, path); - field_value_matches.extend(quote_spanned! { field_span => - Field(#mem_id) => { - if #field_ident.is_some() { - return Err(::serde::de::Error::duplicate_field(#field_str)); - } - #field_ident = Some(_api_macro_map_.next_value()?); + field_option_init_list.extend(quote_spanned! { field_span => + #deserializer + + let mut #field_ident = None; + }); + + field_value_matches.extend(quote_spanned! { field_span => + Field(#mem_id) => { + if #field_ident.is_some() { + return Err(::serde::de::Error::duplicate_field(#field_str)); + } + let tmp: #deserializer_name = _api_macro_map_.next_value()?; + #field_ident = Some(tmp.value); + } + }); } - }); + None => { + field_option_init_list.extend(quote_spanned! { field_span => + let mut #field_ident = None; + }); + + field_value_matches.extend(quote_spanned! { field_span => + Field(#mem_id) => { + if #field_ident.is_some() { + return Err(::serde::de::Error::duplicate_field(#field_str)); + } + #field_ident = Some(_api_macro_map_.next_value()?); + } + }); + } + } } Ok(quote_spanned! { span =>