1use clap::Parser;
22use config::{Config, ConfigError};
23use serde::Deserialize;
24use std::env;
25use std::path::PathBuf;
26use std::collections::HashMap;
27
28const DEFAULT_CONFIG: &str = include_str!("../config/default.toml");
29const DEFAULT_CONFIG_PATH: &str = "/etc/hyprstream/config.toml";
30
31#[derive(Parser, Debug)]
37#[command(author, version, about)]
38pub struct CliArgs {
39 #[arg(short, long, value_name = "FILE")]
41 config: Option<PathBuf>,
42
43 #[arg(long, env = "HYPRSTREAM_SERVER_HOST")]
45 host: Option<String>,
46
47 #[arg(long, env = "HYPRSTREAM_SERVER_PORT")]
49 port: Option<u16>,
50
51 #[arg(long, env = "HYPRSTREAM_LOG_LEVEL")]
53 log_level: Option<String>,
54
55 #[arg(long, env = "HYPRSTREAM_ENGINE")]
57 engine: Option<String>,
58
59 #[arg(long, env = "HYPRSTREAM_ENGINE_CONNECTION")]
61 engine_connection: Option<String>,
62
63 #[arg(long, env = "HYPRSTREAM_ENGINE_OPTIONS")]
65 engine_options: Option<Vec<String>>,
66
67 #[arg(long, env = "HYPRSTREAM_ENABLE_CACHE")]
69 enable_cache: Option<bool>,
70
71 #[arg(long, env = "HYPRSTREAM_CACHE_ENGINE")]
73 cache_engine: Option<String>,
74
75 #[arg(long, env = "HYPRSTREAM_CACHE_CONNECTION")]
77 cache_connection: Option<String>,
78
79 #[arg(long, env = "HYPRSTREAM_CACHE_OPTIONS")]
81 cache_options: Option<Vec<String>>,
82
83 #[arg(long, env = "HYPRSTREAM_CACHE_MAX_DURATION")]
85 cache_max_duration: Option<u64>,
86
87 #[arg(long, env = "HYPRSTREAM_ENGINE_USERNAME")]
89 engine_username: Option<String>,
90
91 #[arg(long, env = "HYPRSTREAM_ENGINE_PASSWORD")]
93 engine_password: Option<String>,
94
95 #[arg(long, env = "HYPRSTREAM_CACHE_USERNAME")]
97 cache_username: Option<String>,
98
99 #[arg(long, env = "HYPRSTREAM_CACHE_PASSWORD")]
101 cache_password: Option<String>,
102}
103
104#[derive(Debug, Deserialize)]
110pub struct Settings {
111 pub server: ServerConfig,
113 pub engine: EngineConfig,
115 pub cache: CacheConfig,
117}
118
119#[derive(Debug, Deserialize)]
123pub struct ServerConfig {
124 pub host: String,
126 pub port: u16,
128 #[serde(default = "default_log_level")]
130 pub log_level: String,
131}
132
133fn default_log_level() -> String {
134 "info".to_string()
135}
136
137#[derive(Debug, Deserialize)]
141pub struct EngineConfig {
142 pub engine: String,
144 pub connection: String,
146 #[serde(default)]
148 pub options: std::collections::HashMap<String, String>,
149 #[serde(skip)]
151 pub credentials: Option<Credentials>,
152}
153
154#[derive(Debug, Clone, Deserialize)]
156pub struct Credentials {
157 pub username: String,
159 pub password: String,
161}
162
163#[derive(Debug, Deserialize)]
165pub struct CacheConfig {
166 pub enabled: bool,
168 pub engine: String,
170 pub connection: String,
172 pub options: HashMap<String, String>,
174 #[serde(default)]
176 pub credentials: Option<Credentials>,
177 #[serde(default = "default_ttl")]
179 pub ttl: Option<u64>,
180}
181
182fn default_ttl() -> Option<u64> {
183 Some(3600) }
185
186impl Default for CacheConfig {
187 fn default() -> Self {
188 Self {
189 enabled: false,
190 engine: "duckdb".to_string(),
191 connection: ":memory:".to_string(),
192 options: HashMap::new(),
193 credentials: None,
194 ttl: default_ttl(),
195 }
196 }
197}
198
199fn default_cache_engine() -> String {
200 "duckdb".to_string()
201}
202
203fn default_cache_connection() -> String {
204 ":memory:".to_string()
205}
206
207fn default_cache_duration() -> u64 {
208 3600
209}
210
211impl Settings {
212 pub fn new(cli: CliArgs) -> Result<Self, ConfigError> {
214 let mut builder = Config::builder();
215
216 builder = builder.add_source(config::File::from_str(
218 DEFAULT_CONFIG,
219 config::FileFormat::Toml,
220 ));
221
222 if let Ok(metadata) = std::fs::metadata(DEFAULT_CONFIG_PATH) {
224 if metadata.is_file() {
225 builder = builder.add_source(config::File::from(PathBuf::from(DEFAULT_CONFIG_PATH)));
226 }
227 }
228
229 if let Some(ref config_path) = cli.config {
231 builder = builder.add_source(config::File::from(config_path.clone()));
232 }
233
234 builder = builder.add_source(config::Environment::with_prefix("HYPRSTREAM"));
236
237 if let Some(ref host) = cli.host {
239 builder = builder.set_override("server.host", host.as_str())?;
240 }
241 if let Some(port) = cli.port {
242 builder = builder.set_override("server.port", port)?;
243 }
244
245 if let Some(ref log_level) = cli.log_level {
247 builder = builder.set_override("server.log_level", log_level.as_str())?;
248 }
249
250 if let Some(ref engine) = cli.engine {
252 builder = builder.set_override("engine.engine", engine.as_str())?;
253 }
254 if let Some(ref connection) = cli.engine_connection {
255 builder = builder.set_override("engine.connection", connection.as_str())?;
256 }
257 if let Some(ref options) = cli.engine_options {
258 let options: std::collections::HashMap<String, String> = options
259 .iter()
260 .filter_map(|opt| {
261 let parts: Vec<&str> = opt.split('=').collect();
262 if parts.len() == 2 {
263 Some((parts[0].to_string(), parts[1].to_string()))
264 } else {
265 None
266 }
267 })
268 .collect();
269 builder = builder.set_override("engine.options", options)?;
270 }
271
272 if let Some(enabled) = cli.enable_cache {
274 builder = builder.set_override("cache.enabled", enabled)?;
275 }
276 if let Some(ref engine) = cli.cache_engine {
277 builder = builder.set_override("cache.engine", engine.as_str())?;
278 }
279 if let Some(ref connection) = cli.cache_connection {
280 builder = builder.set_override("cache.connection", connection.as_str())?;
281 }
282 if let Some(ref options) = cli.cache_options {
283 let options: std::collections::HashMap<String, String> = options
284 .iter()
285 .filter_map(|opt| {
286 let parts: Vec<&str> = opt.split('=').collect();
287 if parts.len() == 2 {
288 Some((parts[0].to_string(), parts[1].to_string()))
289 } else {
290 None
291 }
292 })
293 .collect();
294 builder = builder.set_override("cache.options", options)?;
295 }
296 if let Some(duration) = cli.cache_max_duration {
297 builder = builder.set_override("cache.max_duration_secs", duration)?;
298 }
299
300 let mut settings: Settings = builder.build()?.try_deserialize()?;
302
303 settings.engine.credentials = Self::load_engine_credentials(&cli);
305 settings.cache.credentials = Self::load_cache_credentials(&cli);
306
307 Ok(settings)
308 }
309
310 fn load_engine_credentials(cli: &CliArgs) -> Option<Credentials> {
315 if let (Some(username), Some(password)) = (
317 env::var("HYPRSTREAM_ENGINE_USERNAME").ok(),
318 env::var("HYPRSTREAM_ENGINE_PASSWORD").ok(),
319 ) {
320 return Some(Credentials { username, password });
321 }
322
323 if let (Some(username), Some(password)) = (&cli.engine_username, &cli.engine_password) {
325 return Some(Credentials {
326 username: username.clone(),
327 password: password.clone(),
328 });
329 }
330
331 None
332 }
333
334 fn load_cache_credentials(cli: &CliArgs) -> Option<Credentials> {
339 if let (Some(username), Some(password)) = (
341 env::var("HYPRSTREAM_CACHE_USERNAME").ok(),
342 env::var("HYPRSTREAM_CACHE_PASSWORD").ok(),
343 ) {
344 return Some(Credentials { username, password });
345 }
346
347 if let (Some(username), Some(password)) = (&cli.cache_username, &cli.cache_password) {
349 return Some(Credentials {
350 username: username.clone(),
351 password: password.clone(),
352 });
353 }
354
355 None
356 }
357}