lora_server/config/
env.rs1use lora_database::SyncMode;
4
5use super::errors::ConfigError;
6use super::help::{help_text, version_text};
7use super::{
8 ConfigOutcome, ServerConfig, DEFAULT_HOST, DEFAULT_PORT, HOST_ENV, PORT_ENV, SNAPSHOT_PATH_ENV,
9 WAL_DIR_ENV, WAL_SYNC_MODE_ENV,
10};
11
12#[derive(Debug, Default, Clone)]
15pub struct EnvInputs {
16 pub host: Option<String>,
17 pub port: Option<String>,
18 pub snapshot_path: Option<String>,
19 pub wal_dir: Option<String>,
20 pub wal_sync_mode: Option<String>,
21}
22
23pub fn resolve<I>(args: I, env: EnvInputs) -> Result<ConfigOutcome, ConfigError>
28where
29 I: IntoIterator<Item = String>,
30{
31 let mut iter = args.into_iter();
32 let _program = iter.next();
33
34 let mut cli_host: Option<String> = None;
35 let mut cli_port: Option<String> = None;
36 let mut cli_snapshot_path: Option<String> = None;
37 let mut cli_restore_from: Option<String> = None;
38 let mut cli_wal_dir: Option<String> = None;
39 let mut cli_wal_sync_mode: Option<String> = None;
40
41 while let Some(arg) = iter.next() {
42 match arg.as_str() {
43 "--help" => return Ok(ConfigOutcome::Help(help_text())),
44 "--version" => return Ok(ConfigOutcome::Version(version_text())),
45 "--host" => {
46 let v = iter.next().ok_or(ConfigError::MissingValue("--host"))?;
47 cli_host = Some(v);
48 }
49 "--port" => {
50 let v = iter.next().ok_or(ConfigError::MissingValue("--port"))?;
51 cli_port = Some(v);
52 }
53 "--snapshot-path" => {
54 let v = iter
55 .next()
56 .ok_or(ConfigError::MissingValue("--snapshot-path"))?;
57 cli_snapshot_path = Some(v);
58 }
59 "--restore-from" => {
60 let v = iter
61 .next()
62 .ok_or(ConfigError::MissingValue("--restore-from"))?;
63 cli_restore_from = Some(v);
64 }
65 "--wal-dir" => {
66 let v = iter.next().ok_or(ConfigError::MissingValue("--wal-dir"))?;
67 cli_wal_dir = Some(v);
68 }
69 "--wal-sync-mode" => {
70 let v = iter
71 .next()
72 .ok_or(ConfigError::MissingValue("--wal-sync-mode"))?;
73 cli_wal_sync_mode = Some(v);
74 }
75 s if s.starts_with("--host=") => {
76 cli_host = Some(s["--host=".len()..].to_string());
77 }
78 s if s.starts_with("--port=") => {
79 cli_port = Some(s["--port=".len()..].to_string());
80 }
81 s if s.starts_with("--snapshot-path=") => {
82 cli_snapshot_path = Some(s["--snapshot-path=".len()..].to_string());
83 }
84 s if s.starts_with("--restore-from=") => {
85 cli_restore_from = Some(s["--restore-from=".len()..].to_string());
86 }
87 s if s.starts_with("--wal-dir=") => {
88 cli_wal_dir = Some(s["--wal-dir=".len()..].to_string());
89 }
90 s if s.starts_with("--wal-sync-mode=") => {
91 cli_wal_sync_mode = Some(s["--wal-sync-mode=".len()..].to_string());
92 }
93 s if s.starts_with("--") => return Err(ConfigError::UnknownArg(arg)),
94 _ => return Err(ConfigError::UnexpectedPositional(arg)),
95 }
96 }
97
98 let host = cli_host
99 .or(env.host)
100 .unwrap_or_else(|| DEFAULT_HOST.to_string());
101 if host.trim().is_empty() {
102 return Err(ConfigError::EmptyValue("--host"));
103 }
104
105 let port = match cli_port.or(env.port) {
106 Some(raw) => parse_port(&raw)?,
107 None => DEFAULT_PORT,
108 };
109
110 let snapshot_path = cli_snapshot_path
111 .or(env.snapshot_path)
112 .and_then(non_empty_path);
113 let restore_from = cli_restore_from.and_then(non_empty_path);
114 let wal_dir = cli_wal_dir.or(env.wal_dir).and_then(non_empty_path);
115 let wal_sync_mode = match cli_wal_sync_mode.or(env.wal_sync_mode) {
116 Some(raw) => parse_sync_mode(&raw)?,
117 None => SyncMode::PerCommit,
118 };
119
120 Ok(ConfigOutcome::Run(ServerConfig {
121 host,
122 port,
123 snapshot_path,
124 restore_from,
125 wal_dir,
126 wal_sync_mode,
127 }))
128}
129
130pub fn resolve_from_process() -> Result<ConfigOutcome, ConfigError> {
132 resolve(
133 std::env::args(),
134 EnvInputs {
135 host: std::env::var(HOST_ENV).ok(),
136 port: std::env::var(PORT_ENV).ok(),
137 snapshot_path: std::env::var(SNAPSHOT_PATH_ENV).ok(),
138 wal_dir: std::env::var(WAL_DIR_ENV).ok(),
139 wal_sync_mode: std::env::var(WAL_SYNC_MODE_ENV).ok(),
140 },
141 )
142}
143
144fn non_empty_path(p: String) -> Option<std::path::PathBuf> {
145 if p.trim().is_empty() {
146 None
147 } else {
148 Some(std::path::PathBuf::from(p))
149 }
150}
151
152fn parse_port(raw: &str) -> Result<u16, ConfigError> {
153 let trimmed = raw.trim();
154 if trimmed.is_empty() {
155 return Err(ConfigError::EmptyValue("--port"));
156 }
157 trimmed
158 .parse::<u16>()
159 .map_err(|e| ConfigError::InvalidPort {
160 value: raw.to_string(),
161 reason: e.to_string(),
162 })
163}
164
165fn parse_sync_mode(raw: &str) -> Result<SyncMode, ConfigError> {
166 match raw.trim().to_ascii_lowercase().as_str() {
167 "per-commit" | "per_commit" | "percommit" => Ok(SyncMode::PerCommit),
168 "group" => Ok(SyncMode::Group {
169 interval_ms: 50,
174 }),
175 "none" | "off" => Ok(SyncMode::None),
176 other => Err(ConfigError::InvalidSyncMode(other.to_string())),
177 }
178}