a3s-boot 0.1.3

Adapter-first modular Rust web framework for A3S inspired by Nest.js
Documentation
#![cfg(feature = "events")]

#[cfg(feature = "macros")]
use a3s_boot::Result;
use a3s_boot::{
    A3sEventBus, A3sMemoryEventProvider, BootApplication, EventContext, EventEmitter,
    EventEnvelope, EventModule,
};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};

#[derive(Debug, Deserialize, Serialize)]
struct CatEvent {
    name: String,
}

#[cfg(feature = "macros")]
#[derive(Debug)]
struct MacroEventHandlers {
    observed: Arc<Mutex<Vec<String>>>,
}

#[cfg(feature = "macros")]
#[a3s_boot::event_listener]
impl MacroEventHandlers {
    #[a3s_boot::on_event("cat.created")]
    async fn cat_created(&self, payload: CatEvent, context: EventContext) -> Result<()> {
        let emitter = context.get::<EventEmitter>()?;
        self.observed.lock().unwrap().push(format!(
            "created:{}:{}",
            payload.name,
            emitter.listener_count()?
        ));
        Ok(())
    }

    #[a3s_boot::on_event("cat.*")]
    async fn cat_event(&self, event: EventEnvelope) -> Result<()> {
        self.observed
            .lock()
            .unwrap()
            .push(format!("wildcard:{}", event.name()));
        Ok(())
    }
}

#[cfg(feature = "macros")]
#[tokio::test]
async fn event_listener_macros_register_typed_payload_and_envelope_handlers() {
    let observed = Arc::new(Mutex::new(Vec::new()));
    let handlers = Arc::new(MacroEventHandlers {
        observed: Arc::clone(&observed),
    });
    let app = BootApplication::builder()
        .import(
            EventModule::in_process("events").listeners(Arc::clone(&handlers).event_listeners()),
        )
        .build()
        .unwrap();
    let emitter = app.get::<EventEmitter>().unwrap();

    let count = emitter
        .emit(
            "cat.created",
            &CatEvent {
                name: "Milo".to_string(),
            },
        )
        .await
        .unwrap();

    assert_eq!(count, 2);
    assert_eq!(
        observed.lock().unwrap().as_slice(),
        ["created:Milo:2", "wildcard:cat.created"]
    );
}

#[tokio::test]
async fn event_module_exports_emitter_and_dispatches_registered_listeners() {
    let observed = Arc::new(Mutex::new(Vec::new()));
    let listener_observed = Arc::clone(&observed);
    let app = BootApplication::builder()
        .import(EventModule::in_process("events").listener(
            "cat.created",
            move |event: EventEnvelope, _| {
                let observed = Arc::clone(&listener_observed);
                async move {
                    let payload = event.data_as::<CatEvent>()?;
                    observed.lock().unwrap().push(payload.name);
                    Ok(())
                }
            },
        ))
        .build()
        .unwrap();
    let emitter = app.get::<EventEmitter>().unwrap();

    let count = emitter
        .emit(
            "cat.created",
            &CatEvent {
                name: "Milo".to_string(),
            },
        )
        .await
        .unwrap();

    assert_eq!(count, 1);
    assert_eq!(observed.lock().unwrap().as_slice(), ["Milo"]);

    let bus = app.get::<A3sEventBus>().unwrap();
    let emitter_bus = emitter.event_bus();
    assert!(Arc::ptr_eq(&bus, &emitter_bus));

    let stored = emitter
        .event_bus()
        .list_events(Some("cat"), 10)
        .await
        .unwrap();
    assert_eq!(stored.len(), 1);
    assert_eq!(stored[0].event_type, "cat.created");
    assert_eq!(stored[0].payload["name"], "Milo");
}

#[tokio::test]
async fn event_emitter_supports_wildcard_patterns_in_registration_order() {
    let observed = Arc::new(Mutex::new(Vec::new()));
    let first_observed = Arc::clone(&observed);
    let second_observed = Arc::clone(&observed);
    let app = BootApplication::builder()
        .import(
            EventModule::in_process("events")
                .listener("cat.*", move |event: EventEnvelope, _| {
                    let observed = Arc::clone(&first_observed);
                    async move {
                        observed
                            .lock()
                            .unwrap()
                            .push(format!("wildcard:{}", event.name()));
                        Ok(())
                    }
                })
                .listener("cat.created", move |event: EventEnvelope, _| {
                    let observed = Arc::clone(&second_observed);
                    async move {
                        observed
                            .lock()
                            .unwrap()
                            .push(format!("exact:{}", event.name()));
                        Ok(())
                    }
                }),
        )
        .build()
        .unwrap();
    let emitter = app.get::<EventEmitter>().unwrap();

    let count = emitter
        .emit(
            "cat.created",
            &CatEvent {
                name: "Luna".to_string(),
            },
        )
        .await
        .unwrap();
    emitter
        .emit(
            "dog.created",
            &CatEvent {
                name: "Ada".to_string(),
            },
        )
        .await
        .unwrap();

    assert_eq!(count, 2);
    assert_eq!(
        observed.lock().unwrap().as_slice(),
        ["wildcard:cat.created", "exact:cat.created"]
    );
}

#[tokio::test]
async fn event_listener_context_can_resolve_the_emitter_provider() {
    let observed = Arc::new(Mutex::new(Vec::new()));
    let listener_observed = Arc::clone(&observed);
    let app = BootApplication::builder()
        .import(EventModule::in_process("events").listener(
            "cat.created",
            move |_event: EventEnvelope, context: EventContext| {
                let observed = Arc::clone(&listener_observed);
                async move {
                    let emitter = context.get::<EventEmitter>()?;
                    let bus = context.get::<A3sEventBus>()?;
                    observed.lock().unwrap().push(format!(
                        "{}:{}",
                        emitter.listener_count()?,
                        bus.provider_name()
                    ));
                    Ok(())
                }
            },
        ))
        .build()
        .unwrap();
    let emitter = app.get::<EventEmitter>().unwrap();

    emitter
        .emit(
            "cat.created",
            &CatEvent {
                name: "Nori".to_string(),
            },
        )
        .await
        .unwrap();

    assert_eq!(observed.lock().unwrap().as_slice(), ["1:memory"]);
}

#[test]
fn event_module_supports_named_and_global_exports() {
    let app = BootApplication::builder()
        .import(
            EventModule::in_process("events")
                .named("application-events")
                .named_event_bus("application-event-bus")
                .global(),
        )
        .build()
        .unwrap();

    let emitter = app.get_named::<EventEmitter>("application-events").unwrap();
    let bus = app
        .get_named::<A3sEventBus>("application-event-bus")
        .unwrap();
    let emitter_bus = emitter.event_bus();

    assert_eq!(emitter.listener_count().unwrap(), 0);
    assert!(Arc::ptr_eq(&bus, &emitter_bus));
}

#[test]
fn event_module_can_wrap_a3s_event_providers() {
    let app = BootApplication::builder()
        .import(EventModule::from_provider(
            "events",
            A3sMemoryEventProvider::default(),
        ))
        .build()
        .unwrap();

    let emitter = app.get::<EventEmitter>().unwrap();

    assert_eq!(emitter.event_bus().provider_name(), "memory");
}