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 <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2022-05-11 16:00:40 +02:00
parent 44735fe5d6
commit 7fe84f8e15
4 changed files with 66 additions and 8 deletions

View File

@ -1,3 +1,5 @@
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{Meta, NestedMeta}; use syn::{Meta, NestedMeta};
use crate::util::{self, default_false, parse_str_value_to_option, set_bool}; 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 { pub struct UpdaterFieldAttributes {
/// Skip this field in the updater. /// Skip this field in the updater.
skip: Option<syn::LitBool>, skip: Option<syn::LitBool>,
// /// Change the type for the updater.
/// Change the type for the updater.
ty: Option<syn::TypePath>, ty: Option<syn::TypePath>,
/// Replace any `#[serde]` attributes on the field with these (accumulates).
serde: Vec<syn::Attribute>,
} }
impl UpdaterFieldAttributes { impl UpdaterFieldAttributes {
pub fn from_attributes(input: &mut Vec<syn::Attribute>) -> Self { pub fn from_attributes(input: &mut Vec<syn::Attribute>) -> Self {
let mut this = Self::default(); 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 this
} }
fn parse(&mut self, input: NestedMeta) -> Result<(), syn::Error> { fn parse(&mut self, attr: &syn::Attribute, input: NestedMeta) -> Result<(), syn::Error> {
match input { match input {
NestedMeta::Lit(lit) => bail!(lit => "unexpected literal"), 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 { match meta {
Meta::Path(ref path) if path.is_ident("skip") => { Meta::Path(ref path) if path.is_ident("skip") => {
set_bool(&mut self.skip, path, true); set_bool(&mut self.skip, path, true);
@ -35,6 +41,16 @@ impl UpdaterFieldAttributes {
parse_str_value_to_option(&mut self.ty, nv) parse_str_value_to_option(&mut self.ty, nv)
} }
Meta::NameValue(m) => bail!(&m => "invalid updater attribute: {:?}", m.path), 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::List(m) => bail!(&m => "invalid updater attribute: {:?}", m.path),
Meta::Path(m) => bail!(&m => "invalid updater attribute: {:?}", m), Meta::Path(m) => bail!(&m => "invalid updater attribute: {:?}", m),
} }
@ -49,4 +65,11 @@ impl UpdaterFieldAttributes {
pub fn ty(&self) -> Option<&syn::TypePath> { pub fn ty(&self) -> Option<&syn::TypePath> {
self.ty.as_ref() self.ty.as_ref()
} }
pub fn replace_serde_attributes(&self, attrs: &mut Vec<syn::Attribute>) {
if !self.serde.is_empty() {
attrs.retain(|attr| !attr.path.is_ident("serde"));
attrs.extend(self.serde.iter().cloned())
}
}
} }

View File

@ -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 // 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 { if let SchemaItem::ExternType(path) = &mut field_schema.schema.item {
*path = syn::ExprPath { *path = syn::ExprPath {

View File

@ -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( pub fn extract_attributes(
attributes: &mut Vec<syn::Attribute>, attributes: &mut Vec<syn::Attribute>,
attr_name: &str, 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) { for attr in std::mem::take(attributes) {
if attr.style != syn::AttrStyle::Outer { if attr.style != syn::AttrStyle::Outer {
@ -847,7 +847,7 @@ pub fn extract_attributes(
}; };
for entry in list.nested { for entry in list.nested {
match func_matching(entry) { match func_matching(&attr, entry) {
Ok(()) => (), Ok(()) => (),
Err(err) => crate::add_error(err), Err(err) => crate::add_error(err),
} }

View File

@ -1,5 +1,7 @@
#![allow(dead_code)] #![allow(dead_code)]
use serde::{Deserialize, Serialize};
use proxmox_schema::{api, ApiType, Updater, UpdaterType}; use proxmox_schema::{api, ApiType, Updater, UpdaterType};
// Helpers for type checks: // Helpers for type checks:
@ -122,3 +124,34 @@ fn test_super_complex() {
assert_eq!(TEST_SCHEMA, SuperComplexUpdater::API_SCHEMA); 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<Self>;
}
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,
}