eventuary-core 0.1.0

Core event model and async IO traits for eventuary
Documentation
use std::future::Future;
use std::sync::Arc;

use futures::future::BoxFuture;

use crate::error::Result;
use crate::event::Event;
use crate::payload::Payload;

pub trait Writer<P = Payload>: Send + Sync
where
    P: Send + Sync,
{
    fn write<'a>(&'a self, event: &'a Event<P>) -> impl Future<Output = Result<()>> + Send + 'a;

    fn write_all<'a>(
        &'a self,
        events: &'a [Event<P>],
    ) -> impl Future<Output = Result<()>> + Send + 'a {
        async move {
            for event in events {
                self.write(event).await?;
            }
            Ok(())
        }
    }
}

impl<T: Writer<P> + ?Sized, P: Send + Sync> Writer<P> for Arc<T> {
    fn write<'a>(&'a self, event: &'a Event<P>) -> impl Future<Output = Result<()>> + Send + 'a {
        (**self).write(event)
    }

    fn write_all<'a>(
        &'a self,
        events: &'a [Event<P>],
    ) -> impl Future<Output = Result<()>> + Send + 'a {
        (**self).write_all(events)
    }
}

impl<T: Writer<P> + ?Sized, P: Send + Sync> Writer<P> for Box<T> {
    fn write<'a>(&'a self, event: &'a Event<P>) -> impl Future<Output = Result<()>> + Send + 'a {
        (**self).write(event)
    }

    fn write_all<'a>(
        &'a self,
        events: &'a [Event<P>],
    ) -> impl Future<Output = Result<()>> + Send + 'a {
        (**self).write_all(events)
    }
}

pub trait DynWriter<P = Payload>: Send + Sync {
    fn write_dyn<'a>(&'a self, event: &'a Event<P>) -> BoxFuture<'a, Result<()>>;
    fn write_all_dyn<'a>(&'a self, events: &'a [Event<P>]) -> BoxFuture<'a, Result<()>>;
}

impl<T: Writer<P> + ?Sized, P: Send + Sync> DynWriter<P> for T {
    fn write_dyn<'a>(&'a self, event: &'a Event<P>) -> BoxFuture<'a, Result<()>> {
        Box::pin(<Self as Writer<P>>::write(self, event))
    }
    fn write_all_dyn<'a>(&'a self, events: &'a [Event<P>]) -> BoxFuture<'a, Result<()>> {
        Box::pin(<Self as Writer<P>>::write_all(self, events))
    }
}

pub type BoxWriter<P = Payload> = Box<dyn DynWriter<P>>;
pub type ArcWriter<P = Payload> = Arc<dyn DynWriter<P>>;

impl<P: Send + Sync> Writer<P> for dyn DynWriter<P> + '_ {
    fn write<'a>(&'a self, event: &'a Event<P>) -> impl Future<Output = Result<()>> + Send + 'a {
        DynWriter::write_dyn(self, event)
    }
    fn write_all<'a>(
        &'a self,
        events: &'a [Event<P>],
    ) -> impl Future<Output = Result<()>> + Send + 'a {
        DynWriter::write_all_dyn(self, events)
    }
}

pub trait WriterExt<P = Payload>: Writer<P> + Sized + 'static
where
    P: Send + Sync,
{
    fn into_boxed(self) -> BoxWriter<P> {
        Box::new(self)
    }

    fn into_arced(self) -> ArcWriter<P> {
        Arc::new(self)
    }
}

impl<T: Writer<P> + Sized + 'static, P: Send + Sync> WriterExt<P> for T {}

pub mod batch;
pub mod encode;
pub mod fanout;
pub mod filtered;
pub mod flat_map;
pub mod inspect;
pub mod map;
pub mod retry;
pub mod timeout;

