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 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/// Error type for API operations.
16#[derive(Debug, Error)]
17pub enum ApiError {
18    /// Errors indicating malformed or illegal requests.
19    #[error("client error: {0}")]
20    Client(String),
21
22    /// Authorization/authentication errors.
23    #[error("auth error: {0}")]
24    Auth(#[from] AuthError),
25
26    /// Service errors, indicating that something went wrong when receiving or executing a request.
27    #[error("service error: {0}")]
28    Service(#[from] ServiceError),
29
30    /// Errors encountered when parsing or executing a batch request.
31    #[error("batch error: {0}")]
32    Batch(#[from] BatchError),
33
34    /// Internal server errors.
35    #[error("internal error: {0}")]
36    Internal(String),
37}
38
39/// Result type for API operations.
40pub type ApiResult<T> = Result<T, ApiError>;
41
42/// A JSON error response returned by the API.
43#[derive(Serialize, Deserialize, Debug)]
44pub struct ApiErrorResponse {
45    /// The main error message.
46    #[serde(default)]
47    detail: Option<String>,
48    /// Chain of error causes.
49    #[serde(default, skip_serializing_if = "Vec::is_empty")]
50    causes: Vec<String>,
51}
52
53impl ApiErrorResponse {
54    /// Creates an error response from an error, extracting the full cause chain.
55    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    /// Returns the HTTP status code appropriate for this error variant.
71    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}