1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use base64::Engine;
5use dioxus_core::CapturedError;
6use serde::{de::DeserializeOwned, Deserialize, Serialize};
7use std::{cell::RefCell, io::Cursor, rc::Rc};
8
9#[cfg(feature = "web")]
10thread_local! {
11 static CONTEXT: RefCell<Option<HydrationContext>> = const { RefCell::new(None) };
12}
13
14#[derive(Default, Clone)]
17pub struct HydrationContext {
18 #[cfg(feature = "web")]
19 suspense_finished: bool,
21 data: Rc<RefCell<HTMLData>>,
22}
23
24impl HydrationContext {
25 pub fn from_serialized(
27 data: &[u8],
28 debug_types: Option<Vec<String>>,
29 debug_locations: Option<Vec<String>>,
30 ) -> Self {
31 Self {
32 #[cfg(feature = "web")]
33 suspense_finished: false,
34 data: Rc::new(RefCell::new(HTMLData::from_serialized(
35 data,
36 debug_types,
37 debug_locations,
38 ))),
39 }
40 }
41
42 pub fn serialized(&self) -> SerializedHydrationData {
44 self.data.borrow().serialized()
45 }
46
47 pub fn create_entry<T>(&self) -> SerializeContextEntry<T> {
49 let entry_index = self.data.borrow_mut().create_entry();
50
51 SerializeContextEntry {
52 index: entry_index,
53 context: self.clone(),
54 phantom: std::marker::PhantomData,
55 }
56 }
57
58 pub fn error_entry(&self) -> SerializeContextEntry<Option<CapturedError>> {
60 let entry_index = self.data.borrow_mut().create_entry_with_id(0);
62
63 SerializeContextEntry {
64 index: entry_index,
65 context: self.clone(),
66 phantom: std::marker::PhantomData,
67 }
68 }
69
70 pub fn extend(&self, other: &Self) {
72 self.data.borrow_mut().extend(&other.data.borrow());
73 }
74
75 #[cfg(feature = "web")]
76 pub fn in_context<T>(&self, f: impl FnOnce() -> T) -> T {
78 CONTEXT.with(|context| {
79 let old = context.borrow().clone();
80 *context.borrow_mut() = Some(self.clone());
81 let result = f();
82 *context.borrow_mut() = old;
83 result
84 })
85 }
86
87 pub(crate) fn insert<T: Transportable<M>, M: 'static>(
88 &self,
89 id: usize,
90 value: &T,
91 location: &'static std::panic::Location<'static>,
92 ) {
93 self.data.borrow_mut().insert(id, value, location);
94 }
95
96 pub(crate) fn get<T: Transportable<M>, M: 'static>(
97 &self,
98 id: usize,
99 ) -> Result<T, TakeDataError> {
100 #[cfg(feature = "web")]
102 if self.suspense_finished {
103 return Err(TakeDataError::DataNotAvailable);
104 }
105 self.data.borrow().get(id)
106 }
107}
108
109pub struct SerializeContextEntry<T> {
112 index: usize,
114 context: HydrationContext,
116 phantom: std::marker::PhantomData<T>,
117}
118
119impl<T> Clone for SerializeContextEntry<T> {
120 fn clone(&self) -> Self {
121 Self {
122 index: self.index,
123 context: self.context.clone(),
124 phantom: std::marker::PhantomData,
125 }
126 }
127}
128
129impl<T> SerializeContextEntry<T> {
130 pub fn insert<M: 'static>(self, value: &T, location: &'static std::panic::Location<'static>)
132 where
133 T: Transportable<M>,
134 {
135 self.context.insert(self.index, value, location);
136 }
137
138 pub fn get<M: 'static>(&self) -> Result<T, TakeDataError>
140 where
141 T: Transportable<M>,
142 {
143 self.context.get(self.index)
144 }
145}
146
147pub fn is_hydrating() -> bool {
149 #[cfg(feature = "web")]
150 {
151 CONTEXT.with(|context| context.borrow().is_some())
153 }
154 #[cfg(not(feature = "web"))]
155 {
156 true
157 }
158}
159
160pub fn serialize_context() -> HydrationContext {
163 #[cfg(feature = "web")]
164 if let Some(current_context) = CONTEXT.with(|context| context.borrow().clone()) {
166 current_context
167 } else {
168 HydrationContext {
170 suspense_finished: true,
171 ..Default::default()
172 }
173 }
174 #[cfg(not(feature = "web"))]
175 {
176 dioxus_core::has_context()
178 .unwrap_or_else(|| dioxus_core::provide_context(HydrationContext::default()))
179 }
180}
181
182pub(crate) struct HTMLData {
183 pub(crate) cursor: usize,
185 pub data: Vec<Option<Vec<u8>>>,
187 #[cfg(debug_assertions)]
193 pub debug_types: Vec<Option<String>>,
194 #[cfg(debug_assertions)]
196 pub debug_locations: Vec<Option<String>>,
197}
198
199impl Default for HTMLData {
200 fn default() -> Self {
201 Self {
202 cursor: 1,
203 data: Vec::new(),
204 #[cfg(debug_assertions)]
205 debug_types: Vec::new(),
206 #[cfg(debug_assertions)]
207 debug_locations: Vec::new(),
208 }
209 }
210}
211
212impl HTMLData {
213 #[allow(unused)]
214 fn from_serialized(
215 data: &[u8],
216 debug_types: Option<Vec<String>>,
217 debug_locations: Option<Vec<String>>,
218 ) -> Self {
219 let data = ciborium::from_reader(Cursor::new(data)).unwrap();
220 Self {
221 cursor: 1,
222 data,
223 #[cfg(debug_assertions)]
224 debug_types: debug_types
225 .unwrap_or_default()
226 .into_iter()
227 .map(Some)
228 .collect(),
229 #[cfg(debug_assertions)]
230 debug_locations: debug_locations
231 .unwrap_or_default()
232 .into_iter()
233 .map(Some)
234 .collect(),
235 }
236 }
237
238 fn create_entry(&mut self) -> usize {
240 let id = self.cursor;
241 self.cursor += 1;
242 self.create_entry_with_id(id)
243 }
244
245 fn create_entry_with_id(&mut self, id: usize) -> usize {
246 while id + 1 > self.data.len() {
247 self.data.push(None);
248 #[cfg(debug_assertions)]
249 {
250 self.debug_types.push(None);
251 self.debug_locations.push(None);
252 }
253 }
254 id
255 }
256
257 fn insert<T: Transportable<M>, M: 'static>(
259 &mut self,
260 id: usize,
261 value: &T,
262 #[allow(unused)] location: &'static std::panic::Location<'static>,
263 ) {
264 let serialized = value.transport_to_bytes();
265 self.data[id] = Some(serialized);
266 #[cfg(debug_assertions)]
267 {
268 self.debug_types[id] = Some(std::any::type_name::<T>().to_string());
269 self.debug_locations[id] = Some(location.to_string());
270 }
271 }
272
273 fn get<T: Transportable<M>, M: 'static>(&self, index: usize) -> Result<T, TakeDataError> {
275 if index >= self.data.len() {
276 tracing::trace!(
277 "Tried to take more data than was available, len: {}, index: {}; This is normal if the server function was started on the client, but may indicate a bug if the server function result should be deserialized from the server",
278 self.data.len(),
279 index
280 );
281 return Err(TakeDataError::DataNotAvailable);
282 }
283 let bytes = self.data[index].as_ref();
284 match bytes {
285 Some(bytes) => match T::transport_from_bytes(bytes) {
286 Ok(x) => Ok(x),
287 Err(err) => {
288 #[cfg(debug_assertions)]
289 {
290 let debug_type = self.debug_types.get(index);
291 let debug_locations = self.debug_locations.get(index);
292
293 if let (Some(Some(debug_type)), Some(Some(debug_locations))) =
294 (debug_type, debug_locations)
295 {
296 let client_type = std::any::type_name::<T>();
297 let client_location = std::panic::Location::caller();
298 tracing::error!(
300 "Error deserializing data: {err:?}\n\nThis type was serialized on the server at {debug_locations} with the type name {debug_type}. The client failed to deserialize the type {client_type} at {client_location}.",
301 );
302 return Err(TakeDataError::DeserializationError(err));
303 }
304 }
305 tracing::error!("Error deserializing data: {:?}", err);
307 Err(TakeDataError::DeserializationError(err))
308 }
309 },
310 None => Err(TakeDataError::DataPending),
311 }
312 }
313
314 pub(crate) fn extend(&mut self, other: &Self) {
316 if self.data.is_empty() {
318 self.data.push(None);
319 #[cfg(debug_assertions)]
320 {
321 self.debug_types.push(None);
322 self.debug_locations.push(None);
323 }
324 }
325
326 let mut other_data_iter = other.data.iter().cloned();
327 #[cfg(debug_assertions)]
328 let mut other_debug_types_iter = other.debug_types.iter().cloned();
329 #[cfg(debug_assertions)]
330 let mut other_debug_locations_iter = other.debug_locations.iter().cloned();
331
332 if let Some(Some(other_error)) = other_data_iter.next() {
334 self.data[0] = Some(other_error.clone());
335 #[cfg(debug_assertions)]
336 {
337 self.debug_types[0] = other_debug_types_iter.next().unwrap_or(None);
338 self.debug_locations[0] = other_debug_locations_iter.next().unwrap_or(None);
339 }
340 }
341
342 self.data.extend(other_data_iter);
344 #[cfg(debug_assertions)]
345 {
346 self.debug_types.extend(other_debug_types_iter);
347 self.debug_locations.extend(other_debug_locations_iter);
348 }
349 }
350
351 pub(crate) fn serialized(&self) -> SerializedHydrationData {
353 let mut serialized = Vec::new();
354 ciborium::into_writer(&self.data, &mut serialized).unwrap();
355
356 let data = base64::engine::general_purpose::STANDARD.encode(serialized);
357
358 #[cfg(debug_assertions)]
359 let format_js_list_of_strings = |list: &[Option<String>]| {
360 let body = list
361 .iter()
362 .map(|s| match s {
363 Some(s) => {
364 let escaped = s
366 .replace(r#"\"#, r#"\\"#)
367 .replace("\n", r#"\n"#)
368 .replace(r#"""#, r#"\""#);
369
370 format!(r#""{escaped}""#)
371 }
372 None => r#""unknown""#.to_string(),
373 })
374 .collect::<Vec<_>>()
375 .join(",");
376 format!("[{}]", body)
377 };
378
379 SerializedHydrationData {
380 data,
381 #[cfg(debug_assertions)]
382 debug_types: format_js_list_of_strings(&self.debug_types),
383 #[cfg(debug_assertions)]
384 debug_locations: format_js_list_of_strings(&self.debug_locations),
385 }
386 }
387}
388
389pub struct SerializedHydrationData {
392 pub data: String,
394 #[cfg(debug_assertions)]
396 pub debug_types: String,
397 #[cfg(debug_assertions)]
399 pub debug_locations: String,
400}
401
402#[derive(Debug)]
404pub enum TakeDataError {
405 DeserializationError(ciborium::de::Error<std::io::Error>),
407 DataNotAvailable,
409 DataPending,
411}
412
413impl std::fmt::Display for TakeDataError {
414 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415 match self {
416 Self::DeserializationError(e) => write!(f, "DeserializationError: {}", e),
417 Self::DataNotAvailable => write!(f, "DataNotAvailable"),
418 Self::DataPending => write!(f, "DataPending"),
419 }
420 }
421}
422
423impl std::error::Error for TakeDataError {}
424
425pub fn head_element_hydration_entry() -> SerializeContextEntry<bool> {
427 serialize_context().create_entry()
428}
429
430pub trait Transportable<M = ()>: 'static {
442 fn transport_to_bytes(&self) -> Vec<u8>;
444
445 fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>
447 where
448 Self: Sized;
449}
450
451impl<T> Transportable<()> for T
452where
453 T: Serialize + DeserializeOwned + 'static,
454{
455 fn transport_to_bytes(&self) -> Vec<u8> {
456 let mut serialized = Vec::new();
457 ciborium::into_writer(self, &mut serialized).unwrap();
458 serialized
459 }
460
461 fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>
462 where
463 Self: Sized,
464 {
465 ciborium::from_reader(Cursor::new(bytes))
466 }
467}
468
469#[derive(Serialize, Deserialize)]
470struct TransportResultErr<T> {
471 error: Result<T, CapturedError>,
472}
473
474#[doc(hidden)]
475pub struct TransportViaErrMarker;
476
477impl<T> Transportable<TransportViaErrMarker> for Result<T, anyhow::Error>
478where
479 T: Serialize + DeserializeOwned + 'static,
480{
481 fn transport_to_bytes(&self) -> Vec<u8> {
482 let err = TransportResultErr {
483 error: self
484 .as_ref()
485 .map_err(|e| CapturedError::from_display(e.to_string())),
486 };
487
488 let mut serialized = Vec::new();
489 ciborium::into_writer(&err, &mut serialized).unwrap();
490 serialized
491 }
492
493 fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>
494 where
495 Self: Sized,
496 {
497 let err: TransportResultErr<T> = ciborium::from_reader(Cursor::new(bytes))?;
498 match err.error {
499 Ok(value) => Ok(Ok(value)),
500 Err(captured) => Ok(Err(anyhow::Error::msg(captured.to_string()))),
501 }
502 }
503}
504
505#[doc(hidden)]
506pub struct TransportCapturedError;
507#[derive(Serialize, Deserialize)]
508struct TransportError {
509 error: String,
510}
511
512impl Transportable<TransportCapturedError> for CapturedError {
513 fn transport_to_bytes(&self) -> Vec<u8> {
514 let err = TransportError {
515 error: self.to_string(),
516 };
517
518 let mut serialized = Vec::new();
519 ciborium::into_writer(&err, &mut serialized).unwrap();
520 serialized
521 }
522
523 fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>
524 where
525 Self: Sized,
526 {
527 let err: TransportError = ciborium::from_reader(Cursor::new(bytes))?;
528 Ok(dioxus_core::CapturedError::msg::<String>(err.error))
529 }
530}