razeri-plugin-api 0.12.0

Extension traits and registries for Razeri plugins
Documentation
// Copyright (C) 2026 Gabriel Shea
// Licensed under either of the Apache License, Version 2.0 or the MIT
// license, at your option.

//! Stdout event sink — writes events as JSON to stderr.
//!
//! Uses stderr rather than stdout so it does not interfere with
//! structured program output on stdout.

use std::sync::Arc;

use async_trait::async_trait;

use crate::builder::EventSinkRegistration;
use crate::error::EventSinkError;
use crate::event::{EventSink, RazeriEvent};

/// Writes each event as a single-line JSON object to stderr.
pub struct StdoutSink;

impl StdoutSink {
    /// Create a new `StdoutSink`.
    #[must_use]
    pub fn new() -> Self {
        Self
    }
}

impl Default for StdoutSink {
    fn default() -> Self {
        Self::new()
    }
}

#[async_trait]
impl EventSink for StdoutSink {
    fn name(&self) -> &'static str {
        "stdout"
    }

    async fn on_event(&self, event: &RazeriEvent) -> Result<(), EventSinkError> {
        let json = serde_json::to_string(event)
            .map_err(|e| EventSinkError::SinkError(format!("serialization failed: {e}")))?;
        eprintln!("{json}");
        Ok(())
    }

    async fn flush(&self) -> Result<(), EventSinkError> {
        Ok(())
    }
}

inventory::submit! {
    EventSinkRegistration::new("stdout", || Arc::new(StdoutSink::new()))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::event::{RazeriEvent, WorkflowContext};
    use chrono::Utc;

    fn test_workflow() -> WorkflowContext {
        WorkflowContext::new("test-wf").unwrap()
    }

    #[tokio::test]
    async fn stdout_sink_accepts_events() {
        let sink = StdoutSink::new();
        assert_eq!(sink.name(), "stdout");

        let event = RazeriEvent::design_validated(
            "test-stdout-1",
            Utc::now(),
            test_workflow(),
            "test.yaml",
            0,
            0,
        )
        .unwrap();
        let result = sink.on_event(&event).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn stdout_sink_flush_is_noop() {
        let sink = StdoutSink::new();
        assert!(sink.flush().await.is_ok());
    }
}