[pbs-devel] [PATCH proxmox 09/18] api-macro: suport AllOf on structs
Wolfgang Bumiller
w.bumiller at proxmox.com
Fri Dec 18 12:25:57 CET 2020
Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
proxmox-api-macro/src/api/mod.rs | 19 +++++
proxmox-api-macro/src/api/structs.rs | 123 ++++++++++++++++++++++++---
proxmox-api-macro/tests/allof.rs | 118 +++++++++++++++++++++++++
3 files changed, 246 insertions(+), 14 deletions(-)
create mode 100644 proxmox-api-macro/tests/allof.rs
diff --git a/proxmox-api-macro/src/api/mod.rs b/proxmox-api-macro/src/api/mod.rs
index 815e167..3cf1309 100644
--- a/proxmox-api-macro/src/api/mod.rs
+++ b/proxmox-api-macro/src/api/mod.rs
@@ -397,6 +397,11 @@ impl SchemaObject {
}
}
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.properties_.is_empty()
+ }
+
#[inline]
fn properties_mut(&mut self) -> &mut [(FieldName, bool, Schema)] {
&mut self.properties_
@@ -458,6 +463,20 @@ impl SchemaObject {
.find(|p| p.0.as_ident_str() == key)
}
+ fn remove_property_by_ident(&mut self, key: &str) -> bool {
+ match self
+ .properties_
+ .iter()
+ .position(|(name, _, _)| name.as_ident_str() == key)
+ {
+ Some(index) => {
+ self.properties_.remove(index);
+ true
+ }
+ None => false,
+ }
+ }
+
fn extend_properties(&mut self, new_fields: Vec<(FieldName, bool, Schema)>) {
self.properties_.extend(new_fields);
self.sort_properties();
diff --git a/proxmox-api-macro/src/api/structs.rs b/proxmox-api-macro/src/api/structs.rs
index a101308..db6a290 100644
--- a/proxmox-api-macro/src/api/structs.rs
+++ b/proxmox-api-macro/src/api/structs.rs
@@ -21,7 +21,7 @@ use quote::quote_spanned;
use super::Schema;
use crate::api::{self, SchemaItem};
use crate::serde;
-use crate::util::{self, FieldName, JSONObject};
+use crate::util::{self, FieldName, JSONObject, Maybe};
pub fn handle_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result<TokenStream, Error> {
match &stru.fields {
@@ -142,6 +142,9 @@ fn handle_regular_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result<T
let container_attrs = serde::ContainerAttrib::try_from(&stru.attrs[..])?;
+ let mut all_of_schemas = TokenStream::new();
+ let mut to_remove = Vec::new();
+
if let syn::Fields::Named(ref fields) = &stru.fields {
for field in &fields.named {
let attrs = serde::SerdeAttrib::try_from(&field.attrs[..])?;
@@ -162,19 +165,34 @@ fn handle_regular_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result<T
}
};
- if attrs.flatten {
- if let Some(field) = schema_fields.remove(&name) {
- error!(
- field.0.span(),
- "flattened field should not appear in schema, \
- its name does not appear in serialized data",
- );
- }
- }
-
match schema_fields.remove(&name) {
Some(field_def) => {
+ if attrs.flatten {
+ to_remove.push(name.clone());
+
+ let name = &field_def.0;
+ let optional = &field_def.1;
+ let schema = &field_def.2;
+ if schema.description.is_explicit() {
+ error!(
+ name.span(),
+ "flattened field should not have a description, \
+ it does not appear in serialized data as a field",
+ );
+ }
+
+ if *optional {
+ error!(name.span(), "optional flattened fields are not supported");
+ }
+ }
+
handle_regular_field(field_def, field, false)?;
+
+ if attrs.flatten {
+ all_of_schemas.extend(quote::quote! {&});
+ field_def.2.to_schema(&mut all_of_schemas)?;
+ all_of_schemas.extend(quote::quote! {,});
+ }
}
None => {
let mut field_def = (
@@ -183,7 +201,15 @@ fn handle_regular_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result<T
Schema::blank(span),
);
handle_regular_field(&mut field_def, field, true)?;
- new_fields.push(field_def);
+
+ if attrs.flatten {
+ all_of_schemas.extend(quote::quote! {&});
+ field_def.2.to_schema(&mut all_of_schemas)?;
+ all_of_schemas.extend(quote::quote! {,});
+ to_remove.push(name.clone());
+ } else {
+ new_fields.push(field_def);
+ }
}
}
}
@@ -200,14 +226,83 @@ fn handle_regular_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result<T
);
}
- // add the fields we derived:
if let api::SchemaItem::Object(ref mut obj) = &mut schema.item {
+ // remove flattened fields
+ for field in to_remove {
+ if !obj.remove_property_by_ident(&field) {
+ error!(
+ schema.span,
+ "internal error: failed to remove property {:?} from object schema", field,
+ );
+ }
+ }
+
+ // add derived fields
obj.extend_properties(new_fields);
} else {
panic!("handle_regular_struct with non-object schema");
}
- finish_schema(schema, &stru, &stru.ident)
+ if all_of_schemas.is_empty() {
+ finish_schema(schema, &stru, &stru.ident)
+ } else {
+ let name = &stru.ident;
+
+ // take out the inner object schema's description
+ let description = match schema.description.take().ok() {
+ Some(description) => description,
+ None => {
+ error!(schema.span, "missing description on api type struct");
+ syn::LitStr::new("<missing description>", schema.span)
+ }
+ };
+ // and replace it with a "dummy"
+ schema.description = Maybe::Derived(syn::LitStr::new(
+ &format!("<INNER: {}>", description.value()),
+ description.span(),
+ ));
+
+ // now check if it even has any fields
+ let has_fields = match &schema.item {
+ api::SchemaItem::Object(obj) => !obj.is_empty(),
+ _ => panic!("object schema is not an object schema?"),
+ };
+
+ let (inner_schema, inner_schema_ref) = if has_fields {
+ // if it does, we need to create an "inner" schema to merge into the AllOf schema
+ let obj_schema = {
+ let mut ts = TokenStream::new();
+ schema.to_schema(&mut ts)?;
+ ts
+ };
+
+ (
+ quote_spanned!(name.span() =>
+ const INNER_API_SCHEMA: ::proxmox::api::schema::Schema = #obj_schema;
+ ),
+ quote_spanned!(name.span() => &Self::INNER_API_SCHEMA,),
+ )
+ } else {
+ // otherwise it stays empty
+ (TokenStream::new(), TokenStream::new())
+ };
+
+ Ok(quote_spanned!(name.span() =>
+ #stru
+ impl #name {
+ #inner_schema
+ pub const API_SCHEMA: ::proxmox::api::schema::Schema =
+ ::proxmox::api::schema::AllOfSchema::new(
+ #description,
+ &[
+ #inner_schema_ref
+ #all_of_schemas
+ ],
+ )
+ .schema();
+ }
+ ))
+ }
}
/// Field handling:
diff --git a/proxmox-api-macro/tests/allof.rs b/proxmox-api-macro/tests/allof.rs
new file mode 100644
index 0000000..56e86d7
--- /dev/null
+++ b/proxmox-api-macro/tests/allof.rs
@@ -0,0 +1,118 @@
+//! Testing the `AllOf` schema on structs and methods.
+
+use proxmox::api::schema;
+use proxmox_api_macro::api;
+
+use serde::{Deserialize, Serialize};
+
+pub const NAME_SCHEMA: schema::Schema = schema::StringSchema::new("Name.").schema();
+pub const VALUE_SCHEMA: schema::Schema = schema::IntegerSchema::new("Value.").schema();
+pub const INDEX_SCHEMA: schema::Schema = schema::IntegerSchema::new("Index.").schema();
+pub const TEXT_SCHEMA: schema::Schema = schema::StringSchema::new("Text.").schema();
+
+#[api(
+ properties: {
+ name: { schema: NAME_SCHEMA },
+ value: { schema: VALUE_SCHEMA },
+ }
+)]
+/// Name and value.
+#[derive(Deserialize, Serialize)]
+struct NameValue {
+ name: String,
+ value: u64,
+}
+
+#[api(
+ properties: {
+ index: { schema: INDEX_SCHEMA },
+ text: { schema: TEXT_SCHEMA },
+ }
+)]
+/// Index and text.
+#[derive(Deserialize, Serialize)]
+struct IndexText {
+ index: u64,
+ text: String,
+}
+
+#[api(
+ properties: {
+ nv: { type: NameValue },
+ it: { type: IndexText },
+ },
+)]
+/// Name, value, index and text.
+#[derive(Deserialize, Serialize)]
+struct Nvit {
+ #[serde(flatten)]
+ nv: NameValue,
+
+ #[serde(flatten)]
+ it: IndexText,
+}
+
+#[test]
+fn test_nvit() {
+ const TEST_NAME_VALUE_SCHEMA: ::proxmox::api::schema::Schema =
+ ::proxmox::api::schema::ObjectSchema::new(
+ "Name and value.",
+ &[
+ ("name", false, &NAME_SCHEMA),
+ ("value", false, &VALUE_SCHEMA),
+ ],
+ )
+ .schema();
+
+ const TEST_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::AllOfSchema::new(
+ "Name, value, index and text.",
+ &[&TEST_NAME_VALUE_SCHEMA, &IndexText::API_SCHEMA],
+ )
+ .schema();
+
+ assert_eq!(TEST_SCHEMA, Nvit::API_SCHEMA);
+}
+
+#[api(
+ properties: {
+ nv: { type: NameValue },
+ it: { type: IndexText },
+ },
+)]
+/// Extra Schema
+#[derive(Deserialize, Serialize)]
+struct WithExtra {
+ #[serde(flatten)]
+ nv: NameValue,
+
+ #[serde(flatten)]
+ it: IndexText,
+
+ /// Extra field.
+ extra: String,
+}
+
+#[test]
+fn test_extra() {
+ const INNER_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::ObjectSchema::new(
+ "<INNER: Extra Schema>",
+ &[(
+ "extra",
+ false,
+ &::proxmox::api::schema::StringSchema::new("Extra field.").schema(),
+ )],
+ )
+ .schema();
+
+ const TEST_SCHEMA: ::proxmox::api::schema::Schema = ::proxmox::api::schema::AllOfSchema::new(
+ "Extra Schema",
+ &[
+ &INNER_SCHEMA,
+ &NameValue::API_SCHEMA,
+ &IndexText::API_SCHEMA,
+ ],
+ )
+ .schema();
+
+ assert_eq!(TEST_SCHEMA, WithExtra::API_SCHEMA);
+}
--
2.20.1
More information about the pbs-devel
mailing list