194c44c
use gitbaby::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("internal error: {0}")]
Internal(String),
}
impl From<sea_orm::DbErr> 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<sea_orm::TransactionError<sea_orm::DbErr>> for GitError {
fn from(err: sea_orm::TransactionError<sea_orm::DbErr>) -> 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<sea_orm::TransactionError<GitError>> for GitError {
fn from(err: sea_orm::TransactionError<GitError>) -> Self {
match err {
sea_orm::TransactionError::Connection(db_err) => Self::from(db_err),
sea_orm::TransactionError::Transaction(git_err) => git_err,
}
}
}
impl From<std::io::Error> 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::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()
}
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),
}
}