dioxus_fullstack_protocol/
lib.rs1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use base64::Engine;
5use dioxus_core::CapturedError;
6use serde::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: Serialize>(
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: serde::de::DeserializeOwned>(
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(self, value: &T, location: &'static std::panic::Location<'static>)
132 where
133 T: Serialize,
134 {
135 self.context.insert(self.index, value, location);
136 }
137
138 pub fn get(&self) -> Result<T, TakeDataError>
140 where
141 T: serde::de::DeserializeOwned,
142 {
143 self.context.get(self.index)
144 }
145}
146
147pub fn serialize_context() -> HydrationContext {
150 #[cfg(feature = "web")]
151 if let Some(current_context) = CONTEXT.with(|context| context.borrow().clone()) {
153 current_context
154 } else {
155 HydrationContext {
157 suspense_finished: true,
158 ..Default::default()
159 }
160 }
161 #[cfg(not(feature = "web"))]
162 {
163 dioxus_core::prelude::has_context()
165 .unwrap_or_else(|| dioxus_core::prelude::provide_context(HydrationContext::default()))
166 }
167}
168
169pub(crate) struct HTMLData {
170 pub(crate) cursor: usize,
172 pub data: Vec<Option<Vec<u8>>>,
174 #[cfg(debug_assertions)]
180 pub debug_types: Vec<Option<String>>,
181 #[cfg(debug_assertions)]
183 pub debug_locations: Vec<Option<String>>,
184}
185
186impl Default for HTMLData {
187 fn default() -> Self {
188 Self {
189 cursor: 1,
190 data: Vec::new(),
191 #[cfg(debug_assertions)]
192 debug_types: Vec::new(),
193 #[cfg(debug_assertions)]
194 debug_locations: Vec::new(),
195 }
196 }
197}
198
199impl HTMLData {
200 fn from_serialized(
201 data: &[u8],
202 debug_types: Option<Vec<String>>,
203 debug_locations: Option<Vec<String>>,
204 ) -> Self {
205 let data = ciborium::from_reader(Cursor::new(data)).unwrap();
206 Self {
207 cursor: 1,
208 data,
209 #[cfg(debug_assertions)]
210 debug_types: debug_types
211 .unwrap_or_default()
212 .into_iter()
213 .map(Some)
214 .collect(),
215 #[cfg(debug_assertions)]
216 debug_locations: debug_locations
217 .unwrap_or_default()
218 .into_iter()
219 .map(Some)
220 .collect(),
221 }
222 }
223
224 fn create_entry(&mut self) -> usize {
226 let id = self.cursor;
227 self.cursor += 1;
228 self.create_entry_with_id(id)
229 }
230
231 fn create_entry_with_id(&mut self, id: usize) -> usize {
232 while id + 1 > self.data.len() {
233 self.data.push(None);
234 #[cfg(debug_assertions)]
235 {
236 self.debug_types.push(None);
237 self.debug_locations.push(None);
238 }
239 }
240 id
241 }
242
243 fn insert<T: Serialize>(
245 &mut self,
246 id: usize,
247 value: &T,
248 location: &'static std::panic::Location<'static>,
249 ) {
250 let mut serialized = Vec::new();
251 ciborium::into_writer(value, &mut serialized).unwrap();
252 self.data[id] = Some(serialized);
253 #[cfg(debug_assertions)]
254 {
255 self.debug_types[id] = Some(std::any::type_name::<T>().to_string());
256 self.debug_locations[id] = Some(location.to_string());
257 }
258 }
259
260 fn get<T: serde::de::DeserializeOwned>(&self, index: usize) -> Result<T, TakeDataError> {
262 if index >= self.data.len() {
263 tracing::trace!(
264 "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",
265 self.data.len(),
266 index
267 );
268 return Err(TakeDataError::DataNotAvailable);
269 }
270 let bytes = self.data[index].as_ref();
271 match bytes {
272 Some(bytes) => match ciborium::from_reader(Cursor::new(bytes)) {
273 Ok(x) => Ok(x),
274 Err(err) => {
275 #[cfg(debug_assertions)]
276 {
277 let debug_type = self.debug_types.get(index);
278 let debug_locations = self.debug_locations.get(index);
279
280 if let (Some(Some(debug_type)), Some(Some(debug_locations))) =
281 (debug_type, debug_locations)
282 {
283 let client_type = std::any::type_name::<T>();
284 let client_location = std::panic::Location::caller();
285 tracing::error!(
287 "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}.",
288 );
289 return Err(TakeDataError::DeserializationError(err));
290 }
291 }
292 tracing::error!("Error deserializing data: {:?}", err);
294 Err(TakeDataError::DeserializationError(err))
295 }
296 },
297 None => Err(TakeDataError::DataPending),
298 }
299 }
300
301 pub(crate) fn extend(&mut self, other: &Self) {
303 if self.data.is_empty() {
305 self.data.push(None);
306 #[cfg(debug_assertions)]
307 {
308 self.debug_types.push(None);
309 self.debug_locations.push(None);
310 }
311 }
312
313 let mut other_data_iter = other.data.iter().cloned();
314 #[cfg(debug_assertions)]
315 let mut other_debug_types_iter = other.debug_types.iter().cloned();
316 #[cfg(debug_assertions)]
317 let mut other_debug_locations_iter = other.debug_locations.iter().cloned();
318
319 if let Some(Some(other_error)) = other_data_iter.next() {
321 self.data[0] = Some(other_error.clone());
322 #[cfg(debug_assertions)]
323 {
324 self.debug_types[0] = other_debug_types_iter.next().unwrap_or(None);
325 self.debug_locations[0] = other_debug_locations_iter.next().unwrap_or(None);
326 }
327 }
328
329 self.data.extend(other_data_iter);
331 #[cfg(debug_assertions)]
332 {
333 self.debug_types.extend(other_debug_types_iter);
334 self.debug_locations.extend(other_debug_locations_iter);
335 }
336 }
337
338 pub(crate) fn serialized(&self) -> SerializedHydrationData {
340 let mut serialized = Vec::new();
341 ciborium::into_writer(&self.data, &mut serialized).unwrap();
342
343 let data = base64::engine::general_purpose::STANDARD.encode(serialized);
344
345 let format_js_list_of_strings = |list: &[Option<String>]| {
346 let body = list
347 .iter()
348 .map(|s| match s {
349 Some(s) => format!(r#""{s}""#),
350 None => r#""unknown""#.to_string(),
351 })
352 .collect::<Vec<_>>()
353 .join(",");
354 format!("[{}]", body)
355 };
356
357 SerializedHydrationData {
358 data,
359 #[cfg(debug_assertions)]
360 debug_types: format_js_list_of_strings(&self.debug_types),
361 #[cfg(debug_assertions)]
362 debug_locations: format_js_list_of_strings(&self.debug_locations),
363 }
364 }
365}
366
367pub struct SerializedHydrationData {
370 pub data: String,
372 #[cfg(debug_assertions)]
374 pub debug_types: String,
375 #[cfg(debug_assertions)]
377 pub debug_locations: String,
378}
379
380#[derive(Debug)]
382pub enum TakeDataError {
383 DeserializationError(ciborium::de::Error<std::io::Error>),
385 DataNotAvailable,
387 DataPending,
389}
390
391impl std::fmt::Display for TakeDataError {
392 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
393 match self {
394 Self::DeserializationError(e) => write!(f, "DeserializationError: {}", e),
395 Self::DataNotAvailable => write!(f, "DataNotAvailable"),
396 Self::DataPending => write!(f, "DataPending"),
397 }
398 }
399}
400
401impl std::error::Error for TakeDataError {}
402
403pub fn head_element_hydration_entry() -> SerializeContextEntry<bool> {
405 serialize_context().create_entry()
406}