• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

iggy-rs / iggy / 13280939567

12 Feb 2025 08:18AM UTC coverage: 75.132% (-0.06%) from 75.192%
13280939567

Pull #1514

github

web-flow
Merge d9a2e8c28 into 19db87131
Pull Request #1514: Epilogue part 1

0 of 24 new or added lines in 2 files covered. (0.0%)

2 existing lines in 2 files now uncovered.

25022 of 33304 relevant lines covered (75.13%)

9818.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.21
/server/src/configs/config_provider.rs
1
use crate::configs::server::ServerConfig;
2
use crate::server_error::ConfigError;
3
use crate::IGGY_ROOT_PASSWORD_ENV;
4
use figment::{
5
    providers::{Format, Toml},
6
    value::{Dict, Map as FigmentMap, Tag, Value as FigmentValue},
7
    Error, Figment, Metadata, Profile, Provider,
8
};
9
use std::{env, future::Future, path::Path};
10
use toml::{map::Map, Value as TomlValue};
11
use tracing::debug;
12

13
const DEFAULT_CONFIG_PROVIDER: &str = "file";
14
const DEFAULT_CONFIG_PATH: &str = "configs/server.toml";
15
const SECRET_KEYS: [&str; 6] = [
16
    IGGY_ROOT_PASSWORD_ENV,
17
    "IGGY_DATA_MAINTENANCE_ARCHIVER_S3_KEY_SECRET",
18
    "IGGY_HTTP_JWT_ENCODING_SECRET",
19
    "IGGY_HTTP_JWT_DECODING_SECRET",
20
    "IGGY_TCP_TLS_PASSWORD",
21
    "IGGY_SYSTEM_ENCRYPTION_KEY",
22
];
23

24
pub enum ConfigProviderKind {
25
    File(FileConfigProvider),
26
}
27

28
impl ConfigProviderKind {
29
    pub async fn load_config(&self) -> Result<ServerConfig, ConfigError> {
131✔
30
        match self {
131✔
31
            Self::File(p) => p.load_config().await,
131✔
32
        }
33
    }
131✔
34
}
35

36
pub trait ConfigProvider {
37
    fn load_config(&self) -> impl Future<Output = Result<ServerConfig, ConfigError>>;
38
}
39

40
#[derive(Debug)]
41
pub struct FileConfigProvider {
42
    path: String,
43
}
44

45
pub struct CustomEnvProvider {
46
    prefix: String,
47
}
48

49
impl FileConfigProvider {
50
    pub fn new(path: String) -> Self {
264✔
51
        Self { path }
264✔
52
    }
264✔
53
}
54

