mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
Add support for postcard codec in server_fn (#2879)
* Add support for postcard codec * Add postcard feature set exclusions * Add postcard server fn encoding example
This commit is contained in:
parent
1d99764740
commit
6bba233ba7
5 changed files with 169 additions and 1 deletions
|
@ -16,6 +16,7 @@ server_fn = { path = "../../server_fn", features = [
|
|||
"serde-lite",
|
||||
"rkyv",
|
||||
"multipart",
|
||||
"postcard",
|
||||
] }
|
||||
log = "0.4.22"
|
||||
simple_logger = "5.0"
|
||||
|
|
|
@ -6,7 +6,8 @@ use server_fn::{
|
|||
client::{browser::BrowserClient, Client},
|
||||
codec::{
|
||||
Encoding, FromReq, FromRes, GetUrl, IntoReq, IntoRes, MultipartData,
|
||||
MultipartFormData, Rkyv, SerdeLite, StreamingText, TextStream,
|
||||
MultipartFormData, Postcard, Rkyv, SerdeLite, StreamingText,
|
||||
TextStream,
|
||||
},
|
||||
request::{browser::BrowserRequest, ClientReq, Req},
|
||||
response::{browser::BrowserResponse, ClientRes, Res},
|
||||
|
@ -65,6 +66,7 @@ pub fn HomePage() -> impl IntoView {
|
|||
<h2>"Alternative Encodings"</h2>
|
||||
<ServerFnArgumentExample/>
|
||||
<RkyvExample/>
|
||||
<PostcardExample/>
|
||||
<FileUpload/>
|
||||
<FileUploadWithProgress/>
|
||||
<FileWatcher/>
|
||||
|
@ -880,3 +882,67 @@ pub fn CustomClientExample() -> impl IntoView {
|
|||
})>Click me</button>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct PostcardData {
|
||||
name: String,
|
||||
age: u32,
|
||||
hobbies: Vec<String>,
|
||||
}
|
||||
|
||||
/// This server function uses Postcard for both input and output encoding.
|
||||
/// Postcard provides efficient binary serialization, almost as fast as rkyv, while also being
|
||||
/// serde compatible
|
||||
#[server(input = Postcard, output = Postcard)]
|
||||
pub async fn postcard_example(
|
||||
data: PostcardData,
|
||||
) -> Result<PostcardData, ServerFnError> {
|
||||
// Simulate some processing time
|
||||
tokio::time::sleep(std::time::Duration::from_millis(250)).await;
|
||||
|
||||
// Modify the data to demonstrate server-side changes
|
||||
let mut modified_data = data.clone();
|
||||
modified_data.age += 1;
|
||||
modified_data.hobbies.push("Rust programming".to_string());
|
||||
|
||||
Ok(modified_data)
|
||||
}
|
||||
|
||||
/// This component demonstrates the usage of Postcard encoding with server functions.
|
||||
/// It allows incrementing the age of a person and shows how the data is
|
||||
/// serialized, sent to the server, processed, and returned.
|
||||
#[component]
|
||||
pub fn PostcardExample() -> impl IntoView {
|
||||
// Initialize the input data
|
||||
let (input, set_input) = signal(PostcardData {
|
||||
name: "Alice".to_string(),
|
||||
age: 30,
|
||||
hobbies: vec!["reading".to_string(), "hiking".to_string()],
|
||||
});
|
||||
|
||||
// Create a resource that will call the server function whenever the input changes
|
||||
let postcard_result = Resource::new(
|
||||
move || input.get(),
|
||||
|data| async move { postcard_example(data).await },
|
||||
);
|
||||
|
||||
view! {
|
||||
<h3>Using <code>postcard</code> encoding</h3>
|
||||
<p>"This example demonstrates using Postcard for efficient binary serialization."</p>
|
||||
<button on:click=move |_| {
|
||||
// Update the input data when the button is clicked
|
||||
set_input.update(|data| {
|
||||
data.age += 1;
|
||||
});
|
||||
}>
|
||||
"Increment Age"
|
||||
</button>
|
||||
// Display the current input data
|
||||
<p>"Input: " {move || format!("{:?}", input.get())}</p>
|
||||
<Transition>
|
||||
// Display the result from the server, which will update automatically
|
||||
// when the input changes due to the resource
|
||||
<p>"Result: " {move || postcard_result.get().map(|r| format!("{:?}", r))}</p>
|
||||
</Transition>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ serde-lite = { version = "0.5.0", features = ["derive"], optional = true }
|
|||
futures = "0.3.30"
|
||||
http = { version = "1.1" }
|
||||
ciborium = { version = "0.2.2", optional = true }
|
||||
postcard = { version = "1", features = ["alloc"], optional = true }
|
||||
hyper = { version = "1.4", optional = true }
|
||||
bytes = "1.7"
|
||||
http-body-util = { version = "0.1.2", optional = true }
|
||||
|
@ -108,6 +109,7 @@ url = ["dep:serde_qs"]
|
|||
cbor = ["dep:ciborium"]
|
||||
rkyv = ["dep:rkyv"]
|
||||
msgpack = ["dep:rmp-serde"]
|
||||
postcard = ["dep:postcard"]
|
||||
default-tls = ["reqwest?/default-tls"]
|
||||
rustls = ["reqwest?/rustls-tls"]
|
||||
reqwest = ["dep:reqwest"]
|
||||
|
@ -196,4 +198,24 @@ skip_feature_sets = [
|
|||
"url",
|
||||
"serde-lite",
|
||||
],
|
||||
[
|
||||
"postcard",
|
||||
"json",
|
||||
],
|
||||
[
|
||||
"postcard",
|
||||
"cbor",
|
||||
],
|
||||
[
|
||||
"postcard",
|
||||
"url",
|
||||
],
|
||||
[
|
||||
"postcard",
|
||||
"serde-lite",
|
||||
],
|
||||
[
|
||||
"postcard",
|
||||
"rkyv",
|
||||
],
|
||||
]
|
||||
|
|
|
@ -49,6 +49,11 @@ mod msgpack;
|
|||
#[cfg(feature = "msgpack")]
|
||||
pub use msgpack::*;
|
||||
|
||||
#[cfg(feature = "postcard")]
|
||||
mod postcard;
|
||||
#[cfg(feature = "postcard")]
|
||||
pub use postcard::*;
|
||||
|
||||
mod stream;
|
||||
use crate::error::ServerFnError;
|
||||
use futures::Future;
|
||||
|
|
74
server_fn/src/codec/postcard.rs
Normal file
74
server_fn/src/codec/postcard.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
|
||||
use crate::{
|
||||
error::ServerFnError,
|
||||
request::{ClientReq, Req},
|
||||
response::{ClientRes, Res},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use http::Method;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
/// A codec for Postcard.
|
||||
pub struct Postcard;
|
||||
|
||||
impl Encoding for Postcard {
|
||||
const CONTENT_TYPE: &'static str = "application/x-postcard";
|
||||
const METHOD: Method = Method::POST;
|
||||
}
|
||||
|
||||
impl<T, Request, Err> IntoReq<Postcard, Request, Err> for T
|
||||
where
|
||||
Request: ClientReq<Err>,
|
||||
T: Serialize,
|
||||
{
|
||||
fn into_req(
|
||||
self,
|
||||
path: &str,
|
||||
accepts: &str,
|
||||
) -> Result<Request, ServerFnError<Err>> {
|
||||
let data = postcard::to_allocvec(&self)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
Request::try_new_post_bytes(
|
||||
path,
|
||||
Postcard::CONTENT_TYPE,
|
||||
accepts,
|
||||
Bytes::from(data),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Request, Err> FromReq<Postcard, Request, Err> for T
|
||||
where
|
||||
Request: Req<Err> + Send,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
async fn from_req(req: Request) -> Result<Self, ServerFnError<Err>> {
|
||||
let data = req.try_into_bytes().await?;
|
||||
postcard::from_bytes::<T>(&data)
|
||||
.map_err(|e| ServerFnError::Args(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Response, Err> IntoRes<Postcard, Response, Err> for T
|
||||
where
|
||||
Response: Res<Err>,
|
||||
T: Serialize + Send,
|
||||
{
|
||||
async fn into_res(self) -> Result<Response, ServerFnError<Err>> {
|
||||
let data = postcard::to_allocvec(&self)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
|
||||
Response::try_from_bytes(Postcard::CONTENT_TYPE, Bytes::from(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Response, Err> FromRes<Postcard, Response, Err> for T
|
||||
where
|
||||
Response: ClientRes<Err> + Send,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
async fn from_res(res: Response) -> Result<Self, ServerFnError<Err>> {
|
||||
let data = res.try_into_bytes().await?;
|
||||
postcard::from_bytes(&data)
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue