leptos_server/
shared.rs

1use crate::{FromEncodedStr, IntoEncodedString};
2#[cfg(feature = "rkyv")]
3use codee::binary::RkyvCodec;
4#[cfg(feature = "serde-wasm-bindgen")]
5use codee::string::JsonSerdeWasmCodec;
6#[cfg(feature = "miniserde")]
7use codee::string::MiniserdeCodec;
8#[cfg(feature = "serde-lite")]
9use codee::SerdeLite;
10use codee::{
11    string::{FromToStringCodec, JsonSerdeCodec},
12    Decoder, Encoder,
13};
14use std::{
15    fmt::{Debug, Display},
16    hash::Hash,
17    marker::PhantomData,
18    ops::{Deref, DerefMut},
19};
20
21/// A smart pointer that allows you to share identical, synchronously-loaded data between the
22/// server and the client.
23///
24/// If this constructed on the server, it serializes its value into the shared context. If it is
25/// constructed on the client during hydration, it reads its value from the shared context. If
26/// it it constructed on the client at any other time, it simply runs on the client.
27#[derive(Debug)]
28pub struct SharedValue<T, Ser = JsonSerdeCodec> {
29    value: T,
30    ser: PhantomData<Ser>,
31}
32
33impl<T, Ser> SharedValue<T, Ser> {
34    /// Returns the inner value.
35    pub fn into_inner(self) -> T {
36        self.value
37    }
38}
39
40impl<T> SharedValue<T, JsonSerdeCodec>
41where
42    JsonSerdeCodec: Encoder<T> + Decoder<T>,
43    <JsonSerdeCodec as Encoder<T>>::Error: Debug,
44    <JsonSerdeCodec as Decoder<T>>::Error: Debug,
45    <JsonSerdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
46    <JsonSerdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
47    <<JsonSerdeCodec as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
48        Debug,
49{
50    /// Wraps the initial value.
51    ///
52    /// If this is on the server, the function will be invoked and the value serialized. When it runs
53    /// on the client, it will be deserialized without running the function again.
54    ///
55    /// This uses the [`JsonSerdeCodec`] encoding.
56    pub fn new(initial: impl FnOnce() -> T) -> Self {
57        SharedValue::new_with_encoding(initial)
58    }
59}
60
61impl<T> SharedValue<T, FromToStringCodec>
62where
63    FromToStringCodec: Encoder<T> + Decoder<T>,
64    <FromToStringCodec as Encoder<T>>::Error: Debug,
65    <FromToStringCodec as Decoder<T>>::Error: Debug,
66    <FromToStringCodec as Encoder<T>>::Encoded: IntoEncodedString,
67    <FromToStringCodec as Decoder<T>>::Encoded: FromEncodedStr,
68    <<FromToStringCodec as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
69        Debug,
70{
71    /// Wraps the initial value.
72    ///
73    /// If this is on the server, the function will be invoked and the value serialized. When it runs
74    /// on the client, it will be deserialized without running the function again.
75    ///
76    /// This uses the [`FromToStringCodec`] encoding.
77    pub fn new_str(initial: impl FnOnce() -> T) -> Self {
78        SharedValue::new_with_encoding(initial)
79    }
80}
81
82#[cfg(feature = "serde-lite")]
83impl<T> SharedValue<T, SerdeLite<JsonSerdeCodec>>
84where
85    SerdeLite<JsonSerdeCodec>: Encoder<T> + Decoder<T>,
86    <SerdeLite<JsonSerdeCodec> as Encoder<T>>::Error: Debug,
87    <SerdeLite<JsonSerdeCodec> as Decoder<T>>::Error: Debug,
88    <SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
89    <SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
90    <<SerdeLite<JsonSerdeCodec> as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
91        Debug,
92{
93    /// Wraps the initial value.
94    ///
95    /// If this is on the server, the function will be invoked and the value serialized. When it runs
96    /// on the client, it will be deserialized without running the function again.
97    ///
98    /// This uses the [`SerdeLite`] encoding.
99    pub fn new_serde_lite(initial: impl FnOnce() -> T) -> Self {
100        SharedValue::new_with_encoding(initial)
101    }
102}
103
104#[cfg(feature = "serde-wasm-bindgen")]
105impl<T> SharedValue<T, JsonSerdeWasmCodec>
106where
107    JsonSerdeWasmCodec: Encoder<T> + Decoder<T>,
108    <JsonSerdeWasmCodec as Encoder<T>>::Error: Debug,
109    <JsonSerdeWasmCodec as Decoder<T>>::Error: Debug,
110    <JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
111    <JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
112    <<JsonSerdeWasmCodec as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
113        Debug,
114{
115    /// Wraps the initial value.
116    ///
117    /// If this is on the server, the function will be invoked and the value serialized. When it runs
118    /// on the client, it will be deserialized without running the function again.
119    ///
120    /// This uses the [`JsonSerdeWasmCodec`] encoding.
121    pub fn new_serde_wb(initial: impl FnOnce() -> T) -> Self {
122        SharedValue::new_with_encoding(initial)
123    }
124}
125
126#[cfg(feature = "miniserde")]
127impl<T> SharedValue<T, MiniserdeCodec>
128where
129    MiniserdeCodec: Encoder<T> + Decoder<T>,
130    <MiniserdeCodec as Encoder<T>>::Error: Debug,
131    <MiniserdeCodec as Decoder<T>>::Error: Debug,
132    <MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
133    <MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
134    <<MiniserdeCodec as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
135        Debug,
136{
137    /// Wraps the initial value.
138    ///
139    /// If this is on the server, the function will be invoked and the value serialized. When it runs
140    /// on the client, it will be deserialized without running the function again.
141    ///
142    /// This uses the [`MiniserdeCodec`] encoding.
143    pub fn new_miniserde(initial: impl FnOnce() -> T) -> Self {
144        SharedValue::new_with_encoding(initial)
145    }
146}
147
148#[cfg(feature = "rkyv")]
149impl<T> SharedValue<T, RkyvCodec>
150where
151    RkyvCodec: Encoder<T> + Decoder<T>,
152    <RkyvCodec as Encoder<T>>::Error: Debug,
153    <RkyvCodec as Decoder<T>>::Error: Debug,
154    <RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
155    <RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
156    <<RkyvCodec as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
157        Debug,
158{
159    /// Wraps the initial value.
160    ///
161    /// If this is on the server, the function will be invoked and the value serialized. When it runs
162    /// on the client, it will be deserialized without running the function again.
163    ///
164    /// This uses the [`RkyvCodec`] encoding.
165    pub fn new_rkyv(initial: impl FnOnce() -> T) -> Self {
166        SharedValue::new_with_encoding(initial)
167    }
168}
169
170impl<T, Ser> SharedValue<T, Ser>
171where
172    Ser: Encoder<T> + Decoder<T>,
173    <Ser as Encoder<T>>::Error: Debug,
174    <Ser as Decoder<T>>::Error: Debug,
175    <Ser as Encoder<T>>::Encoded: IntoEncodedString,
176    <Ser as Decoder<T>>::Encoded: FromEncodedStr,
177    <<Ser as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
178        Debug,
179{
180    /// Wraps the initial value.
181    ///
182    /// If this is on the server, the function will be invoked and the value serialized. When it runs
183    /// on the client, it will be deserialized without running the function again.
184    ///
185    /// This uses `Ser` as an encoding.
186    pub fn new_with_encoding(initial: impl FnOnce() -> T) -> Self {
187        let value: T;
188        #[cfg(feature = "hydration")]
189        {
190            use reactive_graph::owner::Owner;
191            use std::borrow::Borrow;
192
193            let sc = Owner::current_shared_context();
194            let id = sc.as_ref().map(|sc| sc.next_id()).unwrap_or_default();
195            let serialized = sc.as_ref().and_then(|sc| sc.read_data(&id));
196            let hydrating =
197                sc.as_ref().map(|sc| sc.during_hydration()).unwrap_or(false);
198            value = if hydrating {
199                let value = match serialized {
200                    None => {
201                        #[cfg(feature = "tracing")]
202                        tracing::error!("couldn't deserialize");
203                        None
204                    }
205                    Some(data) => {
206                        match <Ser as Decoder<T>>::Encoded::from_encoded_str(
207                            &data,
208                        ) {
209                            #[allow(unused_variables)] // used in tracing
210                            Err(e) => {
211                                #[cfg(feature = "tracing")]
212                                tracing::error!(
213                                    "couldn't deserialize from {data:?}: {e:?}"
214                                );
215                                None
216                            }
217                            Ok(encoded) => {
218                                let decoded = Ser::decode(encoded.borrow());
219                                #[cfg(feature = "tracing")]
220                                let decoded = decoded
221                                    .inspect_err(|e| tracing::error!("{e:?}"));
222                                decoded.ok()
223                            }
224                        }
225                    }
226                };
227                value.unwrap_or_else(initial)
228            } else {
229                let init = initial();
230                #[cfg(feature = "ssr")]
231                if let Some(sc) = sc {
232                    if sc.get_is_hydrating() {
233                        match Ser::encode(&init)
234                            .map(IntoEncodedString::into_encoded_string)
235                        {
236                            Ok(value) => sc.write_async(
237                                id,
238                                Box::pin(async move { value }),
239                            ),
240                            #[allow(unused_variables)] // used in tracing
241                            Err(e) => {
242                                #[cfg(feature = "tracing")]
243                                tracing::error!("couldn't serialize: {e:?}");
244                            }
245                        }
246                    }
247                }
248                init
249            }
250        }
251        #[cfg(not(feature = "hydration"))]
252        {
253            value = initial();
254        }
255        Self {
256            value,
257            ser: PhantomData,
258        }
259    }
260}
261
262impl<T, Ser> Deref for SharedValue<T, Ser> {
263    type Target = T;
264
265    fn deref(&self) -> &Self::Target {
266        &self.value
267    }
268}
269
270impl<T, Ser> DerefMut for SharedValue<T, Ser> {
271    fn deref_mut(&mut self) -> &mut Self::Target {
272        &mut self.value
273    }
274}
275
276impl<T, Ser> PartialEq for SharedValue<T, Ser>
277where
278    T: PartialEq,
279{
280    fn eq(&self, other: &Self) -> bool {
281        self.value == other.value
282    }
283}
284
285impl<T, Ser> Eq for SharedValue<T, Ser> where T: Eq {}
286
287impl<T, Ser> Display for SharedValue<T, Ser>
288where
289    T: Display,
290{
291    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292        write!(f, "{}", self.value)
293    }
294}
295
296impl<T, Ser> Hash for SharedValue<T, Ser>
297where
298    T: Hash,
299{
300    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
301        self.value.hash(state);
302    }
303}
304
305impl<T, Ser> PartialOrd for SharedValue<T, Ser>
306where
307    T: PartialOrd,
308{
309    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
310        self.value.partial_cmp(&other.value)
311    }
312}
313
314impl<T, Ser> Ord for SharedValue<T, Ser>
315where
316    T: Ord,
317{
318    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
319        self.value.cmp(&other.value)
320    }
321}