Skip to main content

api/
error.rs

1use dioxus::{
2    fullstack::AsStatusCode,
3    prelude::{ServerFnError, StatusCode},
4};
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8/// Represents errors that can occur in the API layer.
9#[derive(Debug, Serialize, Deserialize, Error)]
10pub enum ApiError {
11    /// Indicates a client-side error, such as invalid input or malformed request.
12    #[error("bad request: {0}")]
13    BadRequest(String),
14
15    /// Indicates that the user is not authorized to perform the requested action
16    #[error("forbidden: {0}")]
17    Forbidden(String),
18
19    /// Indicates that the requested resource could not be found.
20    #[error("not found")]
21    NotFound,
22
23    /// Represents a domain-level error, originating from business logic validation.
24    #[error("domain error: {0}")]
25    Domain(String),
26
27    /// Represents an unexpected error in production builds.
28    #[cfg(not(debug_assertions))]
29    #[error("An unexpected error occurred")]
30    Unexpected,
31
32    /// Represents an unexpected error in debug builds, including a descriptive message.
33    #[cfg(debug_assertions)]
34    #[error("An unexpected error occurred {message}")]
35    Unexpected {
36        /// Description of what happened and / or where.
37        message: String,
38    },
39}
40
41impl AsStatusCode for ApiError {
42    fn as_status_code(&self) -> StatusCode {
43        match self {
44            ApiError::BadRequest(_) => StatusCode::FORBIDDEN,
45            ApiError::Forbidden(_) => StatusCode::FORBIDDEN,
46            ApiError::NotFound => StatusCode::NOT_FOUND,
47            ApiError::Domain(_) => StatusCode::BAD_REQUEST,
48            #[cfg(not(debug_assertions))]
49            ApiError::Unexpected => StatusCode::INTERNAL_SERVER_ERROR,
50            #[cfg(debug_assertions)]
51            ApiError::Unexpected { .. } => StatusCode::INTERNAL_SERVER_ERROR,
52        }
53    }
54}
55
56#[cfg(not(debug_assertions))]
57impl From<ServerFnError> for ApiError {
58    fn from(_value: ServerFnError) -> Self {
59        ApiError::Unexpected
60    }
61}
62
63#[cfg(debug_assertions)]
64impl From<ServerFnError> for ApiError {
65    fn from(value: ServerFnError) -> Self {
66        ApiError::Unexpected {
67            message: value.to_string(),
68        }
69    }
70}
71
72#[cfg(feature = "server")]
73mod server_only {
74    use dioxus::prelude::*;
75    use server::error::ServerError;
76
77    use super::*;
78
79    impl From<ServerError> for ApiError {
80        fn from(err: ServerError) -> Self {
81            match err {
82                ServerError::Forbidden(msg) => ApiError::Forbidden(msg),
83                ServerError::NotFound => ApiError::NotFound,
84                ServerError::Domain(e) => ApiError::Domain(e.to_string()),
85                ServerError::Internal(e) => {
86                    error!("Internal server error: {e:#}");
87                    #[cfg(debug_assertions)]
88                    {
89                        ApiError::Unexpected {
90                            message: e.to_string(),
91                        }
92                    }
93                    #[cfg(not(debug_assertions))]
94                    {
95                        ApiError::Unexpected
96                    }
97                }
98            }
99        }
100    }
101}