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        message: String,
36
37        /// Optional HTTP status code associated with the error.
38        #[serde(skip_serializing_if = "Option::is_none")]
39        code: Option<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: None,
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: Some(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: None,
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, .. } => match code {
139                Some(code) => http::StatusCode::from_u16(code)
140                    .unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR),
141                None => http::StatusCode::INTERNAL_SERVER_ERROR,
142            },
143            ServerFnError::Request(err) => match err {
144                RequestError::Status(_, code) => http::StatusCode::from_u16(code)
145                    .unwrap_or(http::StatusCode::INTERNAL_SERVER_ERROR),
146                _ => http::StatusCode::INTERNAL_SERVER_ERROR,
147            },
148            ServerFnError::StreamError(_)
149            | ServerFnError::Registration(_)
150            | ServerFnError::UnsupportedRequestMethod(_)
151            | ServerFnError::MiddlewareError(_)
152            | ServerFnError::Deserialization(_)
153            | ServerFnError::Serialization(_)
154            | ServerFnError::Args(_)
155            | ServerFnError::MissingArg(_)
156            | ServerFnError::Response(_) => http::StatusCode::INTERNAL_SERVER_ERROR,
157        }
158    }
159}
160
161impl From<RequestError> for ServerFnError {
162    fn from(value: RequestError) -> Self {
163        ServerFnError::Request(value)
164    }
165}
166
167impl From<HttpError> for ServerFnError {
168    fn from(value: HttpError) -> Self {
169        ServerFnError::ServerError {
170            message: value.message.unwrap_or_else(|| {
171                value
172                    .status
173                    .canonical_reason()
174                    .unwrap_or("Unknown error")
175                    .to_string()
176            }),
177            code: Some(value.status.as_u16()),
178            details: None,
179        }
180    }
181}
182
183impl IntoResponse for ServerFnError {
184    fn into_response(self) -> axum_core::response::Response {
185        match self {
186            Self::ServerError {
187                message,
188                code,
189                details,
190            } => {
191                let status = code
192                    .and_then(|c| StatusCode::from_u16(c).ok())
193                    .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
194                let body = if let Some(details) = details {
195                    serde_json::json!({
196                        "error": message,
197                        "details": details,
198                    })
199                } else {
200                    serde_json::json!({
201                        "error": message,
202                    })
203                };
204                let body = axum_core::body::Body::from(
205                    serde_json::to_string(&body)
206                        .unwrap_or_else(|_| "{\"error\":\"Internal Server Error\"}".to_string()),
207                );
208                axum_core::response::Response::builder()
209                    .status(status)
210                    .header("Content-Type", "application/json")
211                    .body(body)
212                    .unwrap_or_else(|_| {
213                        axum_core::response::Response::builder()
214                            .status(StatusCode::INTERNAL_SERVER_ERROR)
215                            .body(axum_core::body::Body::from(
216                                "{\"error\":\"Internal Server Error\"}",
217                            ))
218                            .unwrap()
219                    })
220            }
221            _ => {
222                let status: StatusCode = self.clone().into();
223                let body = axum_core::body::Body::from(
224                    serde_json::json!({
225                        "error": self.to_string(),
226                    })
227                    .to_string(),
228                );
229                axum_core::response::Response::builder()
230                    .status(status)
231                    .header("Content-Type", "application/json")
232                    .body(body)
233                    .unwrap_or_else(|_| {
234                        axum_core::response::Response::builder()
235                            .status(StatusCode::INTERNAL_SERVER_ERROR)
236                            .body(axum_core::body::Body::from(
237                                "{\"error\":\"Internal Server Error\"}",
238                            ))
239                            .unwrap()
240                    })
241            }
242        }
243    }
244}
245
246/// An error type representing issues that can occur while making requests.
247///
248/// This is made to paper over the reqwest::Error type which we don't want to export here and
249/// is limited in many ways.
250#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
251pub enum RequestError {
252    /// An error occurred when building the request.
253    #[error("error building request: {0}")]
254    Builder(String),
255
256    /// An error occurred when serializing the request body.
257    #[error("error serializing request body: {0}")]
258    Serialization(String),
259
260    /// An error occurred when following a redirect.
261    #[error("error following redirect: {0}")]
262    Redirect(String),
263
264    /// An error occurred when receiving a non-2xx status code.
265    #[error("error receiving status code: {0} ({1})")]
266    Status(String, u16),
267
268    /// An error occurred when a request times out.
269    #[error("error timing out: {0}")]
270    Timeout(String),
271
272    /// An error occurred when sending a request.
273    #[error("error sending request: {0}")]
274    Request(String),
275
276    /// An error occurred when upgrading a connection.
277    #[error("error upgrading connection: {0}")]
278    Connect(String),
279
280    /// An error occurred when there is a request or response body error.
281    #[error("request or response body error: {0}")]
282    Body(String),
283
284    /// An error occurred when decoding the response body.
285    #[error("error decoding response body: {0}")]
286    Decode(String),
287}
288
289impl RequestError {
290    pub fn status(&self) -> Option<StatusCode> {
291        match self {
292            RequestError::Status(_, code) => Some(StatusCode::from_u16(*code).ok()?),
293            _ => None,
294        }
295    }
296
297    pub fn status_code(&self) -> Option<u16> {
298        match self {
299            RequestError::Status(_, code) => Some(*code),
300            _ => None,
301        }
302    }
303}