nanoservices_utils/
errors.rs

1//! `NanoServiceError` structs are the way in which nanoservices can pass errors between each other and to the client
2//! if the `ResponseError` trait is implemented for the specific web-framework being used. The `NanoServiceErrorStatus`
3//! enum is used to define the status of the error.
4use serde::{Deserialize, Serialize};
5use bitcode::{Encode, Decode};
6use thiserror::Error;
7use std::fmt;
8use revision::revisioned;
9
10#[cfg(feature = "actix")]
11use actix_web::{
12    HttpResponse, 
13    error::ResponseError, 
14    http::StatusCode
15};
16
17#[cfg(feature = "rocket")]
18use rocket::{
19    http::Status,
20    response::{Responder, Response},
21    Request,
22};
23
24#[cfg(feature = "axum")]
25use axum::{
26    response::{IntoResponse, Response as AxumResponse},
27    http::StatusCode as AxumStatusCode,
28    Json
29};
30
31#[cfg(feature = "hyper")]
32use hyper::{
33    Response as HyperResponse, 
34    body::Bytes, 
35    StatusCode as HyperStatusCode,
36    header
37};
38#[cfg(feature = "hyper")]
39use http_body_util::Full;
40
41
42#[derive(Error, Debug, Serialize, Deserialize, PartialEq, Clone, Encode, Decode)]
43#[revisioned(revision = 1)]
44pub enum NanoServiceErrorStatus {
45    #[error("Requested resource was not found")]
46    NotFound,
47    #[error("You are forbidden to access requested resource.")]
48    Forbidden,
49    #[error("Unknown Internal Error")]
50    Unknown,
51    #[error("Bad Request")]
52    BadRequest,
53    #[error("Conflict")]
54    Conflict,
55    #[error("Unauthorized")]
56    Unauthorized,
57    #[error("Contract not supported")]
58    ContractNotSupported,
59}
60
61
62/// The custom error that Actix web automatically converts to a HTTP response.
63///
64/// # Fields
65/// * `message` - The message of the error.
66/// * `status` - The status of the error.
67#[derive(Serialize, Deserialize, Debug, Error, PartialEq, Clone, Encode, Decode)]
68#[revisioned(revision = 1)]
69pub struct NanoServiceError {
70    pub message: String,
71    pub status: NanoServiceErrorStatus
72}
73
74impl NanoServiceError {
75
76    /// Constructs a new error.
77    ///
78    /// # Arguments
79    /// * `message` - The message of the error.
80    /// * `status` - The status of the error.
81    ///
82    /// # Returns
83    /// * `CustomError` - The new error.
84    pub fn new(message: String, status: NanoServiceErrorStatus) -> NanoServiceError {
85        NanoServiceError {
86            message,
87            status
88        }
89    }
90}
91
92impl fmt::Display for NanoServiceError {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        write!(f, "{}", self.message)
95    }
96}
97
98
99#[cfg(feature = "actix")]
100impl ResponseError for NanoServiceError {
101
102    /// Yields the status code for the error.
103    ///
104    /// # Returns
105    /// * `StatusCode` - The status code for the error.
106    fn status_code(&self) -> StatusCode {
107        match self.status {
108            NanoServiceErrorStatus::NotFound =>
109                StatusCode::NOT_FOUND,
110            NanoServiceErrorStatus::Forbidden =>
111                StatusCode::FORBIDDEN,
112            NanoServiceErrorStatus::Unknown =>
113                StatusCode::INTERNAL_SERVER_ERROR,
114            NanoServiceErrorStatus::BadRequest =>
115                StatusCode::BAD_REQUEST,
116            NanoServiceErrorStatus::Conflict =>
117                StatusCode::CONFLICT,
118            NanoServiceErrorStatus::Unauthorized =>
119                StatusCode::UNAUTHORIZED,
120            NanoServiceErrorStatus::ContractNotSupported =>
121                StatusCode::NOT_IMPLEMENTED
122        }
123    }
124
125    /// Constructs a HTTP response for the error.
126    ///
127    /// # Returns
128    /// * `HttpResponse` - The HTTP response for the error.
129    fn error_response(&self) -> HttpResponse {
130        let status_code = self.status_code();
131        HttpResponse::build(status_code).json(self.message.clone())
132    }
133}
134
135
136#[cfg(feature = "rocket")]
137#[rocket::async_trait]
138impl<'r> Responder<'r, 'static> for NanoServiceError {
139    fn respond_to(self, _: &'r Request<'_>) -> rocket::response::Result<'static> {
140        let status = match self.status {
141            NanoServiceErrorStatus::NotFound => Status::NotFound,
142            NanoServiceErrorStatus::Forbidden => Status::Forbidden,
143            NanoServiceErrorStatus::Unknown => Status::InternalServerError,
144            NanoServiceErrorStatus::BadRequest => Status::BadRequest,
145            NanoServiceErrorStatus::Conflict => Status::Conflict,
146            NanoServiceErrorStatus::Unauthorized => Status::Unauthorized,
147            NanoServiceErrorStatus::ContractNotSupported => Status::NotImplemented
148        };
149
150        Response::build()
151            .status(status)
152            .sized_body(self.message.len(), std::io::Cursor::new(self.message))
153            .ok()
154    }
155}
156
157/// Implementing the IntoResponse trait for Axum.
158#[cfg(feature = "axum")]
159impl IntoResponse for NanoServiceError {
160    fn into_response(self) -> AxumResponse {
161        let status_code = match self.status {
162            NanoServiceErrorStatus::NotFound => AxumStatusCode::NOT_FOUND,
163            NanoServiceErrorStatus::Forbidden => AxumStatusCode::FORBIDDEN,
164            NanoServiceErrorStatus::Unknown => AxumStatusCode::INTERNAL_SERVER_ERROR,
165            NanoServiceErrorStatus::BadRequest => AxumStatusCode::BAD_REQUEST,
166            NanoServiceErrorStatus::Conflict => AxumStatusCode::CONFLICT,
167            NanoServiceErrorStatus::Unauthorized => AxumStatusCode::UNAUTHORIZED,
168            NanoServiceErrorStatus::ContractNotSupported => AxumStatusCode::NOT_IMPLEMENTED
169        };
170        
171        (status_code, Json(self.message)).into_response()
172    }
173}
174
175#[cfg(feature = "hyper")]
176impl NanoServiceError {
177    pub fn into_hyper_response(self) -> HyperResponse<Full<Bytes>> {
178        let status_code = match self.status {
179            NanoServiceErrorStatus::NotFound => HyperStatusCode::NOT_FOUND,
180            NanoServiceErrorStatus::Forbidden => HyperStatusCode::FORBIDDEN,
181            NanoServiceErrorStatus::Unknown => HyperStatusCode::INTERNAL_SERVER_ERROR,
182            NanoServiceErrorStatus::BadRequest => HyperStatusCode::BAD_REQUEST,
183            NanoServiceErrorStatus::Conflict => HyperStatusCode::CONFLICT,
184            NanoServiceErrorStatus::Unauthorized => HyperStatusCode::UNAUTHORIZED,
185            NanoServiceErrorStatus::ContractNotSupported => HyperStatusCode::NOT_IMPLEMENTED
186        };
187
188        let json_body = serde_json::to_string(&self.message).unwrap();
189
190        HyperResponse::builder()
191                .header(header::CONTENT_TYPE, "application/json")
192                .status(status_code)
193                .body(Full::new(Bytes::from(json_body)))
194                .unwrap()
195    }
196}
197
198
199
200#[macro_export]
201macro_rules! safe_eject {
202    ($e:expr, $err_status:expr) => {
203        $e.map_err(|x| NanoServiceError::new(x.to_string(), $err_status))
204    };
205    ($e:expr, $err_status:expr, $message_context:expr) => {
206        $e.map_err(|x| NanoServiceError::new(
207                format!("{}: {}", $message_context, x.to_string()),
208                $err_status
209            )
210        )
211    };
212}