use std::any::type_name;
use ruststream::OutgoingMessage;
use ruststream_sqlx_dialect::TableSpec;
#[cfg(feature = "testing")]
use sqlx::Pool;
use super::broker::Shared;
use super::database::QueueDatabase;
#[cfg(feature = "testing")]
use super::database::notify::Listening;
use super::database::notify::announce;
use super::engine::Events;
use super::error::SqlxBrokerError;
use super::events::Publish;
#[cfg(feature = "testing")]
use super::testing::{cancelled, off_clock};
use crate::home;
mod repository;
mod routed;
mod routes;
mod wake;
pub use repository::{Repository, RepositoryPublisher};
pub use routed::{Routed, RoutedPublisher};
use routes::Route;
pub(crate) use routes::Routes;
pub(crate) use wake::{TableWake, Wakes};
pub(crate) fn table_of(spec: &TableSpec<'_>) -> String {
spec.schema().map_or_else(
|| spec.table().to_owned(),
|schema| format!("{schema}.{}", spec.table()),
)
}
fn failed(
message: &OutgoingMessage<'_>,
table: &TableSpec<'_>,
row: &'static str,
source: sqlx::Error,
) -> SqlxBrokerError {
SqlxBrokerError::Publish {
name: message.name().to_owned(),
table: table_of(table),
row,
source: Box::new(source),
}
}
fn fits<DB, Row>(message: &OutgoingMessage<'_>) -> Result<(), SqlxBrokerError>
where
DB: QueueDatabase,
Row: Publish<DB> + Events<DB>,
{
Row::unfit_header(message.headers()).map_or(Ok(()), |header| {
Err(SqlxBrokerError::Header {
name: message.name().to_owned(),
table: table_of(&Row::SPEC),
row: type_name::<Row>(),
header: header.to_owned(),
})
})
}
async fn insert<DB, Row>(
shared: &Shared<DB>,
wake: &'static TableWake,
message: &OutgoingMessage<'_>,
) -> Result<(), SqlxBrokerError>
where
DB: QueueDatabase,
Row: Publish<DB> + Events<DB>,
{
let failed = |source| failed(message, &Row::SPEC, type_name::<Row>(), source);
if shared.is_closed() {
return Err(SqlxBrokerError::Closed);
}
fits::<DB, Row>(message)?;
#[cfg(feature = "testing")]
if shared.harness.in_process() {
let notify = shared.listening.as_ref().map(Listening::notify);
return publish_off_clock::<DB, Row>(&shared.pool, notify, wake, message)
.await
.map_err(failed);
}
let mut conn = home::acquire(&shared.pool, &shared.runtime)
.await
.map_err(failed)?;
Row::publish(&mut conn, message).await.map_err(failed)?;
if let Some(listening) = &shared.listening {
announce::<DB>(listening.notify(), &mut conn, wake, message.name()).await;
}
Ok(())
}
async fn publish_row<DB, Row>(
conn: &mut DB::Connection,
message: &OutgoingMessage<'_>,
) -> Result<(), SqlxBrokerError>
where
DB: QueueDatabase,
Row: Publish<DB> + Events<DB>,
{
fits::<DB, Row>(message)?;
Row::publish(conn, message)
.await
.map_err(|source| failed(message, &Row::SPEC, type_name::<Row>(), source))
}
pub(crate) async fn insert_routed<DB: QueueDatabase>(
shared: &Shared<DB>,
route: &dyn Route<DB>,
wake: &'static TableWake,
message: &OutgoingMessage<'_>,
) -> Result<(), SqlxBrokerError> {
if shared.is_closed() {
return Err(SqlxBrokerError::Closed);
}
#[cfg(feature = "testing")]
if shared.harness.in_process() {
route.insert_in_process(shared, wake, message).await?;
wake.wake(message.name());
return Ok(());
}
let mut conn = home::acquire(&shared.pool, &shared.runtime)
.await
.map_err(|source| {
let description = route.description();
failed(message, &description.spec, description.row, source)
})?;
route.insert(&mut conn, message).await?;
if let Some(listening) = &shared.listening {
announce::<DB>(listening.notify(), &mut conn, wake, message.name()).await;
}
wake.wake(message.name());
Ok(())
}
#[cfg(feature = "testing")]
async fn publish_off_clock<DB, Row>(
pool: &Pool<DB>,
notify: Option<&'static str>,
wake: &'static TableWake,
message: &OutgoingMessage<'_>,
) -> Result<(), sqlx::Error>
where
DB: QueueDatabase,
Row: Publish<DB>,
{
let pool = pool.clone();
let name = message.name().to_owned();
let payload = message.payload().to_vec();
let headers = message.headers().clone();
off_clock(async move {
let mut conn = pool.acquire().await?;
let message = OutgoingMessage::new(&name, &payload).with_headers(headers);
Row::publish(&mut conn, &message).await?;
if let Some(notify) = notify {
announce::<DB>(notify, &mut conn, wake, &name).await;
}
Ok(())
})
.await
.unwrap_or_else(|| Err(cancelled()))
}
async fn write<DB, Row>(
shared: &Shared<DB>,
wake: &'static TableWake,
message: &OutgoingMessage<'_>,
) -> Result<(), SqlxBrokerError>
where
DB: QueueDatabase,
Row: Publish<DB> + Events<DB>,
{
#[cfg(feature = "testing")]
shared.harness.expect(message.name());
let inserted = insert::<DB, Row>(shared, wake, message).await;
#[cfg(feature = "testing")]
match &inserted {
Ok(()) => shared.harness.published(message),
Err(_) => shared.harness.refused(message.name()),
}
if inserted.is_ok() {
wake.wake(message.name());
}
inserted
}