use std::path::Path; use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct BridgeConfig { pub irc: IrcConfig, pub owncast: OwncastConfig, #[serde(default)] pub bridge: BridgeSettings, #[serde(default)] pub control: ControlConfig, } #[derive(Debug, Clone, Deserialize)] pub struct IrcConfig { pub server: String, #[serde(default = "default_irc_port")] pub port: u16, #[serde(default)] pub tls: bool, #[serde(default = "default_nick")] pub nick: String, pub channel: String, } #[derive(Debug, Deserialize)] pub struct OwncastConfig { pub url: String, #[serde(default = "default_webhook_port")] pub webhook_port: u16, #[serde(default)] pub websocket_enabled: bool, #[serde(default = "default_health_poll_interval")] pub health_poll_interval_secs: u64, } #[derive(Debug, Deserialize)] pub struct BridgeSettings { #[serde(default = "default_irc_prefix")] pub irc_prefix: String, #[serde(default = "default_owncast_prefix")] pub owncast_prefix: String, #[serde(default)] pub message_buffer_size: usize, } #[derive(Debug, Deserialize)] pub struct ControlConfig { #[serde(default = "default_socket_path")] pub socket_path: String, } impl BridgeConfig { pub fn load(path: &Path) -> anyhow::Result { let contents = std::fs::read_to_string(path)?; let config: BridgeConfig = toml::from_str(&contents)?; Ok(config) } pub fn owncast_access_token(&self) -> anyhow::Result { std::env::var("OWNCAST_ACCESS_TOKEN") .map_err(|_| anyhow::anyhow!("OWNCAST_ACCESS_TOKEN env var not set")) } } fn default_irc_port() -> u16 { 6667 } fn default_nick() -> String { "owncast-bridge".to_string() } fn default_webhook_port() -> u16 { 9078 } fn default_health_poll_interval() -> u64 { 30 } fn default_irc_prefix() -> String { "[IRC]".to_string() } fn default_owncast_prefix() -> String { "[OC]".to_string() } fn default_socket_path() -> String { "/tmp/owncast-irc-bridge.sock".to_string() } impl Default for BridgeSettings { fn default() -> Self { Self { irc_prefix: default_irc_prefix(), owncast_prefix: default_owncast_prefix(), message_buffer_size: 0, } } } impl Default for ControlConfig { fn default() -> Self { Self { socket_path: default_socket_path() } } } #[cfg(test)] impl BridgeConfig { pub fn default_for_test() -> Self { Self { irc: IrcConfig { server: "localhost".to_string(), port: 6667, tls: false, nick: "test-bot".to_string(), channel: "#test".to_string(), }, owncast: OwncastConfig { url: "http://localhost:8080".to_string(), webhook_port: 9078, websocket_enabled: false, health_poll_interval_secs: 30, }, bridge: BridgeSettings::default(), control: ControlConfig::default(), } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_minimal_config() { let toml_str = r##" [irc] server = "irc.example.com" channel = "#test" [owncast] url = "https://owncast.example.com" "##; let config: BridgeConfig = toml::from_str(toml_str).unwrap(); assert_eq!(config.irc.server, "irc.example.com"); assert_eq!(config.irc.port, 6667); assert_eq!(config.irc.tls, false); assert_eq!(config.irc.nick, "owncast-bridge"); assert_eq!(config.irc.channel, "#test"); assert_eq!(config.owncast.url, "https://owncast.example.com"); assert_eq!(config.owncast.webhook_port, 9078); assert_eq!(config.owncast.websocket_enabled, false); assert_eq!(config.owncast.health_poll_interval_secs, 30); assert_eq!(config.bridge.irc_prefix, "[IRC]"); assert_eq!(config.bridge.owncast_prefix, "[OC]"); assert_eq!(config.bridge.message_buffer_size, 0); assert_eq!(config.control.socket_path, "/tmp/owncast-irc-bridge.sock"); } #[test] fn test_parse_full_config() { let toml_str = r##" [irc] server = "irc.example.com" port = 6697 tls = true nick = "mybot" channel = "#mychan" [owncast] url = "https://oc.example.com" webhook_port = 8888 websocket_enabled = true health_poll_interval_secs = 10 [bridge] irc_prefix = "" owncast_prefix = "" message_buffer_size = 50 [control] socket_path = "/var/run/bridge.sock" "##; let config: BridgeConfig = toml::from_str(toml_str).unwrap(); assert_eq!(config.irc.port, 6697); assert!(config.irc.tls); assert_eq!(config.irc.nick, "mybot"); assert_eq!(config.owncast.webhook_port, 8888); assert!(config.owncast.websocket_enabled); assert_eq!(config.bridge.message_buffer_size, 50); assert_eq!(config.control.socket_path, "/var/run/bridge.sock"); } #[test] fn test_access_token_from_env() { std::env::set_var("OWNCAST_ACCESS_TOKEN", "test-token-123"); let config = BridgeConfig::default_for_test(); let token = config.owncast_access_token(); assert_eq!(token.unwrap(), "test-token-123"); std::env::remove_var("OWNCAST_ACCESS_TOKEN"); } }