Skip to main content

objectstore_server/endpoints/
common.rs

1//! Common types and utilities for API endpoints.
2
3use 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/// Error type for API operations.
17#[derive(Debug, Error)]
18pub enum ApiError {
19    /// Errors indicating malformed or illegal requests.
20    #[error("client error: {0}")]
21    Client(String),
22
23    /// Authorization/authentication errors.
24    #[error("auth error: {0}")]
25    Auth(#[from] AuthError),
26
27    /// Service errors, indicating that something went wrong when receiving or executing a request.
28    #[error("service error: {0}")]
29    Service(#[from] ServiceError),
30
31    /// Errors encountered when parsing or executing a batch request.
32    #[error("batch error: {0}")]
33    Batch(#[from] BatchError),
34
35    /// Internal server errors.
36    #[error("internal error: {0}")]
37    Internal(String),
38}
39
40/// Result type for API operations.
41pub type ApiResult<T> = Result<T, ApiError>;
42
43/// A JSON error response returned by the API.
44#[derive(Serialize, Deserialize, Debug)]
45pub struct ApiErrorResponse {
46    /// The main error message.
47    #[serde(default)]
48    detail: Option<String>,
49    /// Chain of error causes.
50    #[serde(default, skip_serializing_if = "Vec::is_empty")]
51    causes: Vec<String>,
52}
53
54impl ApiErrorResponse {
55    /// Creates an error response from an error, extracting the full cause chain.
56    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    /// Returns the HTTP status code appropriate for this error variant.
72    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                objectstore_log::error!(!!self, "error serializing batch response");
83                StatusCode::INTERNAL_SERVER_ERROR
84            }
85
86            ApiError::Auth(AuthError::BadRequest(_)) => StatusCode::BAD_REQUEST,
87            ApiError::Auth(AuthError::ValidationFailure(_))
88            | ApiError::Auth(AuthError::VerificationFailure) => StatusCode::UNAUTHORIZED,
89            ApiError::Auth(AuthError::NotPermitted) => StatusCode::FORBIDDEN,
90            ApiError::Auth(AuthError::InternalError(_)) => {
91                objectstore_log::error!(!!self, "auth system error");
92                StatusCode::INTERNAL_SERVER_ERROR
93            }
94
95            ApiError::Service(ServiceError::Client(_)) => StatusCode::BAD_REQUEST,
96            ApiError::Service(ServiceError::Metadata(_)) => StatusCode::BAD_REQUEST,
97            ApiError::Service(ServiceError::RangeNotSatisfiable { .. }) => {
98                StatusCode::RANGE_NOT_SATISFIABLE
99            }
100            ApiError::Service(ServiceError::InvalidUploadId(_)) => StatusCode::BAD_REQUEST,
101            ApiError::Service(ServiceError::AtCapacity) => StatusCode::TOO_MANY_REQUESTS,
102            ApiError::Service(ServiceError::NotImplemented) => StatusCode::NOT_IMPLEMENTED,
103            ApiError::Service(_) => {
104                objectstore_log::error!(!!self, "error handling request");
105                StatusCode::INTERNAL_SERVER_ERROR
106            }
107
108            ApiError::Internal(_) => {
109                objectstore_log::error!(!!self, "internal error");
110                StatusCode::INTERNAL_SERVER_ERROR
111            }
112        }
113    }
114}
115
116impl IntoResponse for ApiError {
117    fn into_response(self) -> Response {
118        let body = ApiErrorResponse::from_error(&self);
119        (self.status(), Json(body)).into_response()
120    }
121}
122
123/// Inserts `Accept-Ranges: bytes` into the response headers.
124pub fn insert_accept_ranges(response: &mut Response) {
125    response.headers_mut().insert(
126        http::header::ACCEPT_RANGES,
127        HeaderValue::from_static("bytes"),
128    );
129}