ruststream_sqlx/outbox/registry/
tracked.rs1use std::future::Future;
4use std::marker::PhantomData;
5
6use ruststream::runtime::{Context, Handler, HandlerOutcome};
7use ruststream::{Bytes, OutgoingMessage, Publisher};
8use sqlx::Database;
9
10use super::list::{Nil, RecordList, RecordNames, Registered};
11use crate::outbox::error::OutboxError;
12use crate::outbox::store::Store;
13
14#[diagnostic::on_unimplemented(
68 message = "`{Self}` is not a name the outbox tracks",
69 label = "not a `TrackedName`",
70 note = "implement `TrackedName` for `{Self}` with the name as `NAME`, or register the name as \
71 a string with `register`"
72)]
73pub trait TrackedName: 'static {
74 const NAME: &'static str;
76}
77
78#[derive(Debug)]
81pub struct Checked<Name, Rest>(pub(super) Rest, pub(super) PhantomData<fn() -> Name>);
82
83impl<Name, Rest: Clone> Clone for Checked<Name, Rest> {
84 fn clone(&self) -> Self {
85 Self(self.0.clone(), PhantomData)
86 }
87}
88
89impl<Name, Rest: Copy> Copy for Checked<Name, Rest> {}
90
91#[doc(hidden)]
93pub trait Lacks<Name: TrackedName> {
94 const LACKS: bool;
96}
97
98impl<Name: TrackedName> Lacks<Name> for Nil {
99 const LACKS: bool = true;
100}
101
102impl<Name: TrackedName, Record, Rest: Lacks<Name>> Lacks<Name> for Registered<Record, Rest> {
103 const LACKS: bool = <Rest as Lacks<Name>>::LACKS;
104}
105
106impl<Name: TrackedName, Earlier: TrackedName, Rest: Lacks<Name>> Lacks<Name>
107 for Checked<Earlier, Rest>
108{
109 const LACKS: bool = !same(Earlier::NAME, Name::NAME) && <Rest as Lacks<Name>>::LACKS;
110}
111
112const fn same(one: &str, other: &str) -> bool {
114 let (one, other) = (one.as_bytes(), other.as_bytes());
115 if one.len() != other.len() {
116 return false;
117 }
118 let mut index = 0;
119 while index < one.len() {
120 if one[index] != other[index] {
121 return false;
122 }
123 index += 1;
124 }
125 true
126}
127
128impl<Name: 'static, Rest: RecordNames> RecordNames for Checked<Name, Rest> {
129 #[inline]
130 fn contains(&self, name: &str) -> bool {
131 self.0.contains(name)
132 }
133}
134
135impl<DB: Database, Name: 'static, Rest: RecordList<DB>> RecordList<DB> for Checked<Name, Rest> {
136 fn record<'a>(
137 &'a self,
138 pool: &'a Store<DB>,
139 msg: &'a OutgoingMessage<'_>,
140 ) -> impl Future<Output = Option<Result<Bytes, OutboxError>>> + Send + 'a {
141 self.0.record(pool, msg)
142 }
143
144 fn deliver<'a, M, C, S, H>(
145 &'a self,
146 pool: &'a Store<DB>,
147 handler: &'a H,
148 msg: &'a M,
149 ctx: &'a mut Context<'_, C, S>,
150 ) -> impl Future<Output = HandlerOutcome> + Send + 'a
151 where
152 M: Sync,
153 C: Send,
154 S: Send + Sync,
155 H: Handler<M, C, S>,
156 {
157 self.0.deliver(pool, handler, msg, ctx)
158 }
159
160 fn republish<'a, Live: Publisher>(
161 &'a self,
162 pool: &'a Store<DB>,
163 publisher: &'a Live,
164 only: Option<&'a [&'static str]>,
165 ) -> impl Future<Output = Result<(), OutboxError>> + Send + 'a {
166 self.0.republish(pool, publisher, only)
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::{Checked, Lacks, Nil, PhantomData, RecordNames, Registered, TrackedName};
173 use crate::outbox::registry::list::tests::node;
174
175 struct Orders;
176
177 impl TrackedName for Orders {
178 const NAME: &'static str = "orders";
179 }
180
181 struct Order;
182
183 impl TrackedName for Order {
184 const NAME: &'static str = "order";
185 }
186
187 struct Refunds;
188
189 impl TrackedName for Refunds {
190 const NAME: &'static str = "refunds";
191 }
192
193 type Tracked = Registered<(), Checked<Refunds, Registered<(), Checked<Orders, Nil>>>>;
194
195 #[test]
196 fn a_name_tracked_by_type_is_found_by_its_text_alone() {
197 const {
198 assert!(!<Tracked as Lacks<Orders>>::LACKS);
199 assert!(!<Tracked as Lacks<Refunds>>::LACKS);
200 assert!(<Tracked as Lacks<Order>>::LACKS);
201 assert!(<Nil as Lacks<Orders>>::LACKS);
202 }
203 let names = node(
204 "refunds",
205 Checked(node("orders", Nil), PhantomData::<fn() -> Refunds>),
206 );
207 assert!(names.contains("orders"));
208 assert!(names.contains("refunds"));
209 assert!(!names.contains("order"));
210 }
211}