From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <d.csapak@proxmox.com>
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 48114BE74
 for <pbs-devel@lists.proxmox.com>; Fri,  8 Apr 2022 11:56:45 +0200 (CEST)
Received: from firstgate.proxmox.com (localhost [127.0.0.1])
 by firstgate.proxmox.com (Proxmox) with ESMTP id 3EF13D351
 for <pbs-devel@lists.proxmox.com>; Fri,  8 Apr 2022 11:56:15 +0200 (CEST)
Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com
 [94.136.29.106])
 (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 5AB32D222
 for <pbs-devel@lists.proxmox.com>; Fri,  8 Apr 2022 11:56:08 +0200 (CEST)
Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1])
 by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 30D8E45A0A
 for <pbs-devel@lists.proxmox.com>; Fri,  8 Apr 2022 11:56:08 +0200 (CEST)
From: Dominik Csapak <d.csapak@proxmox.com>
To: pbs-devel@lists.proxmox.com
Date: Fri,  8 Apr 2022 11:56:03 +0200
Message-Id: <20220408095606.2767234-5-d.csapak@proxmox.com>
X-Mailer: git-send-email 2.30.2
In-Reply-To: <20220408095606.2767234-1-d.csapak@proxmox.com>
References: <20220408095606.2767234-1-d.csapak@proxmox.com>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-SPAM-LEVEL: Spam detection results:  0
 AWL -0.010 Adjusted score from AWL reputation of From: address
 BAYES_00                 -1.9 Bayes spam probability is 0 to 1%
 KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment
 POISEN_SPAM_PILL          0.1 Meta: its spam
 POISEN_SPAM_PILL_2        0.1 random spam to be learned in bayes
 POISEN_SPAM_PILL_4        0.1 random spam to be learned in bayes
 SPF_HELO_NONE           0.001 SPF: HELO does not publish an SPF Record
 SPF_PASS               -0.001 SPF: sender matches SPF record
 T_SCC_BODY_TEXT_LINE    -0.01 -
Subject: [pbs-devel] [PATCH proxmox 4/4] proxmox-api-macro: add 'streaming'
 option
X-BeenThere: pbs-devel@lists.proxmox.com
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: Proxmox Backup Server development discussion
 <pbs-devel.lists.proxmox.com>
List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pbs-devel>, 
 <mailto:pbs-devel-request@lists.proxmox.com?subject=unsubscribe>
List-Archive: <http://lists.proxmox.com/pipermail/pbs-devel/>
List-Post: <mailto:pbs-devel@lists.proxmox.com>
List-Help: <mailto:pbs-devel-request@lists.proxmox.com?subject=help>
List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel>, 
 <mailto:pbs-devel-request@lists.proxmox.com?subject=subscribe>
X-List-Received-Date: Fri, 08 Apr 2022 09:56:45 -0000

to generate the `Streaming` variants of the ApiHandler

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 proxmox-api-macro/src/api/method.rs | 127 +++++++++++++++++++---------
 proxmox-api-macro/tests/api1.rs     |  16 ++++
 2 files changed, 104 insertions(+), 39 deletions(-)

diff --git a/proxmox-api-macro/src/api/method.rs b/proxmox-api-macro/src/api/method.rs
index f82cc53..1db6124 100644
--- a/proxmox-api-macro/src/api/method.rs
+++ b/proxmox-api-macro/src/api/method.rs
@@ -169,6 +169,12 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
         .transpose()?
         .unwrap_or(false);
 
