dioxus_fullstack_core/
error.rs1use axum_core::response::IntoResponse;
2use futures_util::TryStreamExt;
3use http::StatusCode;
4use serde::{Deserialize, Serialize};
5use std::fmt::Debug;
6
7use crate::HttpError;
8
9pub type ServerFnResult<T = ()> = std::result::Result<T, ServerFnError>;
23
24#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub enum ServerFnError {
28 #[error("error running server function: {message} (details: {details:#?})")]
34 ServerError {
35 message: String,
36
37 #[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 #[error("error reaching server to call server function: {0} ")]
47 Request(RequestError),
48
49 #[error("error reading response body stream: {0}")]
51 StreamError(String),
52
53 #[error("error while trying to register the server function: {0}")]
55 Registration(String),
56
57 #[error("error trying to build `HTTP` method request: {0}")]
59 UnsupportedRequestMethod(String),
60
61 #[error("error running middleware: {0}")]
63 MiddlewareError(String),
64
65 #[error("error deserializing server function results: {0}")]
67 Deserialization(String),
68
69 #[error("error serializing server function arguments: {0}")]
71 Serialization(String),
72
73 #[error("error deserializing server function arguments: {0}")]
75 Args(String),
76
77 #[error("missing argument {0}")]
79 MissingArg(String),
80
81 #[error("error creating response {0}")]
83 Response(String),
84}
85
86impl ServerFnError {
87 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 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#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
251pub enum RequestError {
252 #[error("error building request: {0}")]
254 Builder(String),
255
256 #[error("error serializing request body: {0}")]
258 Serialization(String),
259
260 #[error("error following redirect: {0}")]
262 Redirect(String),
263
264 #[error("error receiving status code: {0} ({1})")]
266 Status(String, u16),
267
268 #[error("error timing out: {0}")]
270 Timeout(String),
271
272 #[error("error sending request: {0}")]
274 Request(String),
275
276 #[error("error upgrading connection: {0}")]
278 Connect(String),
279
280 #[error("request or response body error: {0}")]
282 Body(String),
283
284 #[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}