pub use batch::{BatchWriter, BatchWriterConfig};
pub use encode::{EncodeWriter, WriterTypedExt};
pub use fanout::FanoutWriter;
pub use filtered::FilteredWriter;
pub use flat_map::{FlatMapWriter, TryFlatMapWriter};
pub use inspect::{InspectWriter, InspectWriterHooks};
pub use map::{MapWriter, TryMapWriter};
pub use retry::{RetryWriter, RetryWriterConfig};
pub use timeout::TimeoutWriter;

#[cfg(test)]
mod tests {
    use super::*;

    use std::sync::atomic::{AtomicUsize, Ordering};

    struct CountingWriter {
        writes: Arc<AtomicUsize>,
    }

    impl Writer for CountingWriter {
        async fn write(&self, _: &Event) -> Result<()> {
            self.writes.fetch_add(1, Ordering::SeqCst);
            Ok(())
        }
    }

    fn ev() -> Event {
        Event::create(
            "org",
            "/x",
            "thing.happened",
            "thing-1",
            crate::payload::Payload::from_string("p"),
        )
        .unwrap()
    }

    #[tokio::test]
    async fn into_boxed_yields_dyn_writer() {
        let writes = Arc::new(AtomicUsize::new(0));
        let writer: BoxWriter = CountingWriter {
            writes: Arc::clone(&writes),
        }
        .into_boxed();
        writer.write(&ev()).await.unwrap();
        writer.write_all(&[ev(), ev()]).await.unwrap();
        assert_eq!(writes.load(Ordering::SeqCst), 3);
    }

    #[tokio::test]
    async fn into_arced_yields_shared_writer() {
        let writes = Arc::new(AtomicUsize::new(0));
        let writer: ArcWriter = CountingWriter {
            writes: Arc::clone(&writes),
        }
        .into_arced();
        let clone = Arc::clone(&writer);
        writer.write(&ev()).await.unwrap();
        clone.write(&ev()).await.unwrap();
        assert_eq!(writes.load(Ordering::SeqCst), 2);
    }

    #[tokio::test]
    async fn box_blanket_passes_as_generic_writer() {
        async fn take<W: Writer>(w: W, e: &Event) {
            w.write(e).await.unwrap();
        }
        let writes = Arc::new(AtomicUsize::new(0));
        let boxed: BoxWriter = CountingWriter {
            writes: Arc::clone(&writes),
        }
        .into_boxed();
        take(boxed, &ev()).await;
        assert_eq!(writes.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn arc_blanket_passes_as_generic_writer() {
        async fn take<W: Writer>(w: W, e: &Event) {
            w.write(e).await.unwrap();
        }
        let writes = Arc::new(AtomicUsize::new(0));
        let arced: ArcWriter = CountingWriter {
            writes: Arc::clone(&writes),
        }
        .into_arced();
        take(arced, &ev()).await;
        assert_eq!(writes.load(Ordering::SeqCst), 1);
    }

    #[derive(Debug, Clone, PartialEq, Eq)]
    struct UserUpdated {
        user_id: String,
    }

    fn typed_ev() -> Event<UserUpdated> {
        Event::create(
            "org",
            "/users",
            "user.updated",
            "thing-1",
            UserUpdated {
                user_id: "u-1".to_owned(),
            },
        )
        .unwrap()
    }

    struct TypedCountingWriter {
        writes: Arc<AtomicUsize>,
    }

    impl Writer<UserUpdated> for TypedCountingWriter {
        async fn write(&self, _: &Event<UserUpdated>) -> Result<()> {
            self.writes.fetch_add(1, Ordering::SeqCst);
            Ok(())
        }
    }

    #[tokio::test]
    async fn typed_writer_into_boxed_yields_dyn_writer() {
        let writes = Arc::new(AtomicUsize::new(0));
        let writer: BoxWriter<UserUpdated> = TypedCountingWriter {
            writes: Arc::clone(&writes),
        }
        .into_boxed();

        writer.write(&typed_ev()).await.unwrap();
        assert_eq!(writes.load(Ordering::SeqCst), 1);
    }
}