objectstore_server/endpoints/
common.rs1use std::error::Error;
4
5use axum::Json;
6use axum::http::StatusCode;
7use axum::response::{IntoResponse, Response};
8use http::HeaderValue;
9use objectstore_service::error::Error as ServiceError;
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13use crate::auth::AuthError;
14use crate::extractors::batch::BatchError;
15
16#[derive(Debug, Error)]
18pub enum ApiError {
19 #[error("client error: {0}")]
21 Client(String),
22
23 #[error("auth error: {0}")]
25 Auth(#[from] AuthError),
26
27 #[error("service error: {0}")]
29 Service(#[from] ServiceError),
30
31 #[error("batch error: {0}")]
33 Batch(#[from] BatchError),
34
35 #[error("internal error: {0}")]
37 Internal(String),
38}
39
40pub type ApiResult<T> = Result<T, ApiError>;
42
43#[derive(Serialize, Deserialize, Debug)]
45pub struct ApiErrorResponse {
46 #[serde(default)]
48 detail: Option<String>,
49 #[serde(default, skip_serializing_if = "Vec::is_empty")]
51 causes: Vec<String>,
52}
53
54impl ApiErrorResponse {
55 pub fn from_error<E: Error + ?Sized>(error: &E) -> Self {
57 let detail = Some(error.to_string());
58
59 let mut causes = Vec::new();
60 let mut source = error.source();
61 while let Some(s) = source {
62 causes.push(s.to_string());
63 source = s.source();
64 }
65
66 Self { detail, causes }
67 }
68}
69
70impl ApiError {
71 pub fn status(&self) -> StatusCode {
73 match &self {
74 ApiError::Client(_) => StatusCode::BAD_REQUEST,
75
76 ApiError::Batch(BatchError::BadRequest(_))
77 | ApiError::Batch(BatchError::Metadata(_))
78 | ApiError::Batch(BatchError::Multipart(_)) => StatusCode::BAD_REQUEST,
79 ApiError::Batch(BatchError::LimitExceeded(_)) => StatusCode::PAYLOAD_TOO_LARGE,
80 ApiError::Batch(BatchError::RateLimited) => StatusCode::TOO_MANY_REQUESTS,
81 ApiError::Batch(BatchError::ResponseSerialization { .. }) => {
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::UnknownKey) => StatusCode::UNAUTHORIZED,
89 ApiError::Auth(AuthError::UnsupportedPresignedMethod) => StatusCode::FORBIDDEN,
90 ApiError::Auth(AuthError::NotPermitted) => StatusCode::FORBIDDEN,
91 ApiError::Auth(AuthError::InternalError(_)) => StatusCode::INTERNAL_SERVER_ERROR,
92
93 ApiError::Service(ServiceError::Client(_)) => StatusCode::BAD_REQUEST,
94 ApiError::Service(ServiceError::Metadata(_)) => StatusCode::BAD_REQUEST,
95 ApiError::Service(ServiceError::RangeNotSatisfiable { .. }) => {
96 StatusCode::RANGE_NOT_SATISFIABLE
97 }
98 ApiError::Service(ServiceError::InvalidUploadId(_)) => StatusCode::BAD_REQUEST,
99 ApiError::Service(ServiceError::AtCapacity) => StatusCode::TOO_MANY_REQUESTS,
100 ApiError::Service(ServiceError::NotImplemented) => StatusCode::NOT_IMPLEMENTED,
101 ApiError::Service(_) => StatusCode::INTERNAL_SERVER_ERROR,
102
103 ApiError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
104 }
105 }
106
107 pub fn capture(&self) {
112 if matches!(self, ApiError::Service(_)) {
114 return;
115 }
116
117 if self.status().is_server_error() {
118 objectstore_log::error!(!!self, "error handling request");
119 }
120 }
121}
122
123impl IntoResponse for ApiError {
124 fn into_response(self) -> Response {
125 self.capture();
126 let body = ApiErrorResponse::from_error(&self);
127 (self.status(), Json(body)).into_response()
128 }
129}
130
131pub fn insert_accept_ranges(response: &mut Response) {
133 response.headers_mut().insert(
134 http::header::ACCEPT_RANGES,
135 HeaderValue::from_static("bytes"),
136 );
137}