mirror of
https://git.proxmox.com/git/proxmox
synced 2025-05-29 00:56:52 +00:00
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:
parent
44735fe5d6
commit
7fe84f8e15
@ -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<syn::LitBool>,
|
||||
// /// Change the type for the updater.
|
||||
|
||||
/// Change the type for the updater.
|
||||
ty: Option<syn::TypePath>,
|
||||
|
||||
/// Replace any `#[serde]` attributes on the field with these (accumulates).
|
||||
serde: Vec<syn::Attribute>,
|
||||
}
|
||||
|
||||
impl UpdaterFieldAttributes {
|
||||
pub fn from_attributes(input: &mut Vec<syn::Attribute>) -> 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<syn::Attribute>) {
|
||||
if !self.serde.is_empty() {
|
||||
attrs.retain(|attr| !attr.path.is_ident("serde"));
|
||||
attrs.extend(self.serde.iter().cloned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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<syn::Attribute>,
|
||||
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),
|
||||
}
|
||||
|
@ -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<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,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user