dioxus_fullstack_core/
error.rs

1use axum_core::response::IntoResponse;
2use futures_util::TryStreamExt;
3use http::StatusCode;
4use serde::{Deserialize, Serialize};
5use std::fmt::Debug;
6
7use crate::HttpError;
8
9/// A default result type for server functions, which can either be successful or contain an error. The [`ServerFnResult`] type
10/// is a convenient alias for a `Result` type that uses [`ServerFnError`] as the error type.
11///
12/// # Example
13/// ```rust
14/// use dioxus::prelude::*;
15///
16/// #[server]
17/// async fn parse_number(number: String) -> Result<f32> {
18///     let parsed_number: f32 = number.parse()?;
19///     Ok(parsed_number)
20/// }
21/// ```
22pub type ServerFnResult<T = ()> = std::result::Result<T, ServerFnError>;
23
24/// The error type for the server function system. This enum encompasses all possible errors that can occur
25/// during the registration, invocation, and processing of server functions.
26#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub enum ServerFnError {
28    /// Occurs when there is an error while actually running the function on the server.
29    ///
30    /// The `details` field can optionally contain additional structured information about the error.
31    /// When passing typed errors from the server to the client, the `details` field contains the serialized
32    /// representation of the error.
33    #[error("error running server function: {message} (details: {details:#?})")]
34    ServerError {
35        /// A human-readable message describing the error.
36        message: String,
37
38        /// HTTP status code associated with the error.
39        code: u16,
40
41        #[serde(skip_serializing_if = "Option::is_none")]
42        details: Option<serde_json::Value>,
43    },
44
45    /// Occurs on the client if there is a network error while trying to run function on server.
46    #[error("error reaching server to call server function: {0} ")]
47    Request(RequestError),
48
49    /// Occurs on the client if there is an error while trying to read the response body as a stream.
50    #[error("error reading response body stream: {0}")]
51    StreamError(String),
52
53    /// Error while trying to register the server function (only occurs in case of poisoned RwLock).
54    #[error("error while trying to register the server function: {0}")]
55    Registration(String),
56
57    /// Occurs on the client if trying to use an unsupported `HTTP` method when building a request.
58    #[error("error trying to build `HTTP` method request: {0}")]
59    UnsupportedRequestMethod(String),
60
61    /// Occurs when there is an error while actually running the middleware on the server.
62    #[error("error running middleware: {0}")]
63    MiddlewareError(String),
64
65    /// Occurs on the client if there is an error deserializing the server's response.
66    #[error("error deserializing server function results: {0}")]
67    Deserialization(String),
68
69    /// Occurs on the client if there is an error serializing the server function arguments.
70    #[error("error serializing server function arguments: {0}")]
71    Serialization(String),
72
73    /// Occurs on the server if there is an error deserializing one of the arguments that's been sent.
74    #[error("error deserializing server function arguments: {0}")]
75    Args(String),
76
77    /// Occurs on the server if there's a missing argument.
78    #[error("missing argument {0}")]
79    MissingArg(String),
80
81    /// Occurs on the server if there is an error creating an HTTP response.
82    #[error("error creating response {0}")]
83    Response(String),
84}
85
86impl ServerFnError {
87    /// Create a new server error (status code 500) with a message.
88    pub fn new(f: impl ToString) -> Self {
89        ServerFnError::ServerError {
90            message: f.to_string(),
91            details: None,
92            code: 500,
93        }
94    }
95
96    /// Create a new server error (status code 500) with a message and details.
97    pub async fn from_axum_response(resp: axum_core::response::Response) -> Self {
98        let status = resp.status();
99        let message = resp
100            .into_body()
101            .into_data_stream()
102            .try_fold(Vec::new(), |mut acc, chunk| async move {
103                acc.extend_from_slice(&chunk);
104                Ok(acc)
105            })
106            .await
107            .ok()
108            .and_then(|bytes| String::from_utf8(bytes).ok())
109            .unwrap_or_else(|| status.canonical_reason().unwrap_or("").to_string());
110
111        ServerFnError::ServerError {
112            message,
113            code: status.as_u16(),
114            details: None,
115        }
116    }
117}
118
119impl From<anyhow::Error> for ServerFnError {
120    fn from(value: anyhow::Error) -> Self {
121        ServerFnError::ServerError {
122            message: value.to_string(),
123            details: None,
124            code: 500,
125        }
126    }
127}
128
129impl From<serde_json::Error> for ServerFnError {
130    fn from(value: serde_json::Error) -> Self {
131        ServerFnError::Deserialization(value.to_string())
132    }
133}
134
135impl From<ServerFnError> for http::StatusCode {
136    fn from(value: ServerFnError) -> Self {
137        match value {
138            ServerFnError::ServerError { code, .. } => {
139                http::StatusCode::from_u16(code).unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR)
140            }
141            ServerFnError::Request(err) => match err {
142                RequestError::Status(_, code) => http::StatusCode::from_u16(code)
143                    .unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR),
144                _ => http::StatusCode::INTERNAL_SERVER_ERROR,
145            },
146            ServerFnError::StreamError(_)
147            | ServerFnError::Registration(_)
148            | ServerFnError::UnsupportedRequestMethod(_)
149            | ServerFnError::MiddlewareError(_)
150            | ServerFnError::Deserialization(_)
151            | ServerFnError::Serialization(_)
152            | ServerFnError::Args(_)
153            | ServerFnError::MissingArg(_)
154            | ServerFnError::Response(_) => http::StatusCode::INTERNAL_SERVER_ERROR,
155        }
156    }
157}
158
159impl From<RequestError> for ServerFnError {
160    fn from(value: RequestError) -> Self {
161        ServerFnError::Request(value)
162    }
163}
164
165impl From<ServerFnError> for HttpError {
166    fn from(value: ServerFnError) -> Self {
167        let status = StatusCode::from_u16(match &value {
168            ServerFnError::ServerError { code, .. } => *code,
169            _ => 500,
170        })
171        .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
172        HttpError {
173            status,
174            message: Some(value.to_string()),
175        }
176    }
177}
178
179impl From<HttpError> for ServerFnError {
180    fn from(value: HttpError) -> Self {
181        ServerFnError::ServerError {
182            message: value.message.unwrap_or_else(|| {
183                value
184                    .status
185                    .canonical_reason()
186                    .unwrap_or("Unknown error")
187                    .to_string()
188            }),
189            code: value.status.as_u16(),
190            details: None,
191        }
192    }
193}
194
195impl IntoResponse for ServerFnError {
196    fn into_response(self) -> axum_core::response::Response {
197        match self {
198            Self::ServerError {
199                message,
200                code,
201                details,
202            } => {
203                let status =
204                    StatusCode::from_u16(code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
205                let body = if let Some(details) = details {
206                    serde_json::json!({
207                        "error": message,
208                        "details": details,
209                    })
210                } else {
211                    serde_json::json!({
212                        "error": message,
213                    })
214                };
215                let body = axum_core::body::Body::from(
216                    serde_json::to_string(&body)
217                        .unwrap_or_else(|_| "{\"error\":\"Internal Server Error\"}".to_string()),
218                );
219                axum_core::response::Response::builder()
220                    .status(status)
221                    .header("Content-Type", "application/json")
222                    .body(body)
223                    .unwrap_or_else(|_| {
224                        axum_core::response::Response::builder()
225                            .status(StatusCode::INTERNAL_SERVER_ERROR)
226                            .body(axum_core::body::Body::from(
227                                "{\"error\":\"Internal Server Error\"}",
228                            ))
229                            .unwrap()
230                    })
231            }
232            _ => {
233                let status: StatusCode = self.clone().into();
234                let body = axum_core::body::Body::from(
235                    serde_json::json!({
236                        "error": self.to_string(),
237                    })
238                    .to_string(),
239                );
240                axum_core::response::Response::builder()
241                    .status(status)
242                    .header("Content-Type", "application/json")
243                    .body(body)
244                    .unwrap_or_else(|_| {
245                        axum_core::response::Response::builder()
246                            .status(StatusCode::INTERNAL_SERVER_ERROR)
247                            .body(axum_core::body::Body::from(
248                                "{\"error\":\"Internal Server Error\"}",
249                            ))
250                            .unwrap()
251                    })
252            }
253        }
254    }
255}
256
257/// An error type representing issues that can occur while making requests.
258///
259/// This is made to paper over the reqwest::Error type which we don't want to export here and
260/// is limited in many ways.
261#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
262pub enum RequestError {
263    /// An error occurred when building the request.
264    #[error("error building request: {0}")]
265    Builder(String),
266
267    /// An error occurred when serializing the request body.
268    #[error("error serializing request body: {0}")]
269    Serialization(String),
270
271    /// An error occurred when following a redirect.
272    #[error("error following redirect: {0}")]
273    Redirect(String),
274
275    /// An error occurred when receiving a non-2xx status code.
276    #[error("error receiving status code: {0} ({1})")]
277    Status(String, u16),
278
279    /// An error occurred when a request times out.
280    #[error("error timing out: {0}")]
281    Timeout(String),
282
283    /// An error occurred when sending a request.
284    #[error("error sending request: {0}")]
285    Request(String),
286
287    /// An error occurred when upgrading a connection.
288    #[error("error upgrading connection: {0}")]
289    Connect(String),
290
291    /// An error occurred when there is a request or response body error.
292    #[error("request or response body error: {0}")]
293    Body(String),
294
295    /// An error occurred when decoding the response body.
296    #[error("error decoding response body: {0}")]
297    Decode(String),
298}
299
300impl RequestError {
301    pub fn status(&self) -> Option<StatusCode> {
302        match self {
303            RequestError::Status(_, code) => Some(StatusCode::from_u16(*code).ok()?),
304            _ => None,
305        }
306    }
307
308    pub fn status_code(&self) -> Option<u16> {
309        match self {
310            RequestError::Status(_, code) => Some(*code),
311            _ => None,
312        }
313    }
314}