venturi 0.3.0

A durable, PostgreSQL-backed job queue for Rust — controlled flow from backlog to worker.
Documentation
//! Public error types for venturi's queue and storage operations.
//!
//! Everything a producer or worker call can return surfaces as [`enum@Error`], a
//! `thiserror` enum. The crate keeps `anyhow`/`eyre` out of its public API so
//! consumers can match on concrete failure modes. Handler results use a separate
//! [`crate::outcome::TaskError`] (the execution side); this type is the
//! infrastructure side: pools, the driver, migrations, and serialization.

use thiserror::Error;

/// The error type returned by queue, worker, and storage operations.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
    /// A database statement failed at the driver level.
    #[error("while talking to PostgreSQL: {0}")]
    Database(#[from] tokio_postgres::Error),

    /// A connection could not be obtained from the pool.
    #[error("while acquiring a pooled connection: {0}")]
    Pool(#[from] deadpool_postgres::PoolError),

    /// The connection pool could not be built from the given configuration.
    #[error("while building the connection pool: {0}")]
    PoolBuild(#[from] deadpool_postgres::BuildError),

    /// Applying the schema migrations failed.
    #[error("while applying migrations: {0}")]
    Migration(#[from] refinery_core::Error),

    /// A task payload or carried state could not be (de)serialized to JSON.
    #[error("while (de)serializing a job payload or carry: {0}")]
    Serialization(#[from] serde_json::Error),

    /// A stored job referenced a `kind` that is not registered with this worker.
    #[error("no handler registered for job kind {kind:?}")]
    UnknownKind {
        /// The unregistered discriminator read from storage.
        kind: String,
    },

    /// A configuration value was invalid (for example an empty table prefix).
    #[error("invalid configuration: {0}")]
    Config(String),
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn display_includes_the_underlying_source() {
        // The driver/serde variants carry their detail in `source()`; the worker
        // logs and the dead-job journal note render `Display`, so `Display` must
        // surface the underlying message rather than the bare context.
        let serde_err = serde_json::from_str::<i32>("not a number").unwrap_err();
        let source_text = serde_err.to_string();
        let shown = Error::from(serde_err).to_string();

        assert!(
            shown.contains("(de)serializing"),
            "keeps the context: {shown}"
        );
        assert!(
            shown.contains(&source_text),
            "includes the source detail {source_text:?}: {shown}"
        );
    }
}