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::<EventDBConnectionError>() => {
153                // Event DB failed
154                debug!(error=?err, "Handling Response for Event DB Error");
155                set_event_db_liveness(false);
156                Self::service_unavailable(err, RetryAfterOption::Default)
157            },
158            err if err.is::<CassandraSessionError>() => {
159                // Index DB failed
160                debug!(error=?err, "Handling Response for Index DB Error");
161                set_index_db_liveness(false);
162                Self::service_unavailable(err, RetryAfterOption::Default)
163            },
164            err => {
165                debug!(error=?err, "Handling Response for Internal Error");
166                Self::internal_error(err)
167            },
168        }
169    }
170
171    /// Handle a 503 service unavailable error response with passing a response `msg`.
172    /// Its different with the original `service_unavailable` as it does not handles an
173    /// error, though its no need to log the id of this response.
174    ///
175    /// Returns a 503 Service unavailable Error response.
176    pub(crate) fn service_unavailable_with_msg(
177        msg: String,
178        retry: RetryAfterOption,
179    ) -> Self {
180        let error = ServiceUnavailable::new(Some(msg));
181        let retry = match retry {
182            RetryAfterOption::Default => Some(RetryAfterHeader::default()),
183            RetryAfterOption::None => None,
184            RetryAfterOption::Some(value) => Some(value),
185        };
186        WithErrorResponses::Error(ErrorResponses::ServiceUnavailable(Json(error), retry))
187    }
188
189    /// Handle a 503 service unavailable error response.
190    ///
191    /// Returns a 503 Service unavailable Error response.
192    pub(crate) fn service_unavailable(
193        err: &anyhow::Error,
194        retry: RetryAfterOption,
195    ) -> Self {
196        let error = ServiceUnavailable::new(None);
197        error!(id=%error.id(), error=?err, retry_after=?retry);
198        let retry = match retry {
199            RetryAfterOption::Default => Some(RetryAfterHeader::default()),
200            RetryAfterOption::None => None,
201            RetryAfterOption::Some(value) => Some(value),
202        };
203        WithErrorResponses::Error(ErrorResponses::ServiceUnavailable(Json(error), retry))
204    }
205
206    /// Handle a 500 internal error response.
207    ///
208    /// Returns a 500 Internal Error response.
209    pub(crate) fn internal_error(err: &anyhow::Error) -> Self {
210        let error = InternalServerError::new(None);
211        error!(id=%error.id(), error=?err);
212        WithErrorResponses::Error(ErrorResponses::ServerError(Json(error)))
213    }
214
215    /// Handle a 401 unauthorized response.
216    ///
217    /// Returns a 401 Unauthorized response.
218    /// Its OK if we actually never call this.  Required for the API.
219    /// May be generated by the ingress.
220    pub(crate) fn unauthorized(text: String) -> Self {
221        WithErrorResponses::Error(ErrorResponses::unauthorized(text))
222    }
223
224    /// Handle a 403 forbidden response.
225    ///
226    /// Returns a 403 Forbidden response.
227    /// Its OK if we actually never call this.  Required for the API.
228    /// May be generated by the ingress.
229    #[allow(dead_code)]
230    pub(crate) fn forbidden(roles: Option<Vec<String>>) -> Self {
231        WithErrorResponses::Error(ErrorResponses::forbidden(roles))
232    }
233
234    /// Handle a 412 precondition failed response.
235    ///
236    /// Returns a 412 precondition failed response.
237    fn precondition_failed(errors: Vec<poem::Error>) -> Self {
238        let error = PreconditionFailed::new(errors);
239        WithErrorResponses::Error(ErrorResponses::PreconditionFailed(Json(error)))
240    }
241
242    /// Handle a 429 rate limiting response.
243    ///
244    /// Returns a 429 Rate limit response.
245    /// Its OK if we actually never call this.  Required for the API.
246    /// May be generated by the ingress.
247    #[allow(dead_code)]
248    pub(crate) fn rate_limit(retry_after: Option<RetryAfterHeader>) -> Self {
249        let retry_after = retry_after.unwrap_or_default();
250        let error = TooManyRequests::new(None);
251        WithErrorResponses::Error(ErrorResponses::TooManyRequests(Json(error), retry_after))
252    }
253}
254
255impl<T: ApiResponse> From<T> for WithErrorResponses<T> {
256    fn from(val: T) -> Self {
257        Self::With(val)
258    }
259}
260
261impl<T: ApiResponse> ApiResponse for WithErrorResponses<T> {
262    const BAD_REQUEST_HANDLER: bool = true;
263
264    fn meta() -> MetaResponses {
265        let t_meta = T::meta();
266        let default_meta = ErrorResponses::meta();
267
268        let mut responses = HashSet::new();
269        responses.extend(
270            t_meta
271                .responses
272                .into_iter()
273                .map(FilteredByStatusCodeResponse),
274        );
275        responses.extend(
276            default_meta
277                .responses
278                .into_iter()
279                .map(FilteredByStatusCodeResponse),
280        );
281
282        let responses =
283            responses
284                .into_iter()
285                .map(|val| {
286                    let mut response = val.0;
287                    // Make modifications to the responses to set common headers
288                    if let Some(status) = response.status {
289                        // Only 2xx and 4xx responses get RateLimit Headers.
290                        if (200..300).contains(&status) || (400..500).contains(&status) {
291                            response.headers.insert(0usize, MetaHeader {
292                                name: "RateLimit".to_string(),
293                                description: Some("RateLimit Header.".to_string()),
294                                required: false,
295                                deprecated: false,
296                                schema: <RateLimitHeader as poem_openapi::types::Type>::schema_ref(),
297                            });
298                        }
299
300                        // All responses get Access-Control-Allow-Origin headers
301                        response.headers.insert(0usize, MetaHeader {
302                            name: "Access-Control-Allow-Origin".to_string(),
303                            description: Some("Access-Control-Allow-Origin Header.".to_string()),
304                            required: false,
305                            deprecated: false,
306                            schema: <AccessControlAllowOriginHeader as poem_openapi::types::Type>::schema_ref(),
307                        });
308                    }
309                    response
310                })
311                .collect();
312
313        // Add Rate limiting headers to ALL 2xx and 4xx responses
314        // for response in responses.iter_mut() {
315        //    response.
316        //    debug!(response = response);
317        //}
318
319        MetaResponses { responses }
320    }
321
322    fn register(registry: &mut Registry) {
323        ErrorResponses::register(registry);
324        T::register(registry);
325    }
326
327    fn from_parse_request_error(err: poem::Error) -> Self {
328        if err.status() == StatusCode::UNAUTHORIZED {
329            WithErrorResponses::unauthorized(err.to_string())
330        } else if err.status() == StatusCode::FORBIDDEN {
331            WithErrorResponses::forbidden(Some(vec![err.to_string()]))
332        } else {
333            WithErrorResponses::precondition_failed(vec![err])
334        }
335    }
336}
337
338impl<T: IntoResponse + Send> IntoResponse for WithErrorResponses<T> {
339    fn into_response(self) -> poem::Response {
340        match self {
341            Self::With(t) => t.into_response(),
342            Self::Error(default) => default.into_response(),
343        }
344    }
345}
346
347/// `FilteredByStatusCodeResponse` is used to filter out duplicate responses by status
348/// code.
349struct FilteredByStatusCodeResponse(MetaResponse);
350impl PartialEq for FilteredByStatusCodeResponse {
351    fn eq(
352        &self,
353        other: &Self,
354    ) -> bool {
355        self.0.status.eq(&other.0.status)
356    }
357}
358impl Eq for FilteredByStatusCodeResponse {}
359impl Hash for FilteredByStatusCodeResponse {
360    fn hash<H: Hasher>(
361        &self,
362        state: &mut H,
363    ) {
364        self.0.status.hash(state);
365    }
366}