Skip to main content

backbone_outbox/
error.rs

1//! Error type for the durable outbox/inbox/relay.
2
3/// Errors from staging, relaying, or deduping durable events.
4#[derive(Debug, thiserror::Error)]
5pub enum OutboxError {
6    /// A database error.
7    #[error("db: {0}")]
8    Db(#[from] sqlx::Error),
9    /// A schema name that is not a safe SQL identifier (`^[a-z_][a-z0-9_]*$`).
10    #[error("invalid schema identifier: {0:?}")]
11    InvalidSchema(String),
12    /// The relay's `publish` callback failed for a record; the row is left unpublished for retry.
13    #[error("publish failed: {0}")]
14    Publish(String),
15    /// The relay's `publish` callback GAVE UP on a record — its transport exhausted its own retries
16    /// or dead-lettered it, and will not deliver this event however many times it is handed over.
17    ///
18    /// Distinct from [`OutboxError::Publish`] on purpose. A transport that has given up used to have
19    /// no way to say so: returning `Ok` marked the row published, which reads as delivered while the
20    /// effect was dropped, and returning `Publish` re-handed the same doomed record on every pass
21    /// forever. This variant marks the row dead — not published, visible in the table, and
22    /// re-emittable once the cause is fixed.
23    #[error("publish exhausted: {0}")]
24    Exhausted(String),
25}
26
27/// Result alias for outbox operations.
28pub type Result<T> = std::result::Result<T, OutboxError>;
29
30/// Reject a schema name that is not a safe, lowercase SQL identifier. Schema names come from trusted
31/// module config, but they are interpolated into DDL/DML (Postgres has no bind parameter for an
32/// identifier), so we validate defensively.
33pub(crate) fn validate_schema(schema: &str) -> Result<()> {
34    let ok = !schema.is_empty()
35        && schema.len() <= 63
36        && schema.bytes().next().map(|b| b == b'_' || b.is_ascii_lowercase()).unwrap_or(false)
37        && schema.bytes().all(|b| b == b'_' || b.is_ascii_lowercase() || b.is_ascii_digit());
38    if ok {
39        Ok(())
40    } else {
41        Err(OutboxError::InvalidSchema(schema.to_string()))
42    }
43}