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.connect_timeout_seconds",
63 "proxy.request_timeout_seconds",
64 "postgresql.enabled",
66 "postgresql.address",
67 "postgresql.port",
68 "postgresql.db",
69 "postgresql.user",
70 "postgresql.pwd",
71 "s3.enabled",
73 "s3.access_key",
74 "s3.secret_key",
75 "s3.region",
76 "s3.endpoint",
77 "s3.allow_http",
78 "s3.crates_bucket",
79 "s3.cratesio_bucket",
80 "s3.toolchain_bucket",
81 "s3.connect_timeout_seconds",
82 "s3.request_timeout_seconds",
83 "setup.admin_pwd",
85 "setup.admin_token",
86 "oauth2.enabled",
88 "oauth2.issuer_url",
89 "oauth2.client_id",
90 "oauth2.client_secret",
91 "oauth2.scopes",
92 "oauth2.auto_provision_users",
93 "oauth2.admin_group_claim",
94 "oauth2.admin_group_value",
95 "oauth2.read_only_group_claim",
96 "oauth2.read_only_group_value",
97 "oauth2.button_text",
98 "toolchain.enabled",
100 "toolchain.max_size",
101];
102
103pub type SourceMap = HashMap<String, ConfigSource>;
105
106fn env_var_name(key: &str) -> String {
109 format!("KELLNR_{}", key.to_uppercase().replace('.', "__"))
110}
111
112pub fn init_default_sources() -> SourceMap {
114 SETTING_KEYS
115 .iter()
116 .map(|k| (k.to_string(), ConfigSource::Default))
117 .collect()
118}
119
120pub fn track_env_sources(sources: &mut SourceMap) {
122 for key in SETTING_KEYS {
123 if std::env::var(env_var_name(key)).is_ok() {
124 sources.insert((*key).to_string(), ConfigSource::Env);
125 }
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_init_default_sources() {
135 let sources = init_default_sources();
136 assert!(sources.len() > 50); assert_eq!(
138 sources.get("registry.data_dir"),
139 Some(&ConfigSource::Default)
140 );
141 assert_eq!(sources.get("local.port"), Some(&ConfigSource::Default));
142 }
143
144 #[test]
145 fn test_setting_keys_count() {
146 assert!(SETTING_KEYS.len() > 50);
147 }
148
149 #[test]
150 fn test_env_var_name() {
151 assert_eq!(
152 env_var_name("registry.data_dir"),
153 "KELLNR_REGISTRY__DATA_DIR"
154 );
155 assert_eq!(env_var_name("local.port"), "KELLNR_LOCAL__PORT");
156 assert_eq!(
157 env_var_name("oauth2.auto_provision_users"),
158 "KELLNR_OAUTH2__AUTO_PROVISION_USERS"
159 );
160 }
161
162 #[test]
163 fn test_config_source_serialization() {
164 assert_eq!(
165 serde_json::to_string(&ConfigSource::Default).unwrap(),
166 "\"default\""
167 );
168 assert_eq!(
169 serde_json::to_string(&ConfigSource::Toml).unwrap(),
170 "\"toml\""
171 );
172 assert_eq!(
173 serde_json::to_string(&ConfigSource::Env).unwrap(),
174 "\"env\""
175 );
176 assert_eq!(
177 serde_json::to_string(&ConfigSource::Cli).unwrap(),
178 "\"cli\""
179 );
180 }
181}