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,
37
38 code: 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: 500,
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: 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<HttpError> for ServerFnError {
166 fn from(value: HttpError) -> Self {
167 ServerFnError::ServerError {
168 message: value.message.unwrap_or_else(|| {
169 value
170 .status
171 .canonical_reason()
172 .unwrap_or("Unknown error")
173 .to_string()
174 }),
175 code: value.status.as_u16(),
176 details: None,
177 }
178 }
179}
180
181impl IntoResponse for ServerFnError {
182 fn into_response(self) -> axum_core::response::Response {
183 match self {
184 Self::ServerError {
185 message,
186 code,
187 details,
188 } => {
189 let status =
190 StatusCode::from_u16(code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
191 let body = if let Some(details) = details {
192 serde_json::json!({
193 "error": message,
194 "details": details,
195 })
196 } else {
197 serde_json::json!({
198 "error": message,
199 })
200 };
201 let body = axum_core::body::Body::from(
202 serde_json::to_string(&body)
203 .unwrap_or_else(|_| "{\"error\":\"Internal Server Error\"}".to_string()),
204 );
205 axum_core::response::Response::builder()
206 .status(status)
207 .header("Content-Type", "application/json")
208 .body(body)
209 .unwrap_or_else(|_| {
210 axum_core::response::Response::builder()
211 .status(StatusCode::INTERNAL_SERVER_ERROR)
212 .body(axum_core::body::Body::from(
213 "{\"error\":\"Internal Server Error\"}",
214 ))
215 .unwrap()
216 })
217 }
218 _ => {
219 let status: StatusCode = self.clone().into();
220 let body = axum_core::body::Body::from(
221 serde_json::json!({
222 "error": self.to_string(),
223 })
224 .to_string(),
225 );
226 axum_core::response::Response::builder()
227 .status(status)
228 .header("Content-Type", "application/json")
229 .body(body)
230 .unwrap_or_else(|_| {
231 axum_core::response::Response::builder()
232 .status(StatusCode::INTERNAL_SERVER_ERROR)
233 .body(axum_core::body::Body::from(
234 "{\"error\":\"Internal Server Error\"}",
235 ))
236 .unwrap()
237 })
238 }
239 }
240 }
241}
242
243#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
248pub enum RequestError {
249 #[error("error building request: {0}")]
251 Builder(String),
252
253 #[error("error serializing request body: {0}")]
255 Serialization(String),
256
257 #[error("error following redirect: {0}")]
259 Redirect(String),
260
261 #[error("error receiving status code: {0} ({1})")]
263 Status(String, u16),
264
265 #[error("error timing out: {0}")]
267 Timeout(String),
268
269 #[error("error sending request: {0}")]
271 Request(String),
272
273 #[error("error upgrading connection: {0}")]
275 Connect(String),
276
277 #[error("request or response body error: {0}")]
279 Body(String),
280
281 #[error("error decoding response body: {0}")]
283 Decode(String),
284}
285
286impl RequestError {
287 pub fn status(&self) -> Option<StatusCode> {
288 match self {
289 RequestError::Status(_, code) => Some(StatusCode::from_u16(*code).ok()?),
290 _ => None,
291 }
292 }
293
294 pub fn status_code(&self) -> Option<u16> {
295 match self {
296 RequestError::Status(_, code) => Some(*code),
297 _ => None,
298 }
299 }
300}