55
impl CustomEnvProvider {
56
    pub fn new(prefix: &str) -> Self {
264✔
57
        Self {
264✔
58
            prefix: prefix.to_string(),
264✔
59
        }
264✔
60
    }
264✔
61

62
    fn walk_toml_table_to_dict(prefix: &str, table: Map<String, TomlValue>, dict: &mut Dict) {
9,768✔
63
        for (key, value) in table {
47,520✔
64
            let new_prefix = if prefix.is_empty() {
37,752✔
65
                key.clone()
2,376✔
66
            } else {
67
                format!("{}.{}", prefix, key)
35,376✔
68
            };
69
            match value {
37,752✔
70
                TomlValue::Table(inner_table) => {
9,504✔
71
                    let mut nested_dict = Dict::new();
9,504✔
72
                    Self::walk_toml_table_to_dict(&new_prefix, inner_table, &mut nested_dict);
9,504✔
73
                    dict.insert(key, FigmentValue::from(nested_dict));
9,504✔
74
                }
9,504✔
75
                _ => {
28,248✔
76
                    dict.insert(key, Self::toml_to_figment_value(&value));
28,248✔
77
                }
28,248✔
78
            }
79
        }
80
    }
9,768✔
81

82
    fn insert_overridden_values_from_env(
533✔
83
        source: &Dict,
533✔
84
        target: &mut Dict,
533✔
85
        keys: Vec<String>,
533✔
86
        value: FigmentValue,
533✔
87
    ) {
533✔
88
        if keys.is_empty() {
533✔
89
            return;
×
90
        }
533✔
91

533✔
92
        debug!("Keys for env variable: {:?}", keys);
533✔
93

94
        let mut current_source = source;
533✔
95
        let mut current_target = target;
533✔
96
        let mut combined_keys = Vec::new();
533✔
97

98
        for key in keys.iter() {
1,074✔
99
            combined_keys.push(key.clone());
1,074✔
100
            let key_to_check = combined_keys.join("_");
1,074✔
101
            debug!("Checking key: {}", key_to_check);
1,074✔
102

103
            match current_source.get(&key_to_check) {
1,074✔
104
                Some(FigmentValue::Dict(_, inner_source_dict)) => {
535✔
105
                    if !current_target.contains_key(&key_to_check) {
535✔
106
                        current_target.insert(
531✔
107
                            key_to_check.clone(),
531✔
108
                            FigmentValue::Dict(Tag::Default, Dict::new()),
531✔
109
                        );
531✔
110
                        debug!(
531✔
UNCOV
111
                            "Adding empty Dict for key: {} because it was found in current_source",
×
112
                            key_to_check
113
                        );
114
                    }
4✔
115

116
                    if let Some(FigmentValue::Dict(_, ref mut actual_inner_target_dict)) =
535✔
117
                        current_target.get_mut(&key_to_check)
535✔
118
                    {
535✔
119
                        current_source = inner_source_dict;
535✔
120
                        current_target = actual_inner_target_dict;
535✔
121
                        combined_keys.clear();
535✔
122
                    } else {
535✔
123
                        return;
×
124
                    }
125
                }
126
                Some(FigmentValue::Bool(_, _))
127
                | Some(FigmentValue::String(_, _))
128
                | Some(FigmentValue::Num(_, _))
129
                | Some(FigmentValue::Array(_, _)) => {
130
                    debug!("Overriding key: {} with value {:?}", key_to_check, value);
533✔
131
                    current_target.insert(key_to_check.clone(), value);
533✔
132
                    combined_keys.clear();
533✔
133
                    return;
533✔
134
                }
135
                _ => {
136
                    continue;
6✔
137
                }
138
            }
139
        }
140
    }
533✔
141

142
    fn toml_to_figment_value(toml_value: &TomlValue) -> FigmentValue {
30,624✔
143
        match toml_value {
30,624✔
144
            TomlValue::String(s) => FigmentValue::from(s.clone()),
18,216✔
145
            TomlValue::Integer(i) => FigmentValue::from(*i),
1,320✔
146
            TomlValue::Float(f) => FigmentValue::from(*f),
×
147
            TomlValue::Boolean(b) => FigmentValue::from(*b),
9,504✔
148
            TomlValue::Array(arr) => {
1,584✔
149
                let vec: Vec<FigmentValue> = arr.iter().map(Self::toml_to_figment_value).collect();
1,584✔
150
                FigmentValue::from(vec)
1,584✔
151
            }
152
            TomlValue::Table(tbl) => {
×
153
                let mut dict = figment::value::Dict::new();
×
154
                for (key, value) in tbl.iter() {
×
155
                    dict.insert(key.clone(), Self::toml_to_figment_value(value));
×
156
                }
×
157
                FigmentValue::from(dict)
×
158
            }
159
            TomlValue::Datetime(_) => todo!("not implemented yet!"),
×
160
        }
161
    }
30,624✔
162

163
    fn try_parse_value(value: &str) -> FigmentValue {
533✔
164
        if value.starts_with('[') && value.ends_with(']') {
533✔
165
            let value = value.trim_start_matches('[').trim_end_matches(']');
×
166
            let values: Vec<FigmentValue> = value.split(',').map(Self::try_parse_value).collect();
×
167
            return FigmentValue::from(values);
×
168
        }
533✔
169
        if value == "true" {
533✔
170
            return FigmentValue::from(true);
3✔
171
        }
530✔
172
        if value == "false" {
530✔
173
            return FigmentValue::from(false);
4✔
174
        }
526✔
175
        if let Ok(int_val) = value.parse::<i64>() {
526✔
176
            return FigmentValue::from(int_val);
×
177
        }
526✔
178
        if let Ok(float_val) = value.parse::<f64>() {
526✔
179
            return FigmentValue::from(float_val);
×
180
        }
526✔
181
        FigmentValue::from(value)
526✔
182
    }
533✔
183
}
184

