macro: replace named struct handler

We now derive Serialize and Deserialize automatically.
This way we'll be able to add verifiers right into the
structs, support our 'rename' functionality, and our
'default' handling etc. which needs to be compatible with
what we have in perl.
Ideally this will also give us the option to mark structs as
being perl-compatible "property strings"
(PVE::JSONSchema::parse_property_string()) and automatically
derive FromStr for structs on demand.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2019-07-19 11:03:37 +02:00
parent 0a8a9cdb5b
commit ec1ad87193
2 changed files with 168 additions and 78 deletions

View File

@ -479,32 +479,182 @@ fn handle_struct_unnamed(
fn handle_struct_named(
mut definition: Object,
name: &Ident,
type_ident: &Ident,
item: &syn::FieldsNamed,
) -> Result<TokenStream, Error> {
let mut verify_entries = None;
let common = CommonTypeDefinition::from_object(&mut definition)?;
for (key, value) in definition {
match key.as_str() {
"fields" => {
verify_entries = Some(handle_named_struct_fields(item, value.expect_object()?)?);
}
other => bail!("unknown api definition field: {}", other),
}
}
let mut field_def = definition.remove("fields")
.ok_or_else(|| c_format_err!(definition.span(), "missing 'fields' entry"))?
.expect_object()?;
use std::iter::FromIterator;
let verifiers = TokenStream::from_iter(
verify_entries.ok_or_else(|| format_err!("missing 'fields' definition for struct"))?,
);
let field_count = item.named.len();
let type_s = type_ident.to_string();
let type_span = type_ident.span();
let type_str = syn::LitStr::new(&type_s, type_span);
let struct_type_str = syn::LitStr::new(&format!("struct {}", type_s), type_span);
let struct_type_field_str =
syn::LitStr::new(&format!("struct {} field name", type_s), type_span);
let mut serialize_entries = TokenStream::new();
let mut field_option_init_list = TokenStream::new();
let mut field_option_check_or_default_list = TokenStream::new();
let mut accessors = TokenStream::new();
let mut field_name_str_list = TokenStream::new(); // ` "member1", "member2", `
let mut field_ident_list = TokenStream::new(); // ` member1, member2, `
let mut field_name_matches = TokenStream::new(); // ` "member0" => 0, "member1" => 1, `
let mut field_value_matches = TokenStream::new();
let mut visitor_ident = Ident::new(&format!("{}Visitor", type_s), type_span);
let mut mem_id: isize = 0;
for field in item.named.iter() {
mem_id += 1;
let field_ident = field.ident
.as_ref()
.ok_or_else(|| c_format_err!(field => "missing field name"))?;
let field_s = field_ident.to_string();
let def = field_def
.remove(&field_s)
.ok_or_else(|| {
c_format_err!(field => "missing api description entry for field {}", field_s)
})?;
let field_span = field_ident.span();
let field_str = syn::LitStr::new(&field_s, field_span);
field_name_str_list.extend(quote_spanned! { field_span => #field_str, });
field_ident_list.extend(quote_spanned! { field_span => #field_ident, });
serialize_entries.extend(quote_spanned! { field_span =>
state.serialize_field(#field_str, &self.#field_ident)?;
});
field_option_init_list.extend(quote_spanned! { field_span =>
let mut #field_ident = None;
});
field_option_check_or_default_list.extend(quote_spanned! { field_span =>
let #field_ident = #field_ident.ok_or_else(|| {
::serde::de::Error::missing_field(#field_str)
})?;
});
field_name_matches.extend(quote_spanned! { field_span =>
#field_str => Field(#mem_id),
});
field_value_matches.extend(quote_spanned! { field_span =>
Field(#mem_id) => {
if #field_ident.is_some() {
return Err(::serde::de::Error::duplicate_field(#field_str));
}
#field_ident = Some(__api_macro__map.next_value()?);
}
});
}
let description = common.description;
let parse_cli = common.cli.quote(&name);
Ok(quote! {
impl ::proxmox::api::ApiType for #name {
let parse_cli = common.cli.quote(&type_ident);
Ok(quote_spanned! { item.span() =>
impl ::serde::ser::Serialize for #type_ident {
fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
where
S: ::serde::ser::Serializer,
{
use ::serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct(#type_str, #field_count)?;
#serialize_entries
state.end()
}
}
impl<'de> ::serde::de::Deserialize<'de> for #type_ident {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::de::Deserializer<'de>,
{
#[repr(transparent)]
struct Field(isize);
impl<'de> ::serde::de::Deserialize<'de> for Field {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::de::Deserializer<'de>,
{
struct FieldVisitor;
impl<'de> ::serde::de::Visitor<'de> for FieldVisitor {
type Value = Field;
fn expecting(
&self,
formatter: &mut ::std::fmt::Formatter,
) -> ::std::fmt::Result {
formatter.write_str(#struct_type_field_str)
}
fn visit_str<E>(self, value: &str) -> ::std::result::Result<Field, E>
where
E: ::serde::de::Error,
{
Ok(match value {
#field_name_matches
_ => {
return Err(
::serde::de::Error::unknown_field(value, FIELDS)
);
}
})
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
struct #visitor_ident;
impl<'de> ::serde::de::Visitor<'de> for #visitor_ident {
type Value = #type_ident;
fn expecting(
&self,
formatter: &mut ::std::fmt::Formatter,
) -> ::std::fmt::Result {
formatter.write_str(#struct_type_str)
}
fn visit_map<V>(
self,
mut __api_macro__map: V,
) -> ::std::result::Result<#type_ident, V::Error>
where
V: ::serde::de::MapAccess<'de>,
{
#field_option_init_list
while let Some(__api_macro__key) = __api_macro__map.next_key()? {
match __api_macro__key {
#field_value_matches
_ => unreachable!(),
}
}
#field_option_check_or_default_list
Ok(#type_ident {
#field_ident_list
})
}
}
const FIELDS: &'static [&'static str] = &[ #field_name_str_list ];
deserializer.deserialize_struct(#type_str, FIELDS, #visitor_ident)
}
}
impl ::proxmox::api::ApiType for #type_ident {
fn type_info() -> &'static ::proxmox::api::TypeInfo {
const INFO: ::proxmox::api::TypeInfo = ::proxmox::api::TypeInfo {
name: stringify!(#name),
name: #type_str,
description: #description,
complete_fn: None, // FIXME!
parse_cli: #parse_cli,
@ -513,72 +663,13 @@ fn handle_struct_named(
}
fn verify(&self) -> ::std::result::Result<(), ::failure::Error> {
#verifiers
// FIXME: #verifiers
Ok(())
}
}
})
}
fn handle_named_struct_fields(
item: &syn::FieldsNamed,
mut field_def: Object,
) -> Result<Vec<TokenStream>, Error> {
let mut verify_entries = Vec::new();
for field in item.named.iter() {
let name = &field.ident;
let name_str = name
.as_ref()
.expect("field name in struct of named fields")
.to_string();
let this = quote! { self.#name };
let def = field_def
.remove(&name_str)
.ok_or_else(|| {
c_format_err!(name.span(), "missing field in definition: '{}'", name_str)
})?;
let def = match def {
Expression::Expr(syn::Expr::Lit(lit)) => match lit.lit {
syn::Lit::Str(description) => ParameterDefinition::builder()
.description(Some(description))
.build()
.unwrap(),
other => c_bail!(other.span(), "expected description as literal string"),
},
Expression::Object(obj) => ParameterDefinition::from_object(obj)?,
other => c_bail!(other.span(), "expected description or field definition"),
};
def.add_verifiers(&name_str, this, &mut verify_entries);
}
if !field_def.is_empty() {
// once SliceConcatExt is stable we can join(",") on the fields...
let mut span = None;
let mut missing = String::new();
for key in field_def.keys() {
if !missing.is_empty() {
missing.push_str(", ");
}
if span.is_none() {
span = Some(key.span());
}
missing.push_str(key.as_str());
}
c_bail!(
span.unwrap(),
"the following struct fields are not handled in the api definition: {}",
missing
);
}
Ok(verify_entries)
}
/// Enums are string types. Note that we usually use lower case enum values, but rust wants
/// CamelCase, so unless otherwise requested by the user (todo!), we convert CamelCase to
/// underscore_case automatically.

View File

@ -40,7 +40,6 @@ fn validate_hostname(name: &str) -> Result<(), Error> {
},
cli: false,
})]
#[derive(Deserialize, Serialize)]
pub struct Person {
name: String,
id: usize,