use git_lfs_api::{ApiError, ObjectError};
use git_lfs_pointer::OidParseError;
use git_lfs_store::StoreError;
#[derive(Debug, thiserror::Error)]
pub enum TransferError {
#[error("server error for object: {} ({})", .0.message, .0.code)]
ServerObject(ObjectError),
#[error("server returned no download action for object")]
NoDownloadAction,
#[error("{}", format_action_status(*.status, .url))]
ActionStatus { status: u16, url: String },
#[error("http error: {0}")]
Http(#[from] reqwest::Error),
#[error("local io error: {0}")]
Io(#[from] std::io::Error),
#[error("store error: {0}")]
Store(#[from] StoreError),
#[error("invalid oid from server: {0}")]
InvalidOid(#[from] OidParseError),
#[error("unsupported hash algorithm: {0}")]
UnsupportedHashAlgo(String),
}
fn format_action_status(status: u16, url: &str) -> String {
let prefix = match status {
400 => "Client error:",
401 | 403 => "Authorization error:",
404 => "Repository or object not found:",
422 => "Unprocessable entity:",
429 => "Rate limit exceeded:",
500 => "Server error:",
501 => "Not Implemented:",
503 => "LFS is temporarily unavailable:",
507 => "Insufficient server storage:",
509 => "Bandwidth limit exceeded:",
_ if status < 500 => return format!("LFS: Client error {url} from HTTP {status}"),
_ => return format!("LFS: Server error {url} from HTTP {status}"),
};
format!("LFS: {prefix} {url}")
}
impl TransferError {
pub fn is_retryable(&self) -> bool {
match self {
TransferError::Http(e) => {
!e.is_decode() && !e.is_builder()
}
TransferError::ActionStatus { status, .. } => {
matches!(status, 408 | 429 | 500..=599)
}
TransferError::Io(_) => true,
TransferError::ServerObject(_)
| TransferError::NoDownloadAction
| TransferError::Store(_)
| TransferError::InvalidOid(_)
| TransferError::UnsupportedHashAlgo(_) => false,
}
}
}
impl From<ApiError> for TransferError {
fn from(value: ApiError) -> Self {
match value {
ApiError::Transport(e) => TransferError::Http(e),
other => {
TransferError::Io(std::io::Error::other(other.to_string()))
}
}
}
}
#[derive(Debug, Default)]
pub struct Report {
pub succeeded: Vec<String>,
pub failed: Vec<(String, TransferError)>,
}
impl Report {
pub fn is_complete_success(&self) -> bool {
self.failed.is_empty()
}
}