185
impl Provider for CustomEnvProvider {
186
    fn metadata(&self) -> Metadata {
264✔
187
        Metadata::named("iggy-server config")
264✔
188
    }
264✔
189

190
    fn data(&self) -> Result<FigmentMap<Profile, Dict>, Error> {
264✔
191
        let default_config = toml::to_string(&ServerConfig::default())
264✔
192
            .expect("Cannot serialize default ServerConfig. Something's terribly wrong.");
264✔
193
        let toml_value: TomlValue = toml::from_str(&default_config).unwrap();
264✔
194
        let mut source_dict = Dict::new();
264✔
195
        if let TomlValue::Table(table) = toml_value {
264✔
196
            Self::walk_toml_table_to_dict("", table, &mut source_dict);
264✔
197
        }
264✔
198

199
        let mut new_dict = Dict::new();
264✔
200
        for (key, mut value) in env::vars() {
41,189✔
201
            let env_key = key.to_uppercase();
40,925✔
202
            if !env_key.starts_with(self.prefix.as_str()) {
40,925✔
203
                continue;
40,392✔
204
            }
533✔
205
            let keys: Vec<String> = env_key[self.prefix.len()..]
533✔
206
                .split('_')
533✔
207
                .map(|k| k.to_lowercase())
1,074✔
208
                .collect();
533✔
209
            let env_var_value = Self::try_parse_value(&value);
533✔
210
            if SECRET_KEYS.contains(&env_key.as_str()) {
533✔
211
                value = "******".to_string();
×
212
            }
533✔
213

214
            println!("{env_key} value changed to: {value} from environment variable");
533✔
215
            Self::insert_overridden_values_from_env(
533✔
216
                &source_dict,
533✔
217
                &mut new_dict,
533✔
218
                keys.clone(),
533✔
219
                env_var_value.clone(),
533✔
220
            );
533✔
221
        }
222
        let mut data = FigmentMap::new();
264✔
223
        data.insert(Profile::default(), new_dict);
264✔
224

264✔
225
        Ok(data)
264✔
226
    }
264✔
227
}
228

229
pub fn resolve(config_provider_type: &str) -> Result<ConfigProviderKind, ConfigError> {
131✔
230
    match config_provider_type {
131✔
231
        DEFAULT_CONFIG_PROVIDER => {
131✔
232
            let path =
131✔
233
                env::var("IGGY_CONFIG_PATH").unwrap_or_else(|_| DEFAULT_CONFIG_PATH.to_string());
131✔
234
            Ok(ConfigProviderKind::File(FileConfigProvider::new(path)))
131✔
235
        }
236
        _ => Err(ConfigError::InvalidConfigurationProvider {
×
237
            provider_type: config_provider_type.to_string(),
×
238
        }),
×
239
    }
240
}
131✔
241

242
/// This does exactly the same as Figment does internally.
243
fn file_exists<P: AsRef<Path>>(path: P) -> bool {
264✔
244
    let path = path.as_ref();
264✔
245

264✔
246
    if path.is_absolute() {
264✔
247
        return path.is_file();
2✔
248
    }
262✔
249

250
    let cwd = match std::env::current_dir() {
262✔
251
        Ok(dir) => dir,
262✔
252
        Err(_) => return false,
×
253
    };
254

255
    let mut current_dir = cwd.as_path();
262✔
256
    loop {
257
        let file_path = current_dir.join(path);
393✔
258
        if file_path.is_file() {
393✔
259
            return true;
262✔
260
        }
131✔
261

131✔
262
        current_dir = match current_dir.parent() {
131✔
263
            Some(parent) => parent,
131✔
264
            None => return false,
×
265
        };
266
    }
267
}
264✔
268

269
impl ConfigProvider for FileConfigProvider {
270
    async fn load_config(&self) -> Result<ServerConfig, ConfigError> {
264✔
271
        println!("Loading config from path: '{}'...", self.path);
264✔
272

264✔
273
        // Include the default configuration from server.toml
264✔
274
        let embedded_default_config = Toml::string(include_str!("../../../configs/server.toml"));
264✔
275

264✔
276
        // Start with the default configuration
264✔
277
        let mut config_builder = Figment::new().merge(embedded_default_config);
264✔
278

264✔
279
        // If the server.toml file exists, merge it into the configuration
264✔
280
        if file_exists(&self.path) {
264✔
281
            println!("Found configuration file at path: '{}'.", self.path);
264✔
282
            config_builder = config_builder.merge(Toml::file(&self.path));
264✔
283
        } else {
264✔
284
            println!(
×
285
                "Configuration file not found at path: '{}'. Using default configuration from embedded server.toml.",
×
286
                self.path
×
287
            );
×
288
        }
×
289

290
        // Merge environment variables into the configuration
291
        config_builder = config_builder.merge(CustomEnvProvider::new("IGGY_"));
264✔
292

264✔
293
        // Finally, attempt to extract the final configuration
264✔
294
        let config_result: Result<ServerConfig, figment::Error> = config_builder.extract();
264✔
295

264✔
296
        match config_result {
264✔
297
            Ok(config) => {
264✔
298
                println!("Config loaded successfully.");
264✔
299
                println!("Using Config: {config}");
264✔
300
                Ok(config)
264✔
301
            }
302
            Err(_) => Err(ConfigError::CannotLoadConfiguration),
×
303
        }
304
    }
264✔
305
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc