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
22pub 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);