ruststream_sqlx/outbox/
republish.rs1use std::any::type_name;
5use std::fmt;
6use std::future::Future;
7use std::pin::Pin;
8use std::task::{Context, Poll};
9
10use ruststream::{OutgoingMessage, PayloadForm, Publisher};
11use sqlx::Database;
12
13use super::OUTBOX_ID_HEADER;
14use super::database::Defaults;
15use super::error::OutboxError;
16use super::events::Tracked;
17use super::publish::id_value;
18use super::switch::enabled;
19use crate::outbox::store::Store;
20
21#[must_use = "a republish does nothing until it is awaited"]
25pub struct Republishing(
26 Pin<Box<dyn Future<Output = Result<(), OutboxError>> + Send>>,
29);
30
31impl Republishing {
32 pub(super) fn new(
33 body: impl Future<Output = Result<(), OutboxError>> + Send + 'static,
34 ) -> Self {
35 Self(Box::pin(async move {
36 if enabled() { body.await } else { Ok(()) }
37 }))
38 }
39}
40
41impl Future for Republishing {
42 type Output = Result<(), OutboxError>;
43
44 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
45 self.get_mut().0.as_mut().poll(cx)
46 }
47}
48
49impl fmt::Debug for Republishing {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 f.debug_struct("Republishing").finish_non_exhaustive()
52 }
53}
54
55pub(super) async fn recover_and_publish<DB, Record, Live>(
57 name: &'static str,
58 defaults: &Defaults,
59 pool: &Store<DB>,
60 publisher: &Live,
61) -> Result<(), OutboxError>
62where
63 DB: Database,
64 Record: Tracked<DB>,
65 Live: Publisher,
66{
67 let pool = pool.get().ok_or(OutboxError::NoPool { name })?;
68 let recovered = async {
69 let mut conn = pool.acquire().await?;
70 Record::recover_records(&mut conn, name, defaults).await
71 }
72 .await
73 .map_err(|source| OutboxError::Recover {
74 name,
75 record: type_name::<Record>(),
76 source,
77 })?;
78 for mut record in recovered {
79 let mut headers = record.take_headers();
80 headers.insert(OUTBOX_ID_HEADER, id_value(record.id()));
81 let payload = <Live::Payload as PayloadForm>::Form::from(record.payload());
82 let msg = OutgoingMessage::with_payload(record.name(), payload).with_headers(headers);
83 if let Err(source) = publisher.publish(msg, None).await {
84 return Err(OutboxError::Republish {
85 name,
86 record: type_name::<Record>(),
87 id: record.id().to_string(),
88 source: Box::new(source),
89 });
90 }
91 }
92 Ok(())
93}