use std::fmt;
use std::marker::PhantomData;
use std::sync::Arc;
use ruststream::Publisher;
use sqlx::{Database, Pool};
use super::database::OutboxDatabase;
use super::error::PoolAlreadySet;
use super::layer::TrackingLayer;
use super::publish::TrackingPublishLayer;
use super::republish::Republishing;
use super::spec::OutboxTable;
use super::wrap::TrackedPublisher;
use crate::outbox::store::Store;
mod list;
mod tracked;
use list::defaults;
pub use list::{Nil, RecordList, RecordNames, Registered};
pub use tracked::{Checked, Lacks, TrackedName};
pub struct Outbox<DB: Database, Records = Nil> {
store: Arc<Store<DB>>,
records: Records,
}
impl<DB: Database, Records: Copy> Clone for Outbox<DB, Records> {
fn clone(&self) -> Self {
Self {
store: Arc::clone(&self.store),
records: self.records,
}
}
}
impl<DB: Database, Records: fmt::Debug> fmt::Debug for Outbox<DB, Records> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Outbox")
.field("records", &self.records)
.field("pool_set", &self.store.get().is_some())
.finish()
}
}
impl<DB: Database> Outbox<DB, Nil> {
#[must_use]
pub fn new(pool: Pool<DB>) -> Self {
Self {
store: Arc::new(Store::from(pool)),
records: Nil,
}
}
#[must_use]
pub fn deferred() -> Self {
Self {
store: Arc::new(Store::default()),
records: Nil,
}
}
}
impl<DB: Database, Records: RecordNames> Outbox<DB, Records> {
#[must_use]
#[track_caller]
pub fn register<Record: OutboxTable>(
self,
name: &'static str,
) -> Outbox<DB, Registered<Record, Records>>
where
DB: OutboxDatabase,
Registered<Record, Records>: RecordList<DB>,
{
assert!(
!self.records.contains(name),
"`{name}` is registered with the outbox twice; register each name once"
);
Outbox {
store: self.store,
records: Registered {
name,
defaults: defaults::<DB, Record>(),
rest: self.records,
record: PhantomData,
},
}
}
#[must_use]
#[track_caller]
pub fn track<Record: OutboxTable, Name: TrackedName>(
self,
) -> Outbox<DB, Registered<Record, Checked<Name, Records>>>
where
DB: OutboxDatabase,
Records: Lacks<Name>,
Registered<Record, Checked<Name, Records>>: RecordList<DB>,
{
const {
assert!(
<Records as Lacks<Name>>::LACKS,
"a name is tracked with the outbox twice: track each name once"
);
}
assert!(
!self.records.contains(Name::NAME),
"`{}` is registered with the outbox twice; register each name once",
Name::NAME
);
Outbox {
store: self.store,
records: Registered {
name: Name::NAME,
defaults: defaults::<DB, Record>(),
rest: Checked(self.records, PhantomData),
record: PhantomData,
},
}
}
pub fn set_pool(&self, pool: Pool<DB>) -> Result<(), PoolAlreadySet> {
self.store.set(pool).map_err(|_| PoolAlreadySet)
}
}
impl<DB: Database, Records: RecordList<DB>> Outbox<DB, Records> {
#[must_use]
pub fn layer(&self) -> TrackingLayer<DB, Records> {
TrackingLayer::new(Arc::clone(&self.store), self.records)
}
#[must_use]
pub fn publish_layer(&self) -> TrackingPublishLayer<DB, Records> {
TrackingPublishLayer::new(Arc::clone(&self.store), self.records)
}
pub fn republish<Live: Publisher + 'static>(
&self,
) -> impl FnOnce(Live) -> Republishing + Send + 'static {
self.republishing(None)
}
#[track_caller]
pub fn republish_names<Live: Publisher + 'static>(
&self,
names: impl IntoIterator<Item = &'static str>,
) -> impl FnOnce(Live) -> Republishing + Send + 'static {
let names: Vec<&'static str> = names.into_iter().collect();
for name in &names {
assert!(
self.records.contains(name),
"`{name}` is not registered with the outbox, so it has nothing to republish"
);
}
self.republishing(Some(names))
}
fn republishing<Live: Publisher + 'static>(
&self,
only: Option<Vec<&'static str>>,
) -> impl FnOnce(Live) -> Republishing + Send + 'static {
let pool = Arc::clone(&self.store);
let records = self.records;
move |publisher: Live| {
Republishing::new(
async move { records.republish(&pool, &publisher, only.as_deref()).await },
)
}
}
#[must_use]
pub fn wrap<Live: Publisher>(&self, publisher: Live) -> TrackedPublisher<Live, DB, Records> {
TrackedPublisher::new(publisher, Arc::clone(&self.store), self.records)
}
}