use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SinkConfig {
pub name: String,
#[serde(rename = "type")]
pub sink_type: SinkType,
#[serde(default)]
pub config: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum SinkType {
Push,
InApp,
Feed,
WebSocket,
Webhook,
Counter,
Custom,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sink_config_push() {
let yaml = r#"
name: push-notification
type: push
config:
provider: expo
retry_count: 3
"#;
let config: SinkConfig = serde_yaml::from_str(yaml).unwrap();
assert_eq!(config.name, "push-notification");
assert_eq!(config.sink_type, SinkType::Push);
assert_eq!(
config.config.get("provider").unwrap(),
&serde_json::Value::String("expo".to_string())
);
assert_eq!(
config.config.get("retry_count").unwrap(),
&serde_json::json!(3)
);
}
#[test]
fn test_sink_config_in_app() {
let yaml = r#"
name: in-app-notification
type: in_app
config:
ttl: 30d
"#;
let config: SinkConfig = serde_yaml::from_str(yaml).unwrap();
assert_eq!(config.name, "in-app-notification");
assert_eq!(config.sink_type, SinkType::InApp);
}
#[test]
fn test_sink_config_webhook() {
let yaml = r#"
name: analytics-webhook
type: webhook
config:
url: https://analytics.example.com/events
method: POST
headers:
Authorization: "Bearer token123"
"#;
let config: SinkConfig = serde_yaml::from_str(yaml).unwrap();
assert_eq!(config.name, "analytics-webhook");
assert_eq!(config.sink_type, SinkType::Webhook);
assert!(config.config.contains_key("url"));
assert!(config.config.contains_key("headers"));
}
#[test]
fn test_sink_config_websocket() {
let yaml = r#"
name: live-updates
type: web_socket
config:
filter_by: recipient_id
"#;
let config: SinkConfig = serde_yaml::from_str(yaml).unwrap();
assert_eq!(config.name, "live-updates");
assert_eq!(config.sink_type, SinkType::WebSocket);
}
#[test]
fn test_sink_config_counter() {
let yaml = r#"
name: like-counter
type: counter
config:
field: like_count
operation: increment
"#;
let config: SinkConfig = serde_yaml::from_str(yaml).unwrap();
assert_eq!(config.name, "like-counter");
assert_eq!(config.sink_type, SinkType::Counter);
}
#[test]
fn test_sink_config_no_config() {
let yaml = r#"
name: simple-sink
type: in_app
"#;
let config: SinkConfig = serde_yaml::from_str(yaml).unwrap();
assert_eq!(config.name, "simple-sink");
assert!(config.config.is_empty());
}
#[test]
fn test_sink_type_serde_roundtrip() {
let types = vec![
SinkType::Push,
SinkType::InApp,
SinkType::Feed,
SinkType::WebSocket,
SinkType::Webhook,
SinkType::Counter,
SinkType::Custom,
];
for t in &types {
let json = serde_json::to_string(t).unwrap();
let roundtrip: SinkType = serde_json::from_str(&json).unwrap();
assert_eq!(*t, roundtrip);
}
}
#[test]
fn test_multiple_sinks_yaml() {
let yaml = r#"
- name: push-notification
type: push
config:
provider: expo
- name: in-app-notification
type: in_app
config:
ttl: 30d
- name: websocket
type: web_socket
config:
filter_by: recipient_id
"#;
let sinks: Vec<SinkConfig> = serde_yaml::from_str(yaml).unwrap();
assert_eq!(sinks.len(), 3);
assert_eq!(sinks[0].sink_type, SinkType::Push);
assert_eq!(sinks[1].sink_type, SinkType::InApp);
assert_eq!(sinks[2].sink_type, SinkType::WebSocket);
}
}