config.rs (5346B)
1 use anyhow::{Context, Result}; 2 use serde::{Deserialize, Serialize}; 3 use ini::Ini; 4 use std::path::Path; 5 6 #[derive(Debug, Clone, Serialize, Deserialize)] 7 pub struct Config { 8 pub server: ServerConfig, 9 pub database: DatabaseConfig, 10 pub crypto: CryptoConfig, 11 pub webhook_worker: WebhookWorkerConfig, 12 } 13 14 #[derive(Debug, Clone, Serialize, Deserialize)] 15 pub struct ServerConfig { 16 pub host: Option<String>, 17 pub port: Option<u16>, 18 pub socket_path: Option<String>, 19 } 20 21 impl ServerConfig { 22 pub fn validate(&self) -> Result<()> { 23 let has_tcp = self.host.is_some() || self.port.is_some(); 24 let has_unix = self.socket_path.is_some(); 25 26 if has_tcp && has_unix { 27 anyhow::bail!("Cannot specify both TCP (host/port) and Unix socket (socket_path)"); 28 } 29 30 if !has_tcp && !has_unix { 31 anyhow::bail!("Must specify either TCP (host/port) or Unix socket (socket_path)"); 32 } 33 34 if has_tcp && (self.host.is_none() || self.port.is_none()) { 35 anyhow::bail!("Host and port must be specified for TCP"); 36 } 37 38 Ok(()) 39 } 40 41 pub fn is_unix_socket(&self) -> bool { 42 self.socket_path.is_some() 43 } 44 } 45 46 #[derive(Debug, Clone, Serialize, Deserialize)] 47 pub struct DatabaseConfig { 48 pub url: String, 49 } 50 51 #[derive(Debug, Clone, Serialize, Deserialize)] 52 pub struct CryptoConfig { 53 pub nonce_bytes: usize, 54 pub token_bytes: usize, 55 pub authorization_code_bytes: usize, 56 } 57 58 #[derive(Debug, Clone, Serialize, Deserialize)] 59 pub struct WebhookWorkerConfig { 60 pub retry_delay_server_error: i64, 61 pub retry_delay_forbidden: i64, 62 pub retry_delay_other: i64, 63 pub fallback_poll_secs: u64, 64 pub batch_size: i64, 65 } 66 67 impl Config { 68 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> { 69 let ini = Ini::load_from_file(path.as_ref()) 70 .context("Failed to load config file")?; 71 72 let server_section = ini 73 .section(Some("server")) 74 .context("Missing [server] section")?; 75 76 let host = server_section.get("host").map(|s| s.to_string()); 77 let port = server_section 78 .get("port") 79 .map(|s| s.parse::<u16>()) 80 .transpose() 81 .context("Invalid port")?; 82 let socket_path = server_section.get("socket_path").map(|s| s.to_string()); 83 84 let server = ServerConfig { 85 host, 86 port, 87 socket_path, 88 }; 89 90 server.validate()?; 91 92 let database_section = ini 93 .section(Some("database")) 94 .context("Missing [database] section")?; 95 96 let database = DatabaseConfig { 97 url: database_section 98 .get("url") 99 .context("Missing database.url")? 100 .to_string(), 101 }; 102 103 let crypto_section = ini 104 .section(Some("crypto")) 105 .context("Missing [crypto] section")?; 106 107 let crypto = CryptoConfig { 108 nonce_bytes: crypto_section 109 .get("nonce_bytes") 110 .context("Missing crypto.nonce_bytes")? 111 .parse() 112 .context("Invalid crypto.nonce_bytes")?, 113 token_bytes: crypto_section 114 .get("token_bytes") 115 .context("Missing crypto.token_bytes")? 116 .parse() 117 .context("Invalid crypto.token_bytes")?, 118 authorization_code_bytes: crypto_section 119 .get("authorization_code_bytes") 120 .context("Missing crypto.authorization_code_bytes")? 121 .parse() 122 .context("Invalid crypto.authorization_code_bytes")?, 123 }; 124 125 let webhook_worker_section = ini 126 .section(Some("webhook_worker")) 127 .context("Missing [webhook_worker] section")?; 128 129 let webhook_worker = WebhookWorkerConfig { 130 retry_delay_server_error: webhook_worker_section 131 .get("retry_delay_server_error") 132 .context("Missing webhook_worker.retry_delay_server_error")? 133 .parse() 134 .context("Invalid webhook_worker.retry_delay_server_error")?, 135 retry_delay_forbidden: webhook_worker_section 136 .get("retry_delay_forbidden") 137 .context("Missing webhook_worker.retry_delay_forbidden")? 138 .parse() 139 .context("Invalid webhook_worker.retry_delay_forbidden")?, 140 retry_delay_other: webhook_worker_section 141 .get("retry_delay_other") 142 .context("Missing webhook_worker.retry_delay_other")? 143 .parse() 144 .context("Invalid webhook_worker.retry_delay_other")?, 145 fallback_poll_secs: webhook_worker_section 146 .get("fallback_poll_secs") 147 .context("Missing webhook_worker.fallback_poll_secs")? 148 .parse() 149 .context("Invalid webhook_worker.fallback_poll_secs")?, 150 batch_size: webhook_worker_section 151 .get("batch_size") 152 .context("Missing webhook_worker.batch_size")? 153 .parse() 154 .context("Invalid webhook_worker.batch_size")?, 155 }; 156 157 Ok(Config { 158 server, 159 database, 160 crypto, 161 webhook_worker, 162 }) 163 } 164 } 165