cat_gateway/service/common/responses/
mod.rs1use 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::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};
36
37#[derive(ApiResponse)]
39pub(crate) enum ErrorResponses {
40 #[oai(status = 400)]
45 #[allow(dead_code)]
46 BadRequest,
47
48 #[oai(status = 401)]
53 Unauthorized(Json<Unauthorized>),
54
55 #[oai(status = 403)]
60 Forbidden(Json<Forbidden>),
61
62 #[oai(status = 414)]
67 #[allow(dead_code)]
68 UriTooLong,
69
70 #[oai(status = 412)]
74 PreconditionFailed(Json<PreconditionFailed>),
75
76 #[oai(status = 429)]
80 TooManyRequests(
81 Json<TooManyRequests>,
82 #[oai(header = "Retry-After")] RetryAfterHeader,
83 ),
84
85 #[oai(status = 431)]
89 #[allow(dead_code)]
90 RequestHeaderFieldsTooLarge,
91
92 #[oai(status = 500)]
98 ServerError(Json<InternalServerError>),
99
100 #[oai(status = 503)]
107 ServiceUnavailable(
108 Json<ServiceUnavailable>,
109 #[oai(header = "Retry-After")] Option<RetryAfterHeader>,
110 ),
111}
112
113impl ErrorResponses {
114 pub(crate) fn unauthorized() -> Self {
120 let error = Unauthorized::new(None);
121 ErrorResponses::Unauthorized(Json(error))
122 }
123
124 pub(crate) fn forbidden(roles: Option<Vec<String>>) -> Self {
130 let error = Forbidden::new(None, roles);
131 ErrorResponses::Forbidden(Json(error))
132 }
133}
134
135pub(crate) enum WithErrorResponses<T> {
137 With(T),
139 Error(ErrorResponses),
141}
142
143impl<T> WithErrorResponses<T> {
144 pub(crate) fn handle_error(err: &anyhow::Error) -> Self {
147 match err {
148 err if err.is::<bb8::RunError<tokio_postgres::Error>>() => {
149 Self::service_unavailable(err, RetryAfterOption::Default)
150 },
151 err => Self::internal_error(err),
152 }
153 }
154
155 pub(crate) fn service_unavailable(err: &anyhow::Error, retry: RetryAfterOption) -> Self {
159 let error = ServiceUnavailable::new(None);
160 error!(id=%error.id(), error=?err, retry_after=?retry);
161 let retry = match retry {
162 RetryAfterOption::Default => Some(RetryAfterHeader::default()),
163 RetryAfterOption::None => None,
164 RetryAfterOption::Some(value) => Some(value),
165 };
166 WithErrorResponses::Error(ErrorResponses::ServiceUnavailable(Json(error), retry))
167 }
168
169 pub(crate) fn internal_error(err: &anyhow::Error) -> Self {
173 let error = InternalServerError::new(None);
174 error!(id=%error.id(), error=?err);
175 WithErrorResponses::Error(ErrorResponses::ServerError(Json(error)))
176 }
177
178 pub(crate) fn unauthorized() -> Self {
184 WithErrorResponses::Error(ErrorResponses::unauthorized())
185 }
186
187 #[allow(dead_code)]
193 pub(crate) fn forbidden(roles: Option<Vec<String>>) -> Self {
194 WithErrorResponses::Error(ErrorResponses::forbidden(roles))
195 }
196
197 fn precondition_failed(errors: Vec<poem::Error>) -> Self {
201 let error = PreconditionFailed::new(errors);
202 WithErrorResponses::Error(ErrorResponses::PreconditionFailed(Json(error)))
203 }
204
205 #[allow(dead_code)]
211 pub(crate) fn rate_limit(retry_after: Option<RetryAfterHeader>) -> Self {
212 let retry_after = retry_after.unwrap_or_default();
213 let error = TooManyRequests::new(None);
214 WithErrorResponses::Error(ErrorResponses::TooManyRequests(Json(error), retry_after))
215 }
216}
217
218impl<T: ApiResponse> From<T> for WithErrorResponses<T> {
219 fn from(val: T) -> Self {
220 Self::With(val)
221 }
222}
223
224impl<T: ApiResponse> ApiResponse for WithErrorResponses<T> {
225 const BAD_REQUEST_HANDLER: bool = true;
226
227 fn meta() -> MetaResponses {
228 let t_meta = T::meta();
229 let default_meta = ErrorResponses::meta();
230
231 let mut responses = HashSet::new();
232 responses.extend(
233 t_meta
234 .responses
235 .into_iter()
236 .map(FilteredByStatusCodeResponse),
237 );
238 responses.extend(
239 default_meta
240 .responses
241 .into_iter()
242 .map(FilteredByStatusCodeResponse),
243 );
244
245 let responses =
246 responses
247 .into_iter()
248 .map(|val| {
249 let mut response = val.0;
250 if let Some(status) = response.status {
252 if (200..300).contains(&status) || (400..500).contains(&status) {
254 response.headers.insert(0usize, MetaHeader {
255 name: "RateLimit".to_string(),
256 description: Some("RateLimit Header.".to_string()),
257 required: false,
258 deprecated: false,
259 schema: <RateLimitHeader as poem_openapi::types::Type>::schema_ref(),
260 });
261 }
262
263 response.headers.insert(0usize, MetaHeader {
265 name: "Access-Control-Allow-Origin".to_string(),
266 description: Some("Access-Control-Allow-Origin Header.".to_string()),
267 required: false,
268 deprecated: false,
269 schema: <AccessControlAllowOriginHeader as poem_openapi::types::Type>::schema_ref(),
270 });
271 }
272 response
273 })
274 .collect();
275
276 MetaResponses { responses }
283 }
284
285 fn register(registry: &mut Registry) {
286 ErrorResponses::register(registry);
287 T::register(registry);
288 }
289
290 fn from_parse_request_error(err: poem::Error) -> Self {
291 if err.status() == StatusCode::UNAUTHORIZED {
292 WithErrorResponses::unauthorized()
293 } else {
294 WithErrorResponses::precondition_failed(vec![err])
295 }
296 }
297}
298
299impl<T: IntoResponse + Send> IntoResponse for WithErrorResponses<T> {
300 fn into_response(self) -> poem::Response {
301 match self {
302 Self::With(t) => t.into_response(),
303 Self::Error(default) => default.into_response(),
304 }
305 }
306}
307
308struct FilteredByStatusCodeResponse(MetaResponse);
311impl PartialEq for FilteredByStatusCodeResponse {
312 fn eq(&self, other: &Self) -> bool {
313 self.0.status.eq(&other.0.status)
314 }
315}
316impl Eq for FilteredByStatusCodeResponse {}
317impl Hash for FilteredByStatusCodeResponse {
318 fn hash<H: Hasher>(&self, state: &mut H) {
319 self.0.status.hash(state);
320 }
321}