use std::future::Future;
use std::marker::PhantomData;
use ruststream::runtime::{Context, Handler, HandlerOutcome};
use ruststream::{Bytes, OutgoingMessage, Publisher};
use sqlx::Database;
use super::list::{Nil, RecordList, RecordNames, Registered};
use crate::outbox::error::OutboxError;
use crate::outbox::store::Store;
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a name the outbox tracks",
label = "not a `TrackedName`",
note = "implement `TrackedName` for `{Self}` with the name as `NAME`, or register the name as \
a string with `register`"
)]
pub trait TrackedName: 'static {
const NAME: &'static str;
}
#[derive(Debug)]
pub struct Checked<Name, Rest>(pub(super) Rest, pub(super) PhantomData<fn() -> Name>);
impl<Name, Rest: Clone> Clone for Checked<Name, Rest> {
fn clone(&self) -> Self {
Self(self.0.clone(), PhantomData)
}
}
impl<Name, Rest: Copy> Copy for Checked<Name, Rest> {}
#[doc(hidden)]
pub trait Lacks<Name: TrackedName> {
const LACKS: bool;
}
impl<Name: TrackedName> Lacks<Name> for Nil {
const LACKS: bool = true;
}
impl<Name: TrackedName, Record, Rest: Lacks<Name>> Lacks<Name> for Registered<Record, Rest> {
const LACKS: bool = <Rest as Lacks<Name>>::LACKS;
}
impl<Name: TrackedName, Earlier: TrackedName, Rest: Lacks<Name>> Lacks<Name>
for Checked<Earlier, Rest>
{
const LACKS: bool = !same(Earlier::NAME, Name::NAME) && <Rest as Lacks<Name>>::LACKS;
}
const fn same(one: &str, other: &str) -> bool {
let (one, other) = (one.as_bytes(), other.as_bytes());
if one.len() != other.len() {
return false;
}
let mut index = 0;
while index < one.len() {
if one[index] != other[index] {
return false;
}
index += 1;
}
true
}
impl<Name: 'static, Rest: RecordNames> RecordNames for Checked<Name, Rest> {
#[inline]
fn contains(&self, name: &str) -> bool {
self.0.contains(name)
}
}
impl<DB: Database, Name: 'static, Rest: RecordList<DB>> RecordList<DB> for Checked<Name, Rest> {
fn record<'a>(
&'a self,
pool: &'a Store<DB>,
msg: &'a OutgoingMessage<'_>,
) -> impl Future<Output = Option<Result<Bytes, OutboxError>>> + Send + 'a {
self.0.record(pool, msg)
}
fn deliver<'a, M, C, S, H>(
&'a self,
pool: &'a Store<DB>,
handler: &'a H,
msg: &'a M,
ctx: &'a mut Context<'_, C, S>,
) -> impl Future<Output = HandlerOutcome> + Send + 'a
where
M: Sync,
C: Send,
S: Send + Sync,
H: Handler<M, C, S>,
{
self.0.deliver(pool, handler, msg, ctx)
}
fn republish<'a, Live: Publisher>(
&'a self,
pool: &'a Store<DB>,
publisher: &'a Live,
only: Option<&'a [&'static str]>,
) -> impl Future<Output = Result<(), OutboxError>> + Send + 'a {
self.0.republish(pool, publisher, only)
}
}
#[cfg(test)]
mod tests {
use super::{Checked, Lacks, Nil, PhantomData, RecordNames, Registered, TrackedName};
use crate::outbox::registry::list::tests::node;
struct Orders;
impl TrackedName for Orders {
const NAME: &'static str = "orders";
}
struct Order;
impl TrackedName for Order {
const NAME: &'static str = "order";
}
struct Refunds;
impl TrackedName for Refunds {
const NAME: &'static str = "refunds";
}
type Tracked = Registered<(), Checked<Refunds, Registered<(), Checked<Orders, Nil>>>>;
#[test]
fn a_name_tracked_by_type_is_found_by_its_text_alone() {
const {
assert!(!<Tracked as Lacks<Orders>>::LACKS);
assert!(!<Tracked as Lacks<Refunds>>::LACKS);
assert!(<Tracked as Lacks<Order>>::LACKS);
assert!(<Nil as Lacks<Orders>>::LACKS);
}
let names = node(
"refunds",
Checked(node("orders", Nil), PhantomData::<fn() -> Refunds>),
);
assert!(names.contains("orders"));
assert!(names.contains("refunds"));
assert!(!names.contains("order"));
}
}