1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use utoipa::ToSchema;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
8#[serde(rename_all = "lowercase")]
9pub enum ConfigSource {
10 Default,
12 Toml,
14 Env,
16 Cli,
18}
19
20const SETTING_KEYS: &[&str] = &[
22 "registry.data_dir",
24 "registry.session_age_seconds",
25 "registry.cache_size",
26 "registry.max_crate_size",
27 "registry.max_db_connections",
28 "registry.auth_required",
29 "registry.required_crate_fields",
30 "registry.new_crates_restricted",
31 "registry.cookie_signing_key",
32 "registry.allow_ownerless_crates",
33 "registry.token_cache_enabled",
34 "registry.token_cache_ttl_seconds",
35 "registry.token_cache_max_capacity",
36 "registry.token_db_retry_count",
37 "registry.token_db_retry_delay_ms",
38 "registry.download_timeout_seconds",
39 "registry.download_max_concurrent",
40 "registry.download_counter_flush_seconds",
41 "local.ip",
43 "local.port",
44 "origin.hostname",
46 "origin.port",
47 "origin.protocol",
48 "origin.path",
49 "log.level",
51 "log.format",
52 "log.level_web_server",
53 "docs.enabled",
55 "docs.max_size",
56 "proxy.enabled",
58 "proxy.num_threads",
59 "proxy.download_on_update",
60 "proxy.url",
61 "proxy.index",
62 "proxy.api",
63 "proxy.connect_timeout_seconds",
64 "proxy.request_timeout_seconds",
65 "postgresql.enabled",
67 "postgresql.address",
68 "postgresql.port",
69 "postgresql.db",
70 "postgresql.user",
71 "postgresql.pwd",
72 "s3.enabled",
74 "s3.access_key",
75 "s3.secret_key",
76 "s3.region",
77 "s3.endpoint",
78 "s3.allow_http",
79 "s3.crates_bucket",
80 "s3.cratesio_bucket",
81 "s3.toolchain_bucket",
82 "s3.connect_timeout_seconds",
83 "s3.request_timeout_seconds",
84 "setup.admin_pwd",
86 "setup.admin_token",
87 "oauth2.enabled",
89 "oauth2.issuer_url",
90 "oauth2.client_id",
91 "oauth2.client_secret",
92 "oauth2.scopes",
93 "oauth2.auto_provision_users",
94 "oauth2.admin_group_claim",
95 "oauth2.admin_group_value",
96 "oauth2.read_only_group_claim",
97 "oauth2.read_only_group_value",
98 "oauth2.button_text",
99 "toolchain.enabled",
101 "toolchain.max_size",
102];
103
104pub type SourceMap = HashMap<String, ConfigSource>;
106
107fn env_var_name(key: &str) -> String {
110 format!("KELLNR_{}", key.to_uppercase().replace('.', "__"))
111}
112
113pub fn init_default_sources() -> SourceMap {
115 SETTING_KEYS
116 .iter()
117 .map(|k| (k.to_string(), ConfigSource::Default))
118 .collect()
119}
120
121pub fn track_env_sources(sources: &mut SourceMap) {
123 for key in SETTING_KEYS {
124 if std::env::var(env_var_name(key)).is_ok() {
125 sources.insert((*key).to_string(), ConfigSource::Env);
126 }
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn test_init_default_sources() {
136 let sources = init_default_sources();
137 assert!(sources.len() > 50); assert_eq!(
139 sources.get("registry.data_dir"),
140 Some(&ConfigSource::Default)
141 );
142 assert_eq!(sources.get("local.port"), Some(&ConfigSource::Default));
143 }
144
145 #[test]
146 fn test_setting_keys_count() {
147 assert!(SETTING_KEYS.len() > 50);
148 }
149
150 #[test]
151 fn test_env_var_name() {
152 assert_eq!(
153 env_var_name("registry.data_dir"),
154 "KELLNR_REGISTRY__DATA_DIR"
155 );
156 assert_eq!(env_var_name("local.port"), "KELLNR_LOCAL__PORT");
157 assert_eq!(
158 env_var_name("oauth2.auto_provision_users"),
159 "KELLNR_OAUTH2__AUTO_PROVISION_USERS"
160 );
161 }
162
163 #[test]
164 fn test_config_source_serialization() {
165 assert_eq!(
166 serde_json::to_string(&ConfigSource::Default).unwrap(),
167 "\"default\""
168 );
169 assert_eq!(
170 serde_json::to_string(&ConfigSource::Toml).unwrap(),
171 "\"toml\""
172 );
173 assert_eq!(
174 serde_json::to_string(&ConfigSource::Env).unwrap(),
175 "\"env\""
176 );
177 assert_eq!(
178 serde_json::to_string(&ConfigSource::Cli).unwrap(),
179 "\"cli\""
180 );
181 }
182}