objectstore_server/endpoints/
common.rs1use std::error::Error;
4
5use axum::Json;
6use axum::http::StatusCode;
7use axum::response::{IntoResponse, Response};
8use objectstore_service::error::Error as 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 #[error("internal error: {0}")]
36 Internal(String),
37}
38
39pub type ApiResult<T> = Result<T, ApiError>;
41
42#[derive(Serialize, Deserialize, Debug)]
44pub struct ApiErrorResponse {
45 #[serde(default)]
47 detail: Option<String>,
48 #[serde(default, skip_serializing_if = "Vec::is_empty")]
50 causes: Vec<String>,
51}
52
53impl ApiErrorResponse {
54 pub fn from_error<E: Error + ?Sized>(error: &E) -> Self {
56 let detail = Some(error.to_string());
57
58 let mut causes = Vec::new();
59 let mut source = error.source();
60 while let Some(s) = source {
61 causes.push(s.to_string());
62 source = s.source();
63 }
64
65 Self { detail, causes }
66 }
67}
68
69impl ApiError {
70 pub fn status(&self) -> StatusCode {
72 match &self {
73 ApiError::Client(_) => StatusCode::BAD_REQUEST,
74
75 ApiError::Batch(BatchError::BadRequest(_))
76 | ApiError::Batch(BatchError::Metadata(_))
77 | ApiError::Batch(BatchError::Multipart(_)) => StatusCode::BAD_REQUEST,
78 ApiError::Batch(BatchError::LimitExceeded(_)) => StatusCode::PAYLOAD_TOO_LARGE,
79 ApiError::Batch(BatchError::RateLimited) => StatusCode::TOO_MANY_REQUESTS,
80 ApiError::Batch(BatchError::ResponseSerialization { .. }) => {
81 objectstore_log::error!(!!self, "error serializing batch response");
82 StatusCode::INTERNAL_SERVER_ERROR
83 }
84
85 ApiError::Auth(AuthError::BadRequest(_)) => StatusCode::BAD_REQUEST,
86 ApiError::Auth(AuthError::ValidationFailure(_))
87 | ApiError::Auth(AuthError::VerificationFailure) => StatusCode::UNAUTHORIZED,
88 ApiError::Auth(AuthError::NotPermitted) => StatusCode::FORBIDDEN,
89 ApiError::Auth(AuthError::InternalError(_)) => {
90 objectstore_log::error!(!!self, "auth system error");
91 StatusCode::INTERNAL_SERVER_ERROR
92 }
93
94 ApiError::Service(ServiceError::Client(_)) => StatusCode::BAD_REQUEST,
95 ApiError::Service(ServiceError::Metadata(_)) => StatusCode::BAD_REQUEST,
96 ApiError::Service(ServiceError::InvalidUploadId(_)) => StatusCode::BAD_REQUEST,
97 ApiError::Service(ServiceError::AtCapacity) => StatusCode::TOO_MANY_REQUESTS,
98 ApiError::Service(ServiceError::NotImplemented) => StatusCode::NOT_IMPLEMENTED,
99 ApiError::Service(_) => {
100 objectstore_log::error!(!!self, "error handling request");
101 StatusCode::INTERNAL_SERVER_ERROR
102 }
103
104 ApiError::Internal(_) => {
105 objectstore_log::error!(!!self, "internal error");
106 StatusCode::INTERNAL_SERVER_ERROR
107 }
108 }
109 }
110}
111
112impl IntoResponse for ApiError {
113 fn into_response(self) -> Response {
114 let body = ApiErrorResponse::from_error(&self);
115 (self.status(), Json(body)).into_response()
116 }
117}