+    let streaming: bool = attribs
+        .remove("streaming")
+        .map(TryFrom::try_from)
+        .transpose()?
+        .unwrap_or(false);
+
     if !attribs.is_empty() {
         error!(
             attribs.span(),
@@ -195,6 +201,7 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
         &mut func,
         &mut wrapper_ts,
         &mut default_consts,
+        streaming,
     )?;
 
     // input schema is done, let's give the method body a chance to extract default parameters:
@@ -217,10 +224,11 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result<T
         returns_schema_setter = quote! { .returns(#inner) };
     }
 
-    let api_handler = if is_async {
-        quote! { ::proxmox_router::ApiHandler::Async(&#api_func_name) }
-    } else {
-        quote! { ::proxmox_router::ApiHandler::Sync(&#api_func_name) }
+    let api_handler = match (streaming, is_async) {
+        (true, true) => quote! { ::proxmox_router::ApiHandler::StreamingAsync(&#api_func_name) },
+        (true, false) => quote! { ::proxmox_router::ApiHandler::StreamingSync(&#api_func_name) },
+        (false, true) => quote! { ::proxmox_router::ApiHandler::Async(&#api_func_name) },
+        (false, false) => quote! { ::proxmox_router::ApiHandler::Sync(&#api_func_name) },
     };
 
     Ok(quote_spanned! { func.sig.span() =>
@@ -279,6 +287,7 @@ fn handle_function_signature(
     func: &mut syn::ItemFn,
     wrapper_ts: &mut TokenStream,
     default_consts: &mut TokenStream,
+    streaming: bool,
 ) -> Result<Ident, Error> {
     let sig = &func.sig;
     let is_async = sig.asyncness.is_some();
@@ -414,6 +423,7 @@ fn handle_function_signature(
         wrapper_ts,
         default_consts,
         is_async,
+        streaming,
     )
 }
 
@@ -471,6 +481,7 @@ fn create_wrapper_function(
     wrapper_ts: &mut TokenStream,
     default_consts: &mut TokenStream,
     is_async: bool,
+    streaming: bool,
 ) -> Result<Ident, Error> {
     let api_func_name = Ident::new(
         &format!("api_function_{}", &func.sig.ident),
@@ -512,45 +523,83 @@ fn create_wrapper_function(
         _ => Some(quote!(?)),
     };
 
-    let body = quote! {
-        if let ::serde_json::Value::Object(ref mut input_map) = &mut input_params {
-            #body
-            Ok(::serde_json::to_value(#func_name(#args) #await_keyword #question_mark)?)
-        } else {
-            ::anyhow::bail!("api function wrapper called with a non-object json value");
-        }
-    };
-
-    if is_async {
-        wrapper_ts.extend(quote! {
-            fn #api_func_name<'a>(
-                mut input_params: ::serde_json::Value,
-                api_method_param: &'static ::proxmox_router::ApiMethod,
-                rpc_env_param: &'a mut dyn ::proxmox_router::RpcEnvironment,
-            ) -> ::proxmox_router::ApiFuture<'a> {
-                //async fn func<'a>(
-                //    mut input_params: ::serde_json::Value,
-                //    api_method_param: &'static ::proxmox_router::ApiMethod,
-                //    rpc_env_param: &'a mut dyn ::proxmox_router::RpcEnvironment,
-                //) -> ::std::result::Result<::serde_json::Value, ::anyhow::Error> {
-                //    #body
-                //}
-                //::std::boxed::Box::pin(async move {
-                //    func(input_params, api_method_param, rpc_env_param).await
-                //})
-                ::std::boxed::Box::pin(async move { #body })
+    let body = if streaming {
+        quote! {
+            if let ::serde_json::Value::Object(ref mut input_map) = &mut input_params {
+                #body
+                let res = #func_name(#args) #await_keyword #question_mark;
+                let res: ::std::boxed::Box<dyn ::proxmox_router::SerializableReturn + Send> = ::std::boxed::Box::new(res);
+                Ok(res)
+            } else {
+                ::anyhow::bail!("api function wrapper called with a non-object json value");
             }
-        });
+        }
     } else {
-        wrapper_ts.extend(quote! {
-            fn #api_func_name(
-                mut input_params: ::serde_json::Value,
-                api_method_param: &::proxmox_router::ApiMethod,
-                rpc_env_param: &mut dyn ::proxmox_router::RpcEnvironment,
-            ) -> ::std::result::Result<::serde_json::Value, ::anyhow::Error> {
+        quote! {
+            if let ::serde_json::Value::Object(ref mut input_map) = &mut input_params {
                 #body
+                Ok(::serde_json::to_value(#func_name(#args) #await_keyword #question_mark)?)
+            } else {
+                ::anyhow::bail!("api function wrapper called with a non-object json value");
             }
-        });
+        }
+    };
+
+    match (streaming, is_async) {
+        (true, true) => {
+            wrapper_ts.extend(quote! {
+                fn #api_func_name<'a>(
+                    mut input_params: ::serde_json::Value,
+                    api_method_param: &'static ::proxmox_router::ApiMethod,
+                    rpc_env_param: &'a mut dyn ::proxmox_router::RpcEnvironment,
+                ) -> ::proxmox_router::StreamingApiFuture<'a> {
+                    ::std::boxed::Box::pin(async move { #body })
+                }
+            });
+        }
+        (true, false) => {
+            wrapper_ts.extend(quote! {
+                fn #api_func_name(
+                    mut input_params: ::serde_json::Value,
+                    api_method_param: &::proxmox_router::ApiMethod,
+                    rpc_env_param: &mut dyn ::proxmox_router::RpcEnvironment,
+                ) -> ::std::result::Result<::std::boxed::Box<dyn ::proxmox_router::SerializableReturn + Send>, ::anyhow::Error> {
+                    #body
+                }
+            });
+        }
+        (false, true) => {
+            wrapper_ts.extend(quote! {
+                fn #api_func_name<'a>(
+                    mut input_params: ::serde_json::Value,
+                    api_method_param: &'static ::proxmox_router::ApiMethod,
+                    rpc_env_param: &'a mut dyn ::proxmox_router::RpcEnvironment,
+                ) -> ::proxmox_router::ApiFuture<'a> {
+                    //async fn func<'a>(
+                    //    mut input_params: ::serde_json::Value,
+                    //    api_method_param: &'static ::proxmox_router::ApiMethod,
+                    //    rpc_env_param: &'a mut dyn ::proxmox_router::RpcEnvironment,
+                    //) -> ::std::result::Result<::serde_json::Value, ::anyhow::Error> {
+                    //    #body
+                    //}
+                    //::std::boxed::Box::pin(async move {
+                    //    func(input_params, api_method_param, rpc_env_param).await
+                    //})
+                    ::std::boxed::Box::pin(async move { #body })
+                }
+            });
+        }
+        (false, false) => {
+            wrapper_ts.extend(quote! {
+                fn #api_func_name(
+                    mut input_params: ::serde_json::Value,
+                    api_method_param: &::proxmox_router::ApiMethod,
+                    rpc_env_param: &mut dyn ::proxmox_router::RpcEnvironment,
+                ) -> ::std::result::Result<::serde_json::Value, ::anyhow::Error> {
+                    #body
+                }
+            });
+        }
     }
 
     Ok(api_func_name)
diff --git a/proxmox-api-macro/tests/api1.rs b/proxmox-api-macro/tests/api1.rs
index fd9a338..ef60370 100644
--- a/proxmox-api-macro/tests/api1.rs
+++ b/proxmox-api-macro/tests/api1.rs
@@ -235,6 +235,22 @@ pub fn basic_function() -> Result<(), Error> {
     Ok(())
 }
 
+#[api(
+    streaming: true,
+)]
+/// streaming async call
+pub async fn streaming_async_call() -> Result<(), Error> {
+    Ok(())
+}
+
+#[api(
+    streaming: true,
+)]
+/// streaming sync call
+pub fn streaming_sync_call() -> Result<(), Error> {
+    Ok(())
+}
+
 #[api(
     input: {
         properties: {
-- 
2.30.2