use std::fmt;
use degenbot_aave::RunError as AaveRunError;
use degenbot_config::ConfigError;
use degenbot_db::DbError;
use degenbot_pool_updater::RunError as PoolRunError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExitCode {
Success,
Failure,
Config,
}
impl ExitCode {
#[must_use]
pub const fn code(self) -> i32 {
match self {
Self::Success => 0,
Self::Failure => 1,
Self::Config => 78,
}
}
}
#[derive(Debug)]
pub enum CliError {
BootRefused(String),
Aborted,
DatabaseUpgradeRetired,
DatabaseForeign,
DatabaseNothingToDo,
Database(DbError),
Config(ConfigError),
UnknownChain {
chain: String,
},
UnknownDeployment {
chain_id: u64,
name: String,
},
InvalidBlockTag(String),
BlockResolution(String),
InvalidAddress(String),
InvalidArgument(String),
NoActiveAaveMarkets,
PoolUpdate(PoolRunError),
AaveUpdate(AaveRunError),
RuntimeNested,
OperatorRefused(String),
OperatorProtocol(String),
OperatorHygiene(String),
}
impl CliError {
#[must_use]
pub fn message(&self) -> String {
match self {
Self::BootRefused(message)
| Self::BlockResolution(message)
| Self::InvalidArgument(message)
| Self::OperatorRefused(message)
| Self::OperatorProtocol(message)
| Self::OperatorHygiene(message) => message.clone(),
Self::Aborted => "Aborted!".to_string(),
Self::DatabaseUpgradeRetired => {
"the database upgrades itself at open; for an explicit repair, run \
`degenbot database heal`"
.to_string()
}
Self::DatabaseForeign => {
"The database is unrecognized (a foreign SQLite file); refused.".to_string()
}
Self::DatabaseNothingToDo => {
"The database has no legacy history; there is nothing to cut over.".to_string()
}
Self::Database(err) => err.to_string(),
Self::Config(err) => err.to_string(),
Self::UnknownChain { chain } => format!(
"Unknown chain {chain:?}: expected a chain slug (base, ethereum) or a numeric \
chain id."
),
Self::UnknownDeployment { chain_id, name } => {
format!("The deployments registry has no record for {name:?} on chain {chain_id}.")
}
Self::InvalidBlockTag(tag) => format!("Invalid block tag: {tag}"),
Self::InvalidAddress(address) => format!("Invalid address: {address}"),
Self::NoActiveAaveMarkets => "No active Aave markets found.".to_string(),
Self::PoolUpdate(err) => err.to_string(),
Self::AaveUpdate(err) => err.to_string(),
Self::RuntimeNested => "the command arms own their tokio runtime; do not run them \
from inside an existing runtime"
.to_string(),
}
}
}
impl fmt::Display for CliError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message())
}
}
impl std::error::Error for CliError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Database(err) => Some(err),
Self::Config(err) => Some(err),
Self::PoolUpdate(err) => Some(err),
Self::AaveUpdate(err) => Some(err),
_ => None,
}
}
}
impl From<DbError> for CliError {
fn from(err: DbError) -> Self {
match err {
DbError::UnrecognizedSchema => Self::DatabaseForeign,
other => Self::Database(other),
}
}
}
impl From<ConfigError> for CliError {
fn from(err: ConfigError) -> Self {
Self::Config(err)
}
}
impl From<&CliError> for ExitCode {
fn from(err: &CliError) -> Self {
match err {
CliError::BootRefused(_) => Self::Config,
CliError::Aborted
| CliError::DatabaseUpgradeRetired
| CliError::DatabaseForeign
| CliError::DatabaseNothingToDo
| CliError::Database(_)
| CliError::Config(_)
| CliError::UnknownChain { .. }
| CliError::UnknownDeployment { .. }
| CliError::InvalidBlockTag(_)
| CliError::BlockResolution(_)
| CliError::InvalidAddress(_)
| CliError::InvalidArgument(_)
| CliError::NoActiveAaveMarkets
| CliError::PoolUpdate(_)
| CliError::AaveUpdate(_)
| CliError::RuntimeNested
| CliError::OperatorRefused(_)
| CliError::OperatorProtocol(_)
| CliError::OperatorHygiene(_) => Self::Failure,
}
}
}
impl From<CliError> for ExitCode {
fn from(err: CliError) -> Self {
Self::from(&err)
}
}