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::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
35/// Result type for API operations.
36pub type ApiResult<T> = Result<T, ApiError>;
37
38/// A JSON error response returned by the API.
39#[derive(Serialize, Deserialize, Debug)]
40pub struct ApiErrorResponse {
41    /// The main error message.
42    #[serde(default)]
43    detail: Option<String>,
44    /// Chain of error causes.
45    #[serde(default, skip_serializing_if = "Vec::is_empty")]
46    causes: Vec<String>,
47}
48
49impl ApiErrorResponse {
50    /// Creates an error response from an error, extracting the full cause chain.
51    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}