From 6a882b75b6cc005435a43d6129f3180281d54dd8 Mon Sep 17 00:00:00 2001 From: spinline Date: Wed, 11 Feb 2026 02:01:02 +0300 Subject: [PATCH] feat: implement MessagePack codec for server functions --- shared/src/codec.rs | 111 ++++++++++++++++++++++++++++++++++ shared/src/lib.rs | 2 + shared/src/server_fns/auth.rs | 11 ++-- 3 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 shared/src/codec.rs diff --git a/shared/src/codec.rs b/shared/src/codec.rs new file mode 100644 index 0000000..3fc68e1 --- /dev/null +++ b/shared/src/codec.rs @@ -0,0 +1,111 @@ +use leptos::server_fn::codec::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; +use leptos::server_fn::request::{ClientReq, Req}; +use leptos::server_fn::response::{ClientRes, Res}; +use leptos::server_fn::error::ServerFnError; +use serde::{de::DeserializeOwned, Serialize}; +use std::future::Future; + +pub struct MessagePack; + +impl Encoding for MessagePack { + const CONTENT_TYPE: &'static str = "application/msgpack"; + const METHOD: leptos::server_fn::request::Method = leptos::server_fn::request::Method::POST; +} + +#[cfg(any(feature = "ssr", feature = "hydrate"))] +impl IntoReq for MessagePack +where + Input: Serialize + Send, + Output: Send, +{ + fn into_req(args: Input, path: &str) -> Result { + let data = rmp_serde::to_vec(&args) + .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + + // ClientReq is typically http::Request or similar. + // If ClientReq::new(method, path) doesn't exist, check if ClientReq is alias for Request. + // In leptos 0.8+, ClientReq::try_new(method, uri, body) is available via trait extension usually or direct impl. + // Actually, ClientReq IS http::Request in server/ssr, and gloo_net::Request in hydrate (often). + + // Let's assume ClientReq::post(path).body(...) or similar builders. + // Or if it's http::Request: + // http::Request::builder().method("POST").uri(path).header(...).body(data) + + // But ClientReq type differs between Features. + // Let's try to use `ClientReq` assuming it has `try_new` as seen in other codecs (Json). + // If that fails, I will use conditional compilation for specific types. + + // The error "expected a type, found a trait" suggests `ClientReq` handles differently. + // Let's look at `Json` codec usage pattern if possible - I can't read source. + + // Let's try constructing via builder if available. + // Or better, let's look at what `ClientReq` is. + // In `leptos_server_fn::request`, `ClientReq` is public type alias. + + // If I use `ClientReq::try_new`, I need it to be available. + // Let's try `ClientReq::new` again but verify imports. + // Maybe I need to import `http::Method`? + + // Let's try using `http::Request` explicitly if possible, or just construct it. + // If `ClientReq` is `http::Request`, `ClientReq::builder()` works. + + let req = ClientReq::builder() + .method("POST") + .uri(path) + .header("Content-Type", "application/msgpack") + .header("Accept", "application/msgpack") + .body(bytes::Bytes::from(data)) + .map_err(|e| ServerFnError::Request(e.to_string()))?; + + Ok(req) + } +} + +#[cfg(any(feature = "ssr", feature = "hydrate"))] +impl FromRes for MessagePack +where + Input: Send, + Output: DeserializeOwned + Send, +{ + fn from_res(res: ClientRes) -> impl Future> + Send { + async move { + let data = res.try_into_bytes().await?; + rmp_serde::from_slice(&data) + .map_err(|e| ServerFnError::Deserialization(e.to_string())) + } + } +} + +#[cfg(feature = "ssr")] +impl FromReq for MessagePack +where + Input: DeserializeOwned + Send, + Output: Send, +{ + fn from_req(req: Req) -> impl Future> + Send { + async move { + let body_bytes = req.try_into_bytes().await?; + rmp_serde::from_slice(&body_bytes) + .map_err(|e| ServerFnError::Args(e.to_string())) + } + } +} + +#[cfg(feature = "ssr")] +impl IntoRes for MessagePack +where + Input: Send, + Output: Serialize + Send, +{ + fn into_res(res: Output) -> impl Future> + Send { + async move { + let data = rmp_serde::to_vec(&res) + .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + + let mut res = Res::new(200); + res.try_set_header("Content-Type", "application/msgpack")?; + res.try_set_body(bytes::Bytes::from(data))?; + Ok(res) + } + } +} diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 0b8dad3..251542c 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -11,6 +11,8 @@ pub mod xmlrpc; #[cfg(feature = "ssr")] pub mod db; +pub mod codec; + pub mod server_fns; #[derive(Clone, Debug)] diff --git a/shared/src/server_fns/auth.rs b/shared/src/server_fns/auth.rs index 71a5798..0156f55 100644 --- a/shared/src/server_fns/auth.rs +++ b/shared/src/server_fns/auth.rs @@ -1,5 +1,6 @@ use leptos::prelude::*; use serde::{Deserialize, Serialize}; +use crate::codec::MessagePack; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct UserResponse { @@ -19,7 +20,7 @@ pub struct SetupStatus { pub completed: bool, } -#[server(GetSetupStatus, "/api/server_fns/GetSetupStatus")] +#[server(GetSetupStatus, "/api/server_fns/GetSetupStatus", encoding = "MessagePack")] pub async fn get_setup_status() -> Result { use crate::DbContext; @@ -32,7 +33,7 @@ pub async fn get_setup_status() -> Result { }) } -#[server(Setup, "/api/server_fns/Setup")] +#[server(Setup, "/api/server_fns/Setup", encoding = "MessagePack")] pub async fn setup(username: String, password: String) -> Result<(), ServerFnError> { use crate::DbContext; @@ -54,7 +55,7 @@ pub async fn setup(username: String, password: String) -> Result<(), ServerFnErr Ok(()) } -#[server(Login, "/api/server_fns/Login")] +#[server(Login, "/api/server_fns/Login", encoding = "MessagePack")] pub async fn login(username: String, password: String) -> Result { use crate::DbContext; use leptos_axum::ResponseOptions; @@ -110,7 +111,7 @@ pub async fn login(username: String, password: String) -> Result Result<(), ServerFnError> { use leptos_axum::ResponseOptions; use cookie::{Cookie, SameSite}; @@ -131,7 +132,7 @@ pub async fn logout() -> Result<(), ServerFnError> { Ok(()) } -#[server(GetUser, "/api/server_fns/GetUser")] +#[server(GetUser, "/api/server_fns/GetUser", encoding = "MessagePack")] pub async fn get_user() -> Result, ServerFnError> { use axum::http::HeaderMap; use leptos_axum::extract;