Merge in changes from main

This commit is contained in:
Greg Johnston 2022-12-12 09:26:46 -05:00
parent c481e465b0
commit c4cc3e944b
10 changed files with 418 additions and 62 deletions

View file

@ -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 <form> 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, <main>"Hello, world!"</main> }
/// }
///
/// # 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, <MyApp/> }))
/// .route("/{tail:.*}", leptos_actix::render_app_to_stream(render_options, |cx| view! { cx, <MyApp/> }))
/// })
/// .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<IV>(
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#"<!DOCTYPE html>
<html>
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#"
<script crossorigin="">(function () {{
var ws = new WebSocket('ws://{socket_ip}:{reload_port}/autoreload');
ws.onmessage = (ev) => {{
console.log(`Reload message: `);
if (ev.data === 'reload') window.location.reload();
}};
ws.onclose = () => console.warn('Autoreload stopped. Manual reload necessary.');
}})()
</script>
"#
),
RustEnv::PROD => "".to_string(),
};
let head = format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<script type="module">import init, {{ hydrate }} from '/pkg/{client_pkg_name}.js'; init().then(hydrate);</script>"#);
<link rel="modulepreload" href="{pkg_path}.js">
<link rel="preload" href="{pkg_path}.wasm" as="fetch" type="application/wasm" crossorigin="">
<script type="module">import init, {{ hydrate }} from '{pkg_path}.js'; init('{pkg_path}.wasm').then(hydrate);</script>
{leptos_autoreload}
"#
);
let tail = "</body></html>";
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::<MetaContext>(cx)
.map(|meta| meta.dehydrate())
.unwrap_or_default();
format!("{head}</head><body>{app}")
}))
.chain(futures::stream::once(async { tail.to_string() }))
format!("{head}</head><body>").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<web::Bytes>),
)
}

View file

@ -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" }

View file

@ -126,6 +126,7 @@
//! # }
//! ```
pub use leptos_config::*;
pub use leptos_core::*;
pub use leptos_dom;
pub use leptos_dom::wasm_bindgen::{JsCast, UnwrapThrowExt};

11
leptos_config/Cargo.toml Normal file
View file

@ -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"

110
leptos_config/src/lib.rs Normal file
View file

@ -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<Self, Self::Err> {
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<String, VarError>> for RustEnv {
fn from(input: &Result<String, VarError>) -> 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,
}
}
}

View file

@ -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"]

View file

@ -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]

View file

@ -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::<ServerFnName>(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::<ServerFnBody>(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<Box<dyn std::future::Future<Output = Result<Self::Output, ::leptos::ServerFnError>>>> {
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<Token![,]>,
prefix: Option<Literal>,
_comma2: Option<Token![,]>,
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,
})
}
}

View file

@ -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" }

View file

@ -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<Box<dyn Future<Output = Result<String, ServerFnError>>>>
type ServerFnTraitObj = dyn Fn(Scope, &[u8]) -> Pin<Box<dyn Future<Output = Result<Payload, ServerFnError>>>>
+ Send
+ Sync;
@ -94,6 +103,17 @@ lazy_static::lazy_static! {
static ref REGISTERED_SERVER_FUNCTIONS: Arc<RwLock<HashMap<&'static str, Arc<ServerFnTraitObj>>>> = Default::default();
}
/// A dual type to hold the possible Response datatypes
#[derive(Debug)]
pub enum Payload {
///Encodes Data using CBOR
Binary(Vec<u8>),
///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<Arc<ServerFnTraitObj>> {
.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<Encoding, Self::Err> {
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<Self> {
let variant_name: String = input.parse::<Literal>()?.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::<Self>(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<u8> = 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<Box<dyn Future<Output = Result<String, ServerFnError>>>>
}) as Pin<Box<dyn Future<Output = Result<Payload, ServerFnError>>>>
});
// 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<T>(url: &str, args: impl ServerFn) -> Result<T, ServerFnError>
pub async fn call_server_fn<T>(
url: &str,
args: impl ServerFn,
enc: Encoding,
) -> Result<T, ServerFnError>
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<u8>),
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<u8> = 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()))
}
}