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.connect_timeout_seconds",
63    "proxy.request_timeout_seconds",
64    // PostgreSQL settings
65    "postgresql.enabled",
66    "postgresql.address",
67    "postgresql.port",
68    "postgresql.db",
69    "postgresql.user",
70    "postgresql.pwd",
71    // S3 settings
72    "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 settings
84    "setup.admin_pwd",
85    "setup.admin_token",
86    // OAuth2 settings
87    "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 settings
99    "toolchain.enabled",
100    "toolchain.max_size",
101];
102
103/// Maps setting keys (e.g., `"registry.data_dir"`) to their configuration source.
104pub type SourceMap = HashMap<String, ConfigSource>;
105
106/// Compute the environment variable name for a given setting key.
107/// E.g., `"registry.data_dir"` becomes `"KELLNR_REGISTRY__DATA_DIR"`.
108fn env_var_name(key: &str) -> String {
109    format!("KELLNR_{}", key.to_uppercase().replace('.', "__"))
110}
111
112/// Initialize a `SourceMap` with all settings set to Default.
113pub fn init_default_sources() -> SourceMap {
114    SETTING_KEYS
115        .iter()
116        .map(|k| (k.to_string(), ConfigSource::Default))
117        .collect()
118}
119
120/// Check which environment variables are set and mark them in the source map.
121pub 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); // We have many settings
137        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}