use std::future::Future;
use ruststream::OutgoingMessage;
use sqlx::{Database, Error};
use super::OutboxRow;
use super::database::Defaults;
use super::dispatch::{AckBy, Declared, DiscardBy, FetchBy, HeadersOf, RecoverBy, RetryBy};
use super::spec::{Declaration, OutboxTable};
#[diagnostic::on_unimplemented(
message = "`{Self}` does not implement `outbox::Publish<{DB}>`, so nothing records what is published under its names",
label = "no `outbox::Publish` for this record",
note = "implement `outbox::Publish<{DB}>` for `{Self}`: its statement inserts the record and returns the id"
)]
pub trait Publish<DB: Database>: OutboxRow {
fn publish(
conn: &mut DB::Connection,
msg: &OutgoingMessage<'_>,
) -> impl Future<Output = Result<Self::Id, Error>> + Send;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` names its own fetch and does not implement `outbox::Fetch<{DB}>`",
label = "the service's own fetch is missing",
note = "implement `outbox::Fetch<{DB}>` for `{Self}`, or drop `fetch` from `#[outbox(custom(..))]` (or `.own::<own::Fetch>()` from its `OutboxSpec`)"
)]
pub trait Fetch<DB: Database>: OutboxRow {
fn fetch(
conn: &mut DB::Connection,
id: &Self::Id,
) -> impl Future<Output = Result<Option<Self>, Error>> + Send;
}
macro_rules! outcome_event {
(
$(#[$doc:meta])* $trait:ident, $method:ident, $event:literal,
message = $message:literal, note = $note:literal
) => {
$(#[$doc])*
#[diagnostic::on_unimplemented(
message = $message,
label = "the service's own event is missing",
note = $note
)]
pub trait $trait<DB: Database>: OutboxRow {
#[doc = concat!("Runs the `", $event, "` event for the record `id` after its handler finished.")]
#[doc = ""]
#[doc = "# Errors"]
#[doc = ""]
#[doc = "The database's error; the record stays unprocessed, and the next startup publishes it again."]
fn $method(
conn: &mut DB::Connection,
id: &Self::Id,
) -> impl Future<Output = Result<(), Error>> + Send;
}
};
}
outcome_event!(
Ack, ack, "ack",
message = "`{Self}` names its own ack and does not implement `outbox::Ack<{DB}>`",
note = "implement `outbox::Ack<{DB}>` for `{Self}`, or drop `ack` from `#[outbox(custom(..))]` (or `.own::<own::Ack>()` from its `OutboxSpec`)"
);
outcome_event!(
Retry, retry, "retry",
message = "`{Self}` names its own retry and does not implement `outbox::Retry<{DB}>`",
note = "implement `outbox::Retry<{DB}>` for `{Self}`, or drop `retry` from `#[outbox(custom(..))]` (or `.own::<own::Retry>()` from its `OutboxSpec`)"
);
outcome_event!(
Discard, discard, "discard",
message = "`{Self}` names its own discard and does not implement `outbox::Discard<{DB}>`",
note = "implement `outbox::Discard<{DB}>` for `{Self}`, or drop `discard` from `#[outbox(custom(..))]` (or `.own::<own::Discard>()` from its `OutboxSpec`)"
);
#[diagnostic::on_unimplemented(
message = "`{Self}` names its own recovery and does not implement `outbox::Recover<{DB}>`",
label = "the service's own recovery is missing",
note = "implement `outbox::Recover<{DB}>` for `{Self}`, or drop `recover` from `#[outbox(custom(..))]` (or `.own::<own::Recover>()` from its `OutboxSpec`)"
)]
pub trait Recover<DB: Database>: OutboxRow {
fn recover(
conn: &mut DB::Connection,
name: &str,
) -> impl Future<Output = Result<Vec<Self>, Error>> + Send;
}
#[doc(hidden)]
pub trait Tracked<DB: Database>: Publish<DB> {
fn take_headers(&mut self) -> ruststream::HeaderMap;
fn fetch_record<'c>(
conn: &'c mut DB::Connection,
id: &'c Self::Id,
defaults: &'c Defaults,
) -> impl Future<Output = Result<Option<Self>, Error>> + Send + 'c;
fn ack_record<'c>(
conn: &'c mut DB::Connection,
id: &'c Self::Id,
defaults: &'c Defaults,
) -> impl Future<Output = Result<(), Error>> + Send + 'c;
fn retry_record<'c>(
conn: &'c mut DB::Connection,
id: &'c Self::Id,
defaults: &'c Defaults,
) -> impl Future<Output = Result<(), Error>> + Send + 'c;
fn discard_record<'c>(
conn: &'c mut DB::Connection,
id: &'c Self::Id,
defaults: &'c Defaults,
) -> impl Future<Output = Result<(), Error>> + Send + 'c;
fn recover_records<'c>(
conn: &'c mut DB::Connection,
name: &'c str,
defaults: &'c Defaults,
) -> impl Future<Output = Result<Vec<Self>, Error>> + Send + 'c;
}
impl<DB, Record> Tracked<DB> for Record
where
DB: Database,
Record: OutboxTable + Publish<DB>,
<Declared<Record> as Declaration>::Headers: HeadersOf<Record>,
<Declared<Record> as Declaration>::OwnFetch: FetchBy<DB, Record>,
<Declared<Record> as Declaration>::OwnAck: AckBy<DB, Record>,
<Declared<Record> as Declaration>::OwnRetry: RetryBy<DB, Record>,
<Declared<Record> as Declaration>::OwnDiscard: DiscardBy<DB, Record>,
<Declared<Record> as Declaration>::OwnRecover: RecoverBy<DB, Record>,
{
#[inline]
fn take_headers(&mut self) -> ruststream::HeaderMap {
<<Declared<Record> as Declaration>::Headers as HeadersOf<Record>>::take(self)
}
fn fetch_record<'c>(
conn: &'c mut DB::Connection,
id: &'c <Record as OutboxTable>::Id,
defaults: &'c Defaults,
) -> impl Future<Output = Result<Option<Self>, Error>> + Send + 'c {
<<Declared<Record> as Declaration>::OwnFetch as FetchBy<DB, Record>>::fetch(
conn, id, defaults,
)
}
fn ack_record<'c>(
conn: &'c mut DB::Connection,
id: &'c <Record as OutboxTable>::Id,
defaults: &'c Defaults,
) -> impl Future<Output = Result<(), Error>> + Send + 'c {
<<Declared<Record> as Declaration>::OwnAck as AckBy<DB, Record>>::ack(conn, id, defaults)
}
fn retry_record<'c>(
conn: &'c mut DB::Connection,
id: &'c <Record as OutboxTable>::Id,
defaults: &'c Defaults,
) -> impl Future<Output = Result<(), Error>> + Send + 'c {
<<Declared<Record> as Declaration>::OwnRetry as RetryBy<DB, Record>>::retry(
conn, id, defaults,
)
}
fn discard_record<'c>(
conn: &'c mut DB::Connection,
id: &'c <Record as OutboxTable>::Id,
defaults: &'c Defaults,
) -> impl Future<Output = Result<(), Error>> + Send + 'c {
<<Declared<Record> as Declaration>::OwnDiscard as DiscardBy<DB, Record>>::discard(
conn, id, defaults,
)
}
fn recover_records<'c>(
conn: &'c mut DB::Connection,
name: &'c str,
defaults: &'c Defaults,
) -> impl Future<Output = Result<Vec<Self>, Error>> + Send + 'c {
<<Declared<Record> as Declaration>::OwnRecover as RecoverBy<DB, Record>>::recover(
conn, name, defaults,
)
}
}