noema-actix-webapi 0.1.0

Actix-web backend runtime on Noema (modules, sqlx, UoW, swagger, WebSocket dispatch)
use noema::core::{Container, Injectable};

/// Injectable log port. Use in handlers and mediator pipeline behaviors:
/// `resolve::<dyn Logger + Send + Sync>()`.
pub trait Logger: Send + Sync {
    fn debug(&self, msg: &str);
    fn info(&self, msg: &str);
    fn warn(&self, msg: &str);
    fn error(&self, msg: &str);
}

/// `tracing` backend for [`Logger`].
#[derive(Clone, Copy, Default)]
pub struct TracingLogger;

impl Logger for TracingLogger {
    fn debug(&self, msg: &str) {
        tracing::debug!("{msg}");
    }

    fn info(&self, msg: &str) {
        tracing::info!("{msg}");
    }

    fn warn(&self, msg: &str) {
        tracing::warn!("{msg}");
    }

    fn error(&self, msg: &str) {
        tracing::error!("{msg}");
    }
}

impl Injectable<Container> for TracingLogger {
    fn inject(_: &Container) -> Self {
        Self
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::{Arc, Mutex};

    struct MemLogger {
        lines: Mutex<Vec<String>>,
    }

    impl Logger for MemLogger {
        fn debug(&self, msg: &str) {
            self.lines.lock().unwrap().push(format!("debug:{msg}"));
        }
        fn info(&self, msg: &str) {
            self.lines.lock().unwrap().push(format!("info:{msg}"));
        }
        fn warn(&self, msg: &str) {
            self.lines.lock().unwrap().push(format!("warn:{msg}"));
        }
        fn error(&self, msg: &str) {
            self.lines.lock().unwrap().push(format!("error:{msg}"));
        }
    }

    #[test]
    fn logger_port_is_usable_from_a_handler() {
        let mem = Arc::new(MemLogger {
            lines: Mutex::new(Vec::new()),
        });
        let log: Arc<dyn Logger + Send + Sync> = Arc::clone(&mem) as _;
        log.info("send CreateUser");
        log.warn("slow query");
        let lines = mem.lines.lock().unwrap();
        assert_eq!(
            lines.as_slice(),
            ["info:send CreateUser", "warn:slow query"]
        );
    }

    #[test]
    fn logger_resolve_is_singleton() {
        let a = noema::resolve::<dyn Logger + Send + Sync>();
        let b = noema::resolve::<dyn Logger + Send + Sync>();
        assert!(std::sync::Arc::ptr_eq(&a, &b));
        a.info("boot");
    }
}