use std::future::Future;
use std::time::Duration;
use ruststream::OutgoingMessage;
use sqlx::{Database, Error};
use super::InboxRow;
use super::time::LeaseRow;
#[diagnostic::on_unimplemented(
message = "`{Self}` runs its own `claim` (`custom(claim)` in `#[inbox(..)]`, or `own::Claim` in its `InboxSpec`) and does not implement `Claim<{DB}>`",
label = "the service's own claim is missing",
note = "implement `Claim<{DB}>` for `{Self}`, or drop `claim` from `custom(..)` (by hand: `.own::<own::Claim>()` and `own::Claim` in `type Table`)"
)]
pub trait Claim<DB: Database>: InboxRow {
fn claim(
conn: &mut DB::Connection,
queue: &str,
limit: i64,
) -> impl Future<Output = Result<Vec<Self::Id>, Error>> + Send;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` runs its own `fetch` (`custom(fetch)` in `#[inbox(..)]`, or `own::Fetch` in its `InboxSpec`) and does not implement `Fetch<{DB}>`",
label = "the service's own fetch is missing",
note = "implement `Fetch<{DB}>` for `{Self}`, or drop `fetch` from `custom(..)` (by hand: `.own::<own::Fetch>()` and `own::Fetch` in `type Table`)"
)]
pub trait Fetch<DB: Database>: InboxRow {
fn fetch(
conn: &mut DB::Connection,
ids: &[Self::Id],
) -> impl Future<Output = Result<Vec<Self>, Error>> + Send;
}
macro_rules! settle_event {
(
$(#[$doc:meta])*
$trait:ident, $method:ident, $event:literal,
gate = $gate:literal, $(uses = $uses:literal,)? $(fields = $fields:literal,)? body = $body:literal,
message = $message:literal, note = $note:literal
$(, $extra:ident: $extra_ty:ty = $extra_value:literal)?
) => {
$(#[$doc])*
#[doc = ""]
#[doc = "# Examples"]
#[doc = ""]
#[doc = "```"]
#[doc = concat!("# #[cfg(", $gate, ")]")]
#[doc = "# mod demo {"]
#[doc = "use std::time::Duration;"]
#[doc = ""]
#[doc = concat!("use ruststream_sqlx::{", stringify!($trait), ", Inbox};")]
#[doc = "use sqlx::{PgConnection, Postgres};"]
$(#[doc = $uses])?
#[doc = ""]
#[doc = "#[derive(Inbox, sqlx::FromRow)]"]
#[doc = concat!("#[inbox(table = \"jobs\", custom(", $event, "))]")]
#[doc = "pub struct Job {"]
#[doc = " #[field(id)]"]
#[doc = " id: i64,"]
$(#[doc = $fields])?
#[doc = " #[field(payload)]"]
#[doc = " payload: Vec<u8>,"]
#[doc = "}"]
#[doc = ""]
#[doc = concat!("impl ", stringify!($trait), "<Postgres> for Job {")]
#[doc = concat!(" async fn ", stringify!($method), "(")]
#[doc = " conn: &mut PgConnection,"]
#[doc = " id: &i64,"]
$(#[doc = concat!(" ", stringify!($extra), ": ", stringify!($extra_ty), ",")])?
#[doc = " ) -> Result<(), sqlx::Error> {"]
#[doc = $body]
#[doc = " Ok(())"]
#[doc = " }"]
#[doc = "}"]
#[doc = "# }"]
#[doc = "# fn main() {}"]
#[doc = "```"]
#[diagnostic::on_unimplemented(
message = $message,
label = "the service's own event is missing",
note = $note
)]
pub trait $trait<DB: Database>: InboxRow {
#[doc = concat!("Runs the `", $event, "` event for the row `id` inside a transaction the crate commits: the claim's, or in the lease form one that first confirms the row still holds the delivery's lease.")]
#[doc = ""]
#[doc = "# Errors"]
#[doc = ""]
#[doc = "The database's error; the transaction rolls back, and the row returns to the queue at once, or in the lease form once its lease runs out."]
fn $method(
conn: &mut DB::Connection,
id: &Self::Id,
$($extra: $extra_ty,)?
) -> impl Future<Output = Result<(), Error>> + Send;
}
};
}
settle_event!(
Ack, ack, "ack",
gate = "feature = \"postgres\"",
body = " // The finished job moves to an archive in one statement.
sqlx::query(
\"WITH done AS (DELETE FROM jobs WHERE id = $1 RETURNING *) \\
INSERT INTO jobs_done SELECT * FROM done\",
)
.bind(id)
.execute(conn)
.await?;",
message = "`{Self}` runs its own `ack` (`custom(ack)` in `#[inbox(..)]`, or `own::Ack` in its `InboxSpec`) and does not implement `Ack<{DB}>`",
note = "implement `Ack<{DB}>` for `{Self}`, or drop `ack` from `custom(..)` (by hand: `.own::<own::Ack>()` and `own::Ack` in `type Table`)"
);
settle_event!(
Retry, retry, "retry",
gate = "feature = \"postgres\"",
body = " sqlx::query(\"UPDATE jobs SET tries = tries + 1 WHERE id = $1\")
.bind(id)
.execute(conn)
.await?;",
message = "`{Self}` runs its own `retry` (`custom(retry)` in `#[inbox(..)]`, or `own::Retry` in its `InboxSpec`) and does not implement `Retry<{DB}>`",
note = "implement `Retry<{DB}>` for `{Self}`, or drop `retry` from `custom(..)` (by hand: `.own::<own::Retry>()` and `own::Retry` in `type Table`)"
);
settle_event!(
RetryAfter, retry_after, "retry_after",
gate = "all(feature = \"postgres\", feature = \"chrono\")",
uses = "use chrono::{DateTime, Utc};",
fields = " #[field(retry_after)]
retry_after: DateTime<Utc>,",
body = " // The delay counts from the database's clock, whatever the host's says.
sqlx::query(
\"UPDATE jobs SET retry_after = now() + make_interval(secs => $2) WHERE id = $1\",
)
.bind(id)
.bind(delay.as_secs_f64())
.execute(conn)
.await?;",
message = "`{Self}` runs its own `retry_after` (`custom(retry_after)` in `#[inbox(..)]`, or `own::RetryAfter` in its `InboxSpec`) and does not implement `RetryAfter<{DB}>`",
note = "implement `RetryAfter<{DB}>` for `{Self}`, or drop `retry_after` from `custom(..)` (by hand: `.own::<own::RetryAfter>()` and `own::RetryAfter` in `type Table`)",
delay: Duration = "Duration::from_secs(30)"
);
settle_event!(
Discard, discard, "discard",
gate = "feature = \"postgres\"",
body = " sqlx::query(\"DELETE FROM jobs WHERE id = $1\")
.bind(id)
.execute(conn)
.await?;",
message = "`{Self}` runs its own `discard` (`custom(discard)` in `#[inbox(..)]`, or `own::Discard` in its `InboxSpec`) and does not implement `Discard<{DB}>`",
note = "implement `Discard<{DB}>` for `{Self}`, or drop `discard` from `custom(..)` (by hand: `.own::<own::Discard>()` and `own::Discard` in `type Table`)"
);
settle_event!(
DeadLetter, dead_letter, "dead_letter",
gate = "feature = \"postgres\"",
body = " // One dead-letter table serves the whole service, whatever the destination names.
sqlx::query(
\"WITH dead AS (DELETE FROM jobs WHERE id = $1 RETURNING *) \\
INSERT INTO dead_jobs SELECT * FROM dead\",
)
.bind(id)
.execute(conn)
.await?;
tracing::warn!(id, destination, \"a job's attempts are spent\");",
message = "`{Self}` runs its own `dead_letter` (`custom(dead_letter)` in `#[inbox(..)]`, or `own::DeadLetter` in its `InboxSpec`) and does not implement `DeadLetter<{DB}>`",
note = "implement `DeadLetter<{DB}>` for `{Self}`, or drop `dead_letter` from `custom(..)` (by hand: `.own::<own::DeadLetter>()` and `own::DeadLetter` in `type Table`)",
destination: &str = "\"jobs.dead\""
);
#[diagnostic::on_unimplemented(
message = "`{Self}` runs its own `extend` (`custom(extend)` in `#[inbox(..)]`, or `own::Extend` in its `InboxSpec`) and does not implement `Extend<{DB}>`",
label = "the service's own extension is missing",
note = "implement `Extend<{DB}>` for `{Self}`, or drop `extend` from `custom(..)` (by hand: `.own::<own::Extend>()` and `own::Extend` in `type Table`)"
)]
pub trait Extend<DB: Database>: LeaseRow {
fn extend(
conn: &mut DB::Connection,
id: &Self::Id,
held: &Self::Lease,
until: &Self::Lease,
) -> impl Future<Output = Result<bool, Error>> + Send;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` runs its own `lock` (`custom(lock)` in `#[inbox(..)]`, or `own::Lock` in its `InboxSpec`) and does not implement `Lock<{DB}>`",
label = "the service's own lock is missing",
note = "implement `Lock<{DB}>` for `{Self}`, or drop `lock` from `custom(..)` (by hand: `.own::<own::Lock>()` and `own::Lock` in `type Table`)"
)]
pub trait Lock<DB: Database>: InboxRow {
fn lock(
conn: &mut DB::Connection,
key: &str,
) -> impl Future<Output = Result<bool, Error>> + Send;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` runs its own `unlock` (`custom(unlock)` in `#[inbox(..)]`, or `own::Unlock` in its `InboxSpec`) and does not implement `Unlock<{DB}>`",
label = "the service's own unlock is missing",
note = "implement `Unlock<{DB}>` for `{Self}`, or drop `unlock` from `custom(..)` (by hand: `.own::<own::Unlock>()` and `own::Unlock` in `type Table`)"
)]
pub trait Unlock<DB: Database>: InboxRow {
fn unlock(
conn: &mut DB::Connection,
key: &str,
) -> impl Future<Output = Result<bool, Error>> + Send;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not implement `Publish<{DB}>`, so nothing writes a published message into its table",
label = "no `Publish` for this row",
note = "implement `Publish<{DB}>` for `{Self}`: its SQL lays the name, the bytes and the headers out in the table"
)]
pub trait Publish<DB: Database>: InboxRow {
fn publish(
conn: &mut DB::Connection,
message: &OutgoingMessage<'_>,
) -> impl Future<Output = Result<(), Error>> + Send;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` has no generated insert for `{Connection}`",
label = "no `Insert` for this connection",
note = "the derive generates it for the connections of the built-in dialects (features \
`postgres`, `mysql` and `sqlite`), unless a `#[sqlx(flatten)]` field hides columns \
from it: write that insert in the service"
)]
pub trait Insert<Connection>: Sync {
fn insert<'c>(
&'c self,
conn: &'c mut Connection,
) -> impl Future<Output = Result<(), Error>> + Send + 'c;
}