2ae9196
ZhenYi
feat(lib/git): add HTTP handler tree with routes and errors across analysis, archives, blobs, branches, commits, conflicts, content, diffs, hooks, lfs, logs, maintenance, packs, patches, pipelines, refs, remotes, repos, signatures, submodules, tags, trees
2ae9196
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 mut resp = (status, self.to_string()).into_response();
if status == StatusCode::UNAUTHORIZED {
resp.headers_mut().insert(
axum::http::header::WWW_AUTHENTICATE,
axum::http::HeaderValue::from_static("Basic realm=\"gitdata\""),
);
}
resp
}
}
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),
}
}