From 7fe84f8e1553119c8dc257fc4d48e7a5c37c3a4c Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Wed, 11 May 2022 16:00:40 +0200 Subject: [PATCH] api-macro: allow overriding field attributes in the updater This allows fixing up things such as `skip_serialize_if` calls like so: #[derive(Updater)] struct Foo { #[serde(skip_serializing_if = "MyType::is_special")] #[updater(serde(skip_serializing_if = "Option::is_none"))] field: MyType, } Signed-off-by: Wolfgang Bumiller --- proxmox-api-macro/src/api/attributes.rs | 33 +++++++++++++++++++++---- proxmox-api-macro/src/api/structs.rs | 2 ++ proxmox-api-macro/src/util.rs | 6 ++--- proxmox-api-macro/tests/updater.rs | 33 +++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/proxmox-api-macro/src/api/attributes.rs b/proxmox-api-macro/src/api/attributes.rs index af0470b9..7d088109 100644 --- a/proxmox-api-macro/src/api/attributes.rs +++ b/proxmox-api-macro/src/api/attributes.rs @@ -1,3 +1,5 @@ +use proc_macro2::TokenStream; +use quote::ToTokens; use syn::{Meta, NestedMeta}; use crate::util::{self, default_false, parse_str_value_to_option, set_bool}; @@ -6,27 +8,31 @@ use crate::util::{self, default_false, parse_str_value_to_option, set_bool}; pub struct UpdaterFieldAttributes { /// Skip this field in the updater. skip: Option, - // /// Change the type for the updater. + + /// Change the type for the updater. ty: Option, + + /// Replace any `#[serde]` attributes on the field with these (accumulates). + serde: Vec, } impl UpdaterFieldAttributes { pub fn from_attributes(input: &mut Vec) -> Self { let mut this = Self::default(); - util::extract_attributes(input, "updater", |meta| this.parse(meta)); + util::extract_attributes(input, "updater", |attr, meta| this.parse(attr, meta)); this } - fn parse(&mut self, input: NestedMeta) -> Result<(), syn::Error> { + fn parse(&mut self, attr: &syn::Attribute, input: NestedMeta) -> Result<(), syn::Error> { match input { NestedMeta::Lit(lit) => bail!(lit => "unexpected literal"), - NestedMeta::Meta(meta) => self.parse_meta(meta), + NestedMeta::Meta(meta) => self.parse_meta(attr, meta), } } - fn parse_meta(&mut self, meta: Meta) -> Result<(), syn::Error> { + fn parse_meta(&mut self, attr: &syn::Attribute, meta: Meta) -> Result<(), syn::Error> { match meta { Meta::Path(ref path) if path.is_ident("skip") => { set_bool(&mut self.skip, path, true); @@ -35,6 +41,16 @@ impl UpdaterFieldAttributes { parse_str_value_to_option(&mut self.ty, nv) } Meta::NameValue(m) => bail!(&m => "invalid updater attribute: {:?}", m.path), + Meta::List(m) if m.path.is_ident("serde") => { + let mut tokens = TokenStream::new(); + m.paren_token + .surround(&mut tokens, |tokens| m.nested.to_tokens(tokens)); + self.serde.push(syn::Attribute { + path: m.path, + tokens, + ..attr.clone() + }); + } Meta::List(m) => bail!(&m => "invalid updater attribute: {:?}", m.path), Meta::Path(m) => bail!(&m => "invalid updater attribute: {:?}", m), } @@ -49,4 +65,11 @@ impl UpdaterFieldAttributes { pub fn ty(&self) -> Option<&syn::TypePath> { self.ty.as_ref() } + + pub fn replace_serde_attributes(&self, attrs: &mut Vec) { + if !self.serde.is_empty() { + attrs.retain(|attr| !attr.path.is_ident("serde")); + attrs.extend(self.serde.iter().cloned()) + } + } } diff --git a/proxmox-api-macro/src/api/structs.rs b/proxmox-api-macro/src/api/structs.rs index dc405ad6..6bb8f91b 100644 --- a/proxmox-api-macro/src/api/structs.rs +++ b/proxmox-api-macro/src/api/structs.rs @@ -514,6 +514,8 @@ fn handle_updater_field( } }; + updater_attrs.replace_serde_attributes(&mut field.attrs); + // we also need to update the schema to point to the updater's schema for `type: Foo` entries if let SchemaItem::ExternType(path) = &mut field_schema.schema.item { *path = syn::ExprPath { diff --git a/proxmox-api-macro/src/util.rs b/proxmox-api-macro/src/util.rs index 2c54b2a8..ddd2084d 100644 --- a/proxmox-api-macro/src/util.rs +++ b/proxmox-api-macro/src/util.rs @@ -817,11 +817,11 @@ pub fn make_derive_attribute(span: Span, content: TokenStream) -> syn::Attribute ) } -/// Extract (remove) an attribute from a list an run a callback on its parameters. +/// Extract (remove) an attribute from a list and run a callback on its parameters. pub fn extract_attributes( attributes: &mut Vec, attr_name: &str, - mut func_matching: impl FnMut(syn::NestedMeta) -> Result<(), syn::Error>, + mut func_matching: impl FnMut(&syn::Attribute, syn::NestedMeta) -> Result<(), syn::Error>, ) { for attr in std::mem::take(attributes) { if attr.style != syn::AttrStyle::Outer { @@ -847,7 +847,7 @@ pub fn extract_attributes( }; for entry in list.nested { - match func_matching(entry) { + match func_matching(&attr, entry) { Ok(()) => (), Err(err) => crate::add_error(err), } diff --git a/proxmox-api-macro/tests/updater.rs b/proxmox-api-macro/tests/updater.rs index bce65e9b..65c8cdba 100644 --- a/proxmox-api-macro/tests/updater.rs +++ b/proxmox-api-macro/tests/updater.rs @@ -1,5 +1,7 @@ #![allow(dead_code)] +use serde::{Deserialize, Serialize}; + use proxmox_schema::{api, ApiType, Updater, UpdaterType}; // Helpers for type checks: @@ -122,3 +124,34 @@ fn test_super_complex() { assert_eq!(TEST_SCHEMA, SuperComplexUpdater::API_SCHEMA); } + +#[api] +/// A simple string wrapper. +#[derive(Default, Deserialize, Serialize, Updater)] +struct MyType(String); + +impl proxmox_schema::UpdaterType for MyType { + type Updater = Option; +} + +impl MyType { + fn should_skip(&self) -> bool { + self.0 == "skipme" + } +} + +#[api( + properties: { + more: { type: MyType }, + }, +)] +/// A struct where we replace serde attributes. +#[derive(Deserialize, Serialize, Updater)] +pub struct WithSerde { + /// Simple string. + data: String, + + #[serde(skip_serializing_if = "MyType::should_skip", default)] + #[updater(serde(skip_serializing_if = "Option::is_none"))] + more: MyType, +}