feat: add `serde-lite` codec for server functions (#2168)

This commit is contained in:
Rakshith Ravi 2024-01-09 01:55:15 +00:00 committed by Greg Johnston
parent f6ce82c9d1
commit a1bd84f3dc
7 changed files with 119 additions and 20 deletions

View File

@ -61,7 +61,7 @@ nightly = [
"leptos_server/nightly",
]
serde = ["leptos_reactive/serde"]
serde-lite = ["leptos_reactive/serde-lite"]
serde-lite = ["leptos_reactive/serde-lite", "server_fn/serde-lite"]
miniserde = ["leptos_reactive/miniserde"]
rkyv = ["leptos_reactive/rkyv"]
tracing = ["leptos_macro/tracing"]

View File

@ -10,7 +10,7 @@ description = "Reactive system for the Leptos web framework."
[dependencies]
slotmap = { version = "1", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
serde-lite = { version = "0.4", optional = true }
serde-lite = { version = "0.5", optional = true }
futures = { version = "0.3" }
js-sys = { version = "0.3", optional = true }
miniserde = { version = "0.1", optional = true }
@ -24,7 +24,7 @@ bytecheck = { version = "0.7", features = [
"simdutf8",
], optional = true }
rustc-hash = "1"
serde-wasm-bindgen = "0.5"
serde-wasm-bindgen = "0.6"
serde_json = "1"
spin-sdk = { version = "2", optional = true }
base64 = "0.21"

View File

@ -33,6 +33,7 @@ multer = { version = "3", optional = true }
## output encodings
# serde
serde_json = "1"
serde-lite = { version = "0.5", features = ["derive"], optional = true }
futures = "0.3"
http = { version = "1" }
ciborium = { version = "0.2", optional = true }
@ -83,6 +84,7 @@ browser = [
"dep:wasm-bindgen-futures",
]
json = []
serde-lite = ["dep:serde-lite"]
multipart = ["dep:multer"]
url = ["dep:serde_qs"]
cbor = ["dep:ciborium"]

View File

@ -2,19 +2,24 @@
mod cbor;
#[cfg(feature = "cbor")]
pub use cbor::*;
#[cfg(feature = "json")]
mod json;
use http::Method;
#[cfg(feature = "json")]
pub use json::*;
#[cfg(feature = "serde-lite")]
mod serde_lite;
#[cfg(feature = "serde-lite")]
pub use serde_lite::*;
#[cfg(feature = "rkyv")]
mod rkyv;
#[cfg(feature = "rkyv")]
pub use rkyv::*;
#[cfg(feature = "url")]
mod url;
use crate::{error::ServerFnError, request::ClientReq};
use futures::Future;
#[cfg(feature = "url")]
pub use url::*;
@ -24,6 +29,9 @@ mod multipart;
pub use multipart::*;
mod stream;
use crate::{error::ServerFnError, request::ClientReq};
use futures::Future;
use http::Method;
pub use stream::*;
pub trait FromReq<CustErr, Request, Encoding>

View File

@ -0,0 +1,82 @@
use super::{Encoding, FromReq, FromRes};
use crate::{
error::ServerFnError,
request::{ClientReq, Req},
response::{ClientRes, Res},
IntoReq, IntoRes,
};
use http::Method;
use serde_lite::{Deserialize, Serialize};
/// Pass arguments and receive responses as JSON in the body of a `POST` request.
pub struct SerdeLite;
impl Encoding for SerdeLite {
const CONTENT_TYPE: &'static str = "application/json";
const METHOD: Method = Method::POST;
}
impl<CustErr, T, Request> IntoReq<CustErr, Request, SerdeLite> for T
where
Request: ClientReq<CustErr>,
T: Serialize + Send,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>> {
let data = serde_json::to_string(
&self
.serialize()
.map_err(|e| ServerFnError::Serialization(e.to_string()))?,
)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
Request::try_new_post(path, accepts, SerdeLite::CONTENT_TYPE, data)
}
}
impl<CustErr, T, Request> FromReq<CustErr, Request, SerdeLite> for T
where
Request: Req<CustErr> + Send + 'static,
T: Deserialize,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
let string_data = req.try_into_string().await?;
Self::deserialize(
&serde_json::from_str(&string_data)
.map_err(|e| ServerFnError::Args(e.to_string()))?,
)
.map_err(|e| ServerFnError::Args(e.to_string()))
}
}
impl<CustErr, T, Response> IntoRes<CustErr, Response, SerdeLite> for T
where
Response: Res<CustErr>,
T: Serialize + Send,
{
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
let data = serde_json::to_string(
&self
.serialize()
.map_err(|e| ServerFnError::Serialization(e.to_string()))?,
)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
Response::try_from_string(SerdeLite::CONTENT_TYPE, data)
}
}
impl<CustErr, T, Response> FromRes<CustErr, Response, SerdeLite> for T
where
Response: ClientRes<CustErr> + Send,
T: Deserialize + Send,
{
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
let data = res.try_into_string().await?;
Self::deserialize(
&&serde_json::from_str(&data)
.map_err(|e| ServerFnError::Args(e.to_string()))?,
)
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
}
}

View File

@ -28,6 +28,9 @@ use request::Req;
use response::{ClientRes, Res};
#[doc(hidden)]
pub use serde;
#[doc(hidden)]
#[cfg(feature = "serde-lite")]
pub use serde_lite;
use std::{fmt::Display, future::Future, pin::Pin, str::FromStr, sync::Arc};
#[doc(hidden)]
pub use xxhash_rust;

View File

@ -74,10 +74,8 @@ pub fn server_macro_impl(
} = args;
let prefix = prefix.unwrap_or_else(|| Literal::string(default_path));
let fn_path = fn_path.unwrap_or_else(|| Literal::string(""));
let input = input.unwrap_or_else(|| syn::parse_quote!(PostUrl));
let input_is_rkyv = input == "Rkyv";
let input_is_multipart = input == "MultipartFormData";
let input = codec_ident(server_fn_path.as_ref(), input);
let input_ident = input.unwrap_or_else(|| syn::parse_quote!(PostUrl));
let input = codec_ident(server_fn_path.as_ref(), input_ident.clone());
let output = output.unwrap_or_else(|| syn::parse_quote!(Json));
let output = codec_ident(server_fn_path.as_ref(), output);
// default to PascalCase version of function name if no struct name given
@ -309,17 +307,23 @@ pub fn server_macro_impl(
}
};
// TODO rkyv derives
let derives = if input_is_multipart {
quote! {}
} else if input_is_rkyv {
todo!("implement derives for Rkyv")
} else {
quote! {
Clone, #server_fn_path::serde::Serialize, #server_fn_path::serde::Deserialize
}
let (is_serde, derives) = match input_ident.to_string().as_str() {
"Rkyv" => todo!("implement derives for Rkyv"),
"MultipartFormData" => (false, quote! {}),
"SerdeLite" => (
true,
quote! {
Clone, #server_fn_path::serde_lite::Serialize, #server_fn_path::serde_lite::Deserialize
},
),
_ => (
true,
quote! {
Clone, #server_fn_path::serde::Serialize, #server_fn_path::serde::Deserialize
},
),
};
let serde_path = (!input_is_multipart && !input_is_rkyv).then(|| {
let serde_path = is_serde.then(|| {
quote! {
#[serde(crate = #serde_path)]
}