use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
AccessControl(#[from] crate::access_control::Error),
#[error(transparent)]
EventStore(#[from] crate::event_store::Error),
#[error(transparent)]
Projections(#[from] crate::projections::Error),
#[error(transparent)]
Bootstrap(#[from] crate::bootstrap::Error),
#[error(transparent)]
Encryption(#[from] crate::encryption::Error),
#[error(transparent)]
Blind(#[from] crate::blind::Error),
#[error(transparent)]
Secrets(#[from] crate::secrets::Error),
#[error(transparent)]
Approvals(#[from] crate::approvals::Error),
#[error("command rejected: {reason}")]
CommandRejected {
reason: String,
kind: String,
},
#[error("this CommandType has no decide() registered in the running process")]
NoDeciderRegistered,
#[error("database error: {0}")]
Database(#[from] sqlx::Error),
#[error("command batch failed: {message}")]
BatchFailed {
code: String,
message: String,
},
#[error("migration error: {0}")]
Migration(#[from] sqlx::migrate::MigrateError),
#[error("{what} has a value this build does not support; update all skilj crates to the same version")]
Unsupported {
what: &'static str,
},
#[error("not processed: an earlier item of the batch failed")]
NotProcessed,
}
impl Error {
pub fn another_instance_can_do_it(&self) -> bool {
match self {
Error::BatchFailed { code, .. } => skilj_retry::another_instance_can_do_it(code),
other => skilj_retry::another_instance_can_do_it(other.code()),
}
}
}
pub trait SkiljRejection {
fn code(&self) -> &str;
fn message(&self) -> String;
fn internal_detail(&self) -> Option<String> {
None
}
}
impl SkiljRejection for Error {
fn code(&self) -> &str {
match self {
Error::AccessControl(e) => e.code(),
Error::EventStore(e) => e.code(),
Error::Projections(e) => e.code(),
Error::Bootstrap(e) => e.code(),
Error::Encryption(e) => e.code(),
Error::Blind(e) => e.code(),
Error::Secrets(e) => e.code(),
Error::Approvals(e) => e.code(),
Error::CommandRejected { kind, .. } => kind,
Error::NoDeciderRegistered => "no_decider_registered",
Error::Database(_) => "database_error",
Error::Migration(_) => "migration_error",
Error::BatchFailed { code, .. } => code,
Error::Unsupported { .. } => "unsupported",
Error::NotProcessed => "not_processed",
}
}
fn message(&self) -> String {
match self {
Error::AccessControl(e) => e.message(),
Error::EventStore(e) => e.message(),
Error::Projections(e) => e.message(),
Error::Bootstrap(e) => e.message(),
Error::Encryption(e) => e.message(),
Error::Blind(e) => e.message(),
Error::Secrets(e) => e.message(),
Error::Approvals(e) => e.message(),
Error::CommandRejected { reason, .. } => reason.clone(),
Error::NoDeciderRegistered => self.to_string(),
Error::Database(sqlx::Error::RowNotFound) => {
"a record this request needed no longer exists".to_string()
}
Error::Database(sqlx::Error::PoolTimedOut) => {
"the server's database connections are all busy - retry shortly".to_string()
}
Error::Database(_) => "an internal database error occurred".to_string(),
Error::Migration(_) => "an internal database migration error occurred".to_string(),
Error::BatchFailed { message, .. } => message.clone(),
Error::Unsupported { .. } => self.to_string(),
Error::NotProcessed => self.to_string(),
}
}
fn internal_detail(&self) -> Option<String> {
match self {
Error::Database(e) => Some(e.to_string()),
Error::Migration(e) => Some(e.to_string()),
_ => None,
}
}
}
impl Error {
pub fn batch_failed(cause: &Error) -> Error {
Error::BatchFailed {
code: cause.code().to_string(),
message: cause.message(),
}
}
pub fn batch_failed_msg(message: impl Into<String>) -> Error {
Error::BatchFailed {
code: "database_error".to_string(),
message: message.into(),
}
}
pub fn row_not_found() -> Error {
Error::Database(sqlx::Error::RowNotFound)
}
pub fn configuration(message: impl Into<String>) -> Error {
Error::Database(sqlx::Error::Configuration(message.into().into()))
}
}
pub type Result<T> = std::result::Result<T, Error>;