cat_gateway/service/common/responses/
mod.rs

1//! Generic Responses are all contained in their own modules, grouped by response codes.
2
3use std::{
4    collections::HashSet,
5    hash::{Hash, Hasher},
6};
7
8use code_401_unauthorized::Unauthorized;
9use code_403_forbidden::Forbidden;
10use code_412_precondition_failed::PreconditionFailed;
11use code_429_too_many_requests::TooManyRequests;
12use code_503_service_unavailable::ServiceUnavailable;
13use poem::{http::StatusCode, IntoResponse};
14use poem_openapi::{
15    payload::Json,
16    registry::{MetaHeader, MetaResponse, MetaResponses, Registry},
17    ApiResponse,
18};
19use tracing::{debug, error};
20
21mod code_401_unauthorized;
22mod code_403_forbidden;
23mod code_412_precondition_failed;
24mod code_429_too_many_requests;
25
26pub(crate) mod code_500_internal_server_error;
27pub(crate) mod code_503_service_unavailable;
28
29use code_500_internal_server_error::InternalServerError;
30
31use super::types::headers::{
32    access_control_allow_origin::AccessControlAllowOriginHeader,
33    ratelimit::RateLimitHeader,
34    retry_after::{RetryAfterHeader, RetryAfterOption},
35};
36use crate::{
37    db::{event::EventDBConnectionError, index::session::CassandraSessionError},
38    service::utilities::health::{set_event_db_liveness, set_index_db_liveness},
39};
40
41/// Default error responses
42#[derive(ApiResponse)]
43pub(crate) enum ErrorResponses {
44    /// ## Bad Request
45    ///
46    /// The client has not sent valid request, could be an invalid HTTP in general or
47    /// provided not correct headers, path or query arguments.
48    #[oai(status = 400)]
49    #[allow(dead_code)]
50    BadRequest,
51
52    /// ## Unauthorized
53    ///
54    /// The client has not sent valid authentication credentials for the requested
55    /// resource.
56    #[oai(status = 401)]
57    Unauthorized(Json<Unauthorized>),
58
59    /// ## Forbidden
60    ///
61    /// The client has not sent valid authentication credentials for the requested
62    /// resource.
63    #[oai(status = 403)]
64    Forbidden(Json<Forbidden>),
65
66    /// ## URI Too Long
67    ///
68    /// The client sent a request with the URI is longer than the server is willing to
69    /// interpret
70    #[oai(status = 414)]
71    #[allow(dead_code)]
72    UriTooLong,
73
74    /// ## Precondition Failed
75    ///
76    /// The client has not sent valid data in its request, headers, parameters or body.
77    #[oai(status = 412)]
78    PreconditionFailed(Json<PreconditionFailed>),
79
80    /// ## Too Many Requests
81    ///
82    /// The client has sent too many requests in a given amount of time.
83    #[oai(status = 429)]
84    TooManyRequests(
85        Json<TooManyRequests>,
86        #[oai(header = "Retry-After")] RetryAfterHeader,
87    ),
88
89    /// ## Request Header Fields Too Large
90    ///
91    /// The client sent a request with too large header fields.
92    #[oai(status = 431)]
93    #[allow(dead_code)]
94    RequestHeaderFieldsTooLarge,
95
96    /// ## Internal Server Error.
97    ///
98    /// An internal server error occurred.
99    ///
100    /// *The contents of this response should be reported to the projects issue tracker.*
101    #[oai(status = 500)]
102    ServerError(Json<InternalServerError>),
103
104    /// ## Service Unavailable
105    ///
106    /// The service is not available, try again later.
107    ///
108    /// *This is returned when the service either has not started,
109    /// or has become unavailable.*
110    #[oai(status = 503)]
111    ServiceUnavailable(
112        Json<ServiceUnavailable>,
113        #[oai(header = "Retry-After")] Option<RetryAfterHeader>,
114    ),
115}
116
117impl ErrorResponses {
118    /// Handle a 401 unauthorized response.
119    ///
120    /// Returns a 401 Unauthorized response.
121    /// Its OK if we actually never call this.  Required for the API.
122    /// May be generated by the ingress.
123    pub(crate) fn unauthorized(text: String) -> Self {
124        let error = Unauthorized::new(Some(text));
125        ErrorResponses::Unauthorized(Json(error))
126    }
127
128    /// Handle a 403 forbidden response.
129    ///
130    /// Returns a 403 Forbidden response.
131    /// Its OK if we actually never call this.  Required for the API.
132    /// May be generated by the ingress.
133    pub(crate) fn forbidden(roles: Option<Vec<String>>) -> Self {
134        let error = Forbidden::new(None, roles);
135        ErrorResponses::Forbidden(Json(error))
136    }
137}
138
139/// Combine provided responses type with the default responses under one type.
140pub(crate) enum WithErrorResponses<T> {
141    /// Provided responses
142    With(T),
143    /// Error responses
144    Error(ErrorResponses),
145}
146
147impl<T> WithErrorResponses<T> {
148    /// Handle a 5xx response.
149    /// Returns a Server Error or a Service Unavailable response.
150    pub(crate) fn handle_error(err: &anyhow::Error) -> Self {
151        match err {
152            err if err.is::<bb8::RunError<tokio_postgres::Error>>()
153                || err.is::<EventDBConnectionError>() =>
154            {
155                // Event DB failed
156                debug!(error=?err, "Handling Response for Event DB Error");
157                set_event_db_liveness(false);
158                Self::service_unavailable(err, RetryAfterOption::Default)
159            },
160            err if err.is::<CassandraSessionError>() => {
161                // Index DB failed
162                debug!(error=?err, "Handling Response for Index DB Error");
163                set_index_db_liveness(false);
164                Self::service_unavailable(err, RetryAfterOption::Default)
165            },
166            err => {
167                debug!(error=?err, "Handling Response for Internal Error");
168                Self::internal_error(err)
169            },
170        }
171    }
172
173    /// Handle a 503 service unavailable error response with passing a response `msg`.
174    /// Its different with the original `service_unavailable` as it does not handles an
175    /// error, though its no need to log the id of this response.
176    ///
177    /// Returns a 503 Service unavailable Error response.
178    pub(crate) fn service_unavailable_with_msg(msg: String, retry: RetryAfterOption) -> Self {
179        let error = ServiceUnavailable::new(Some(msg));
180        let retry = match retry {
181            RetryAfterOption::Default => Some(RetryAfterHeader::default()),
182            RetryAfterOption::None => None,
183            RetryAfterOption::Some(value) => Some(value),
184        };
185        WithErrorResponses::Error(ErrorResponses::ServiceUnavailable(Json(error), retry))
186    }
187
188    /// Handle a 503 service unavailable error response.
189    ///
190    /// Returns a 503 Service unavailable Error response.
191    pub(crate) fn service_unavailable(err: &anyhow::Error, retry: RetryAfterOption) -> Self {
192        let error = ServiceUnavailable::new(None);
193        error!(id=%error.id(), error=?err, retry_after=?retry);
194        let retry = match retry {
195            RetryAfterOption::Default => Some(RetryAfterHeader::default()),
196            RetryAfterOption::None => None,
197            RetryAfterOption::Some(value) => Some(value),
198        };
199        WithErrorResponses::Error(ErrorResponses::ServiceUnavailable(Json(error), retry))
200    }
201
202    /// Handle a 500 internal error response.
203    ///
204    /// Returns a 500 Internal Error response.
205    pub(crate) fn internal_error(err: &anyhow::Error) -> Self {
206        let error = InternalServerError::new(None);
207        error!(id=%error.id(), error=?err);
208        WithErrorResponses::Error(ErrorResponses::ServerError(Json(error)))
209    }
210
211    /// Handle a 401 unauthorized response.
212    ///
213    /// Returns a 401 Unauthorized response.
214    /// Its OK if we actually never call this.  Required for the API.
215    /// May be generated by the ingress.
216    pub(crate) fn unauthorized(text: String) -> Self {
217        WithErrorResponses::Error(ErrorResponses::unauthorized(text))
218    }
219
220    /// Handle a 403 forbidden response.
221    ///
222    /// Returns a 403 Forbidden response.
223    /// Its OK if we actually never call this.  Required for the API.
224    /// May be generated by the ingress.
225    #[allow(dead_code)]
226    pub(crate) fn forbidden(roles: Option<Vec<String>>) -> Self {
227        WithErrorResponses::Error(ErrorResponses::forbidden(roles))
228    }
229
230    /// Handle a 412 precondition failed response.
231    ///
232    /// Returns a 412 precondition failed response.
233    fn precondition_failed(errors: Vec<poem::Error>) -> Self {
234        let error = PreconditionFailed::new(errors);
235        WithErrorResponses::Error(ErrorResponses::PreconditionFailed(Json(error)))
236    }
237
238    /// Handle a 429 rate limiting response.
239    ///
240    /// Returns a 429 Rate limit response.
241    /// Its OK if we actually never call this.  Required for the API.
242    /// May be generated by the ingress.
243    #[allow(dead_code)]
244    pub(crate) fn rate_limit(retry_after: Option<RetryAfterHeader>) -> Self {
245        let retry_after = retry_after.unwrap_or_default();
246        let error = TooManyRequests::new(None);
247        WithErrorResponses::Error(ErrorResponses::TooManyRequests(Json(error), retry_after))
248    }
249}
250
251impl<T: ApiResponse> From<T> for WithErrorResponses<T> {
252    fn from(val: T) -> Self {
253        Self::With(val)
254    }
255}
256
257impl<T: ApiResponse> ApiResponse for WithErrorResponses<T> {
258    const BAD_REQUEST_HANDLER: bool = true;
259
260    fn meta() -> MetaResponses {
261        let t_meta = T::meta();
262        let default_meta = ErrorResponses::meta();
263
264        let mut responses = HashSet::new();
265        responses.extend(
266            t_meta
267                .responses
268                .into_iter()
269                .map(FilteredByStatusCodeResponse),
270        );
271        responses.extend(
272            default_meta
273                .responses
274                .into_iter()
275                .map(FilteredByStatusCodeResponse),
276        );
277
278        let responses =
279            responses
280                .into_iter()
281                .map(|val| {
282                    let mut response = val.0;
283                    // Make modifications to the responses to set common headers
284                    if let Some(status) = response.status {
285                        // Only 2xx and 4xx responses get RateLimit Headers.
286                        if (200..300).contains(&status) || (400..500).contains(&status) {
287                            response.headers.insert(0usize, MetaHeader {
288                                name: "RateLimit".to_string(),
289                                description: Some("RateLimit Header.".to_string()),
290                                required: false,
291                                deprecated: false,
292                                schema: <RateLimitHeader as poem_openapi::types::Type>::schema_ref(),
293                            });
294                        }
295
296                        // All responses get Access-Control-Allow-Origin headers
297                        response.headers.insert(0usize, MetaHeader {
298                            name: "Access-Control-Allow-Origin".to_string(),
299                            description: Some("Access-Control-Allow-Origin Header.".to_string()),
300                            required: false,
301                            deprecated: false,
302                            schema: <AccessControlAllowOriginHeader as poem_openapi::types::Type>::schema_ref(),
303                        });
304                    }
305                    response
306                })
307                .collect();
308
309        // Add Rate limiting headers to ALL 2xx and 4xx responses
310        // for response in responses.iter_mut() {
311        //    response.
312        //    debug!(response = response);
313        //}
314
315        MetaResponses { responses }
316    }
317
318    fn register(registry: &mut Registry) {
319        ErrorResponses::register(registry);
320        T::register(registry);
321    }
322
323    fn from_parse_request_error(err: poem::Error) -> Self {
324        if err.status() == StatusCode::UNAUTHORIZED {
325            WithErrorResponses::unauthorized(err.to_string())
326        } else if err.status() == StatusCode::FORBIDDEN {
327            WithErrorResponses::forbidden(Some(vec![err.to_string()]))
328        } else {
329            WithErrorResponses::precondition_failed(vec![err])
330        }
331    }
332}
333
334impl<T: IntoResponse + Send> IntoResponse for WithErrorResponses<T> {
335    fn into_response(self) -> poem::Response {
336        match self {
337            Self::With(t) => t.into_response(),
338            Self::Error(default) => default.into_response(),
339        }
340    }
341}
342
343/// `FilteredByStatusCodeResponse` is used to filter out duplicate responses by status
344/// code.
345struct FilteredByStatusCodeResponse(MetaResponse);
346impl PartialEq for FilteredByStatusCodeResponse {
347    fn eq(&self, other: &Self) -> bool {
348        self.0.status.eq(&other.0.status)
349    }
350}
351impl Eq for FilteredByStatusCodeResponse {}
352impl Hash for FilteredByStatusCodeResponse {
353    fn hash<H: Hasher>(&self, state: &mut H) {
354        self.0.status.hash(state);
355    }
356}