Skip to main content

InboxSpec

A table described by hand implements InboxTable: a const InboxSpec chain, its settings as types in type Table, and the same statements as the derive.

The derive and the description by hand are two spellings of one public API, and they build the same statements and the same per-message code. A service writes the description by hand for a declaration a reviewer reads as it is, or for a struct that holds fewer fields than the table has columns. TABLE holds the description: InboxSpec::new takes the table's name and the id column, and each setter adds a setting. Every setter is a const fn, so the description is a constant and costs nothing at run time. type Id names the id's type, and fn id lends it off the row.

type Table names the chain's final type, because stable Rust does not infer the type of an associated constant; a chain that does not match it is a type mismatch. The type lists the settings the crate reads, in the order the chain sets them:

  • the form: Lease<Time> or Advisory;
  • payload mode: Payload;
  • the roles a delivery reads off a row: Key, Attempt, Headers, HeaderFields;
  • the time roles with their time type: RetryAfter<Time>, ProcessedAt<Time>;
  • Fifo, the clock Clock<Source> and the opening Opens<Level>;
  • the events the service writes itself, the markers of spec::own.

The column-only setters, within, group, priority, data, fetching and selecting_all, leave the type as it is. A setting the chain leaves out keeps its default: the row lock form, row mode, the host's clock, the database's own opening and the crate's events. Each role a delivery reads names a small trait the struct implements.

RuleDeriveBy hand
an id and a table namecompilethe constructor's arguments
one form, one message mode, one clock, one opening; each typed role and each own event oncecompilethe setter's bound, at compile time
a role in the type has its trait; an own event is implementedcompilea trait bound, at compile time
the form rules of Eventscompilea bound on type Table, at compile time
a column-only role set once; a column named oncecompilethe constant's evaluation, at build time
a lock key names a column the table hascompilethe statement's prepare, at startup

A refusal at build time names the table and the rule, and points at the crate's check rather than the service's line.

Example​

One table, email_jobs, described twice: Welcome with the derive and Reminder by hand, each serving its own group.

use chrono::{DateTime, Utc};
use ruststream_sqlx::dialect::Column;
use ruststream_sqlx::spec::{Attempt, Lease, Payload};
use ruststream_sqlx::{AttemptRow, InboxSpec, InboxTable, PayloadRow};

/// The derive writes the description from the struct.
#[derive(Inbox, sqlx::FromRow)]
#[inbox(table = "email_jobs")]
struct Welcome {
#[field(id, generated)]
job_id: i64,
#[field(group)]
name: String,
#[field(attempt, generated)]
attempt: i16,
#[field(locked_until)]
locked_until: Option<DateTime<Utc>>,
#[field(payload)]
payload: Vec<u8>,
}

/// The same table by hand: the struct holds only what the service reads.
#[derive(sqlx::FromRow)]
struct Reminder {
job_id: i64,
attempt: i16,
payload: Vec<u8>,
}

impl InboxTable for Reminder {
type Id = i64;
// The settings the crate reads as types, in the order the chain sets them.
type Table = InboxSpec<(Lease<DateTime<Utc>>, Attempt, Payload)>;
const TABLE: Self::Table = InboxSpec::new("email_jobs", Column::new("job_id").generated())
.lease(Column::new("locked_until"))
.group(Column::new("name"))
.attempt(Column::new("attempt").generated())
.payload(Column::new("payload"));

fn id(&self) -> &i64 {
&self.job_id
}
}

impl AttemptRow for Reminder {
type Attempt = i16;

fn attempt(&self) -> &i16 {
&self.attempt
}
}

impl PayloadRow for Reminder {
type Column = Vec<u8>;

fn payload(&self) -> &[u8] {
&self.payload
}
}