1use std::collections::BTreeMap;
10use std::fmt;
11
12pub const DEFAULT_ADDR: &str = "127.0.0.1:8000";
14
15pub const DEFAULT_DATABASE: &str = "default";
17
18#[derive(Clone, Default, PartialEq, Eq)]
22pub struct CliSettings {
23 addr: Option<String>,
24 databases: BTreeMap<String, String>,
25}
26
27impl CliSettings {
28 pub fn new() -> Self {
30 Self::default()
31 }
32
33 #[must_use]
35 pub fn addr(mut self, addr: impl Into<String>) -> Self {
36 self.addr = Some(addr.into());
37 self
38 }
39
40 #[must_use]
42 pub fn database(mut self, alias: impl Into<String>, url: impl Into<String>) -> Self {
43 self.databases.insert(alias.into(), url.into());
44 self
45 }
46
47 #[must_use]
49 pub fn databases(mut self, databases: BTreeMap<String, String>) -> Self {
50 self.databases = databases;
51 self
52 }
53
54 pub fn configured_addr(&self) -> Option<&str> {
56 self.addr.as_deref()
57 }
58
59 pub fn database_urls(&self) -> &BTreeMap<String, String> {
61 &self.databases
62 }
63
64 pub fn database_url(&self, alias: &str) -> Option<&str> {
66 self.databases.get(alias).map(String::as_str)
67 }
68
69 pub fn resolve_addr(&self, flag: Option<&str>, env: Option<&str>) -> String {
73 [flag, env, self.addr.as_deref()]
74 .into_iter()
75 .flatten()
76 .find(|addr| !addr.trim().is_empty())
77 .unwrap_or(DEFAULT_ADDR)
78 .to_owned()
79 }
80}
81
82impl fmt::Debug for CliSettings {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 f.debug_struct("CliSettings")
85 .field("addr", &self.addr)
86 .field("databases", &self.databases.keys().collect::<Vec<_>>())
87 .finish()
88 }
89}
90
91impl From<&siderite_config::Settings> for CliSettings {
92 fn from(settings: &siderite_config::Settings) -> Self {
94 let databases = settings
95 .databases
96 .iter()
97 .map(|(alias, db)| (alias.clone(), db.url.expose().clone()))
98 .collect();
99 Self::new()
100 .addr(settings.server.addr.clone())
101 .databases(databases)
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn converts_from_loaded_settings() {
111 let settings = siderite_config::ConfigBuilder::new()
112 .set("server.addr", "127.0.0.1:9100")
113 .set("databases.default.url", "sqlite::memory:")
114 .build()
115 .unwrap();
116 let cli = CliSettings::from(&settings);
117 assert_eq!(cli.configured_addr(), Some("127.0.0.1:9100"));
118 assert_eq!(cli.database_url("default"), Some("sqlite::memory:"));
119 assert!(!format!("{cli:?}").contains("sqlite::memory:"));
120 }
121
122 #[test]
123 fn addr_precedence_is_flag_env_settings_default() {
124 let settings = CliSettings::new().addr("0.0.0.0:9000");
125 assert_eq!(
126 settings.resolve_addr(Some("1.1.1.1:1"), Some("2.2.2.2:2")),
127 "1.1.1.1:1"
128 );
129 assert_eq!(settings.resolve_addr(None, Some("2.2.2.2:2")), "2.2.2.2:2");
130 assert_eq!(settings.resolve_addr(None, None), "0.0.0.0:9000");
131 assert_eq!(CliSettings::new().resolve_addr(None, None), DEFAULT_ADDR);
132 }
133
134 #[test]
135 fn empty_values_are_unset() {
136 let settings = CliSettings::new().addr("0.0.0.0:9000");
137 assert_eq!(settings.resolve_addr(Some(""), Some(" ")), "0.0.0.0:9000");
138 assert_eq!(
139 CliSettings::new().addr("").resolve_addr(None, None),
140 DEFAULT_ADDR
141 );
142 }
143
144 #[test]
145 fn debug_never_prints_urls() {
146 let settings = CliSettings::new().database("default", "postgres://user:hunter2@db/app");
147 let shown = format!("{settings:?}");
148 assert!(shown.contains("default"));
149 assert!(!shown.contains("hunter2"), "{shown}");
150 assert!(!shown.contains("postgres://"), "{shown}");
151 }
152
153 #[test]
154 fn database_lookup() {
155 let settings = CliSettings::new()
156 .database("default", "sqlite::memory:")
157 .database("replica", "sqlite::memory:");
158 assert_eq!(settings.database_url("default"), Some("sqlite::memory:"));
159 assert_eq!(settings.database_url("other"), None);
160 assert_eq!(settings.database_urls().len(), 2);
161 }
162}