forked from Proxmox/proxmox
api-macro: type-key support for derived enums
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
4a154b3cb5
commit
839f508f55
@ -68,3 +68,45 @@ impl UpdaterFieldAttributes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct EnumFieldAttributes {
|
||||||
|
/// Change the "type-key" for this entry type..
|
||||||
|
type_key: Option<syn::LitStr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnumFieldAttributes {
|
||||||
|
pub fn from_attributes(input: &mut Vec<syn::Attribute>) -> Self {
|
||||||
|
let mut this = Self::default();
|
||||||
|
|
||||||
|
for attr in std::mem::take(input) {
|
||||||
|
if attr.style != syn::AttrStyle::Outer || !attr.path().is_ident("api") {
|
||||||
|
input.push(attr);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match attr.parse_nested_meta(|meta| this.parse(meta)) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(err) => crate::add_error(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(&mut self, meta: ParseNestedMeta<'_>) -> Result<(), syn::Error> {
|
||||||
|
let path = &meta.path;
|
||||||
|
|
||||||
|
if path.is_ident("type_key") {
|
||||||
|
util::duplicate(&self.type_key, path);
|
||||||
|
self.type_key = Some(meta.value()?.parse()?);
|
||||||
|
} else {
|
||||||
|
return Err(meta.error(format!("invalid api attribute: {path:?}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_key(&self) -> Option<&syn::LitStr> {
|
||||||
|
self.type_key.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ use proc_macro2::{Ident, Span, TokenStream};
|
|||||||
use quote::quote_spanned;
|
use quote::quote_spanned;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
|
use super::attributes::EnumFieldAttributes;
|
||||||
use super::Schema;
|
use super::Schema;
|
||||||
use crate::serde;
|
use crate::serde;
|
||||||
use crate::util::{self, FieldName, JSONObject, JSONValue, Maybe};
|
use crate::util::{self, FieldName, JSONObject, JSONValue, Maybe};
|
||||||
@ -158,7 +159,7 @@ fn handle_string_enum(
|
|||||||
|
|
||||||
fn handle_section_config_enum(
|
fn handle_section_config_enum(
|
||||||
mut attribs: JSONObject,
|
mut attribs: JSONObject,
|
||||||
enum_ty: syn::ItemEnum,
|
mut enum_ty: syn::ItemEnum,
|
||||||
) -> Result<TokenStream, Error> {
|
) -> Result<TokenStream, Error> {
|
||||||
let name = &enum_ty.ident;
|
let name = &enum_ty.ident;
|
||||||
|
|
||||||
@ -186,6 +187,13 @@ fn handle_section_config_enum(
|
|||||||
Some(name) => name.try_into()?,
|
Some(name) => name.try_into()?,
|
||||||
None => bail!(name => "missing 'id-property' property for SectionConfig style enum"),
|
None => bail!(name => "missing 'id-property' property for SectionConfig style enum"),
|
||||||
};
|
};
|
||||||
|
let with_type_key: TokenStream = match attribs.remove("type-key") {
|
||||||
|
Some(value) => {
|
||||||
|
let value: syn::LitStr = value.try_into()?;
|
||||||
|
quote_spanned!(value.span() => .with_type_key(#value))
|
||||||
|
}
|
||||||
|
None => TokenStream::new(),
|
||||||
|
};
|
||||||
|
|
||||||
let container_attrs = serde::ContainerAttrib::try_from(&enum_ty.attrs[..])?;
|
let container_attrs = serde::ContainerAttrib::try_from(&enum_ty.attrs[..])?;
|
||||||
let Some(tag) = container_attrs.tag.as_ref() else {
|
let Some(tag) = container_attrs.tag.as_ref() else {
|
||||||
@ -195,7 +203,7 @@ fn handle_section_config_enum(
|
|||||||
let mut variants = TokenStream::new();
|
let mut variants = TokenStream::new();
|
||||||
let mut register_sections = TokenStream::new();
|
let mut register_sections = TokenStream::new();
|
||||||
let mut to_type = TokenStream::new();
|
let mut to_type = TokenStream::new();
|
||||||
for variant in &enum_ty.variants {
|
for variant in &mut enum_ty.variants {
|
||||||
let field = match &variant.fields {
|
let field = match &variant.fields {
|
||||||
syn::Fields::Unnamed(field) if field.unnamed.len() == 1 => &field.unnamed[0],
|
syn::Fields::Unnamed(field) if field.unnamed.len() == 1 => &field.unnamed[0],
|
||||||
_ => bail!(variant => "SectionConfig style enum can only have newtype variants"),
|
_ => bail!(variant => "SectionConfig style enum can only have newtype variants"),
|
||||||
@ -212,6 +220,13 @@ fn handle_section_config_enum(
|
|||||||
syn::LitStr::new(&name.to_string(), name.span())
|
syn::LitStr::new(&name.to_string(), name.span())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let field_attrs = EnumFieldAttributes::from_attributes(&mut variant.attrs);
|
||||||
|
let with_type_key = if let Some(key) = field_attrs.type_key() {
|
||||||
|
quote_spanned!(key.span() => .with_type_key(#key))
|
||||||
|
} else {
|
||||||
|
TokenStream::new()
|
||||||
|
};
|
||||||
|
|
||||||
let variant_ident = &variant.ident;
|
let variant_ident = &variant.ident;
|
||||||
let ty = &field.ty;
|
let ty = &field.ty;
|
||||||
variants.extend(quote_spanned! { variant.ident.span() =>
|
variants.extend(quote_spanned! { variant.ident.span() =>
|
||||||
@ -221,17 +236,20 @@ fn handle_section_config_enum(
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
register_sections.extend(quote_spanned! { variant.ident.span() =>
|
register_sections.extend(quote_spanned! { variant.ident.span() =>
|
||||||
this.register_plugin(::proxmox_section_config::SectionConfigPlugin::new(
|
this.register_plugin(
|
||||||
#variant_string.to_string(),
|
::proxmox_section_config::SectionConfigPlugin::new(
|
||||||
Some(#id_property.to_string()),
|
#variant_string.to_string(),
|
||||||
const {
|
Some(#id_property.to_string()),
|
||||||
match &<#ty as ::proxmox_schema::ApiType>::API_SCHEMA {
|
const {
|
||||||
::proxmox_schema::Schema::Object(schema) => schema,
|
match &<#ty as ::proxmox_schema::ApiType>::API_SCHEMA {
|
||||||
::proxmox_schema::Schema::OneOf(schema) => schema,
|
::proxmox_schema::Schema::Object(schema) => schema,
|
||||||
_ => panic!("enum requires an object schema"),
|
::proxmox_schema::Schema::OneOf(schema) => schema,
|
||||||
|
_ => panic!("enum requires an object schema"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
));
|
#with_type_key
|
||||||
|
);
|
||||||
});
|
});
|
||||||
to_type.extend(quote_spanned! { variant.ident.span() =>
|
to_type.extend(quote_spanned! { variant.ident.span() =>
|
||||||
Self::#variant_ident(_) => #variant_string,
|
Self::#variant_ident(_) => #variant_string,
|
||||||
@ -265,7 +283,8 @@ fn handle_section_config_enum(
|
|||||||
.type_property_entry
|
.type_property_entry
|
||||||
.2
|
.2
|
||||||
};
|
};
|
||||||
let mut this = ::proxmox_section_config::SectionConfig::new(id_schema);
|
let mut this = ::proxmox_section_config::SectionConfig::new(id_schema)
|
||||||
|
#with_type_key;
|
||||||
#register_sections
|
#register_sections
|
||||||
this
|
this
|
||||||
})
|
})
|
||||||
|
@ -23,6 +23,10 @@ pub struct TypeB {
|
|||||||
|
|
||||||
/// An age.
|
/// An age.
|
||||||
age: u64,
|
age: u64,
|
||||||
|
|
||||||
|
/// The internally tagged type.
|
||||||
|
#[serde(rename = "the-type")]
|
||||||
|
ty: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
@ -37,6 +41,7 @@ pub struct TypeB {
|
|||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum Config {
|
pub enum Config {
|
||||||
A(TypeA),
|
A(TypeA),
|
||||||
|
#[api(type_key = "the-type")]
|
||||||
B(TypeB),
|
B(TypeB),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +71,7 @@ fn test_config() {
|
|||||||
Config::B(TypeB {
|
Config::B(TypeB {
|
||||||
id: "the-b".to_string(),
|
id: "the-b".to_string(),
|
||||||
age: 42,
|
age: 42,
|
||||||
|
ty: "B".to_string(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -73,3 +79,70 @@ fn test_config() {
|
|||||||
.expect("failed to write out test section config");
|
.expect("failed to write out test section config");
|
||||||
assert_eq!(raw, content);
|
assert_eq!(raw, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[api]
|
||||||
|
/// Type A2.
|
||||||
|
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct TypeA2 {
|
||||||
|
/// The id.
|
||||||
|
id: String,
|
||||||
|
|
||||||
|
/// Some name.
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
/// The internally tagged type.
|
||||||
|
ty: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
"id-property": "id",
|
||||||
|
"id-schema": {
|
||||||
|
type: String,
|
||||||
|
description: "A config ID",
|
||||||
|
max_length: 16,
|
||||||
|
},
|
||||||
|
"type-key": "ty",
|
||||||
|
)]
|
||||||
|
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
|
#[serde(tag = "type", rename_all = "lowercase")]
|
||||||
|
pub enum ConfigTypeKey {
|
||||||
|
A2(TypeA2),
|
||||||
|
#[api(type_key = "the-type")]
|
||||||
|
B(TypeB),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_global_type_key() {
|
||||||
|
let content = "\
|
||||||
|
a2: the-a\n\
|
||||||
|
\tname The Name\n\
|
||||||
|
\n\
|
||||||
|
b: the-b\n\
|
||||||
|
\tage 42\n\
|
||||||
|
";
|
||||||
|
|
||||||
|
let data = ConfigTypeKey::parse_section_config("a_test_file.cfg", content)
|
||||||
|
.expect("failed to parse test section config");
|
||||||
|
|
||||||
|
assert_eq!(data.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
data["the-a"],
|
||||||
|
ConfigTypeKey::A2(TypeA2 {
|
||||||
|
id: "the-a".to_string(),
|
||||||
|
name: "The Name".to_string(),
|
||||||
|
ty: "a2".to_string(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
data["the-b"],
|
||||||
|
ConfigTypeKey::B(TypeB {
|
||||||
|
id: "the-b".to_string(),
|
||||||
|
age: 42,
|
||||||
|
ty: "b".to_string(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let raw = ConfigTypeKey::write_section_config("a_test_output_file.cfg", &data)
|
||||||
|
.expect("failed to write out test section config");
|
||||||
|
assert_eq!(raw, content);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user