use axum::{
Json,
extract::multipart::MultipartError,
http::StatusCode,
response::{IntoResponse, Response},
};
use forge_core_db::models::{
execution_process::ExecutionProcessError, execution_run::ExecutionRunError,
project::ProjectError, task_attempt::TaskAttemptError,
};
use forge_core_deployment::DeploymentError;
use forge_core_executors::executors::ExecutorError;
use forge_core_services::services::{
auth::AuthError, config::ConfigError, container::ContainerError, drafts::DraftsServiceError,
git::GitServiceError, github_service::GitHubServiceError, image::ImageError,
worktree_manager::WorktreeError,
};
use forge_core_utils::response::ApiResponse;
use git2::Error as Git2Error;
use thiserror::Error;
#[derive(Debug, Error, ts_rs_forge::TS)]
#[ts(type = "string")]
pub enum ApiError {
#[error(transparent)]
Project(#[from] ProjectError),
#[error(transparent)]
TaskAttempt(#[from] TaskAttemptError),
#[error(transparent)]
ExecutionProcess(#[from] ExecutionProcessError),
#[error(transparent)]
ExecutionRun(#[from] ExecutionRunError),
#[error(transparent)]
GitService(#[from] GitServiceError),
#[error(transparent)]
GitHubService(#[from] GitHubServiceError),
#[error(transparent)]
Auth(#[from] AuthError),
#[error(transparent)]
Deployment(#[from] DeploymentError),
#[error(transparent)]
Container(#[from] ContainerError),
#[error(transparent)]
Executor(#[from] ExecutorError),
#[error(transparent)]
Database(#[from] sqlx::Error),
#[error(transparent)]
Worktree(#[from] WorktreeError),
#[error(transparent)]
Config(#[from] ConfigError),
#[error(transparent)]
Image(#[from] ImageError),
#[error(transparent)]
Drafts(#[from] DraftsServiceError),
#[error("Multipart error: {0}")]
Multipart(#[from] MultipartError),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Conflict: {0}")]
Conflict(String),
}
impl From<Git2Error> for ApiError {
fn from(err: Git2Error) -> Self {
ApiError::GitService(GitServiceError::from(err))
}
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let (status_code, error_type) = match &self {
ApiError::Project(_) => (StatusCode::INTERNAL_SERVER_ERROR, "ProjectError"),
ApiError::TaskAttempt(_) => (StatusCode::INTERNAL_SERVER_ERROR, "TaskAttemptError"),
ApiError::ExecutionProcess(err) => match err {
ExecutionProcessError::ExecutionProcessNotFound => {
(StatusCode::NOT_FOUND, "ExecutionProcessError")
}
_ => (StatusCode::INTERNAL_SERVER_ERROR, "ExecutionProcessError"),
},
ApiError::ExecutionRun(err) => match err {
ExecutionRunError::ExecutionRunNotFound => {
(StatusCode::NOT_FOUND, "ExecutionRunError")
}
ExecutionRunError::ProjectNotFound => (StatusCode::NOT_FOUND, "ProjectNotFound"),
_ => (StatusCode::INTERNAL_SERVER_ERROR, "ExecutionRunError"),
},
ApiError::GitService(git_err) => match git_err {
forge_core_services::services::git::GitServiceError::MergeConflicts(_) => {
(StatusCode::CONFLICT, "GitServiceError")
}
forge_core_services::services::git::GitServiceError::RebaseInProgress => {
(StatusCode::CONFLICT, "GitServiceError")
}
_ => (StatusCode::INTERNAL_SERVER_ERROR, "GitServiceError"),
},
ApiError::GitHubService(_) => (StatusCode::INTERNAL_SERVER_ERROR, "GitHubServiceError"),
ApiError::Auth(_) => (StatusCode::INTERNAL_SERVER_ERROR, "AuthError"),
ApiError::Deployment(_) => (StatusCode::INTERNAL_SERVER_ERROR, "DeploymentError"),
ApiError::Container(_) => (StatusCode::INTERNAL_SERVER_ERROR, "ContainerError"),
ApiError::Executor(_) => (StatusCode::INTERNAL_SERVER_ERROR, "ExecutorError"),
ApiError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "DatabaseError"),
ApiError::Worktree(_) => (StatusCode::INTERNAL_SERVER_ERROR, "WorktreeError"),
ApiError::Config(_) => (StatusCode::INTERNAL_SERVER_ERROR, "ConfigError"),
ApiError::Image(img_err) => match img_err {
ImageError::InvalidFormat => (StatusCode::BAD_REQUEST, "InvalidImageFormat"),
ImageError::TooLarge(_, _) => (StatusCode::PAYLOAD_TOO_LARGE, "ImageTooLarge"),
ImageError::NotFound => (StatusCode::NOT_FOUND, "ImageNotFound"),
_ => (StatusCode::INTERNAL_SERVER_ERROR, "ImageError"),
},
ApiError::Drafts(drafts_err) => match drafts_err {
DraftsServiceError::Conflict(_) => (StatusCode::CONFLICT, "ConflictError"),
DraftsServiceError::Database(_) => {
(StatusCode::INTERNAL_SERVER_ERROR, "DatabaseError")
}
DraftsServiceError::Container(_) => {
(StatusCode::INTERNAL_SERVER_ERROR, "ContainerError")
}
DraftsServiceError::Image(_) => (StatusCode::INTERNAL_SERVER_ERROR, "ImageError"),
DraftsServiceError::ExecutionProcess(_) => {
(StatusCode::INTERNAL_SERVER_ERROR, "ExecutionProcessError")
}
},
ApiError::Io(_) => (StatusCode::INTERNAL_SERVER_ERROR, "IoError"),
ApiError::Multipart(_) => (StatusCode::BAD_REQUEST, "MultipartError"),
ApiError::Conflict(_) => (StatusCode::CONFLICT, "ConflictError"),
};
let error_message = match &self {
ApiError::Image(img_err) => match img_err {
ImageError::InvalidFormat => "This file type is not supported. Please upload an image file (PNG, JPG, GIF, WebP, or BMP).".to_string(),
ImageError::TooLarge(size, max) => format!(
"This image is too large ({:.1} MB). Maximum file size is {:.1} MB.",
*size as f64 / 1_048_576.0,
*max as f64 / 1_048_576.0
),
ImageError::NotFound => "Image not found.".to_string(),
_ => {
"Failed to process image. Please try again.".to_string()
}
},
ApiError::GitService(git_err) => match git_err {
forge_core_services::services::git::GitServiceError::MergeConflicts(msg) => msg.clone(),
forge_core_services::services::git::GitServiceError::RebaseInProgress => {
"A rebase is already in progress. Resolve conflicts or abort the rebase, then retry.".to_string()
}
_ => format!("{}: {}", error_type, self),
},
ApiError::Multipart(_) => "Failed to upload file. Please ensure the file is valid and try again.".to_string(),
ApiError::Conflict(msg) => msg.clone(),
ApiError::Drafts(drafts_err) => match drafts_err {
DraftsServiceError::Conflict(msg) => msg.clone(),
DraftsServiceError::Database(_) => format!("{}: {}", error_type, drafts_err),
DraftsServiceError::Container(_) => format!("{}: {}", error_type, drafts_err),
DraftsServiceError::Image(_) => format!("{}: {}", error_type, drafts_err),
DraftsServiceError::ExecutionProcess(_) => {
format!("{}: {}", error_type, drafts_err)
}
},
_ => format!("{}: {}", error_type, self),
};
let response = ApiResponse::<()>::error(&error_message);
(status_code, Json(response)).into_response()
}
}