Skip to main content

eventuary_core/io/
writer.rs

1use std::future::Future;
2use std::sync::Arc;
3
4use futures::future::BoxFuture;
5
6use crate::error::Result;
7use crate::event::Event;
8use crate::payload::Payload;
9
10pub trait Writer<P = Payload>: Send + Sync
11where
12    P: Send + Sync,
13{
14    fn write<'a>(&'a self, event: &'a Event<P>) -> impl Future<Output = Result<()>> + Send + 'a;
15
16    fn write_all<'a>(
17        &'a self,
18        events: &'a [Event<P>],
19    ) -> impl Future<Output = Result<()>> + Send + 'a {
20        async move {
21            for event in events {
22                self.write(event).await?;
23            }
24            Ok(())
25        }
26    }
27}
28
29impl<T: Writer<P> + ?Sized, P: Send + Sync> Writer<P> for Arc<T> {
30    fn write<'a>(&'a self, event: &'a Event<P>) -> impl Future<Output = Result<()>> + Send + 'a {
31        (**self).write(event)
32    }
33
34    fn write_all<'a>(
35        &'a self,
36        events: &'a [Event<P>],
37    ) -> impl Future<Output = Result<()>> + Send + 'a {
38        (**self).write_all(events)
39    }
40}
41
42impl<T: Writer<P> + ?Sized, P: Send + Sync> Writer<P> for Box<T> {
43    fn write<'a>(&'a self, event: &'a Event<P>) -> impl Future<Output = Result<()>> + Send + 'a {
44        (**self).write(event)
45    }
46
47    fn write_all<'a>(
48        &'a self,
49        events: &'a [Event<P>],
50    ) -> impl Future<Output = Result<()>> + Send + 'a {
51        (**self).write_all(events)
52    }
53}
54
55pub trait DynWriter<P = Payload>: Send + Sync {
56    fn write_dyn<'a>(&'a self, event: &'a Event<P>) -> BoxFuture<'a, Result<()>>;
57    fn write_all_dyn<'a>(&'a self, events: &'a [Event<P>]) -> BoxFuture<'a, Result<()>>;
58}
59
60impl<T: Writer<P> + ?Sized, P: Send + Sync> DynWriter<P> for T {
61    fn write_dyn<'a>(&'a self, event: &'a Event<P>) -> BoxFuture<'a, Result<()>> {
62        Box::pin(<Self as Writer<P>>::write(self, event))
63    }
64    fn write_all_dyn<'a>(&'a self, events: &'a [Event<P>]) -> BoxFuture<'a, Result<()>> {
65        Box::pin(<Self as Writer<P>>::write_all(self, events))
66    }
67}
68
69pub type BoxWriter<P = Payload> = Box<dyn DynWriter<P>>;
70pub type ArcWriter<P = Payload> = Arc<dyn DynWriter<P>>;
71
72impl<P: Send + Sync> Writer<P> for dyn DynWriter<P> + '_ {
73    fn write<'a>(&'a self, event: &'a Event<P>) -> impl Future<Output = Result<()>> + Send + 'a {
74        DynWriter::write_dyn(self, event)
75    }
76    fn write_all<'a>(
77        &'a self,
78        events: &'a [Event<P>],
79    ) -> impl Future<Output = Result<()>> + Send + 'a {
80        DynWriter::write_all_dyn(self, events)
81    }
82}
83
84pub trait WriterExt<P = Payload>: Writer<P> + Sized + 'static
85where
86    P: Send + Sync,
87{
88    fn into_boxed(self) -> BoxWriter<P> {
89        Box::new(self)
90    }
91
92    fn into_arced(self) -> ArcWriter<P> {
93        Arc::new(self)
94    }
95}
96
97impl<T: Writer<P> + Sized + 'static, P: Send + Sync> WriterExt<P> for T {}
98
99pub mod batch;
100pub mod encode;
101pub mod fanout;
102pub mod filtered;
103pub mod flat_map;
104pub mod inspect;
105pub mod map;
106pub mod retry;
107pub mod timeout;
108
109pub use batch::{BatchWriter, BatchWriterConfig};
110pub use encode::{EncodeWriter, WriterTypedExt};
111pub use fanout::FanoutWriter;
112pub use filtered::FilteredWriter;
113pub use flat_map::{FlatMapWriter, TryFlatMapWriter};
114pub use inspect::{InspectWriter, InspectWriterHooks};
115pub use map::{MapWriter, TryMapWriter};
116pub use retry::{RetryWriter, RetryWriterConfig};
117pub use timeout::TimeoutWriter;
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    use std::sync::atomic::{AtomicUsize, Ordering};
124
125    struct CountingWriter {
126        writes: Arc<AtomicUsize>,
127    }
128
129    impl Writer for CountingWriter {
130        async fn write(&self, _: &Event) -> Result<()> {
131            self.writes.fetch_add(1, Ordering::SeqCst);
132            Ok(())
133        }
134    }
135
136    fn ev() -> Event {
137        Event::create(
138            "org",
139            "/x",
140            "thing.happened",
141            "thing-1",
142            crate::payload::Payload::from_string("p"),
143        )
144        .unwrap()
145    }
146
147    #[tokio::test]
148    async fn into_boxed_yields_dyn_writer() {
149        let writes = Arc::new(AtomicUsize::new(0));
150        let writer: BoxWriter = CountingWriter {
151            writes: Arc::clone(&writes),
152        }
153        .into_boxed();
154        writer.write(&ev()).await.unwrap();
155        writer.write_all(&[ev(), ev()]).await.unwrap();
156        assert_eq!(writes.load(Ordering::SeqCst), 3);
157    }
158
159    #[tokio::test]
160    async fn into_arced_yields_shared_writer() {
161        let writes = Arc::new(AtomicUsize::new(0));
162        let writer: ArcWriter = CountingWriter {
163            writes: Arc::clone(&writes),
164        }
165        .into_arced();
166        let clone = Arc::clone(&writer);
167        writer.write(&ev()).await.unwrap();
168        clone.write(&ev()).await.unwrap();
169        assert_eq!(writes.load(Ordering::SeqCst), 2);
170    }
171
172    #[tokio::test]
173    async fn box_blanket_passes_as_generic_writer() {
174        async fn take<W: Writer>(w: W, e: &Event) {
175            w.write(e).await.unwrap();
176        }
177        let writes = Arc::new(AtomicUsize::new(0));
178        let boxed: BoxWriter = CountingWriter {
179            writes: Arc::clone(&writes),
180        }
181        .into_boxed();
182        take(boxed, &ev()).await;
183        assert_eq!(writes.load(Ordering::SeqCst), 1);
184    }
185
186    #[tokio::test]
187    async fn arc_blanket_passes_as_generic_writer() {
188        async fn take<W: Writer>(w: W, e: &Event) {
189            w.write(e).await.unwrap();
190        }
191        let writes = Arc::new(AtomicUsize::new(0));
192        let arced: ArcWriter = CountingWriter {
193            writes: Arc::clone(&writes),
194        }
195        .into_arced();
196        take(arced, &ev()).await;
197        assert_eq!(writes.load(Ordering::SeqCst), 1);
198    }
199
200    #[derive(Debug, Clone, PartialEq, Eq)]
201    struct UserUpdated {
202        user_id: String,
203    }
204
205    fn typed_ev() -> Event<UserUpdated> {
206        Event::create(
207            "org",
208            "/users",
209            "user.updated",
210            "thing-1",
211            UserUpdated {
212                user_id: "u-1".to_owned(),
213            },
214        )
215        .unwrap()
216    }
217
218    struct TypedCountingWriter {
219        writes: Arc<AtomicUsize>,
220    }
221
222    impl Writer<UserUpdated> for TypedCountingWriter {
223        async fn write(&self, _: &Event<UserUpdated>) -> Result<()> {
224            self.writes.fetch_add(1, Ordering::SeqCst);
225            Ok(())
226        }
227    }
228
229    #[tokio::test]
230    async fn typed_writer_into_boxed_yields_dyn_writer() {
231        let writes = Arc::new(AtomicUsize::new(0));
232        let writer: BoxWriter<UserUpdated> = TypedCountingWriter {
233            writes: Arc::clone(&writes),
234        }
235        .into_boxed();
236
237        writer.write(&typed_ev()).await.unwrap();
238        assert_eq!(writes.load(Ordering::SeqCst), 1);
239    }
240}