use std::any::type_name;
use std::future::{Future, ready};
use ruststream::HeaderMap;
use sqlx::{Arguments as _, Database, Encode, Error, FromRow, Type};
use super::database::{Defaults, OutboxDatabase, Statements, no_outbox_statement};
use super::events::{Ack, Discard, Fetch, Recover, Retry};
use super::spec::{Described, Headers, OutboxTable, Set, Unset, own};
use crate::{HeaderColumn, HeaderRow};
pub(super) type Declared<Record> = <<Record as OutboxTable>::Table as Described>::Declared;
#[doc(hidden)]
pub trait Slot {
const SET: bool;
}
impl Slot for Unset {
const SET: bool = false;
}
impl<Value> Slot for Set<Value> {
const SET: bool = true;
}
#[doc(hidden)]
pub trait HeadersOf<Record> {
fn take(record: &mut Record) -> HeaderMap;
}
impl<Record> HeadersOf<Record> for Unset {
#[inline]
fn take(_record: &mut Record) -> HeaderMap {
HeaderMap::new()
}
}
impl<Record: HeaderRow> HeadersOf<Record> for Set<Headers> {
#[inline]
fn take(record: &mut Record) -> HeaderMap {
record.headers_mut().take_headers()
}
}
fn statements<'d, DB: OutboxDatabase, Record>(
conn: &DB::Connection,
defaults: &'d Defaults,
event: &'static str,
) -> Result<&'d Statements, Error> {
DB::statements(conn, defaults).ok_or_else(|| no_outbox_statement(type_name::<Record>(), event))
}
fn id_arguments<'q, DB, Record>(id: &'q <Record as OutboxTable>::Id) -> Result<DB::Arguments, Error>
where
DB: Database,
Record: OutboxTable,
<Record as OutboxTable>::Id: Encode<'q, DB> + Type<DB>,
{
let mut arguments = DB::Arguments::default();
arguments.add(id).map_err(Error::Encode)?;
Ok(arguments)
}
#[doc(hidden)]
pub trait FetchBy<DB: Database, Record: OutboxTable> {
fn fetch<'c>(
conn: &'c mut DB::Connection,
id: &'c <Record as OutboxTable>::Id,
defaults: &'c Defaults,
) -> impl Future<Output = Result<Option<Record>, Error>> + Send + 'c;
}
impl<DB, Record> FetchBy<DB, Record> for Unset
where
DB: OutboxDatabase,
Record: OutboxTable + for<'r> FromRow<'r, DB::Row>,
for<'q> <Record as OutboxTable>::Id: Encode<'q, DB> + Type<DB>,
{
async fn fetch<'c>(
conn: &'c mut DB::Connection,
id: &'c <Record as OutboxTable>::Id,
defaults: &'c Defaults,
) -> Result<Option<Record>, Error> {
let sql = statements::<DB, Record>(conn, defaults, "fetch")?.fetch;
DB::fetch_optional(conn, sql, id_arguments::<DB, Record>(id)?).await
}
}
impl<DB: Database, Record: Fetch<DB> + OutboxTable> FetchBy<DB, Record> for Set<own::Fetch> {
fn fetch<'c>(
conn: &'c mut DB::Connection,
id: &'c <Record as OutboxTable>::Id,
_defaults: &'c Defaults,
) -> impl Future<Output = Result<Option<Record>, Error>> + Send + 'c {
Record::fetch(conn, id)
}
}
macro_rules! outcome_by {
(
$(#[$doc:meta])* $by:ident, $event:ident, $method:ident, $own:ident,
|$conn:ident, $id:ident, $defaults:ident| $default:block,
where $($bound:tt)*
) => {
$(#[$doc])*
#[doc(hidden)]
pub trait $by<DB: Database, Record: OutboxTable> {
fn $method<'c>(
conn: &'c mut DB::Connection,
id: &'c <Record as OutboxTable>::Id,
defaults: &'c Defaults,
) -> impl Future<Output = Result<(), Error>> + Send + 'c;
}
impl<DB, Record> $by<DB, Record> for Unset
where
$($bound)*
{
#[allow(clippy::manual_async_fn)]
fn $method<'c>(
$conn: &'c mut DB::Connection,
$id: &'c <Record as OutboxTable>::Id,
$defaults: &'c Defaults,
) -> impl Future<Output = Result<(), Error>> + Send + 'c {
$default
}
}
impl<DB: Database, Record: $event<DB> + OutboxTable> $by<DB, Record> for Set<own::$own> {
fn $method<'c>(
conn: &'c mut DB::Connection,
id: &'c <Record as OutboxTable>::Id,
_defaults: &'c Defaults,
) -> impl Future<Output = Result<(), Error>> + Send + 'c {
<Record as $event<DB>>::$method(conn, id)
}
}
};
}
outcome_by!(
AckBy, Ack, ack, Ack,
|conn, id, defaults| {
async move {
let sql = statements::<DB, Record>(conn, defaults, "ack")?.mark;
DB::execute(conn, sql, id_arguments::<DB, Record>(id)?).await
}
},
where
DB: OutboxDatabase,
Record: OutboxTable,
for<'q> <Record as OutboxTable>::Id: Encode<'q, DB> + Type<DB>,
);
outcome_by!(
DiscardBy, Discard, discard, Discard,
|conn, id, defaults| {
async move {
let sql = statements::<DB, Record>(conn, defaults, "discard")?.mark;
DB::execute(conn, sql, id_arguments::<DB, Record>(id)?).await
}
},
where
DB: OutboxDatabase,
Record: OutboxTable,
for<'q> <Record as OutboxTable>::Id: Encode<'q, DB> + Type<DB>,
);
outcome_by!(
RetryBy, Retry, retry, Retry,
|_conn, _id, _defaults| { ready(Ok(())) },
where
DB: Database,
Record: OutboxTable,
);
#[doc(hidden)]
pub trait RecoverBy<DB: Database, Record: OutboxTable> {
fn recover<'c>(
conn: &'c mut DB::Connection,
name: &'c str,
defaults: &'c Defaults,
) -> impl Future<Output = Result<Vec<Record>, Error>> + Send + 'c;
}
impl<DB, Record> RecoverBy<DB, Record> for Unset
where
DB: OutboxDatabase,
Record: OutboxTable + for<'r> FromRow<'r, DB::Row>,
{
async fn recover<'c>(
conn: &'c mut DB::Connection,
name: &'c str,
defaults: &'c Defaults,
) -> Result<Vec<Record>, Error> {
let sql = statements::<DB, Record>(conn, defaults, "recover")?.recover;
let mut arguments = DB::Arguments::default();
DB::bind_name(&mut arguments, name)?;
DB::fetch_all(conn, sql, arguments).await
}
}
impl<DB: Database, Record: Recover<DB> + OutboxTable> RecoverBy<DB, Record> for Set<own::Recover> {
fn recover<'c>(
conn: &'c mut DB::Connection,
name: &'c str,
_defaults: &'c Defaults,
) -> impl Future<Output = Result<Vec<Record>, Error>> + Send + 'c {
Record::recover(conn, name)
}
}