use CapturedError as dioxus::Ok() so resources still function as expected (#4851)

This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-10-27 15:06:21 -07:00
1 parent 9f0bacc85c
commit 281f68faf5
14 files changed
+200 -54

No files matched your search

+1 -1
View File
@@ -42,7 +42,7 @@ pub fn NavBar() -> Element {
#[component]
pub fn DogView() -> Element {
let mut img_src = use_loader(|| async move {
anyhow::Ok(
dioxus::Ok(
reqwest::get("https://dog.ceo/api/breeds/image/random")
.await?
.json::<serde_json::Value>()
+1 -1
View File
@@ -41,7 +41,7 @@ fn app() -> Element {
events.push(event);
}
anyhow::Ok(())
dioxus::Ok(())
});
rsx! {
+29 -4
View File
@@ -77,8 +77,11 @@ pub(crate) mod innerlude {
pub use anyhow::anyhow;
pub use anyhow::Context as AnyhowContext;
pub use anyhow::Error;
pub use anyhow::Result;
// pub use anyhow::Error as AnyhowError;
// pub type Error = CapturedError;
/// A result type with a default error of [`CapturedError`].
pub type Result<T, E = CapturedError> = std::result::Result<T, E>;
/// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`ScopeId`] or [`ScopeState`].
///
@@ -97,7 +100,7 @@ pub use crate::innerlude::{
suspend, throw_error, try_consume_context, use_after_render, use_before_render, use_drop,
use_hook, use_hook_with_cleanup, with_owner, AnyValue, AnyhowContext, Attribute,
AttributeValue, Callback, CapturedError, Component, ComponentFunction, DynamicNode, Element,
ElementId, Error, ErrorBoundary, ErrorContext, Event, EventHandler, Fragment, HasAttributes,
ElementId, ErrorBoundary, ErrorContext, Event, EventHandler, Fragment, HasAttributes,
IntoAttributeValue, IntoDynNode, LaunchConfig, ListenerCallback, MarkerWrapper, Mutation,
Mutations, NoOpMutations, OptionStringFromMarker, Properties, ReactiveContext, RenderError,
Result, Runtime, RuntimeGuard, ScopeId, ScopeState, SpawnIfAsync, SubscriberList, Subscribers,
@@ -106,5 +109,27 @@ pub use crate::innerlude::{
VNodeInner, VPlaceholder, VText, VirtualDom, WriteMutations,
};
pub use anyhow::Ok;
/// Equivalent to `Ok::<_, dioxus::CapturedError>(value)`.
///
/// This simplifies creation of an `dioxus::Result` in places where type
/// inference cannot deduce the `E` type of the result &mdash; without needing
/// to write`Ok::<_, dioxus::CapturedError>(value)`.
///
/// One might think that `dioxus::Result::Ok(value)` would work in such cases
/// but it does not.
///
/// ```console
/// error[E0282]: type annotations needed for `std::result::Result<i32, E>`
/// --> src/main.rs:11:13
/// |
/// 11 | let _ = dioxus::Result::Ok(1);
/// | - ^^^^^^^^^^^^^^^^^^ cannot infer type for type parameter `E` declared on the enum `Result`
/// | |
/// | consider giving this pattern the explicit type `std::result::Result<i32, E>`, where the type parameter `E` is specified
/// ```
#[allow(non_snake_case)]
pub fn Ok<T>(value: T) -> Result<T, CapturedError> {
Result::Ok(value)
}
pub use const_format;
+35 -4
View File
@@ -51,7 +51,7 @@ impl From<CapturedError> for RenderError {
}
}
impl<E: Into<Error>> From<E> for RenderError {
impl<E: Into<anyhow::Error>> From<E> for RenderError {
fn from(e: E) -> Self {
let anyhow_err = e.into();
@@ -69,13 +69,44 @@ impl<E: Into<Error>> From<E> for RenderError {
/// An `anyhow::Error` wrapped in an `Arc` so it can be cheaply cloned and passed around.
#[derive(Debug, Clone)]
pub struct CapturedError(pub Arc<Error>);
pub struct CapturedError(pub Arc<anyhow::Error>);
impl CapturedError {
/// Create a `CapturedError` from anything that implements `Display`.
pub fn from_display(t: impl Display) -> Self {
Self(Arc::new(anyhow::anyhow!(t.to_string())))
}
/// Create a `CapturedError` from anything that implements `std::error::Error`.
pub fn new<E>(error: E) -> Self
where
E: std::error::Error + Send + Sync + 'static,
{
anyhow::Error::new(error).into()
}
/// Create a `CapturedError` from anything that implements `Display` and `Debug`.
pub fn msg<M>(t: M) -> Self
where
M: Display + Debug + Send + Sync + 'static,
{
anyhow::Error::msg(t).into()
}
/// Create a `CapturedError` from a boxed `std::error::Error`.
pub fn from_boxed(boxed_error: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
anyhow::Error::from_boxed(boxed_error).into()
}
/// Returns the strong count of the underlying error.
pub fn _strong_count(&self) -> usize {
std::sync::Arc::strong_count(&self.0)
}
/// Try to unwrap the underlying error if this is the only reference to it.
pub fn into_inner(self) -> Option<anyhow::Error> {
Arc::try_unwrap(self.0).ok()
}
}
#[cfg(feature = "serialize")]
@@ -100,14 +131,14 @@ impl<'de> serde::Deserialize<'de> for CapturedError {
}
impl std::ops::Deref for CapturedError {
type Target = Error;
type Target = anyhow::Error;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<E: Into<Error>> From<E> for CapturedError {
impl<E: Into<anyhow::Error>> From<E> for CapturedError {
fn from(e: E) -> Self {
Self(Arc::new(e.into()))
}
+7 -4
View File
@@ -331,7 +331,10 @@ impl NativeFileData for DesktopFileData {
fn read_bytes(
&self,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = Result<bytes::Bytes, dioxus_core::Error>> + 'static>,
Box<
dyn std::future::Future<Output = Result<bytes::Bytes, dioxus_core::CapturedError>>
+ 'static,
>,
> {
let path = self.0.clone();
Box::pin(async move { Ok(bytes::Bytes::from(std::fs::read(&path)?)) })
@@ -340,7 +343,7 @@ impl NativeFileData for DesktopFileData {
fn read_string(
&self,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = Result<String, dioxus_core::Error>> + 'static>,
Box<dyn std::future::Future<Output = Result<String, dioxus_core::CapturedError>> + 'static>,
> {
let path = self.0.clone();
Box::pin(async move { Ok(std::fs::read_to_string(&path)?) })
@@ -358,7 +361,7 @@ impl NativeFileData for DesktopFileData {
&self,
) -> std::pin::Pin<
Box<
dyn futures_util::Stream<Item = Result<bytes::Bytes, dioxus_core::Error>>
dyn futures_util::Stream<Item = Result<bytes::Bytes, dioxus_core::CapturedError>>
+ 'static
+ Send,
>,
@@ -374,7 +377,7 @@ impl NativeFileData for DesktopFileData {
// let n = file
// .read(&mut buf)
// .await
// .map_err(|e| dioxus_core::Error::from(e))?;
// .map_err(|e| dioxus_core::CapturedError::from(e))?;
// if n == 0 {
// Ok(None)
// } else {
+1 -1
View File
@@ -27,7 +27,7 @@
pub use dioxus_core;
#[doc(inline)]
pub use dioxus_core::{CapturedError, Error, Ok, Result};
pub use dioxus_core::{CapturedError, Ok, Result};
#[cfg(feature = "launch")]
#[cfg_attr(docsrs, doc(cfg(feature = "launch")))]
+3 -3
View File
@@ -33,7 +33,7 @@ pub fn use_loader<F, T, E>(mut future: impl FnMut() -> F + 'static) -> Result<Lo
where
F: Future<Output = Result<T, E>> + 'static,
T: 'static + PartialEq + Serialize + DeserializeOwned,
E: Into<dioxus_core::Error> + 'static,
E: Into<CapturedError> + 'static,
{
let serialize_context = use_hook(crate::transport::serialize_context);
@@ -101,8 +101,8 @@ where
// Remap the error to the captured error type so it's cheap to clone and pass out, just
// slightly more cumbersome to access the inner error.
let out = out.map_err(|e| {
let anyhow_err: anyhow::Error = e.into();
anyhow_err.into()
let anyhow_err: CapturedError = e.into();
anyhow_err
});
// If this is the first run and we are on the server, cache the data in the slot we reserved for it
+8 -10
View File
@@ -4,7 +4,7 @@
use base64::Engine;
use dioxus_core::CapturedError;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{cell::RefCell, io::Cursor, rc::Rc, sync::Arc};
use std::{cell::RefCell, io::Cursor, rc::Rc};
#[cfg(feature = "web")]
thread_local! {
@@ -429,15 +429,15 @@ pub fn head_element_hydration_entry() -> SerializeContextEntry<bool> {
/// 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`.
/// to customize how types are transported which helps for non-serializable types like `dioxus_core::CapturedError`.
///
/// 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
/// You can also implement `Transportable` for `Result<T, dioxus_core::CapturedError>` 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.
/// Note that transporting a `Result<T, dioxus_core::CapturedError>` will lose various aspects of the original
/// `dioxus_core::CapturedError` 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>;
@@ -474,7 +474,7 @@ struct TransportResultErr<T> {
#[doc(hidden)]
pub struct TransportViaErrMarker;
impl<T> Transportable<TransportViaErrMarker> for Result<T, dioxus_core::Error>
impl<T> Transportable<TransportViaErrMarker> for Result<T, anyhow::Error>
where
T: Serialize + DeserializeOwned + 'static,
{
@@ -497,7 +497,7 @@ where
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()))),
Err(captured) => Ok(Err(anyhow::Error::msg(captured.to_string()))),
}
}
}
@@ -525,8 +525,6 @@ impl Transportable<TransportCapturedError> for CapturedError {
Self: Sized,
{
let err: TransportError = ciborium::from_reader(Cursor::new(bytes))?;
Ok(CapturedError(Arc::new(dioxus_core::Error::msg::<String>(
err.error,
))))
Ok(dioxus_core::CapturedError::msg::<String>(err.error))
}
}
+8 -7
View File
@@ -1,5 +1,6 @@
#![allow(clippy::needless_return)]
use dioxus_core::CapturedError;
use std::{hint::black_box, prelude::rust_2024::Future, sync::atomic::AtomicBool};
/// `Lazy` is a thread-safe, lazily-initialized global variable.
@@ -14,7 +15,7 @@ use std::{hint::black_box, prelude::rust_2024::Future, sync::atomic::AtomicBool}
pub struct Lazy<T> {
value: std::sync::OnceLock<T>,
started_initialization: AtomicBool,
constructor: Option<fn() -> Result<T, dioxus_core::Error>>,
constructor: Option<fn() -> Result<T, CapturedError>>,
_phantom: std::marker::PhantomData<T>,
}
@@ -36,7 +37,7 @@ impl<T: Send + Sync + 'static> Lazy<T> {
where
F: Fn() -> G + Copy,
G: Future<Output = Result<T, E>> + Send + 'static,
E: Into<dioxus_core::Error>,
E: Into<CapturedError>,
{
if std::mem::size_of::<F>() != 0 {
panic!("The constructor function must be a zero-sized type (ZST). Consider using a function pointer or a closure without captured variables.");
@@ -57,10 +58,10 @@ impl<T: Send + Sync + 'static> Lazy<T> {
///
/// This should only be called once during the server setup phase, typically inside `dioxus::serve`.
/// Future calls to this method will return an error containing the provided value.
pub fn set(&self, pool: T) -> Result<(), dioxus_core::Error> {
pub fn set(&self, pool: T) -> Result<(), CapturedError> {
let res = self.value.set(pool);
if res.is_err() {
return Err(anyhow::anyhow!("Lazy value is already initialized."));
return Err(anyhow::anyhow!("Lazy value is already initialized.").into());
}
Ok(())
@@ -71,7 +72,7 @@ impl<T: Send + Sync + 'static> Lazy<T> {
}
/// Initialize the value of the `Lazy` instance if it hasn't been initialized yet.
pub fn initialize(&self) -> Result<(), dioxus_core::Error> {
pub fn initialize(&self) -> Result<(), CapturedError> {
if let Some(constructor) = self.constructor {
// If we're already initializing this value, wait on the receiver.
if self
@@ -115,12 +116,12 @@ impl<T: Send + Sync + 'static> std::ops::Deref for Lazy<T> {
///
/// We call the `rust-call` method of the zero-sized constructor function. This is safe because we're
/// not actually dereferencing any unsafe data, just calling its vtable entry to get the future.
fn blocking_initialize<T, F, G, E>() -> Result<T, dioxus_core::Error>
fn blocking_initialize<T, F, G, E>() -> Result<T, CapturedError>
where
T: Send + Sync + 'static,
F: Fn() -> G + Copy,
G: Future<Output = Result<T, E>> + Send + 'static,
E: Into<dioxus_core::Error>,
E: Into<CapturedError>,
{
assert_eq!(std::mem::size_of::<F>(), 0, "The constructor function must be a zero-sized type (ZST). Consider using a function pointer or a closure without captured variables.");
+66
View File
@@ -566,6 +566,7 @@ mod resp {
use super::*;
use axum::response::Response;
use dioxus_core::CapturedError;
use http::HeaderValue;
/// A trait for converting the result of the Server Function into an Axum response.
@@ -661,6 +662,71 @@ mod resp {
}
}
impl<T> MakeAxumError<CapturedError> for &&ServerFnDecoder<Result<T, CapturedError>> {
fn make_axum_error(
self,
result: Result<Response, CapturedError>,
) -> Result<Response, Response> {
match result {
Ok(res) => Ok(res),
// Optimize the case where we have sole ownership of the error
Err(errr) if errr._strong_count() == 1 => {
let err = errr.into_inner().unwrap();
<&&ServerFnDecoder<Result<T, anyhow::Error>> as MakeAxumError<anyhow::Error>>::make_axum_error(
&&ServerFnDecoder::new(),
Err(err),
)
}
Err(errr) => {
// The `WithHttpError` trait emits ServerFnErrors so we can downcast them here
// to create richer responses.
let payload = match errr.downcast_ref::<ServerFnError>() {
Some(ServerFnError::ServerError {
message,
code,
details,
}) => ErrorPayload {
message: message.clone(),
code: *code,
data: details.clone(),
},
Some(other) => ErrorPayload {
message: other.to_string(),
code: 500,
data: None,
},
None => match errr.downcast_ref::<HttpError>() {
Some(http_err) => ErrorPayload {
message: http_err
.message
.clone()
.unwrap_or_else(|| http_err.status.to_string()),
code: http_err.status.as_u16(),
data: None,
},
None => ErrorPayload {
code: 500,
message: errr.to_string(),
data: None,
},
},
};
let body = serde_json::to_string(&payload).unwrap();
let mut resp = Response::new(body.into());
resp.headers_mut().insert(
http::header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
);
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
Err(resp)
}
}
}
}
impl<T> MakeAxumError<anyhow::Error> for &&ServerFnDecoder<Result<T, anyhow::Error>> {
fn make_axum_error(
self,
+2 -2
View File
@@ -61,7 +61,7 @@ use {
pub fn use_websocket<
In: 'static,
Out: 'static,
E: Into<dioxus_core::Error> + 'static,
E: Into<CapturedError> + 'static,
F: Future<Output = Result<Websocket<In, Out, Enc>, E>> + 'static,
Enc: Encoding,
>(
@@ -72,7 +72,7 @@ pub fn use_websocket<
let status_read = use_hook(|| ReadSignal::new(status));
let connection = use_resource(move || {
let connection = connect_to_websocket().map_err(|e| CapturedError::from(e.into()));
let connection = connect_to_websocket().map_err(|e| e.into());
async move {
let connection = connection.await;
+1 -1
View File
@@ -149,7 +149,7 @@ impl<T, E> std::future::Future for ServerFnRequest<Result<T, E>> {
#[diagnostic::on_unimplemented(
message = "The return type of a server function must be `Result<T, E>`",
note = "`T` is either `impl IntoResponse` *or* `impl Serialize`",
note = "`E` is either `From<ServerFnError> + Serialize`, `dioxus::Error` or `StatusCode`."
note = "`E` is either `From<ServerFnError> + Serialize`, `dioxus::CapturedError` or `StatusCode`."
)]
pub trait AssertIsResult {}
impl<T, E> AssertIsResult for Result<T, E> {}
+29 -12
View File
@@ -30,17 +30,18 @@ impl FileData {
self.inner.last_modified()
}
pub async fn read_bytes(&self) -> Result<Bytes, dioxus_core::Error> {
pub async fn read_bytes(&self) -> Result<Bytes, dioxus_core::CapturedError> {
self.inner.read_bytes().await
}
pub async fn read_string(&self) -> Result<String, dioxus_core::Error> {
pub async fn read_string(&self) -> Result<String, dioxus_core::CapturedError> {
self.inner.read_string().await
}
pub fn byte_stream(
&self,
) -> Pin<Box<dyn Stream<Item = Result<Bytes, dioxus_core::Error>> + Send + 'static>> {
) -> Pin<Box<dyn Stream<Item = Result<Bytes, dioxus_core::CapturedError>> + Send + 'static>>
{
self.inner.byte_stream()
}
@@ -70,13 +71,19 @@ pub trait NativeFileData: Send + Sync {
fn content_type(&self) -> Option<String>;
fn read_bytes(
&self,
) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::Error>> + 'static>>;
) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::CapturedError>> + 'static>>;
fn byte_stream(
&self,
) -> Pin<Box<dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::Error>> + 'static + Send>>;
) -> Pin<
Box<
dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::CapturedError>>
+ 'static
+ Send,
>,
>;
fn read_string(
&self,
) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::Error>> + 'static>>;
) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::CapturedError>> + 'static>>;
fn inner(&self) -> &dyn std::any::Any;
}
@@ -143,7 +150,8 @@ mod serialize {
fn read_bytes(
&self,
) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::Error>> + 'static>> {
) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::CapturedError>> + 'static>>
{
let contents = self.contents.clone();
let path = self.path.clone();
@@ -157,13 +165,16 @@ mod serialize {
return Ok(std::fs::read(path).map(Bytes::from)?);
}
Err(dioxus_core::Error::msg("File contents not available"))
Err(dioxus_core::CapturedError::msg(
"File contents not available",
))
})
}
fn read_string(
&self,
) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::Error>> + 'static>> {
) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::CapturedError>> + 'static>>
{
let contents = self.contents.clone();
let path = self.path.clone();
@@ -177,7 +188,9 @@ mod serialize {
return Ok(std::fs::read_to_string(path)?);
}
Err(dioxus_core::Error::msg("File contents not available"))
Err(dioxus_core::CapturedError::msg(
"File contents not available",
))
})
}
@@ -185,7 +198,9 @@ mod serialize {
&self,
) -> Pin<
Box<
dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::Error>> + 'static + Send,
dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::CapturedError>>
+ 'static
+ Send,
>,
> {
let contents = self.contents.clone();
@@ -201,7 +216,9 @@ mod serialize {
return Ok(std::fs::read(path).map(Bytes::from)?);
}
Err(dioxus_core::Error::msg("File contents not available"))
Err(dioxus_core::CapturedError::msg(
"File contents not available",
))
}))
}
+9 -4
View File
@@ -39,7 +39,7 @@ impl NativeFileData for WebFileData {
fn read_bytes(
&self,
) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::Error>> + 'static>> {
) -> Pin<Box<dyn Future<Output = Result<Bytes, dioxus_core::CapturedError>> + 'static>> {
let file_reader = self.reader.clone();
let file_reader_ = self.reader.clone();
let file = self.file.clone();
@@ -72,7 +72,7 @@ impl NativeFileData for WebFileData {
fn read_string(
&self,
) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::Error>> + 'static>> {
) -> Pin<Box<dyn Future<Output = Result<String, dioxus_core::CapturedError>> + 'static>> {
let file_reader = self.reader.clone();
let file_reader_ = self.reader.clone();
let file = self.file.clone();
@@ -113,8 +113,13 @@ impl NativeFileData for WebFileData {
/// We should maybe update these APIs to use our own custom `ByteBuffer` type to avoid going through `Vec<u8>`?
fn byte_stream(
&self,
) -> Pin<Box<dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::Error>> + 'static + Send>>
{
) -> Pin<
Box<
dyn futures_util::Stream<Item = Result<Bytes, dioxus_core::CapturedError>>
+ 'static
+ Send,
>,
> {
let file = self.file.dyn_ref::<web_sys::Blob>().unwrap().clone();
Box::pin(SendWrapper::new(futures_util::stream::once(async move {
let array_buff = wasm_bindgen_futures::JsFuture::from(file.array_buffer())