Skip to main content

InboxRow

Trait InboxRow 

Source
pub trait InboxRow: QueueRow {
    const SPEC: TableSpec<'static>;
}
Expand description

A struct that describes a queue table; #[derive(Inbox)] implements it.

The struct puts its table in one of two modes. In payload mode a #[field(payload)] field holds the message, and a codec decodes it for the handler (PayloadRow). In row mode the struct has no such field, and the handler takes the struct itself, &Row or &[Row] (see row mode).

§Examples

use ruststream_sqlx::prelude::*;
use serde::Deserialize;
use sqlx::PgPool;

// The derive implements `InboxRow` for `SendEmail`: the table `app.email_jobs`, its id and
// its payload column.
#[derive(Inbox, sqlx::FromRow)]
#[inbox(table = "email_jobs", schema = "app")]
pub struct SendEmail {
    #[field(id)]
    job_id: i64,
    #[field(payload)]
    payload: Vec<u8>,
}

#[derive(Deserialize)]
struct Email {
    to: String,
}

#[subscriber(InboxQueue::<SendEmail>::new("emails"))]
async fn send(email: &Email) -> HandlerOutcome {
    tracing::info!(to = %email.to, "sending");
    HandlerOutcome::ack()
}

pub fn app(pool: PgPool) -> RustStream {
    RustStream::new(AppInfo::new("mailer", "0.1.0")).with_broker(SqlxBroker::new(pool), |b| {
        b.include(send);
    })
}

A struct in row mode hands its handler the row itself: one row, the rows of one claim as a slice, or one row beside its delivery’s transaction:

use ruststream_sqlx::prelude::*;
use serde::{Deserialize, Serialize};
use sqlx::{PgPool, Postgres};

// email_jobs: job_id BIGSERIAL PRIMARY KEY, name TEXT, attempt SMALLINT DEFAULT 1, "to" TEXT,
// subject TEXT NULL, attachments JSONB
/// One mail to send: the table has no payload column, so a handler takes the row itself.
#[derive(Inbox, sqlx::FromRow, Clone)]
#[inbox(table = "email_jobs")]
pub struct SendEmail {
    #[field(id, generated)]
    job_id: i64,
    #[field(group)]
    name: String,
    #[field(attempt, generated)]
    attempt: i16,
    to: String,
    subject: Option<String>,
    #[sqlx(json)]
    attachments: Vec<Attachment>,
}

#[derive(Clone, Serialize, Deserialize)]
pub struct Attachment {
    file: String,
}

#[subscriber(InboxQueue::<SendEmail>::new("emails"))]
async fn send(email: &SendEmail) -> HandlerOutcome {
    if deliver(email).await {
        HandlerOutcome::ack()
    } else {
        HandlerOutcome::retry()
    }
}

/// One SMTP session per claim of up to 64 rows.
#[subscriber(InboxQueue::<SendEmail>::new("newsletters"))]
async fn send_newsletter(emails: &[SendEmail]) -> HandlerOutcome {
    tracing::info!(count = emails.len(), "sent a newsletter page");
    HandlerOutcome::ack()
}

/// The audit row commits with the acknowledgement of the mail's row, or neither does.
#[subscriber(InboxQueue::<SendEmail>::new("receipts"))]
async fn send_receipt(email: &SendEmail, Ctx(mut tx): Ctx<keys::Tx<Postgres>>) -> HandlerOutcome {
    let logged = sqlx::query("INSERT INTO sent_mail (job_id, recipient) VALUES ($1, $2)")
        .bind(email.job_id)
        .bind(&email.to)
        .execute(&mut *tx)
        .await;
    if logged.is_ok() && deliver(email).await {
        HandlerOutcome::ack()
    } else {
        HandlerOutcome::retry()
    }
}

pub fn app(pool: PgPool) -> RustStream {
    RustStream::new(AppInfo::new("mailer", "0.1.0")).with_broker(SqlxBroker::new(pool), |b| {
        b.include(send);
        b.include(send_newsletter.batch(nonzero!(64)));
        b.include(send_receipt.transactional());
    })
}

Required Associated Constants§

Source

const SPEC: TableSpec<'static>

The table the struct describes: its name, its columns and their roles, and the form its rows are claimed in.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§

Source§

impl<Row, Settings> InboxRow for Row
where Row: InboxTable<Table = InboxSpec<Settings>>, Settings: Declaration, Settings::Message: MessageAxis<Row>, Settings::Form: FormAxis, Settings::Opening: OpeningAxis,

Source§

const SPEC: TableSpec<'static>