use crate::rpcgit::error::BabyError; #[derive(Debug, thiserror::Error)] pub enum GitError { #[error("cache error: {0}")] Cache(#[from] 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(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 axum::response::IntoResponse for GitError { fn into_response(self) -> axum::response::Response { use axum::http::StatusCode; let status = 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, }; 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 = (status, body).into_response(); if status == StatusCode::UNAUTHORIZED { resp.headers_mut().insert( axum::http::header::WWW_AUTHENTICATE, axum::http::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), } }