use prost::DecodeError;
use std::error::Error;
use thiserror::Error;
use tokio::{sync::AcquireError, task::JoinError};
use crate::{
config::ConfigBuilderError,
conflict::{SaveConflict, ServerConflict},
};
#[derive(Error, Debug)]
pub enum NetworkError {
#[error("Connection error")]
Connect,
#[error("Connection timeout")]
Timeout,
#[error("Response body error")]
ResponseBody,
#[error("Response status not successful: {status_code}")]
Status { status_code: u16 },
#[error("Network error: {message}")]
Other { message: String },
}
#[derive(Error, Debug)]
pub enum ConflictError {
#[error("Save conflict: {conflict}")]
Save { conflict: SaveConflict },
#[error("Server conflict: {conflict}")]
Server { conflict: ServerConflict },
#[error("Checksum mismatch: expected `{expected}`, got `{actual}`")]
ChecksumMismatch { expected: String, actual: String },
}
#[derive(Error, Debug)]
pub enum MetadataError {
#[error("metadata lock is held by another process (lockfile in use)")]
LockfileInUse,
#[error("failed to decode metadata: {e}")]
MetadataDecodeError { e: DecodeError },
#[error("metadata error: {message}")]
Other { message: String },
}
#[derive(Error, Debug)]
pub enum OdlError {
#[error(transparent)]
Network(#[from] NetworkError),
#[error(transparent)]
Conflict(#[from] ConflictError),
#[error("The input file is empty")]
EmptyInputFile,
#[error("URL decode error: {message}")]
UrlDecodeError { message: String },
#[error("I/O error: {e} {extra_info:?}")]
StdIoError {
e: std::io::Error,
extra_info: Option<String>,
},
#[error("CLI error: {message}")]
CliError { message: String },
#[error("download cancelled")]
Cancelled,
#[error(transparent)]
ConfigBuilderError(#[from] ConfigBuilderError),
#[error(transparent)]
MetadataError(#[from] MetadataError),
#[error("{message}")]
Other {
message: String,
origin: Box<dyn std::error::Error + Send + Sync>,
},
}
impl From<reqwest::Error> for OdlError {
fn from(e: reqwest::Error) -> Self {
if let Some(status) = e.status()
&& !status.is_success()
{
return Self::Network(NetworkError::Status {
status_code: status.as_u16(),
});
}
if e.is_timeout() {
return OdlError::Network(NetworkError::Timeout);
}
if e.is_body() {
return OdlError::Network(NetworkError::ResponseBody);
}
if e.is_connect() {
return OdlError::Network(NetworkError::Connect);
}
if let Some(io_err) = e.source().and_then(|s| s.downcast_ref::<std::io::Error>())
&& io_err.kind() == std::io::ErrorKind::TimedOut
{
return OdlError::Network(NetworkError::Timeout);
}
Self::Network(NetworkError::Other {
message: e.to_string(),
})
}
}
impl From<std::io::Error> for OdlError {
fn from(e: std::io::Error) -> Self {
Self::StdIoError {
e,
extra_info: None,
}
}
}
impl From<JoinError> for OdlError {
fn from(e: JoinError) -> Self {
Self::Other {
message: e.to_string(),
origin: Box::new(e),
}
}
}
impl From<crate::download::DownloadBuilderError> for OdlError {
fn from(e: crate::download::DownloadBuilderError) -> Self {
Self::Other {
message: e.to_string(),
origin: Box::new(e),
}
}
}
impl From<prost::DecodeError> for OdlError {
fn from(e: prost::DecodeError) -> Self {
OdlError::MetadataError(MetadataError::MetadataDecodeError { e })
}
}
impl From<AcquireError> for OdlError {
fn from(e: AcquireError) -> Self {
OdlError::Other {
message: "failed to acquire semaphore permit".to_string(),
origin: Box::new(e),
}
}
}
impl From<keyring::Error> for OdlError {
fn from(e: keyring::Error) -> Self {
OdlError::Other {
message: e.to_string(),
origin: Box::new(e),
}
}
}
#[derive(Error, Debug)]
pub enum DownloadParseError {
#[error("Failed to parse URL: {message}")]
InvalidUrl { message: String },
#[error("Invalid timestamp")]
InvalidTimestamp,
}