From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 75F3061D55 for ; Fri, 18 Dec 2020 12:37:40 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 569B12F8EC for ; Fri, 18 Dec 2020 12:37:39 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id D75F02F819 for ; Fri, 18 Dec 2020 12:37:32 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id A8A044539F for ; Fri, 18 Dec 2020 12:26:25 +0100 (CET) From: Wolfgang Bumiller To: pbs-devel@lists.proxmox.com Date: Fri, 18 Dec 2020 12:25:57 +0100 Message-Id: <20201218112608.6845-10-w.bumiller@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201218112608.6845-1-w.bumiller@proxmox.com> References: <20201218112608.6845-1-w.bumiller@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.017 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [structs.rs, allof.rs, mod.rs] Subject: [pbs-devel] [PATCH proxmox 09/18] api-macro: suport AllOf on structs X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 18 Dec 2020 11:37:40 -0000 Signed-off-by: Wolfgang Bumiller --- 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 { match &stru.fields { @@ -142,6 +142,9 @@ fn handle_regular_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result Result { + 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 Result description, + None => { + error!(schema.span, "missing description on api type struct"); + syn::LitStr::new("", schema.span) + } + }; + // and replace it with a "dummy" + schema.description = Maybe::Derived(syn::LitStr::new( + &format!("", 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( + "", + &[( + "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