use crate::rpcgit::error::BabyError; #[derive(Debug, thiserror::Error)] pub enum GitError { #[error("cache error: {0}")] Cache(#[from] export::cache::CacheError), #[error("invalid argument: {0}")] InvalidArgument(String), #[error("conflict: {0}")] Conflict(String), #[error("resource not found")] NotFound, #[error("unauthorized")] Unauthorized, #[error("forbidden")] Forbidden, #[error("git error: {0}")] Git(#[from] BabyError), #[error("not implemented: {0}")] Unimplemented(&'static str), #[error("internal error: {0}")] Internal(String), } impl From for GitError { fn from(error: export::ExportError) -> Self { match error { export::ExportError::Database(err) => Self::from(err), other => Self::Internal(other.to_string()), } } } impl From for GitError { fn from(error: space::errors::SpaceError) -> Self { match error { space::errors::SpaceError::BadRequest(message) => Self::InvalidArgument(message), space::errors::SpaceError::Unauthorized => Self::Unauthorized, space::errors::SpaceError::Forbidden => Self::Forbidden, space::errors::SpaceError::NotFound => Self::NotFound, space::errors::SpaceError::Conflict(message) => Self::Conflict(message), other => Self::Internal(other.to_string()), } } } impl From for GitError { fn from(err: sea_orm::DbErr) -> Self { if is_unique_violation(&err) { Self::Conflict("resource already exists".to_string()) } else { Self::Internal(format!("database error: {err}")) } } } impl From> for GitError { fn from(err: sea_orm::TransactionError) -> Self { match &err { sea_orm::TransactionError::Transaction(db_err) if is_unique_violation(db_err) => { Self::Conflict("resource already exists".to_string()) } _ => Self::Internal(format!("database transaction error: {err}")), } } } impl From> for GitError { fn from(err: sea_orm::TransactionError) -> Self { match err { sea_orm::TransactionError::Connection(db_err) => Self::from(db_err), sea_orm::TransactionError::Transaction(git_err) => git_err, } } } impl From for GitError { fn from(err: std::io::Error) -> Self { Self::Internal(format!("io error: {err}")) } } impl GitError { fn status(&self) -> actix_web::http::StatusCode { use actix_web::http::StatusCode; match self { Self::NotFound => StatusCode::NOT_FOUND, Self::Unauthorized => StatusCode::UNAUTHORIZED, Self::Forbidden => StatusCode::FORBIDDEN, Self::Conflict(_) => StatusCode::CONFLICT, Self::InvalidArgument(_) => StatusCode::BAD_REQUEST, Self::Unimplemented(_) => StatusCode::NOT_IMPLEMENTED, Self::Cache(_) | Self::Git(_) | Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, } } } impl actix_web::ResponseError for GitError { fn status_code(&self) -> actix_web::http::StatusCode { self.status() } fn error_response(&self) -> actix_web::HttpResponse { let status = self.status(); let body = match self { Self::Cache(_) | Self::Git(_) | Self::Internal(_) => { tracing::warn!(error = %self, "git internal error"); "internal server error".to_string() } Self::Unimplemented(_) => self.to_string(), other => other.to_string(), }; let mut resp = actix_web::HttpResponse::build(status).body(body); if status == actix_web::http::StatusCode::UNAUTHORIZED { resp.headers_mut().insert( actix_web::http::header::WWW_AUTHENTICATE, actix_web::http::header::HeaderValue::from_static("Basic realm=\"gitdata\""), ); } resp } } pub fn is_unique_violation(err: &sea_orm::DbErr) -> bool { let msg = err.to_string().to_lowercase(); msg.contains("unique") || msg.contains("duplicate") || msg.contains("23505") } pub fn map_baby(err: BabyError) -> GitError { use BabyError::*; match err { BranchNotFound(_) | TagNotFound(_) | RefNotFound(_) | RevisionNotFound(_) | BlobNotFound { .. } | RemoteNotFound(_) | PathNotFound { .. } | MergeBaseNotFound(_) => GitError::NotFound, BranchAlreadyExists(name) | TagAlreadyExists(name) | RemoteAlreadyExists(name) => { GitError::Conflict(format!("`{name}` already exists")) } RefUpdateRejected(reason) => GitError::Conflict(reason), RevisionEmpty | RevisionInvalid(_) | InvalidRefName(_) | InvalidBranchName(_) | InvalidTagName(_) | InvalidRemoteUrl(_) | InvalidGitArg(_) | InvalidRange | PathEscapesRepository | InvalidTimestamp(_) | AmbiguousRef { .. } | PayloadTooLarge { .. } => GitError::InvalidArgument(err.to_string()), other => GitError::Git(other), } }