1use axum::{
2 http::StatusCode,
3 response::{
4 IntoResponse,
5 Response,
6 },
7 Json,
8};
9use pipedash_core::domain::DomainError;
10use serde::Serialize;
11
12#[derive(Debug, Serialize)]
13pub struct ApiError {
14 pub code: String,
15 pub message: String,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub details: Option<String>,
18}
19
20impl ApiError {
21 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
22 Self {
23 code: code.into(),
24 message: message.into(),
25 details: None,
26 }
27 }
28}
29
30#[derive(Debug)]
31pub struct AppError {
32 pub status: StatusCode,
33 pub error: ApiError,
34}
35
36impl AppError {
37 pub fn new(status: StatusCode, error: ApiError) -> Self {
38 Self { status, error }
39 }
40
41 pub fn not_found(message: impl Into<String>) -> Self {
42 Self::new(StatusCode::NOT_FOUND, ApiError::new("NOT_FOUND", message))
43 }
44
45 pub fn bad_request(message: impl Into<String>) -> Self {
46 Self::new(
47 StatusCode::BAD_REQUEST,
48 ApiError::new("BAD_REQUEST", message),
49 )
50 }
51
52 pub fn internal(message: impl Into<String>) -> Self {
53 Self::new(
54 StatusCode::INTERNAL_SERVER_ERROR,
55 ApiError::new("INTERNAL_ERROR", message),
56 )
57 }
58
59 pub fn conflict(message: impl Into<String>) -> Self {
60 Self::new(StatusCode::CONFLICT, ApiError::new("CONFLICT", message))
61 }
62
63 pub fn not_initialized() -> Self {
64 Self::new(
65 StatusCode::SERVICE_UNAVAILABLE,
66 ApiError::new(
67 "NOT_INITIALIZED",
68 "Application not initialized - setup required",
69 ),
70 )
71 }
72}
73
74impl IntoResponse for AppError {
75 fn into_response(self) -> Response {
76 (self.status, Json(self.error)).into_response()
77 }
78}
79
80impl From<DomainError> for AppError {
81 fn from(err: DomainError) -> Self {
82 let status = match &err {
83 DomainError::ProviderNotFound(_)
84 | DomainError::PipelineNotFound(_)
85 | DomainError::NotFound(_) => StatusCode::NOT_FOUND,
86 DomainError::InvalidConfig(_) | DomainError::InvalidProviderType(_) => {
87 StatusCode::BAD_REQUEST
88 }
89 DomainError::AuthenticationFailed(_) => StatusCode::UNAUTHORIZED,
90 DomainError::ProviderAuthenticationFailed(_) | DomainError::PermissionDenied(_) => {
91 StatusCode::FORBIDDEN
92 }
93 DomainError::NotSupported(_) => StatusCode::NOT_IMPLEMENTED,
94 _ => StatusCode::INTERNAL_SERVER_ERROR,
95 };
96 AppError::new(status, ApiError::new(err.code(), err.to_string()))
97 }
98}
99
100impl From<anyhow::Error> for AppError {
101 fn from(err: anyhow::Error) -> Self {
102 AppError::internal(err.to_string())
103 }
104}
105
106pub type ApiResult<T> = Result<T, AppError>;