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::{IntoResponse, http::StatusCode};
14use poem_openapi::{
15    ApiResponse,
16    payload::Json,
17    registry::{MetaHeader, MetaResponse, MetaResponses, Registry},
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(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(msg: String) -> Self {
134        let error = Forbidden::new(msg, None);
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    pub(crate) fn forbidden(msg: String) -> Self {
230        WithErrorResponses::Error(ErrorResponses::forbidden(msg))
231    }
232
233    /// Handle a 412 precondition failed response.
234    ///
235    /// Returns a 412 precondition failed response.
236    fn precondition_failed(errors: Vec<poem::Error>) -> Self {
237        let error = PreconditionFailed::new(errors);
238        WithErrorResponses::Error(ErrorResponses::PreconditionFailed(Json(error)))
239    }
240
241    /// Handle a 429 rate limiting response.
242    ///
243    /// Returns a 429 Rate limit response.
244    /// Its OK if we actually never call this.  Required for the API.
245    /// May be generated by the ingress.
246    #[allow(dead_code)]
247    pub(crate) fn rate_limit(retry_after: Option<RetryAfterHeader>) -> Self {
248        let retry_after = retry_after.unwrap_or_default();
249        let error = TooManyRequests::new(None);
250        WithErrorResponses::Error(ErrorResponses::TooManyRequests(Json(error), retry_after))
251    }
252}
253
254impl<T: ApiResponse> From<T> for WithErrorResponses<T> {
255    fn from(val: T) -> Self {
256        Self::With(val)
257    }
258}
259
260impl<T: ApiResponse> ApiResponse for WithErrorResponses<T> {
261    const BAD_REQUEST_HANDLER: bool = true;
262
263    fn meta() -> MetaResponses {
264        let t_meta = T::meta();
265        let default_meta = ErrorResponses::meta();
266
267        let mut responses = HashSet::new();
268        responses.extend(
269            t_meta
270                .responses
271                .into_iter()
272                .map(FilteredByStatusCodeResponse),
273        );
274        responses.extend(
275            default_meta
276                .responses
277                .into_iter()
278                .map(FilteredByStatusCodeResponse),
279        );
280
281        let responses =
282            responses
283                .into_iter()
284                .map(|val| {
285                    let mut response = val.0;
286                    // Make modifications to the responses to set common headers
287                    if let Some(status) = response.status {
288                        // Only 2xx and 4xx responses get RateLimit Headers.
289                        if (200..300).contains(&status) || (400..500).contains(&status) {
290                            response.headers.insert(0usize, MetaHeader {
291                                name: "RateLimit".to_string(),
292                                description: Some("RateLimit Header.".to_string()),
293                                required: false,
294                                deprecated: false,
295                                schema: <RateLimitHeader as poem_openapi::types::Type>::schema_ref(),
296                            });
297                        }
298
299                        // All responses get Access-Control-Allow-Origin headers
300                        response.headers.insert(0usize, MetaHeader {
301                            name: "Access-Control-Allow-Origin".to_string(),
302                            description: Some("Access-Control-Allow-Origin Header.".to_string()),
303                            required: false,
304                            deprecated: false,
305                            schema: <AccessControlAllowOriginHeader as poem_openapi::types::Type>::schema_ref(),
306                        });
307                    }
308                    response
309                })
310                .collect();
311
312        // Add Rate limiting headers to ALL 2xx and 4xx responses
313        // for response in responses.iter_mut() {
314        //    response.
315        //    debug!(response = response);
316        //}
317
318        MetaResponses { responses }
319    }
320
321    fn register(registry: &mut Registry) {
322        ErrorResponses::register(registry);
323        T::register(registry);
324    }
325
326    fn from_parse_request_error(err: poem::Error) -> Self {
327        if err.status() == StatusCode::UNAUTHORIZED {
328            WithErrorResponses::unauthorized(err.to_string())
329        } else if err.status() == StatusCode::FORBIDDEN {
330            WithErrorResponses::forbidden(err.to_string())
331        } else {
332            WithErrorResponses::precondition_failed(vec![err])
333        }
334    }
335}
336
337impl<T: IntoResponse + Send> IntoResponse for WithErrorResponses<T> {
338    fn into_response(self) -> poem::Response {
339        match self {
340            Self::With(t) => t.into_response(),
341            Self::Error(default) => default.into_response(),
342        }
343    }
344}
345
346/// `FilteredByStatusCodeResponse` is used to filter out duplicate responses by status
347/// code.
348struct FilteredByStatusCodeResponse(MetaResponse);
349impl PartialEq for FilteredByStatusCodeResponse {
350    fn eq(
351        &self,
352        other: &Self,
353    ) -> bool {
354        self.0.status.eq(&other.0.status)
355    }
356}
357impl Eq for FilteredByStatusCodeResponse {}
358impl Hash for FilteredByStatusCodeResponse {
359    fn hash<H: Hasher>(
360        &self,
361        state: &mut H,
362    ) {
363        self.0.status.hash(state);
364    }
365}