redirects, middleware, Transport, set/capture error status (#4751)

* wip: redirect

* redirect example

* fix file upload not in a form

* wip

* deny missing cfgs

* wip: add router fallback but it's not working

* fix suspense bug after rebuilds that queued work

* more cleanup

* add test for error codes

* add helpful comment

* bring back middleware annotation

* fix cfg

* add server feature

* fix doctest

* just disable liveview test for now

* fix liveview

* dont send init

* cache more playwright!

* add more pkgs to cache
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-10-10 21:43:57 -07:00
1 parent 8a81475ba8
commit 9243ef4128
35 files changed
+935 -800

No files matched your search

+103 -142
View File
@@ -3,12 +3,8 @@
use base64::Engine;
use dioxus_core::CapturedError;
use serde::Serialize;
use std::{cell::RefCell, io::Cursor, rc::Rc};
// use base64::Engine;
// use dioxus_core::{CapturedError, Error};
// use serde::{de::DeserializeOwned, Deserialize, Serialize};
// use std::{cell::RefCell, io::Cursor, rc::Rc, sync::Arc};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{cell::RefCell, io::Cursor, rc::Rc, sync::Arc};
#[cfg(feature = "web")]
thread_local! {
@@ -88,8 +84,7 @@ impl HydrationContext {
})
}
// pub(crate) fn insert<T: Transportable<M>, M: 'static>(
pub(crate) fn insert<T: Serialize>(
pub(crate) fn insert<T: Transportable<M>, M: 'static>(
&self,
id: usize,
value: &T,
@@ -98,8 +93,7 @@ impl HydrationContext {
self.data.borrow_mut().insert(id, value, location);
}
// pub(crate) fn get<T: Transportable<M>, M: 'static>(
pub(crate) fn get<T: serde::de::DeserializeOwned>(
pub(crate) fn get<T: Transportable<M>, M: 'static>(
&self,
id: usize,
) -> Result<T, TakeDataError> {
@@ -134,35 +128,20 @@ impl<T> Clone for SerializeContextEntry<T> {
impl<T> SerializeContextEntry<T> {
/// Insert data into an entry that was created with [`HydrationContext::create_entry`]
pub fn insert(&self, value: &T, location: &'static std::panic::Location<'static>)
pub fn insert<M: 'static>(self, value: &T, location: &'static std::panic::Location<'static>)
where
T: Serialize,
T: Transportable<M>,
{
self.context.insert(self.index, value, location);
}
/// Grab the data from the serialize context
pub fn get(&self) -> Result<T, TakeDataError>
pub fn get<M: 'static>(&self) -> Result<T, TakeDataError>
where
T: serde::de::DeserializeOwned,
T: Transportable<M>,
{
self.context.get(self.index)
}
// /// Insert data into an entry that was created with [`HydrationContext::create_entry`]
// pub fn insert<M: 'static>(self, value: &T, location: &'static std::panic::Location<'static>)
// where
// T: Transportable<M>,
// {
// self.context.insert(self.index, value, location);
// }
// /// Grab the data from the serialize context
// pub fn get<M: 'static>(&self) -> Result<T, TakeDataError>
// where
// T: Transportable<M>,
// {
// self.context.get(self.index)
// }
}
/// Check if the client is currently rendering a component for hydration. Always returns true on the server.
@@ -276,16 +255,13 @@ impl HTMLData {
}
/// Insert data into an entry that was created with [`Self::create_entry`]
// fn insert<T: Transportable<M>, M: 'static>(
fn insert<T: Serialize>(
fn insert<T: Transportable<M>, M: 'static>(
&mut self,
id: usize,
value: &T,
#[allow(unused)] location: &'static std::panic::Location<'static>,
) {
// let serialized = value.transport_to_bytes();
let mut serialized = Vec::new();
ciborium::into_writer(value, &mut serialized).unwrap();
let serialized = value.transport_to_bytes();
self.data[id] = Some(serialized);
#[cfg(debug_assertions)]
{
@@ -295,8 +271,7 @@ impl HTMLData {
}
/// Get the data from the serialize context
// fn get<T: Transportable<M>, M: 'static>(&self, index: usize) -> Result<T, TakeDataError> {
fn get<T: serde::de::DeserializeOwned>(&self, index: usize) -> Result<T, TakeDataError> {
fn get<T: Transportable<M>, M: 'static>(&self, index: usize) -> Result<T, TakeDataError> {
if index >= self.data.len() {
tracing::trace!(
"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",
@@ -307,8 +282,7 @@ impl HTMLData {
}
let bytes = self.data[index].as_ref();
match bytes {
Some(bytes) => match ciborium::from_reader(Cursor::new(bytes)) {
// Some(bytes) => match T::transport_from_bytes(bytes) {
Some(bytes) => match T::transport_from_bytes(bytes) {
Ok(x) => Ok(x),
Err(err) => {
#[cfg(debug_assertions)]
@@ -453,119 +427,106 @@ pub fn head_element_hydration_entry() -> SerializeContextEntry<bool> {
serialize_context().create_entry()
}
// /// An error that can occur when trying to take data from the server
// #[derive(thiserror::Error, Debug)]
// pub enum TakeDataError {
// /// Deserializing the data failed
// #[error("DeserializationError: {0}")]
// DeserializationError(ciborium::de::Error<std::io::Error>),
/// A `Transportable` type can be safely transported from the server to the client, and be used for
/// hydration. Not all types can sensibly be transported, but many can. This trait makes it possible
/// to customize how types are transported which helps for non-serializable types like `dioxus_core::Error`.
///
/// By default, all types that implement `Serialize` and `DeserializeOwned` are transportable.
///
/// You can also implement `Transportable` for `Result<T, dioxus_core::Error>` where `T` is
/// `Serialize` and `DeserializeOwned` to allow transporting results that may contain errors.
///
/// Note that transporting a `Result<T, dioxus_core::Error>` will lose various aspects of the original
/// `dioxus_core::Error` such as backtraces and source errors, but will preserve the error message.
pub trait Transportable<M = ()>: 'static {
/// Serialize the type to a byte vector for transport
fn transport_to_bytes(&self) -> Vec<u8>;
// /// No data was available
// #[error("DataNotAvailable")]
// DataNotAvailable,
/// Deserialize the type from a byte slice
fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>
where
Self: Sized;
}
// /// The server serialized a placeholder for the data, but it isn't available yet
// #[error("DataPending")]
// DataPending,
// }
impl<T> Transportable<()> for T
where
T: Serialize + DeserializeOwned + 'static,
{
fn transport_to_bytes(&self) -> Vec<u8> {
let mut serialized = Vec::new();
ciborium::into_writer(self, &mut serialized).unwrap();
serialized
}
// /// A `Transportable` type can be safely transported from the server to the client, and be used for
// /// hydration. Not all types can sensibly be transported, but many can. This trait makes it possible
// /// to customize how types are transported which helps for non-serializable types like `dioxus_core::Error`.
// ///
// /// By default, all types that implement `Serialize` and `DeserializeOwned` are transportable.
// ///
// /// You can also implement `Transportable` for `Result<T, dioxus_core::Error>` where `T` is
// /// `Serialize` and `DeserializeOwned` to allow transporting results that may contain errors.
// ///
// /// Note that transporting a `Result<T, dioxus_core::Error>` will lose various aspects of the original
// /// `dioxus_core::Error` such as backtraces and source errors, but will preserve the error message.
// pub trait Transportable<M = ()>: 'static {
// /// Serialize the type to a byte vector for transport
// fn transport_to_bytes(&self) -> Vec<u8>;
fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>
where
Self: Sized,
{
ciborium::from_reader(Cursor::new(bytes))
}
}
// /// Deserialize the type from a byte slice
// fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>
// where
// Self: Sized;
// }
#[derive(Serialize, Deserialize)]
struct TransportResultErr<T> {
error: Result<T, CapturedError>,
}
// impl<T> Transportable<()> for T
// where
// T: Serialize + DeserializeOwned + 'static,
// {
// fn transport_to_bytes(&self) -> Vec<u8> {
// let mut serialized = Vec::new();
// ciborium::into_writer(self, &mut serialized).unwrap();
// serialized
// }
#[doc(hidden)]
pub struct TransportViaErrMarker;
// fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>
// where
// Self: Sized,
// {
// ciborium::from_reader(Cursor::new(bytes))
// }
// }
impl<T> Transportable<TransportViaErrMarker> for Result<T, dioxus_core::Error>
where
T: Serialize + DeserializeOwned + 'static,
{
fn transport_to_bytes(&self) -> Vec<u8> {
let err = TransportResultErr {
error: self
.as_ref()
.map_err(|e| CapturedError::from_display(e.to_string())),
};
// #[derive(Serialize, Deserialize)]
// struct TransportResultErr<T> {
// error: Result<T, CapturedError>,
// }
let mut serialized = Vec::new();
ciborium::into_writer(&err, &mut serialized).unwrap();
serialized
}
// #[doc(hidden)]
// pub struct TransportViaErrMarker;
fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>
where
Self: Sized,
{
let err: TransportResultErr<T> = ciborium::from_reader(Cursor::new(bytes))?;
match err.error {
Ok(value) => Ok(Ok(value)),
Err(captured) => Ok(Err(dioxus_core::Error::msg(captured.to_string()))),
}
}
}
// impl<T> Transportable<TransportViaErrMarker> for Result<T, dioxus_core::Error>
// where
// T: Serialize + DeserializeOwned + 'static,
// {
// fn transport_to_bytes(&self) -> Vec<u8> {
// let err = TransportResultErr {
// error: self
// .as_ref()
// .map_err(|e| CapturedError::from_display(e.to_string())),
// };
#[doc(hidden)]
pub struct TransportCapturedError;
#[derive(Serialize, Deserialize)]
struct TransportError {
error: String,
}
// let mut serialized = Vec::new();
// ciborium::into_writer(&err, &mut serialized).unwrap();
// serialized
// }
impl Transportable<TransportCapturedError> for CapturedError {
fn transport_to_bytes(&self) -> Vec<u8> {
let err = TransportError {
error: self.to_string(),
};
// fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>
// where
// Self: Sized,
// {
// let err: TransportResultErr<T> = ciborium::from_reader(Cursor::new(bytes))?;
// match err.error {
// Ok(value) => Ok(Ok(value)),
// Err(captured) => Ok(Err(dioxus_core::Error::msg(captured.to_string()))),
// }
// }
// }
let mut serialized = Vec::new();
ciborium::into_writer(&err, &mut serialized).unwrap();
serialized
}
// #[doc(hidden)]
// pub struct TransportCapturedError;
// #[derive(Serialize, Deserialize)]
// struct TransportError {
// error: String,
// }
// impl Transportable<TransportCapturedError> for CapturedError {
// fn transport_to_bytes(&self) -> Vec<u8> {
// let err = TransportError {
// error: self.to_string(),
// };
// let mut serialized = Vec::new();
// ciborium::into_writer(&err, &mut serialized).unwrap();
// serialized
// }
// fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>
// where
// Self: Sized,
// {
// let err: TransportError = ciborium::from_reader(Cursor::new(bytes))?;
// Ok(CapturedError(Arc::new(Error::msg::<String>(err.error))))
// }
// }
fn transport_from_bytes(bytes: &[u8]) -> Result<Self, ciborium::de::Error<std::io::Error>>
where
Self: Sized,
{
let err: TransportError = ciborium::from_reader(Cursor::new(bytes))?;
Ok(CapturedError(Arc::new(dioxus_core::Error::msg::<String>(
err.error,
))))
}
}