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 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user