objectstore_server/endpoints/
common.rs1use std::error::Error;
4
5use axum::Json;
6use axum::http::StatusCode;
7use axum::response::{IntoResponse, Response};
8use objectstore_service::ServiceError;
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12use crate::auth::AuthError;
13use crate::extractors::batch::BatchError;
14
15#[derive(Debug, Error)]
17pub enum ApiError {
18 #[error("client error: {0}")]
20 Client(String),
21
22 #[error("auth error: {0}")]
24 Auth(#[from] AuthError),
25
26 #[error("service error: {0}")]
28 Service(#[from] ServiceError),
29
30 #[error("batch error: {0}")]
32 Batch(#[from] BatchError),
33}
34
35pub type ApiResult<T> = Result<T, ApiError>;
37
38#[derive(Serialize, Deserialize, Debug)]
40pub struct ApiErrorResponse {
41 #[serde(default)]
43 detail: Option<String>,
44 #[serde(default, skip_serializing_if = "Vec::is_empty")]
46 causes: Vec<String>,
47}
48
49impl ApiErrorResponse {
50 pub fn from_error<E: Error + ?Sized>(error: &E) -> Self {
52 let detail = Some(error.to_string());
53
54 let mut causes = Vec::new();
55 let mut source = error.source();
56 while let Some(s) = source {
57 causes.push(s.to_string());
58 source = s.source();
59 }
60
61 Self { detail, causes }
62 }
63}
64
65impl ApiError {
66 pub fn status(&self) -> StatusCode {
67 match &self {
68 ApiError::Client(_) => StatusCode::BAD_REQUEST,
69
70 ApiError::Batch(BatchError::BadRequest(_))
71 | ApiError::Batch(BatchError::Metadata(_))
72 | ApiError::Batch(BatchError::Multipart(_)) => StatusCode::BAD_REQUEST,
73 ApiError::Batch(BatchError::LimitExceeded(_)) => StatusCode::PAYLOAD_TOO_LARGE,
74 ApiError::Batch(BatchError::RateLimited) => StatusCode::TOO_MANY_REQUESTS,
75 ApiError::Batch(BatchError::ResponseSerialization { .. }) => {
76 tracing::error!(
77 error = self as &dyn Error,
78 "error serializing batch response"
79 );
80 StatusCode::INTERNAL_SERVER_ERROR
81 }
82
83 ApiError::Auth(AuthError::BadRequest(_)) => StatusCode::BAD_REQUEST,
84 ApiError::Auth(AuthError::ValidationFailure(_))
85 | ApiError::Auth(AuthError::VerificationFailure) => StatusCode::UNAUTHORIZED,
86 ApiError::Auth(AuthError::NotPermitted) => StatusCode::FORBIDDEN,
87 ApiError::Auth(AuthError::InternalError(_)) => {
88 tracing::error!(error = self as &dyn Error, "auth system error");
89 StatusCode::INTERNAL_SERVER_ERROR
90 }
91
92 ApiError::Service(_) => {
93 tracing::error!(error = self as &dyn Error, "error handling request");
94 StatusCode::INTERNAL_SERVER_ERROR
95 }
96 }
97 }
98}
99
100impl IntoResponse for ApiError {
101 fn into_response(self) -> Response {
102 let body = ApiErrorResponse::from_error(&self);
103 (self.status(), Json(body)).into_response()
104 }
105}