9818851
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),
}
auth::auth_error_conversion!(GitError);
api::access_error_conversion!(GitError, InvalidArgument);
api::page_error_conversion!(GitError, InvalidArgument);
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 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),
}
}