pipedash-web 0.9.0

REST API server for Pipedash
Documentation
use axum::{
    http::StatusCode,
    response::{
        IntoResponse,
        Response,
    },
    Json,
};
use pipedash_core::domain::DomainError;
use serde::Serialize;

#[derive(Debug, Serialize)]
pub struct ApiError {
    pub code: String,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<String>,
}

impl ApiError {
    pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
        Self {
            code: code.into(),
            message: message.into(),
            details: None,
        }
    }
}

#[derive(Debug)]
pub struct AppError {
    pub status: StatusCode,
    pub error: ApiError,
}

impl AppError {
    pub fn new(status: StatusCode, error: ApiError) -> Self {
        Self { status, error }
    }

    pub fn not_found(message: impl Into<String>) -> Self {
        Self::new(StatusCode::NOT_FOUND, ApiError::new("NOT_FOUND", message))
    }

    pub fn bad_request(message: impl Into<String>) -> Self {
        Self::new(
            StatusCode::BAD_REQUEST,
            ApiError::new("BAD_REQUEST", message),
        )
    }

    pub fn internal(message: impl Into<String>) -> Self {
        Self::new(
            StatusCode::INTERNAL_SERVER_ERROR,
            ApiError::new("INTERNAL_ERROR", message),
        )
    }

    pub fn conflict(message: impl Into<String>) -> Self {
        Self::new(StatusCode::CONFLICT, ApiError::new("CONFLICT", message))
    }

    pub fn not_initialized() -> Self {
        Self::new(
            StatusCode::SERVICE_UNAVAILABLE,
            ApiError::new(
                "NOT_INITIALIZED",
                "Application not initialized - setup required",
            ),
        )
    }
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        (self.status, Json(self.error)).into_response()
    }
}

impl From<DomainError> for AppError {
    fn from(err: DomainError) -> Self {
        let status = match &err {
            DomainError::ProviderNotFound(_)
            | DomainError::PipelineNotFound(_)
            | DomainError::NotFound(_) => StatusCode::NOT_FOUND,
            DomainError::InvalidConfig(_) | DomainError::InvalidProviderType(_) => {
                StatusCode::BAD_REQUEST
            }
            DomainError::AuthenticationFailed(_) => StatusCode::UNAUTHORIZED,
            DomainError::ProviderAuthenticationFailed(_) | DomainError::PermissionDenied(_) => {
                StatusCode::FORBIDDEN
            }
            DomainError::NotSupported(_) => StatusCode::NOT_IMPLEMENTED,
            _ => StatusCode::INTERNAL_SERVER_ERROR,
        };
        AppError::new(status, ApiError::new(err.code(), err.to_string()))
    }
}

impl From<anyhow::Error> for AppError {
    fn from(err: anyhow::Error) -> Self {
        AppError::internal(err.to_string())
    }
}

pub type ApiResult<T> = Result<T, AppError>;