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;
13
14/// Error type for API operations.
15#[derive(Debug, Error)]
16pub enum ApiError {
17    /// Errors indicating malformed or illegal requests.
18    #[error("client error: {0}")]
19    Client(String),
20
21    /// Authorization/authentication errors.
22    #[error("auth error: {0}")]
23    Auth(#[from] AuthError),
24
25    /// Service errors, indicating that something went wrong when receiving or executing a request.
26    #[error("service error: {0}")]
27    Service(#[from] ServiceError),
28}
29
30/// Result type for API operations.
31pub type ApiResult<T> = Result<T, ApiError>;
32
33/// A JSON error response returned by the API.
34#[derive(Serialize, Deserialize, Debug)]
35pub struct ApiErrorResponse {
36    /// The main error message.
37    #[serde(default)]
38    detail: Option<String>,
39    /// Chain of error causes.
40    #[serde(default, skip_serializing_if = "Vec::is_empty")]
41    causes: Vec<String>,
42}
43
44impl ApiErrorResponse {
45    /// Creates an error response from an error, extracting the full cause chain.
46    pub fn from_error<E: Error + ?Sized>(error: &E) -> Self {
47        let detail = Some(error.to_string());
48
49        let mut causes = Vec::new();
50        let mut source = error.source();
51        while let Some(s) = source {
52            causes.push(s.to_string());
53            source = s.source();
54        }
55
56        Self { detail, causes }
57    }
58}
59
60impl IntoResponse for ApiError {
61    fn into_response(self) -> Response {
62        let status = match &self {
63            ApiError::Client(_) => StatusCode::BAD_REQUEST,
64
65            ApiError::Auth(AuthError::BadRequest(_)) => StatusCode::BAD_REQUEST,
66            ApiError::Auth(AuthError::ValidationFailure(_))
67            | ApiError::Auth(AuthError::VerificationFailure) => StatusCode::UNAUTHORIZED,
68            ApiError::Auth(AuthError::NotPermitted) => StatusCode::FORBIDDEN,
69            ApiError::Auth(AuthError::InternalError(_)) => {
70                tracing::error!(error = &self as &dyn Error, "auth system error");
71                StatusCode::INTERNAL_SERVER_ERROR
72            }
73
74            ApiError::Service(_) => {
75                tracing::error!(error = &self as &dyn Error, "error handling request");
76                StatusCode::INTERNAL_SERVER_ERROR
77            }
78        };
79
80        let body = ApiErrorResponse::from_error(&self);
81        (status, Json(body)).into_response()
82    }
83}