From a1bd84f3dc3060a0a6555dec932613227033d495 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Tue, 9 Jan 2024 01:55:15 +0000 Subject: [PATCH] feat: add `serde-lite` codec for server functions (#2168) --- leptos/Cargo.toml | 2 +- leptos_reactive/Cargo.toml | 4 +- server_fn/Cargo.toml | 2 + server_fn/src/codec/mod.rs | 14 ++++-- server_fn/src/codec/serde_lite.rs | 82 +++++++++++++++++++++++++++++++ server_fn/src/lib.rs | 3 ++ server_fn_macro/src/lib.rs | 32 ++++++------ 7 files changed, 119 insertions(+), 20 deletions(-) create mode 100644 server_fn/src/codec/serde_lite.rs diff --git a/leptos/Cargo.toml b/leptos/Cargo.toml index bbc0ddc4b..730e7ff79 100644 --- a/leptos/Cargo.toml +++ b/leptos/Cargo.toml @@ -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"] diff --git a/leptos_reactive/Cargo.toml b/leptos_reactive/Cargo.toml index 6abd63934..35960a3d4 100644 --- a/leptos_reactive/Cargo.toml +++ b/leptos_reactive/Cargo.toml @@ -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" diff --git a/server_fn/Cargo.toml b/server_fn/Cargo.toml index 2f8bd20fd..53dd8495a 100644 --- a/server_fn/Cargo.toml +++ b/server_fn/Cargo.toml @@ -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"] diff --git a/server_fn/src/codec/mod.rs b/server_fn/src/codec/mod.rs index aacb15953..f4c6fa60d 100644 --- a/server_fn/src/codec/mod.rs +++ b/server_fn/src/codec/mod.rs @@ -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 diff --git a/server_fn/src/codec/serde_lite.rs b/server_fn/src/codec/serde_lite.rs new file mode 100644 index 000000000..c6054c9c2 --- /dev/null +++ b/server_fn/src/codec/serde_lite.rs @@ -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 IntoReq for T +where + Request: ClientReq, + T: Serialize + Send, +{ + fn into_req( + self, + path: &str, + accepts: &str, + ) -> Result> { + 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 FromReq for T +where + Request: Req + Send + 'static, + T: Deserialize, +{ + async fn from_req(req: Request) -> Result> { + 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 IntoRes for T +where + Response: Res, + T: Serialize + Send, +{ + async fn into_res(self) -> Result> { + 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 FromRes for T +where + Response: ClientRes + Send, + T: Deserialize + Send, +{ + async fn from_res(res: Response) -> Result> { + 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())) + } +} diff --git a/server_fn/src/lib.rs b/server_fn/src/lib.rs index add0f537f..9314eba8b 100644 --- a/server_fn/src/lib.rs +++ b/server_fn/src/lib.rs @@ -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; diff --git a/server_fn_macro/src/lib.rs b/server_fn_macro/src/lib.rs index eca952367..b797fee2d 100644 --- a/server_fn_macro/src/lib.rs +++ b/server_fn_macro/src/lib.rs @@ -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)] }