Skip to main content

kellnr_settings/
config_source.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use utoipa::ToSchema;
5
6/// Represents the source of a configuration value.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
8#[serde(rename_all = "lowercase")]
9pub enum ConfigSource {
10    /// Value comes from compiled-in defaults
11    Default,
12    /// Value was set in a TOML configuration file
13    Toml,
14    /// Value was set via environment variable
15    Env,
16    /// Value was set via command-line argument
17    Cli,
18}
19
20/// All setting keys used for configuration tracking.
21const SETTING_KEYS: &[&str] = &[
22    // Registry settings
23    "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 settings
42    "local.ip",
43    "local.port",
44    // Origin settings
45    "origin.hostname",
46    "origin.port",
47    "origin.protocol",
48    "origin.path",
49    // Log settings
50    "log.level",
51    "log.format",
52    "log.level_web_server",
53    // Docs settings
54    "docs.enabled",
55    "docs.max_size",
56    // Proxy settings
57    "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 settings
66    "postgresql.enabled",
67    "postgresql.address",
68    "postgresql.port",
69    "postgresql.db",
70    "postgresql.user",
71    "postgresql.pwd",
72    // S3 settings
73    "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 settings
85    "setup.admin_pwd",
86    "setup.admin_token",
87    // OAuth2 settings
88    "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 settings
100    "toolchain.enabled",
101    "toolchain.max_size",
102];
103
104/// Maps setting keys (e.g., `"registry.data_dir"`) to their configuration source.
105pub type SourceMap = HashMap<String, ConfigSource>;
106
107/// Compute the environment variable name for a given setting key.
108/// E.g., `"registry.data_dir"` becomes `"KELLNR_REGISTRY__DATA_DIR"`.
109fn env_var_name(key: &str) -> String {
110    format!("KELLNR_{}", key.to_uppercase().replace('.', "__"))
111}
112
113/// Initialize a `SourceMap` with all settings set to Default.
114pub fn init_default_sources() -> SourceMap {
115    SETTING_KEYS
116        .iter()
117        .map(|k| (k.to_string(), ConfigSource::Default))
118        .collect()
119}
120
121/// Check which environment variables are set and mark them in the source map.
122pub 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); // We have many settings
138        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}