actix_web_sqlx_tx/
http.rs

1use actix_web::body::BoxBody;
2use actix_web::http::header::ContentType;
3use actix_web::http::StatusCode;
4use actix_web::{error, HttpRequest, Responder};
5use apistos::{ApiComponent, ApiErrorComponent};
6use derive_more::Display;
7use serde::{Deserialize, Serialize};
8use std::error::Error;
9use std::fmt;
10use std::fmt::Formatter;
11use apistos::reference_or::ReferenceOr;
12use schemars::schema::Schema;
13use validator::{ValidationError, ValidationErrors};
14
15pub type Response = Result<HttpResponse, HttpError>;
16
17pub enum HttpResponsePayload {
18    Json(serde_json::Value),
19    Empty,
20}
21
22/// A HTTP response
23/// Original http response from actix_web can not be shared between threads
24/// and cant be used inside async blocks
25/// This struct is a wrapper around actix_web::HttpResponse that can be shared between threads.
26pub struct HttpResponse {
27    pub status: StatusCode,
28    pub payload: HttpResponsePayload,
29    pub headers: Vec<(String, String)>,
30}
31
32
33impl ApiComponent for HttpResponse {
34    fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
35        vec![]
36    }
37
38    fn schema() -> Option<(String, ReferenceOr<Schema>)> {
39        None
40    }
41}
42
43pub struct HttpResponseBuilder {
44    status: StatusCode,
45    headers: Vec<(String, String)>,
46}
47
48impl HttpResponseBuilder {
49    pub fn new(status: StatusCode) -> Self {
50        HttpResponseBuilder {
51            status,
52            headers: vec![],
53        }
54    }
55
56    pub fn add_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
57        self.headers.push((key.into(), value.into()));
58        self
59    }
60
61    pub fn finish(self) -> HttpResponse {
62        HttpResponse {
63            status: self.status.clone(),
64            payload: HttpResponsePayload::Empty,
65            headers: self.headers.clone(),
66        }
67    }
68
69    pub fn json<T>(&self, value: T) -> HttpResponse
70    where
71        T: Serialize + 'static,
72    {
73        match serde_json::to_value(&value) {
74            Ok(body) => HttpResponse {
75                status: self.status.clone(),
76                payload: HttpResponsePayload::Json(body),
77                headers: self.headers.clone(),
78            },
79            Err(_) => {
80                panic!("Failed to serialize response body");
81            }
82        }
83    }
84}
85
86
87impl Responder for HttpResponse {
88    type Body = BoxBody;
89
90    fn respond_to(self, _req: &HttpRequest) -> actix_web::HttpResponse<Self::Body> {
91        let mut http_response_builder = actix_web::HttpResponse::build(self.status);
92
93        for (key, value) in self.headers {
94            http_response_builder.insert_header((key, value));
95        }
96
97        match self.payload {
98            HttpResponsePayload::Json(value) => http_response_builder
99                .content_type("application/json")
100                .json(value),
101            HttpResponsePayload::Empty => http_response_builder.finish(),
102        }
103    }
104}
105
106#[derive(Debug, Serialize, Deserialize, Clone)]
107pub struct ValidationErrorResponse {
108    pub validation_errors: Vec<ValidationError>,
109}
110
111impl ValidationErrorResponse {
112    pub fn from(validation_errors: ValidationErrors) -> ValidationErrorResponse {
113        let validation_errors = validation_errors
114            .field_errors()
115            .into_values()
116            .flat_map(|v| v.clone())
117            .collect();
118
119        ValidationErrorResponse { validation_errors }
120    }
121}
122
123impl Display for ValidationErrorResponse {
124    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
125        write!(f, "{:?}", self.validation_errors)
126    }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct HttpErrorDetailsResponse {
131    pub message: String,
132}
133
134#[derive(Debug, Display, ApiErrorComponent)]
135#[openapi_error(
136    status(code = 500),
137    status(code = 400),
138)]
139pub enum HttpError {
140    DatabaseError(sqlx::Error),
141    ValidationError(ValidationErrorResponse),
142    WithDetails(HttpErrorDetails),
143}
144
145impl Error for HttpError {}
146
147impl error::ResponseError for HttpError {
148    fn status_code(&self) -> StatusCode {
149        match self {
150            HttpError::DatabaseError(_) => StatusCode::INTERNAL_SERVER_ERROR,
151            HttpError::ValidationError(_) => StatusCode::BAD_REQUEST,
152            HttpError::WithDetails(details) => details.status_code,
153        }
154    }
155
156    fn error_response(&self) -> actix_web::HttpResponse {
157        let mut http_response_builder = actix_web::HttpResponse::build(self.status_code());
158        http_response_builder.insert_header(ContentType::json());
159
160        match self {
161            HttpError::DatabaseError(er) => http_response_builder.json(HttpErrorDetailsResponse {
162                message: er.to_string(),
163            }),
164            HttpError::ValidationError(er) => http_response_builder.json(er),
165            HttpError::WithDetails(details) => {
166                for (key, value) in details.headers.iter() {
167                    http_response_builder.insert_header((key.clone(), value.clone()));
168                }
169                http_response_builder.json(HttpErrorDetailsResponse {
170                    message: details.message.clone(),
171                })
172            }
173        }
174    }
175}
176
177impl From<ValidationErrors> for HttpError {
178    fn from(validation_errors: ValidationErrors) -> Self {
179        HttpError::ValidationError(ValidationErrorResponse::from(validation_errors))
180    }
181}
182
183impl From<sqlx::Error> for HttpError {
184    fn from(e: sqlx::Error) -> Self {
185        HttpError::DatabaseError(e)
186    }
187}
188
189#[derive(Debug, Clone)]
190pub struct HttpErrorDetails {
191    pub message: String,
192    pub status_code: StatusCode,
193    pub headers: Vec<(String, String)>,
194}
195
196impl Display for HttpErrorDetails {
197    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
198        let headers = self
199            .headers
200            .iter()
201            .map(|(k, v)| format!("{}: {}", k, v))
202            .collect::<Vec<String>>()
203            .join(", ");
204        write!(
205            f,
206            "{:?}: {:?} ({:?})",
207            self.status_code, self.message, headers
208        )
209    }
210}
211
212macro_rules! http_response_builder {
213    ($name:ident,$status:expr) => {
214        impl HttpResponse {
215            #[allow(non_snake_case, missing_docs)]
216            pub fn $name() -> HttpResponseBuilder {
217                HttpResponseBuilder::new($status)
218            }
219        }
220    };
221}
222
223http_response_builder!(BadRequest, StatusCode::BAD_REQUEST);
224http_response_builder!(Ok, StatusCode::OK);
225http_response_builder!(Created, StatusCode::CREATED);
226http_response_builder!(NotFound, StatusCode::NOT_FOUND);
227
228macro_rules! http_error {
229    ($name:ident,$status_code:expr) => {
230        #[allow(missing_docs, unused)]
231        pub fn $name<T>(message: impl Into<String>) -> Result<T, HttpError> {
232            Err(HttpError::WithDetails(HttpErrorDetails {
233                message: message.into(),
234                status_code: $status_code,
235                headers: vec![],
236            }))
237        }
238    };
239}
240
241http_error!(conflict, StatusCode::CONFLICT);
242
243http_error!(unauthorized, StatusCode::UNAUTHORIZED);
244
245http_error!(bad_request, StatusCode::BAD_REQUEST);
246
247http_error!(not_found, StatusCode::NOT_FOUND);
248
249http_error!(internal_server_error, StatusCode::INTERNAL_SERVER_ERROR);
250
251macro_rules! http_response {
252    ($name:ident,$status:ident) => {
253        #[allow(non_snake_case, missing_docs)]
254        pub fn $name(value: impl Serialize + 'static) -> Response {
255            Ok(HttpResponse::$status().json(value))
256        }
257    };
258}
259
260http_response!(ok, Ok);