From b153ab51ee9bba50057ba65353a7312c94f753c5 Mon Sep 17 00:00:00 2001 From: Azz Date: Sun, 26 Feb 2023 21:12:53 +0000 Subject: [PATCH] feat: support `rkyv` encoding (#577) --- examples/hackernews/src/api.rs | 4 +-- examples/hackernews_axum/src/api.rs | 4 +-- leptos/Cargo.toml | 1 + leptos_reactive/Cargo.toml | 3 ++ leptos_reactive/src/resource.rs | 8 ++--- leptos_reactive/src/serialization.rs | 45 +++++++++++++++++++++------- router/src/components/form.rs | 2 +- 7 files changed, 46 insertions(+), 21 deletions(-) diff --git a/examples/hackernews/src/api.rs b/examples/hackernews/src/api.rs index 1689e0900..79b146665 100644 --- a/examples/hackernews/src/api.rs +++ b/examples/hackernews/src/api.rs @@ -34,7 +34,7 @@ where abort_controller.abort() } }); - T::from_json(&json).ok() + T::de(&json).ok() } #[cfg(feature = "ssr")] @@ -49,7 +49,7 @@ where .text() .await .ok()?; - T::from_json(&json).map_err(|e| log::error!("{e}")).ok() + T::de(&json).map_err(|e| log::error!("{e}")).ok() } #[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)] diff --git a/examples/hackernews_axum/src/api.rs b/examples/hackernews_axum/src/api.rs index e40cf4483..04b043ca2 100644 --- a/examples/hackernews_axum/src/api.rs +++ b/examples/hackernews_axum/src/api.rs @@ -34,7 +34,7 @@ where abort_controller.abort() } }); - T::from_json(&json).ok() + T::de(&json).ok() } #[cfg(feature = "ssr")] @@ -49,7 +49,7 @@ where .text() .await .ok()?; - T::from_json(&json).map_err(|e| log::error!("{e}")).ok() + T::de(&json).map_err(|e| log::error!("{e}")).ok() } #[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)] diff --git a/leptos/Cargo.toml b/leptos/Cargo.toml index b3294c023..e45c181a4 100644 --- a/leptos/Cargo.toml +++ b/leptos/Cargo.toml @@ -50,6 +50,7 @@ stable = [ serde = ["leptos_reactive/serde"] serde-lite = ["leptos_reactive/serde-lite"] miniserde = ["leptos_reactive/miniserde"] +rkyv = ["leptos_reactive/rkyv"] tracing = ["leptos_macro/tracing"] [package.metadata.cargo-all-features] diff --git a/leptos_reactive/Cargo.toml b/leptos_reactive/Cargo.toml index 40c07b137..a7598f180 100644 --- a/leptos_reactive/Cargo.toml +++ b/leptos_reactive/Cargo.toml @@ -14,6 +14,8 @@ serde-lite = { version = "0.3", optional = true } futures = { version = "0.3" } js-sys = "0.3" miniserde = { version = "0.1", optional = true } +rkyv = { version = "0.7.39", features = ["validation", "uuid", "copy", "strict"], optional = true } +bytecheck = { version = "0.6.9", features = ["uuid", "simdutf8_std", "simdutf8"], optional = true } serde-wasm-bindgen = "0.4" serde_json = "1" base64 = "0.21" @@ -45,6 +47,7 @@ stable = [] serde = [] serde-lite = ["dep:serde-lite"] miniserde = ["dep:miniserde"] +rkyv = ["dep:rkyv", "dep:bytecheck"] [package.metadata.cargo-all-features] denylist = ["stable"] diff --git a/leptos_reactive/src/resource.rs b/leptos_reactive/src/resource.rs index 6a1088034..027066ef2 100644 --- a/leptos_reactive/src/resource.rs +++ b/leptos_reactive/src/resource.rs @@ -307,7 +307,7 @@ where context.pending_resources.remove(&id); // no longer pending r.resolved.set(true); - let res = T::from_json(&data) + let res = T::de(&data) .expect_throw("could not deserialize Resource JSON"); r.set_value.update(|n| *n = Some(res)); @@ -326,7 +326,7 @@ where let set_value = r.set_value; let set_loading = r.set_loading; move |res: String| { - let res = T::from_json(&res) + let res = T::de(&res) .expect_throw("could not deserialize Resource JSON"); resolved.set(true); set_value.update(|n| *n = Some(res)); @@ -680,9 +680,7 @@ where if let Some(value) = value.as_ref() { tx.try_send(( id, - value - .to_json() - .expect("could not serialize Resource"), + value.ser().expect("could not serialize Resource"), )) .expect( "failed while trying to write to Resource \ diff --git a/leptos_reactive/src/serialization.rs b/leptos_reactive/src/serialization.rs index f6598c5aa..0bcc42b78 100644 --- a/leptos_reactive/src/serialization.rs +++ b/leptos_reactive/src/serialization.rs @@ -30,27 +30,50 @@ pub trait Serializable where Self: Sized, { - /// Serializes the object to JSON. - fn to_json(&self) -> Result; + /// Serializes the object to a string. + fn ser(&self) -> Result; - /// Deserializes the object from JSON. - fn from_json(json: &str) -> Result; + /// Deserializes the object from some bytes. + fn de(bytes: &str) -> Result; } cfg_if! { + if #[cfg(feature = "rkyv")] { + use rkyv::{Archive, Deserialize, Serialize, ser::serializers::AllocSerializer, de::deserializers::SharedDeserializeMap, validation::validators::DefaultValidator}; + use bytecheck::CheckBytes; + use base64::Engine as _; + use base64::engine::general_purpose::STANDARD_NO_PAD; + + impl Serializable for T + where + T: Serialize>, + T: Archive, + T::Archived: for<'b> CheckBytes> + Deserialize, + { + fn ser(&self) -> Result { + let bytes = rkyv::to_bytes::(&self).map_err(|e| SerializationError::Serialize(Rc::new(e)))?; + Ok(STANDARD_NO_PAD.encode(bytes)) + } + + fn de(serialized: &str) -> Result { + let bytes = STANDARD_NO_PAD.decode(serialized.as_bytes()).map_err(|e| SerializationError::Deserialize(Rc::new(e)))?; + rkyv::from_bytes::(&bytes).map_err(|e| SerializationError::Deserialize(Rc::new(e))) + } + } + } // prefer miniserde if it's chosen - if #[cfg(feature = "miniserde")] { + else if #[cfg(feature = "miniserde")] { use miniserde::{json, Deserialize, Serialize}; impl Serializable for T where T: Serialize + Deserialize, { - fn to_json(&self) -> Result { + fn ser(&self) -> Result { Ok(json::to_string(&self)) } - fn from_json(json: &str) -> Result { + fn de(json: &str) -> Result { json::from_str(json).map_err(|e| SerializationError::Deserialize(Rc::new(e))) } } @@ -64,14 +87,14 @@ cfg_if! { where T: Serialize + Deserialize, { - fn to_json(&self) -> Result { + fn ser(&self) -> Result { let intermediate = self .serialize() .map_err(|e| SerializationError::Serialize(Rc::new(e)))?; serde_json::to_string(&intermediate).map_err(|e| SerializationError::Serialize(Rc::new(e))) } - fn from_json(json: &str) -> Result { + fn de(json: &str) -> Result { let intermediate = serde_json::from_str(&json).map_err(|e| SerializationError::Deserialize(Rc::new(e)))?; Self::deserialize(&intermediate).map_err(|e| SerializationError::Deserialize(Rc::new(e))) @@ -87,11 +110,11 @@ cfg_if! { where T: DeserializeOwned + Serialize, { - fn to_json(&self) -> Result { + fn ser(&self) -> Result { serde_json::to_string(&self).map_err(|e| SerializationError::Serialize(Rc::new(e))) } - fn from_json(json: &str) -> Result { + fn de(json: &str) -> Result { serde_json::from_str(json).map_err(|e| SerializationError::Deserialize(Rc::new(e))) } diff --git a/router/src/components/form.rs b/router/src/components/form.rs index 04a0e8493..4c338b90d 100644 --- a/router/src/components/form.rs +++ b/router/src/components/form.rs @@ -229,7 +229,7 @@ where .await; match body { Ok(json) => { - match O::from_json( + match O::de( &json .as_string() .expect("couldn't get String from JsString"),