dioxus_fullstack/error.rs
1use std::{
2 error::Error,
3 fmt::{Debug, Display},
4 str::FromStr,
5};
6
7use dioxus_core::{CapturedError, RenderError};
8use serde::{de::DeserializeOwned, Serialize};
9use server_fn::{
10 codec::JsonEncoding,
11 error::{FromServerFnError, ServerFnErrorErr},
12};
13
14/// A default result type for server functions, which can either be successful or contain an error. The [`ServerFnResult`] type
15/// is a convenient alias for a `Result` type that uses [`ServerFnError`] as the error type.
16///
17/// # Example
18/// ```rust
19/// use dioxus::prelude::*;
20///
21/// #[server]
22/// async fn parse_number(number: String) -> ServerFnResult<f32> {
23/// let parsed_number: f32 = number.parse()?;
24/// Ok(parsed_number)
25/// }
26/// ```
27pub type ServerFnResult<T = (), E = String> = std::result::Result<T, ServerFnError<E>>;
28
29/// An error type for server functions. This may either be an error that occurred while running the server
30/// function logic, or an error that occurred while communicating with the server inside the server function crate.
31///
32/// ## Usage
33///
34/// You can use the [`ServerFnError`] type in the Error type of your server function result or use the [`ServerFnResult`]
35/// type as the return type of your server function. When you call the server function, you can handle the error directly
36/// or convert it into a [`CapturedError`] to throw into the nearest [`ErrorBoundary`](dioxus_core::ErrorBoundary).
37///
38/// ```rust
39/// use dioxus::prelude::*;
40///
41/// #[server]
42/// async fn parse_number(number: String) -> ServerFnResult<f32> {
43/// // You can convert any error type into the `ServerFnError` with the `?` operator
44/// let parsed_number: f32 = number.parse()?;
45/// Ok(parsed_number)
46/// }
47///
48/// #[component]
49/// fn ParseNumberServer() -> Element {
50/// let mut number = use_signal(|| "42".to_string());
51/// let mut parsed = use_signal(|| None);
52///
53/// rsx! {
54/// input {
55/// value: "{number}",
56/// oninput: move |e| number.set(e.value()),
57/// }
58/// button {
59/// onclick: move |_| async move {
60/// // Call the server function to parse the number
61/// // If the result is Ok, continue running the closure, otherwise bubble up the
62/// // error to the nearest error boundary with `?`
63/// let result = parse_number(number()).await?;
64/// parsed.set(Some(result));
65/// Ok(())
66/// },
67/// "Parse Number"
68/// }
69/// if let Some(value) = parsed() {
70/// p { "Parsed number: {value}" }
71/// } else {
72/// p { "No number parsed yet." }
73/// }
74/// }
75/// }
76/// ```
77///
78/// ## Differences from [`CapturedError`]
79///
80/// Both this error type and [`CapturedError`] can be used to represent boxed errors in dioxus. However, this error type
81/// is more strict about the kinds of errors it can represent. [`CapturedError`] can represent any error that implements
82/// the [`Error`] trait or can be converted to a string. [`CapturedError`] holds onto the type information of the error
83/// and lets you downcast the error to its original type.
84///
85/// [`ServerFnError`] represents server function errors as [`String`]s by default without any additional type information.
86/// This makes it easy to serialize the error to JSON and send it over the wire, but it means that you can't get the
87/// original type information of the error back. If you need to preserve the type information of the error, you can use a
88/// [custom error variant](#custom-error-variants) that holds onto the type information.
89///
90/// ## Custom error variants
91///
92/// The [`ServerFnError`] type accepts a generic type parameter `T` that is used to represent the error type used for server
93/// functions. If you need to keep the type information of your error, you can create a custom error variant that implements
94/// [`Serialize`] and [`DeserializeOwned`]. This allows you to serialize the error to JSON and send it over the wire,
95/// while still preserving the type information.
96///
97/// ```rust
98/// use dioxus::prelude::*;
99/// use serde::{Deserialize, Serialize};
100/// use std::fmt::Debug;
101///
102/// #[derive(Clone, Debug, Serialize, Deserialize)]
103/// pub struct MyCustomError {
104/// message: String,
105/// code: u32,
106/// }
107///
108/// impl MyCustomError {
109/// pub fn new(message: String, code: u32) -> Self {
110/// Self { message, code }
111/// }
112/// }
113///
114/// #[server]
115/// async fn server_function() -> ServerFnResult<String, MyCustomError> {
116/// // Return your custom error
117/// Err(ServerFnError::ServerError(MyCustomError::new(
118/// "An error occurred".to_string(),
119/// 404,
120/// )))
121/// }
122/// ```
123#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
124pub enum ServerFnError<T = String> {
125 /// An error running the server function
126 ServerError(T),
127
128 /// An error communicating with the server
129 CommunicationError(ServerFnErrorErr),
130}
131
132impl ServerFnError {
133 /// Creates a new `ServerFnError` from something that implements `ToString`.
134 ///
135 /// # Examples
136 /// ```rust
137 /// use dioxus::prelude::*;
138 /// use serde::{Serialize, Deserialize};
139 ///
140 /// #[server]
141 /// async fn server_function() -> ServerFnResult<String> {
142 /// // Return your custom error
143 /// Err(ServerFnError::new("Something went wrong"))
144 /// }
145 /// ```
146 pub fn new(error: impl ToString) -> Self {
147 Self::ServerError(error.to_string())
148 }
149}
150
151impl<T> ServerFnError<T> {
152 /// Returns true if the error is a server error
153 pub fn is_server_error(&self) -> bool {
154 matches!(self, ServerFnError::ServerError(_))
155 }
156
157 /// Returns true if the error is a communication error
158 pub fn is_communication_error(&self) -> bool {
159 matches!(self, ServerFnError::CommunicationError(_))
160 }
161
162 /// Returns a reference to the server error if it is a server error
163 /// or `None` if it is a communication error.
164 pub fn server_error(&self) -> Option<&T> {
165 match self {
166 ServerFnError::ServerError(err) => Some(err),
167 ServerFnError::CommunicationError(_) => None,
168 }
169 }
170
171 /// Returns a reference to the communication error if it is a communication error
172 /// or `None` if it is a server error.
173 pub fn communication_error(&self) -> Option<&ServerFnErrorErr> {
174 match self {
175 ServerFnError::ServerError(_) => None,
176 ServerFnError::CommunicationError(err) => Some(err),
177 }
178 }
179}
180
181impl<T: Serialize + DeserializeOwned + Debug + 'static> FromServerFnError for ServerFnError<T> {
182 type Encoder = JsonEncoding;
183
184 fn from_server_fn_error(err: ServerFnErrorErr) -> Self {
185 Self::CommunicationError(err)
186 }
187}
188
189impl<T: FromStr> FromStr for ServerFnError<T> {
190 type Err = <T as FromStr>::Err;
191
192 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
193 std::result::Result::Ok(Self::ServerError(T::from_str(s)?))
194 }
195}
196
197impl<T: Display> Display for ServerFnError<T> {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 match self {
200 ServerFnError::ServerError(err) => write!(f, "Server error: {err}"),
201 ServerFnError::CommunicationError(err) => write!(f, "Communication error: {err}"),
202 }
203 }
204}
205
206impl From<ServerFnError> for CapturedError {
207 fn from(error: ServerFnError) -> Self {
208 Self::from_display(error)
209 }
210}
211
212impl From<ServerFnError> for RenderError {
213 fn from(error: ServerFnError) -> Self {
214 RenderError::Aborted(CapturedError::from(error))
215 }
216}
217
218impl<E: Error> From<E> for ServerFnError {
219 fn from(error: E) -> Self {
220 Self::ServerError(error.to_string())
221 }
222}