sinks.rs 98 lignes · 2768 octets
//! Built-in [`GuardEventSink`] implementations.
//!
//! Two sinks are provided out of the box:
//!
//! - [`TracingSink`] — emits each event via `tracing::warn!` under
//!   the `rustwarden_fediverse_guard` target, picked up by journald,
//!   `tracing_subscriber` console layers, or OpenTelemetry exporters.
//! - [`MultiSink`] — fan-out to several sinks sequentially. A slow
//!   sink does **not** back-pressure the federation hot path because
//!   `emit` is fire-and-forget on each.

use std::sync::Arc;

use async_trait::async_trait;

use crate::events::{GuardEvent, GuardEventSink};

/// Sink that emits each event via `tracing::warn!`.
#[derive(Debug, Default, Clone, Copy)]
pub struct TracingSink;

impl TracingSink {
    /// Build a new tracing sink.
    #[must_use]
    pub const fn new() -> Self {
        Self
    }
}

#[async_trait]
impl GuardEventSink for TracingSink {
    async fn emit(&self, event: &GuardEvent) {
        tracing::warn!(
            target: "rustwarden_fediverse_guard",
            event_kind = ?event.kind,
            severity = ?event.severity,
            domain = %event.domain,
            actor_uri = event.actor_uri.as_deref().unwrap_or(""),
            details = %event.details,
            "fediverse guard event",
        );
    }
}

/// Fan-out sink that calls every child in sequence.
pub struct MultiSink {
    children: Vec<Arc<dyn GuardEventSink>>,
}

impl MultiSink {
    /// Build a new multi-sink from a vector of children.
    #[must_use]
    pub fn new(children: Vec<Arc<dyn GuardEventSink>>) -> Self {
        Self { children }
    }
}

#[async_trait]
impl GuardEventSink for MultiSink {
    async fn emit(&self, event: &GuardEvent) {
        for child in &self.children {
            child.emit(event).await;
        }
    }
}

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

    struct Recorder {
        events: Mutex<Vec<GuardEvent>>,
    }

    #[async_trait]
    impl GuardEventSink for Recorder {
        async fn emit(&self, event: &GuardEvent) {
            self.events.lock().unwrap().push(event.clone());
        }
    }

    #[tokio::test]
    async fn multi_sink_dispatches_to_every_child() {
        let r1 = Arc::new(Recorder {
            events: Mutex::new(Vec::new()),
        });
        let r2 = Arc::new(Recorder {
            events: Mutex::new(Vec::new()),
        });
        let multi = MultiSink::new(vec![r1.clone() as Arc<dyn GuardEventSink>, r2.clone()]);
        let evt = GuardEvent::new(GuardEventKind::SignatureFailure, "x.tld", Severity::Medium);
        multi.emit(&evt).await;
        assert_eq!(r1.events.lock().unwrap().len(), 1);
        assert_eq!(r2.events.lock().unwrap().len(), 1);
    }
}