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:
niklass-l 2024-08-26 22:58:23 +02:00 committed by GitHub
parent 1d99764740
commit 6bba233ba7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 169 additions and 1 deletions

View file

@ -16,6 +16,7 @@ server_fn = { path = "../../server_fn", features = [
"serde-lite",
"rkyv",
"multipart",
"postcard",
] }
log = "0.4.22"
simple_logger = "5.0"

View file

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

View file

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

View file

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

View 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()))
}
}