From c4cc3e944b67e13f3540fd7ee69560f908d0f1d8 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Mon, 12 Dec 2022 09:26:46 -0500 Subject: [PATCH] Merge in changes from `main` --- integrations/actix/src/lib.rs | 111 +++++++++++++----- leptos/Cargo.toml | 1 + leptos/src/lib.rs | 1 + leptos_config/Cargo.toml | 11 ++ leptos_config/src/lib.rs | 110 ++++++++++++++++++ leptos_dom/Cargo.toml | 5 +- leptos_macro/Cargo.toml | 1 + leptos_macro/src/server.rs | 21 +++- leptos_server/Cargo.toml | 6 + leptos_server/src/lib.rs | 213 +++++++++++++++++++++++++++++----- 10 files changed, 418 insertions(+), 62 deletions(-) create mode 100644 leptos_config/Cargo.toml create mode 100644 leptos_config/src/lib.rs diff --git a/integrations/actix/src/lib.rs b/integrations/actix/src/lib.rs index 8cd2679f6..e8bc1d7df 100644 --- a/integrations/actix/src/lib.rs +++ b/integrations/actix/src/lib.rs @@ -1,4 +1,4 @@ -use actix_web::*; +use actix_web::{web::Bytes, *}; use futures::StreamExt; use leptos::*; use leptos_meta::*; @@ -62,9 +62,12 @@ pub fn handle_server_fns() -> Route { disposer.dispose(); runtime.dispose(); - // if this is Accept: application/json then send a serialized JSON response - if let Some("application/json") = accept_header { - HttpResponse::Ok().body(serialized) + let mut res: HttpResponseBuilder; + if accept_header == Some("application/json") + || accept_header == Some("application/x-www-form-urlencoded") + || accept_header == Some("application/cbor") + { + res = HttpResponse::Ok() } // otherwise, it's probably a
submit or something: redirect back to the referrer else { @@ -73,10 +76,23 @@ pub fn handle_server_fns() -> Route { .get("Referer") .and_then(|value| value.to_str().ok()) .unwrap_or("/"); - HttpResponse::SeeOther() - .insert_header(("Location", referer)) - .content_type("application/json") - .body(serialized) + res = HttpResponse::SeeOther(); + res.insert_header(("Location", referer)) + .content_type("application/json"); + }; + match serialized { + Payload::Binary(data) => { + res.content_type("application/cbor"); + res.body(Bytes::from(data)) + } + Payload::Url(data) => { + res.content_type("application/x-www-form-urlencoded"); + res.body(data) + } + Payload::Json(data) => { + res.content_type("application/json"); + res.body(data) + } } } Err(e) => HttpResponse::InternalServerError().body(e.to_string()), @@ -103,32 +119,40 @@ pub fn handle_server_fns() -> Route { /// ``` /// use actix_web::{HttpServer, App}; /// use leptos::*; +/// use std::{env,net::SocketAddr}; /// /// #[component] -/// fn MyApp(cx: Scope) -> Element { +/// fn MyApp(cx: Scope) -> impl IntoView { /// view! { cx,
"Hello, world!"
} /// } /// /// # if false { // don't actually try to run a server in a doctest... /// #[actix_web::main] /// async fn main() -> std::io::Result<()> { -/// HttpServer::new(|| { +/// +/// let addr = SocketAddr::from(([127,0,0,1],3000)); +/// HttpServer::new(move || { +/// let render_options: RenderOptions = RenderOptions::builder().pkg_path("/pkg/leptos_example").reload_port(3001).socket_address(addr.clone()).environment(&env::var("RUST_ENV")).build(); +/// render_options.write_to_file(); /// App::new() /// // {tail:.*} passes the remainder of the URL as the route /// // the actual routing will be handled by `leptos_router` -/// .route("/{tail:.*}", leptos_actix::render_app_to_stream("leptos_example", |cx| view! { cx, })) +/// .route("/{tail:.*}", leptos_actix::render_app_to_stream(render_options, |cx| view! { cx, })) /// }) -/// .bind(("127.0.0.1", 8080))? +/// .bind(&addr)? /// .run() /// .await /// } /// # } /// ``` -pub fn render_app_to_stream( - client_pkg_name: &'static str, - app_fn: impl Fn(leptos::Scope) -> Element + Clone + 'static, -) -> Route { +pub fn render_app_to_stream( + options: RenderOptions, + app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static, +) -> Route +where IV: IntoView +{ web::get().to(move |req: HttpRequest| { + let options = options.clone(); let app_fn = app_fn.clone(); async move { let path = req.path(); @@ -148,29 +172,60 @@ pub fn render_app_to_stream( provide_context(cx, MetaContext::new()); provide_context(cx, req.clone()); - (app_fn)(cx) + (app_fn)(cx).into_view(cx) } }; - let head = format!(r#" - + let pkg_path = &options.pkg_path; + let socket_ip = &options.socket_address.ip().to_string(); + let reload_port = options.reload_port; + + let leptos_autoreload = match options.environment { + RustEnv::DEV => format!( + r#" + + "# + ), + RustEnv::PROD => "".to_string(), + }; + + let head = format!( + r#" + - "#); + + + + {leptos_autoreload} + "# + ); + let tail = ""; - HttpResponse::Ok().content_type("text/html").streaming( - futures::stream::once(async move { head.clone() }) - // TODO this leaks a runtime once per invocation - .chain(render_to_stream(move |cx| { - let app = app(cx); + let html_stream = futures::stream::once(async move { head.clone() }) + .chain(render_to_stream_with_prefix( + app, + |cx| { let head = use_context::(cx) .map(|meta| meta.dehydrate()) .unwrap_or_default(); - format!("{head}{app}") - })) - .chain(futures::stream::once(async { tail.to_string() })) + format!("{head}").into() + } + )) + .chain(futures::stream::once(async { tail.to_string() })); + + HttpResponse::Ok().content_type("text/html").streaming( + html_stream .map(|html| Ok(web::Bytes::from(html)) as Result), ) } diff --git a/leptos/Cargo.toml b/leptos/Cargo.toml index f8851c666..c3f9c57ee 100644 --- a/leptos/Cargo.toml +++ b/leptos/Cargo.toml @@ -9,6 +9,7 @@ description = "Leptos is a full-stack, isomorphic Rust web framework leveraging readme = "../README.md" [dependencies] +leptos_config = { path = "../leptos_config", default-features = false, version = "0.0.18" } leptos_core = { path = "../leptos_core", default-features = false, version = "0.0.18" } leptos_dom = { path = "../leptos_dom", default-features = false, version = "0.0.18" } leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.0.18" } diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs index 81f294b42..fa177508e 100644 --- a/leptos/src/lib.rs +++ b/leptos/src/lib.rs @@ -126,6 +126,7 @@ //! # } //! ``` +pub use leptos_config::*; pub use leptos_core::*; pub use leptos_dom; pub use leptos_dom::wasm_bindgen::{JsCast, UnwrapThrowExt}; diff --git a/leptos_config/Cargo.toml b/leptos_config/Cargo.toml new file mode 100644 index 000000000..20798ac79 --- /dev/null +++ b/leptos_config/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "leptos_config" +version = "0.0.18" +edition = "2021" +authors = ["Greg Johnston"] +license = "MIT" +repository = "https://github.com/gbj/leptos" +description = "Configuraiton for the Leptos web framework." + +[dependencies] +typed-builder = "0.11.0" diff --git a/leptos_config/src/lib.rs b/leptos_config/src/lib.rs new file mode 100644 index 000000000..568af6538 --- /dev/null +++ b/leptos_config/src/lib.rs @@ -0,0 +1,110 @@ +use std::{env::VarError, net::SocketAddr, str::FromStr}; +use typed_builder::TypedBuilder; + +/// This struct serves as a convenient place to store details used for rendering. +/// It's serialized into a file in the root called `.leptos.kdl` for cargo-leptos +/// to watch. It's also used in our actix and axum integrations to generate the +/// correct path for WASM, JS, and Websockets. Its goal is to be the single source +/// of truth for render options +#[derive(TypedBuilder, Clone)] +pub struct RenderOptions { + /// The path and name of the WASM and JS files generated by wasm-bindgen + /// For example, `/pkg/app` might be a valid input if your crate name was `app`. + #[builder(setter(into))] + pub pkg_path: String, + /// Used to control whether the Websocket code for code watching is included. + /// I recommend passing in the result of `env::var("RUST_ENV")` + #[builder(setter(into), default)] + pub environment: RustEnv, + /// Provides a way to control the address leptos is served from. + /// Using an env variable here would allow you to run the same code in dev and prod + /// Defaults to `127.0.0.1:3000` + #[builder(setter(into), default=SocketAddr::from(([127,0,0,1], 3000)))] + pub socket_address: SocketAddr, + /// The port the Websocket watcher listens on. Should match the `reload_port` in cargo-leptos(if using). + /// Defaults to `3001` + #[builder(default = 3001)] + pub reload_port: u32, +} + +impl RenderOptions { + /// Creates a hidden file at ./.leptos_toml so cargo-leptos can monitor settings. We do not read from this file + /// only write to it, you'll want to change the settings in your main function when you create RenderOptions + pub fn write_to_file(&self) { + use std::fs; + let options = format!( + r#"// This file is auto-generated. Changing it will have no effect on leptos. Change these by changing RenderOptions and rerunning +RenderOptions {{ + pkg-path "{}" + environment "{:?}" + socket-address "{:?}" + reload-port {:?} +}} +"#, + self.pkg_path, self.environment, self.socket_address, self.reload_port + ); + fs::write("./.leptos.kdl", options).expect("Unable to write file"); + } +} +/// An enum that can be used to define the environment Leptos is running in. Can be passed to RenderOptions. +/// Setting this to the PROD variant will not include the websockets code for cargo-leptos' watch. +/// Defaults to PROD +#[derive(Debug, Clone)] +pub enum RustEnv { + PROD, + DEV, +} + +impl Default for RustEnv { + fn default() -> Self { + Self::PROD + } +} + +impl FromStr for RustEnv { + type Err = (); + fn from_str(input: &str) -> Result { + let sanitized = input.to_lowercase(); + match sanitized.as_ref() { + "dev" => Ok(Self::DEV), + "development" => Ok(Self::DEV), + "prod" => Ok(Self::PROD), + "production" => Ok(Self::PROD), + _ => Ok(Self::PROD), + } + } +} + +impl From<&str> for RustEnv { + fn from(str: &str) -> Self { + let sanitized = str.to_lowercase(); + match sanitized.as_str() { + "dev" => Self::DEV, + "development" => Self::DEV, + "prod" => Self::PROD, + "production" => Self::PROD, + _ => { + panic!("Environment var is not recognized. Maybe try `dev` or `prod`") + } + } + } +} +impl From<&Result> for RustEnv { + fn from(input: &Result) -> Self { + match input { + Ok(str) => { + let sanitized = str.to_lowercase(); + match sanitized.as_ref() { + "dev" => Self::DEV, + "development" => Self::DEV, + "prod" => Self::PROD, + "production" => Self::PROD, + _ => { + panic!("Environment var is not recognized. Maybe try `dev` or `prod`") + } + } + } + Err(_) => Self::PROD, + } + } +} diff --git a/leptos_dom/Cargo.toml b/leptos_dom/Cargo.toml index 4ed403cb9..a1639186d 100644 --- a/leptos_dom/Cargo.toml +++ b/leptos_dom/Cargo.toml @@ -10,6 +10,7 @@ description = "DOM operations for the Leptos web framework." [dependencies] cfg-if = "1" educe = "0.4" +futures = "0.3" gloo = "0.8" html-escape = "0.2" indexmap = "1.9" @@ -19,6 +20,7 @@ leptos_reactive = { path = "../leptos_reactive", default-features = false, versi pad-adapter = "0.1" paste = "1" rustc-hash = "1.1.0" +serde_json = "1" smallvec = "1" tracing = "0.1" typed-builder = "0.11" @@ -66,5 +68,6 @@ features = [ [features] default = ["web"] -web = [] +web = ["leptos_reactive/csr", "leptos/csr"] +ssr = ["leptos_reactive/ssr", "leptos/ssr"] stable = ["leptos_reactive/stable"] diff --git a/leptos_macro/Cargo.toml b/leptos_macro/Cargo.toml index 42eaed309..d5def274f 100644 --- a/leptos_macro/Cargo.toml +++ b/leptos_macro/Cargo.toml @@ -21,6 +21,7 @@ syn-rsx = "0.9" uuid = { version = "1", features = ["v4"] } leptos_dom = { path = "../leptos_dom", version = "0.0.18" } leptos_reactive = { path = "../leptos_reactive", version = "0.0.18" } +leptos_server = { path = "../leptos_server", version = "0.0.18" } lazy_static = "1.4" [dev-dependencies] diff --git a/leptos_macro/src/server.rs b/leptos_macro/src/server.rs index 0d8424e56..cbc158bb6 100644 --- a/leptos_macro/src/server.rs +++ b/leptos_macro/src/server.rs @@ -1,4 +1,5 @@ use cfg_if::cfg_if; +use leptos_server::Encoding; use proc_macro2::{Literal, TokenStream as TokenStream2}; use quote::quote; use syn::{ @@ -26,9 +27,14 @@ pub fn server_macro_impl(args: proc_macro::TokenStream, s: TokenStream2) -> Resu let ServerFnName { struct_name, prefix, + encoding, .. } = syn::parse::(args)?; let prefix = prefix.unwrap_or_else(|| Literal::string("")); + let encoding = match encoding { + Encoding::Cbor => quote! { ::leptos::Encoding::Cbor }, + Encoding::Url => quote! { ::leptos::Encoding::Url }, + }; let body = syn::parse::(s.into())?; let fn_name = &body.ident; @@ -40,7 +46,10 @@ pub fn server_macro_impl(args: proc_macro::TokenStream, s: TokenStream2) -> Resu if #[cfg(not(feature = "stable"))] { use proc_macro::Span; let span = Span::call_site(); + #[cfg(not(target_os = "windows"))] let url = format!("{}/{}", span.source_file().path().to_string_lossy(), fn_name_as_str).replace("/", "-"); + #[cfg(target_os = "windows")] + let url = format!("{}/{}", span.source_file().path().to_string_lossy(), fn_name_as_str).replace("\\", "-"); } else { let url = fn_name_as_str; } @@ -135,6 +144,10 @@ pub fn server_macro_impl(args: proc_macro::TokenStream, s: TokenStream2) -> Resu #url } + fn encoding() -> ::leptos::Encoding { + #encoding + } + #[cfg(feature = "ssr")] fn call_fn(self, cx: ::leptos::Scope) -> std::pin::Pin>>> { let #struct_name { #(#field_names),* } = self; @@ -157,7 +170,7 @@ pub fn server_macro_impl(args: proc_macro::TokenStream, s: TokenStream2) -> Resu #vis async fn #fn_name(#(#fn_args_2),*) #output_arrow #return_ty { let prefix = #struct_name::prefix().to_string(); let url = prefix + "/" + #struct_name::url(); - ::leptos::call_server_fn(&url, #struct_name { #(#field_names_5),* }).await + ::leptos::call_server_fn(&url, #struct_name { #(#field_names_5),* }, #encoding).await } }) } @@ -166,6 +179,8 @@ pub struct ServerFnName { struct_name: Ident, _comma: Option, prefix: Option, + _comma2: Option, + encoding: Encoding, } impl Parse for ServerFnName { @@ -173,11 +188,15 @@ impl Parse for ServerFnName { let struct_name = input.parse()?; let _comma = input.parse()?; let prefix = input.parse()?; + let _comma2 = input.parse()?; + let encoding = input.parse().unwrap_or(Encoding::Url); Ok(Self { struct_name, _comma, prefix, + _comma2, + encoding, }) } } diff --git a/leptos_server/Cargo.toml b/leptos_server/Cargo.toml index f960edc58..726074956 100644 --- a/leptos_server/Cargo.toml +++ b/leptos_server/Cargo.toml @@ -18,6 +18,12 @@ log = "0.4" serde = { version = "1", features = ["derive"] } serde_urlencoded = "0.7" thiserror = "1" +rmp-serde = "1.1.1" +serde_json = "1.0.89" +quote = "1" +syn = { version = "1", features = ["full", "parsing", "extra-traits"] } +proc-macro2 = "1.0.47" +ciborium = "0.2.0" [dev-dependencies] leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.0" } diff --git a/leptos_server/src/lib.rs b/leptos_server/src/lib.rs index d4144e3a2..888aeee56 100644 --- a/leptos_server/src/lib.rs +++ b/leptos_server/src/lib.rs @@ -27,8 +27,9 @@ //! //! ### `#[server]` //! -//! The `#[server]` macro allows you to annotate a function to indicate that it should only run -//! on the server (i.e., when you have an `ssr` feature in your crate that is enabled). +//! The [`#[server]` macro](leptos::leptos_macro::server) allows you to annotate a function to +//! indicate that it should only run on the server (i.e., when you have an `ssr` feature in your +//! crate that is enabled). //! //! ```rust,ignore //! # use leptos_reactive::*; @@ -62,15 +63,23 @@ //! This should be fairly obvious: we have to serialize arguments to send them to the server, and we //! need to deserialize the result to return it to the client. //! - **Arguments must be implement [serde::Serialize].** They are serialized as an `application/x-www-form-urlencoded` -//! form data using [`serde_urlencoded`](https://docs.rs/serde_urlencoded/latest/serde_urlencoded/). +//! form data using [`serde_urlencoded`](https://docs.rs/serde_urlencoded/latest/serde_urlencoded/) or as `application/cbor` +//! using [`cbor`](https://docs.rs/cbor/latest/cbor/). //! - **The [Scope](leptos_reactive::Scope) comes from the server.** Optionally, the first argument of a server function //! can be a Leptos [Scope](leptos_reactive::Scope). This scope can be used to inject dependencies like the HTTP request //! or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client. pub use form_urlencoded; use leptos_reactive::*; + +use proc_macro2::{Literal, TokenStream}; +use quote::TokenStreamExt; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{future::Future, pin::Pin}; +use std::{future::Future, pin::Pin, str::FromStr}; +use syn::{ + parse::{Parse, ParseStream}, + parse_quote, +}; use thiserror::Error; mod action; @@ -85,7 +94,7 @@ use std::{ }; #[cfg(any(feature = "ssr", doc))] -type ServerFnTraitObj = dyn Fn(Scope, &[u8]) -> Pin>>> +type ServerFnTraitObj = dyn Fn(Scope, &[u8]) -> Pin>>> + Send + Sync; @@ -94,6 +103,17 @@ lazy_static::lazy_static! { static ref REGISTERED_SERVER_FUNCTIONS: Arc>>> = Default::default(); } +/// A dual type to hold the possible Response datatypes +#[derive(Debug)] +pub enum Payload { + ///Encodes Data using CBOR + Binary(Vec), + ///Encodes data in the URL + Url(String), + ///Encodes Data using Json + Json(String), +} + /// Attempts to find a server function registered at the given path. /// /// This can be used by a server to handle the requests, as in the following example (using `actix-web`) @@ -145,6 +165,54 @@ pub fn server_fn_by_path(path: &str) -> Option> { .and_then(|fns| fns.get(path).cloned()) } +/// Holds the current options for encoding types. +/// More could be added, but they need to be serde +#[derive(Debug, PartialEq)] +pub enum Encoding { + /// A Binary Encoding Scheme Called Cbor + Cbor, + /// The Default URL-encoded encoding method + Url, +} + +impl FromStr for Encoding { + type Err = (); + + fn from_str(input: &str) -> Result { + match input { + "URL" => Ok(Encoding::Url), + "Cbor" => Ok(Encoding::Cbor), + _ => Err(()), + } + } +} + +impl quote::ToTokens for Encoding { + fn to_tokens(&self, tokens: &mut TokenStream) { + let option: syn::Ident = match *self { + Encoding::Cbor => parse_quote!(Cbor), + Encoding::Url => parse_quote!(Url), + }; + let expansion: syn::Ident = syn::parse_quote! { + Encoding::#option + }; + tokens.append(expansion); + } +} + +impl Parse for Encoding { + fn parse(input: ParseStream) -> syn::Result { + let variant_name: String = input.parse::()?.to_string(); + + // Need doubled quotes because variant_name doubles it + match variant_name.as_ref() { + "\"Url\"" => Ok(Self::Url), + "\"Cbor\"" => Ok(Self::Cbor), + _ => panic!("Encoding Not Found"), + } + } +} + /// Defines a "server function." A server function can be called from the server or the client, /// but the body of its code will only be run on the server, i.e., if a crate feature `ssr` is enabled. /// @@ -162,7 +230,7 @@ where Self: Serialize + DeserializeOwned + Sized + 'static, { /// The return type of the function. - type Output: Serializable; + type Output: Serialize; /// URL prefix that should be prepended by the client to the generated URL. fn prefix() -> &'static str; @@ -170,6 +238,9 @@ where /// The path at which the server function can be reached on the server. fn url() -> &'static str; + /// The path at which the server function can be reached on the server. + fn encoding() -> Encoding; + /// Runs the function on the server. #[cfg(any(feature = "ssr", doc))] fn call_fn( @@ -189,12 +260,20 @@ where fn register() -> Result<(), ServerFnError> { // create the handler for this server function // takes a String -> returns its async value + let run_server_fn = Arc::new(|cx: Scope, data: &[u8]| { // decode the args - let value = serde_urlencoded::from_bytes::(data) - .map_err(|e| ServerFnError::Deserialization(e.to_string())); + let value = match Self::encoding() { + Encoding::Url => serde_urlencoded::from_bytes(data) + .map_err(|e| ServerFnError::Deserialization(e.to_string())), + Encoding::Cbor => { + println!("Deserialize Cbor!: {:x?}", &data); + ciborium::de::from_reader(data) + .map_err(|e| ServerFnError::Deserialization(e.to_string())) + } + }; Box::pin(async move { - let value = match value { + let value: Self = match value { Ok(v) => v, Err(e) => return Err(e), }; @@ -206,16 +285,26 @@ where }; // serialize the output - let result = match result - .to_json() - .map_err(|e| ServerFnError::Serialization(e.to_string())) - { - Ok(r) => r, - Err(e) => return Err(e), + let result = match Self::encoding() { + Encoding::Url => match serde_json::to_string(&result) + .map_err(|e| ServerFnError::Serialization(e.to_string())) + { + Ok(r) => Payload::Url(r), + Err(e) => return Err(e), + }, + Encoding::Cbor => { + let mut buffer: Vec = Vec::new(); + match ciborium::ser::into_writer(&result, &mut buffer) + .map_err(|e| ServerFnError::Serialization(e.to_string())) + { + Ok(_) => Payload::Binary(buffer), + Err(e) => return Err(e), + } + } }; Ok(result) - }) as Pin>>> + }) as Pin>>> }); // store it in the hashmap @@ -256,20 +345,69 @@ pub enum ServerFnError { /// Executes the HTTP call to call a server function from the client, given its URL and argument type. #[cfg(not(feature = "ssr"))] -pub async fn call_server_fn(url: &str, args: impl ServerFn) -> Result +pub async fn call_server_fn( + url: &str, + args: impl ServerFn, + enc: Encoding, +) -> Result where - T: Serializable + Sized, + T: serde::Serialize + serde::de::DeserializeOwned + Sized, { - let args_form_data = serde_urlencoded::to_string(&args) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + use ciborium::ser::into_writer; + use leptos_dom::js_sys::Uint8Array; + use serde_json::Deserializer as JSONDeserializer; - let resp = gloo_net::http::Request::post(url) - .header("Content-Type", "application/x-www-form-urlencoded") - .header("Accept", "application/json") - .body(args_form_data) - .send() - .await - .map_err(|e| ServerFnError::Request(e.to_string()))?; + #[derive(Debug)] + enum Payload { + Binary(Vec), + Url(String), + } + // log!("ARGS TO ENCODE: {:#}", &args); + let args_encoded = match &enc { + Encoding::Url => Payload::Url( + serde_urlencoded::to_string(&args) + .map_err(|e| ServerFnError::Serialization(e.to_string()))?, + ), + Encoding::Cbor => { + let mut buffer: Vec = Vec::new(); + into_writer(&args, &mut buffer) + .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + Payload::Binary(buffer) + } + }; + + //log!("ENCODED DATA: {:#?}", args_encoded); + + let content_type_header = match &enc { + Encoding::Url => "application/x-www-form-urlencoded", + Encoding::Cbor => "application/cbor", + }; + + let accept_header = match &enc { + Encoding::Url => "application/x-www-form-urlencoded", + Encoding::Cbor => "application/cbor", + }; + + let resp = match args_encoded { + Payload::Binary(b) => { + let slice_ref: &[u8] = &b; + let js_array = Uint8Array::from(slice_ref).buffer(); + gloo_net::http::Request::post(url) + .header("Content-Type", content_type_header) + .header("Accept", accept_header) + .body(js_array) + .send() + .await + .map_err(|e| ServerFnError::Request(e.to_string()))? + } + Payload::Url(s) => gloo_net::http::Request::post(url) + .header("Content-Type", content_type_header) + .header("Accept", accept_header) + .body(s) + .send() + .await + .map_err(|e| ServerFnError::Request(e.to_string()))?, + }; // check for error status let status = resp.status(); @@ -277,10 +415,21 @@ where return Err(ServerFnError::ServerError(resp.status_text())); } - let text = resp - .text() - .await - .map_err(|e| ServerFnError::Deserialization(e.to_string()))?; + if enc == Encoding::Cbor { + let binary = resp + .binary() + .await + .map_err(|e| ServerFnError::Deserialization(e.to_string()))?; - T::from_json(&text).map_err(|e| ServerFnError::Deserialization(e.to_string())) + ciborium::de::from_reader(binary.as_slice()) + .map_err(|e| ServerFnError::Deserialization(e.to_string())) + } else { + let text = resp + .text() + .await + .map_err(|e| ServerFnError::Deserialization(e.to_string()))?; + + let mut deserializer = JSONDeserializer::from_str(&text); + T::deserialize(&mut deserializer).map_err(|e| ServerFnError::Deserialization(e.to_string())) + } }