tsuzuri 0.1.285

Tsuzuri is a Event Sourcing framework for Rust, designed to be simple and easy to use.
Documentation
use crate::{domain_event::DomainEvent, event::Envelope, projection::error::Result};
use async_trait::async_trait;

pub trait Adapter<E>: Projector<E> + Send + Sync + 'static
where
    E: DomainEvent,
{
}

impl<D, E> Adapter<E> for D
where
    D: Projector<E> + Send + Sync + 'static,
    E: DomainEvent,
{
}

#[async_trait]
pub trait Projector<E>: Send + Sync + 'static
where
    E: DomainEvent,
{
    async fn project(&self, event: Envelope<E>) -> Result<()>;
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        event_id::EventIdType,
        message::{self, Metadata},
    };
    use async_trait::async_trait;
    use std::sync::{Arc, Mutex};

    #[derive(Debug, Clone, PartialEq)]
    struct TestEvent {
        pub id: EventIdType,
        pub data: String,
    }

    impl message::Message for TestEvent {
        fn name(&self) -> &'static str {
            "TestEvent"
        }
    }

    impl DomainEvent for TestEvent {
        fn id(&self) -> EventIdType {
            self.id
        }

        fn event_type(&self) -> &'static str {
            "TestEvent"
        }
    }

    #[derive(Clone)]
    struct MockProjector {
        called: Arc<Mutex<Vec<TestEvent>>>,
        should_fail: bool,
    }

    impl MockProjector {
        fn new(should_fail: bool) -> Self {
            Self {
                called: Arc::new(Mutex::new(Vec::new())),
                should_fail,
            }
        }

        fn get_calls(&self) -> Vec<TestEvent> {
            self.called.lock().unwrap().clone()
        }
    }

    #[async_trait]
    impl Projector<TestEvent> for MockProjector {
        async fn project(&self, event: Envelope<TestEvent>) -> Result<()> {
            if self.should_fail {
                return Err(crate::projection::error::ProjectionError::Database(
                    "Mock projection failed".to_string(),
                ));
            }
            self.called.lock().unwrap().push(event.message);
            Ok(())
        }
    }

    #[tokio::test]
    async fn test_projector_success() {
        let projector = MockProjector::new(false);
        let event = TestEvent {
            id: EventIdType::new(),
            data: "test-data".to_string(),
        };
        let envelope = Envelope {
            message: event.clone(),
            metadata: Metadata::default(),
        };

        let result = projector.project(envelope).await;
        assert!(result.is_ok());

        let calls = projector.get_calls();
        assert_eq!(calls.len(), 1);
        assert_eq!(calls[0], event);
    }

    #[tokio::test]
    async fn test_projector_failure() {
        let projector = MockProjector::new(true);
        let event = TestEvent {
            id: EventIdType::new(),
            data: "test-data".to_string(),
        };
        let envelope = Envelope {
            message: event,
            metadata: Metadata::default(),
        };

        let result = projector.project(envelope).await;
        assert!(result.is_err());
        match result {
            Err(crate::projection::error::ProjectionError::Database(msg)) => {
                assert_eq!(msg, "Mock projection failed");
            }
            _ => panic!("Expected Database error"),
        }

        let calls = projector.get_calls();
        assert_eq!(calls.len(), 0);
    }

    #[test]
    fn test_adapter_trait_impl() {
        let projector = MockProjector::new(false);

        fn assert_adapter<T: Adapter<TestEvent>>(_: &T) {}

        assert_adapter(&projector);
    }
}