use core::fmt;
use reliar_core::{Classify, FailureKind, MessageId};
use crate::error::{classify_sqlstate, is_undefined_table};
#[cfg_attr(not(feature = "json"), doc = "```ignore")]
#[cfg_attr(feature = "json", doc = "```no_run")]
#[derive(Debug)]
#[non_exhaustive]
pub enum PostgresOutboxError {
NotMigrated {
source: sqlx::Error,
},
Database {
source: sqlx::Error,
},
}
impl fmt::Display for PostgresOutboxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotMigrated { source } => write!(
f,
"the outbox table does not resolve on this connection's search_path: {source}; \
run reliar_store_postgres::migrate(&pool, ..) and put the migrated schema first \
on search_path — in the connection URL \
(options=-c search_path=reliar,public) or with ALTER ROLE <role> SET \
search_path = reliar, public"
),
Self::Database { source } => write!(f, "database error: {source}"),
}
}
}
impl std::error::Error for PostgresOutboxError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::NotMigrated { source } | Self::Database { source } => Some(source),
}
}
}
impl Classify for PostgresOutboxError {
fn kind(&self) -> FailureKind {
match self {
Self::NotMigrated { .. } => FailureKind::Permanent,
Self::Database { source } => classify_sqlstate(source),
}
}
}
impl From<sqlx::Error> for PostgresOutboxError {
fn from(source: sqlx::Error) -> Self {
map_operational_error(source)
}
}
pub(crate) fn map_operational_error(err: sqlx::Error) -> PostgresOutboxError {
if is_undefined_table(&err) {
return PostgresOutboxError::NotMigrated { source: err };
}
PostgresOutboxError::Database { source: err }
}
impl crate::error::FromDatabaseError for PostgresOutboxError {
fn from_database_error(err: sqlx::Error) -> Self {
map_operational_error(err)
}
}
#[cfg_attr(not(feature = "json"), doc = "```ignore")]
#[cfg_attr(feature = "json", doc = "```no_run")]
#[derive(Debug)]
#[non_exhaustive]
pub enum EnqueueError<E> {
Serialize {
source: E,
},
Duplicate {
id: MessageId,
},
Database {
source: sqlx::Error,
},
}
impl<E: fmt::Display> fmt::Display for EnqueueError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Serialize { source } => {
write!(f, "failed to serialize the envelope body: {source}")
}
Self::Duplicate { id } => write!(f, "message id {id} already exists in the outbox"),
Self::Database { source } => write!(f, "database error: {source}"),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for EnqueueError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Serialize { source } => Some(source),
Self::Database { source } => Some(source),
Self::Duplicate { .. } => None,
}
}
}
impl<E: std::error::Error + Send + Sync + 'static> Classify for EnqueueError<E> {
fn kind(&self) -> FailureKind {
match self {
Self::Serialize { .. } | Self::Duplicate { .. } => FailureKind::Permanent,
Self::Database { source } => classify_sqlstate(source),
}
}
}
pub(crate) fn map_enqueue_error<E>(id: MessageId, err: sqlx::Error) -> EnqueueError<E> {
if is_constraint_violation(&err, "ix_outbox_message_id") {
return EnqueueError::Duplicate { id };
}
EnqueueError::Database { source: err }
}
pub(crate) fn is_constraint_violation(err: &sqlx::Error, constraint: &str) -> bool {
match err {
sqlx::Error::Database(db) => db.constraint() == Some(constraint),
_ => false,
}
}