razeri_plugin_api/
sinks.rs1#[cfg(not(target_arch = "wasm32"))]
23pub mod jsonl;
24pub mod stdout;
25
26use std::sync::Arc;
27
28use crate::error::EventSinkError;
29use crate::event::EventSink;
30
31#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
33pub struct EventSinkConfig {
34 #[serde(rename = "type")]
36 pub sink_type: String,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub path: Option<String>,
40}
41
42pub async fn create_sink(config: &EventSinkConfig) -> Result<Arc<dyn EventSink>, EventSinkError> {
49 match config.sink_type.as_str() {
50 "stdout" => Ok(Arc::new(stdout::StdoutSink::new())),
51 #[cfg(not(target_arch = "wasm32"))]
52 "jsonl" => {
53 let path = config
54 .path
55 .as_deref()
56 .ok_or_else(|| EventSinkError::SinkError("jsonl sink requires 'path'".into()))?;
57 let sink = jsonl::JsonlSink::open(path).await?;
58 Ok(Arc::new(sink))
59 }
60 #[cfg(target_arch = "wasm32")]
61 "jsonl" => Err(EventSinkError::SinkError(
62 "a `jsonl` sink writes a file, and a program built for the browser has none".into(),
63 )),
64 other => Err(EventSinkError::SinkError(format!(
65 "there is no event sink of type `{other}`: the types are `stdout` and `jsonl`. \
66 For any other destination, register a sink of your own"
67 ))),
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[tokio::test]
76 async fn create_stdout_sink_from_config() {
77 let config = EventSinkConfig { sink_type: "stdout".into(), path: None };
78 let sink = create_sink(&config).await.unwrap();
79 assert_eq!(sink.name(), "stdout");
80 }
81
82 #[tokio::test]
83 async fn create_jsonl_sink_from_config() {
84 let dir = tempfile::tempdir().unwrap();
85 let path = dir.path().join("test.jsonl");
86 let config = EventSinkConfig {
87 sink_type: "jsonl".into(),
88 path: Some(path.to_string_lossy().into()),
89 };
90 let sink = create_sink(&config).await.unwrap();
91 assert_eq!(sink.name(), "jsonl");
92 }
93
94 #[tokio::test]
95 async fn create_jsonl_sink_missing_path_returns_error() {
96 let config = EventSinkConfig { sink_type: "jsonl".into(), path: None };
97 let result = create_sink(&config).await;
98 assert!(result.is_err());
99 }
100
101 #[tokio::test]
105 async fn a_webhook_sink_is_refused_by_name() {
106 let config = EventSinkConfig { sink_type: "webhook".into(), path: None };
107 let Err(refusal) = create_sink(&config).await else {
108 panic!("a webhook sink was built, and it sends nothing");
109 };
110 let words = refusal.to_string();
111 assert!(
112 words.contains("`webhook`") && words.contains("`stdout`") && words.contains("`jsonl`"),
113 "{words}"
114 );
115 }
116
117 #[tokio::test]
118 async fn create_unknown_sink_returns_error() {
119 let config = EventSinkConfig { sink_type: "kafka".into(), path: None };
120 let result = create_sink(&config).await;
121 assert!(result.is_err());
122 }
123}