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 8B47461E45 for ; Fri, 18 Dec 2020 12:38:17 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 9AD762FB38 for ; Fri, 18 Dec 2020 12:37:46 +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 EF66C2F820 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 AE1554538C for ; Fri, 18 Dec 2020 12:26:19 +0100 (CET) From: Wolfgang Bumiller To: pbs-devel@lists.proxmox.com Date: Fri, 18 Dec 2020 12:25:51 +0100 Message-Id: <20201218112608.6845-4-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.026 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. [mod.rs, util.rs, method.rs, ext-schema.rs, structs.rs, types.rs, api1.rs] Subject: [pbs-devel] [PATCH proxmox 03/18] api-macro: support optional return values 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:38:17 -0000 The return specification can now include an `optional` field. Signed-off-by: Wolfgang Bumiller --- proxmox-api-macro/src/api/method.rs | 88 +++++++++++++++++++++------ proxmox-api-macro/src/api/mod.rs | 6 +- proxmox-api-macro/src/api/structs.rs | 7 +-- proxmox-api-macro/src/util.rs | 4 +- proxmox-api-macro/tests/api1.rs | 10 +-- proxmox-api-macro/tests/ext-schema.rs | 2 +- proxmox-api-macro/tests/types.rs | 7 ++- 7 files changed, 89 insertions(+), 35 deletions(-) diff --git a/proxmox-api-macro/src/api/method.rs b/proxmox-api-macro/src/api/method.rs index 0d05cbb..3e85d8c 100644 --- a/proxmox-api-macro/src/api/method.rs +++ b/proxmox-api-macro/src/api/method.rs @@ -20,6 +20,66 @@ use syn::Ident; use super::{Schema, SchemaItem}; use crate::util::{self, FieldName, JSONObject, JSONValue, Maybe}; +/// A return type in a schema can have an `optional` flag. Other than that it is just a regular +/// schema. +pub struct ReturnType { + /// If optional, we store `Some(span)`, otherwise `None`. + optional: Option, + + schema: Schema, +} + +impl ReturnType { + fn to_schema(&self, ts: &mut TokenStream) -> Result<(), Error> { + let optional = match self.optional { + Some(span) => quote_spanned! { span => true }, + None => quote! { false }, + }; + + let mut out = TokenStream::new(); + self.schema.to_schema(&mut out)?; + + ts.extend(quote! { + ::proxmox::api::router::ReturnType::new( #optional , &#out ) + }); + Ok(()) + } +} + +impl TryFrom for ReturnType { + type Error = syn::Error; + + fn try_from(value: JSONValue) -> Result { + Self::try_from(value.into_object("a return type definition")?) + } +} + +/// To go from a `JSONObject` to a `ReturnType` we first extract the `optional` flag, then forward +/// to the `Schema` parser. +impl TryFrom for ReturnType { + type Error = syn::Error; + + fn try_from(mut obj: JSONObject) -> Result { + let optional = match obj.remove("optional") { + Some(value) => { + let span = value.span(); + let is_optional: bool = value.try_into()?; + if is_optional { + Some(span) + } else { + None + } + } + None => None, + }; + + Ok(Self { + optional, + schema: obj.try_into()?, + }) + } +} + /// Parse `input`, `returns` and `protected` attributes out of an function annotated /// with an `#[api]` attribute and produce a `const ApiMethod` named after the function. /// @@ -35,7 +95,7 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result = attribs + let mut return_type: Option = attribs .remove("returns") .map(|ret| ret.into_object("return schema definition")?.try_into()) .transpose()?; @@ -78,7 +138,7 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result Result Result Result - pub const #return_schema_name: ::proxmox::api::schema::Schema = #inner; - }; - returns_schema_setter = quote! { .returns(&#return_schema_name) }; + return_type.to_schema(&mut inner)?; + returns_schema_setter = quote! { .returns(#inner) }; } let api_handler = if is_async { @@ -140,8 +192,6 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result - #returns_schema_definition - pub const #input_schema_name: ::proxmox::api::schema::ObjectSchema = #input_schema; @@ -189,7 +239,7 @@ fn check_input_type(input: &syn::FnArg) -> Result<(&syn::PatType, &syn::PatIdent fn handle_function_signature( input_schema: &mut Schema, - returns_schema: &mut Option, + return_type: &mut Option, func: &mut syn::ItemFn, wrapper_ts: &mut TokenStream, default_consts: &mut TokenStream, @@ -322,7 +372,7 @@ fn handle_function_signature( create_wrapper_function( input_schema, - returns_schema, + return_type, param_list, func, wrapper_ts, @@ -379,7 +429,7 @@ fn is_value_type(ty: &syn::Type) -> bool { fn create_wrapper_function( _input_schema: &Schema, - _returns_schema: &Option, + _returns_schema: &Option, param_list: Vec<(FieldName, ParameterType)>, func: &syn::ItemFn, wrapper_ts: &mut TokenStream, diff --git a/proxmox-api-macro/src/api/mod.rs b/proxmox-api-macro/src/api/mod.rs index 81d9177..815e167 100644 --- a/proxmox-api-macro/src/api/mod.rs +++ b/proxmox-api-macro/src/api/mod.rs @@ -15,7 +15,7 @@ use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned}; use syn::parse::{Parse, ParseStream, Parser}; use syn::spanned::Spanned; -use syn::{ExprPath, Ident}; +use syn::{Expr, ExprPath, Ident}; use crate::util::{FieldName, JSONObject, JSONValue, Maybe}; @@ -217,7 +217,7 @@ pub enum SchemaItem { Object(SchemaObject), Array(SchemaArray), ExternType(ExprPath), - ExternSchema(ExprPath), + ExternSchema(Expr), Inferred(Span), } @@ -225,7 +225,7 @@ impl SchemaItem { /// If there's a `type` specified, parse it as that type. Otherwise check for keys which /// uniqueply identify the type, such as "properties" for type `Object`. fn try_extract_from(obj: &mut JSONObject) -> Result { - if let Some(ext) = obj.remove("schema").map(ExprPath::try_from).transpose()? { + if let Some(ext) = obj.remove("schema").map(Expr::try_from).transpose()? { return Ok(SchemaItem::ExternSchema(ext)); } diff --git a/proxmox-api-macro/src/api/structs.rs b/proxmox-api-macro/src/api/structs.rs index 887ffaa..71cdc8a 100644 --- a/proxmox-api-macro/src/api/structs.rs +++ b/proxmox-api-macro/src/api/structs.rs @@ -41,7 +41,7 @@ pub fn handle_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result Result<(), Error> { if schema.description.is_none() { let (doc_comment, doc_span) = util::get_doc_comments(&stru.attrs)?; - util::derive_descriptions(schema, &mut None, &doc_comment, doc_span)?; + util::derive_descriptions(schema, None, &doc_comment, doc_span)?; } Ok(()) @@ -184,8 +184,7 @@ fn handle_regular_struct(attribs: JSONObject, stru: syn::ItemStruct) -> Result Result<(String, Span), pub fn derive_descriptions( input_schema: &mut Schema, - returns_schema: &mut Option, + returns_schema: Option<&mut Schema>, doc_comment: &str, doc_span: Span, ) -> Result<(), Error> { @@ -447,7 +447,7 @@ pub fn derive_descriptions( } if let Some(second) = parts.next() { - if let Some(ref mut returns_schema) = returns_schema { + if let Some(returns_schema) = returns_schema { if returns_schema.description.is_none() { returns_schema.description = Maybe::Derived(syn::LitStr::new(second.trim(), doc_span)); diff --git a/proxmox-api-macro/tests/api1.rs b/proxmox-api-macro/tests/api1.rs index ff37c74..88adb40 100644 --- a/proxmox-api-macro/tests/api1.rs +++ b/proxmox-api-macro/tests/api1.rs @@ -82,7 +82,8 @@ fn create_ticket_schema_check() { ], ), ) - .returns( + .returns(::proxmox::api::router::ReturnType::new( + false, &::proxmox::api::schema::ObjectSchema::new( "A ticket.", &[ @@ -107,7 +108,7 @@ fn create_ticket_schema_check() { ], ) .schema(), - ) + )) .access(Some("Only root can access this."), &Permission::Superuser) .protected(true); assert_eq!(TEST_METHOD, API_METHOD_CREATE_TICKET); @@ -184,7 +185,8 @@ fn create_ticket_direct_schema_check() { ], ), ) - .returns( + .returns(::proxmox::api::router::ReturnType::new( + false, &::proxmox::api::schema::ObjectSchema::new( "A ticket.", &[ @@ -209,7 +211,7 @@ fn create_ticket_direct_schema_check() { ], ) .schema(), - ) + )) .access(None, &Permission::World) .protected(true); assert_eq!(TEST_METHOD, API_METHOD_CREATE_TICKET_DIRECT); diff --git a/proxmox-api-macro/tests/ext-schema.rs b/proxmox-api-macro/tests/ext-schema.rs index e9f847d..9fce967 100644 --- a/proxmox-api-macro/tests/ext-schema.rs +++ b/proxmox-api-macro/tests/ext-schema.rs @@ -120,7 +120,7 @@ pub fn get_some_text() -> Result { returns: { properties: { "text": { - schema: API_RETURN_SCHEMA_GET_SOME_TEXT + schema: *API_METHOD_GET_SOME_TEXT.returns.schema }, }, }, diff --git a/proxmox-api-macro/tests/types.rs b/proxmox-api-macro/tests/types.rs index e121c3f..80d09ba 100644 --- a/proxmox-api-macro/tests/types.rs +++ b/proxmox-api-macro/tests/types.rs @@ -144,7 +144,7 @@ fn selection_test() { selection: { type: Selection }, } }, - returns: { type: Boolean }, + returns: { optional: true, type: Boolean }, )] /// Check a string. /// @@ -167,7 +167,10 @@ fn string_check_schema_test() { ], ), ) - .returns(&::proxmox::api::schema::BooleanSchema::new("Whether the string was \"ok\".").schema()) + .returns(::proxmox::api::router::ReturnType::new( + true, + &::proxmox::api::schema::BooleanSchema::new("Whether the string was \"ok\".").schema(), + )) .protected(false); assert_eq!(TEST_METHOD, API_METHOD_STRING_CHECK); -- 2.20.1