Skip to content

Commit da6a21e

Browse files
committed
refactor: [torrust#855] show toml file location in Figment errors
Before this commit we loaded configuration in Figment always using a Toml string even if the configuration cames from a toml file. WHen there is an error Figment does not show the file location and that's one of the main advantages of using Figment. All errors point to the primary source of the configuration option. This commit fixes that problem leting Figment to load the configuration from the file when the user provides a file. Sample error: ``` Loading configuration from default configuration file: `./share/default/config/tracker.development.sqlite3.toml` ... thread 'main' panicked at src/bootstrap/config.rs:45:32: called `Result::unwrap()` on an `Err` value: ConfigError { source: LocatedError { source: Error { tag: Tag(Default, 2), profile: Some(Profile(Uncased { string: "default" })), metadata: Some(Metadata { name: "TOML file", source: Some(File("/home/developer/torrust/torrust-tracker/./share/default/config/tracker.development.sqlite3.toml")), provide_location: Some(Location { file: "packages/configuration/src/v1/mod.rs", line: 330, col: 18 }), interpolater: }), path: [], kind: Message("TOML parse error at line 2, column 15\n |\n2 | enabled = truee\n | ^\nexpected newline, `#`\n"), prev: None }, location: Location { file: "packages/configuration/src/v1/mod.rs", line: 334, col: 41 } } } note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` Notice how the file path is included is the error: `/home/developer/torrust/torrust-tracker/./share/default/config/tracker.development.sqlite3.toml`
1 parent 92408bc commit da6a21e

File tree

2 files changed

+79
-39
lines changed

2 files changed

+79
-39
lines changed

packages/configuration/src/lib.rs

+20-27
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
pub mod v1;
88

99
use std::collections::HashMap;
10+
use std::env;
1011
use std::sync::Arc;
11-
use std::{env, fs};
1212

1313
use camino::Utf8PathBuf;
1414
use derive_more::Constructor;
@@ -38,7 +38,8 @@ pub struct TrackerPolicy {
3838
/// Information required for loading config
3939
#[derive(Debug, Default, Clone)]
4040
pub struct Info {
41-
tracker_toml: String,
41+
config_toml: Option<String>,
42+
config_toml_path: String,
4243
api_admin_token: Option<String>,
4344
}
4445

@@ -51,38 +52,30 @@ impl Info {
5152
///
5253
#[allow(clippy::needless_pass_by_value)]
5354
pub fn new(
54-
env_var_config: String,
55-
env_var_path_config: String,
56-
default_path_config: String,
55+
env_var_config_toml: String,
56+
env_var_config_toml_path: String,
57+
default_config_toml_path: String,
5758
env_var_api_admin_token: String,
5859
) -> Result<Self, Error> {
59-
let tracker_toml = if let Ok(tracker_toml) = env::var(&env_var_config) {
60-
println!("Loading configuration from env var {env_var_config} ...");
60+
let config_toml = if let Ok(config_toml) = env::var(env_var_config_toml) {
61+
println!("Loading configuration from environment variable {config_toml} ...");
62+
Some(config_toml)
63+
} else {
64+
None
65+
};
6166

62-
tracker_toml
67+
let config_toml_path = if let Ok(config_toml_path) = env::var(env_var_config_toml_path) {
68+
println!("Loading configuration from file: `{config_toml_path}` ...");
69+
config_toml_path
6370
} else {
64-
let config_path = if let Ok(config_path) = env::var(env_var_path_config) {
65-
println!("Loading configuration file: `{config_path}` ...");
66-
67-
config_path
68-
} else {
69-
println!("Loading default configuration file: `{default_path_config}` ...");
70-
71-
default_path_config
72-
};
73-
74-
fs::read_to_string(config_path)
75-
.map_err(|e| Error::UnableToLoadFromConfigFile {
76-
source: (Arc::new(e) as DynError).into(),
77-
})?
78-
.parse()
79-
.map_err(|_e: std::convert::Infallible| Error::Infallible)?
71+
println!("Loading configuration from default configuration file: `{default_config_toml_path}` ...");
72+
default_config_toml_path
8073
};
81-
let api_admin_token = env::var(env_var_api_admin_token).ok();
8274

8375
Ok(Self {
84-
tracker_toml,
85-
api_admin_token,
76+
config_toml,
77+
config_toml_path,
78+
api_admin_token: env::var(env_var_api_admin_token).ok(),
8679
})
8780
}
8881
}

packages/configuration/src/v1/mod.rs

+59-12
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,11 @@ use self::tracker_api::HttpApi;
250250
use self::udp_tracker::UdpTracker;
251251
use crate::{Error, Info};
252252

253+
/// Prefix for env vars that overwrite configuration options.
254+
const CONFIG_OVERRIDE_PREFIX: &str = "TORRUST_TRACKER_CONFIG_OVERRIDE_";
255+
/// Path separator in env var names for nested values in configuration.
256+
const CONFIG_OVERRIDE_SEPARATOR: &str = "__";
257+
253258
/// Core configuration for the tracker.
254259
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
255260
pub struct Configuration {
@@ -315,9 +320,16 @@ impl Configuration {
315320
///
316321
/// Will return `Err` if the environment variable does not exist or has a bad configuration.
317322
pub fn load(info: &Info) -> Result<Configuration, Error> {
318-
let figment = Figment::from(Serialized::defaults(Configuration::default()))
319-
.merge(Toml::string(&info.tracker_toml))
320-
.merge(Env::prefixed("TORRUST_TRACKER__").split("__"));
323+
let figment = if let Some(config_toml) = &info.config_toml {
324+
// Config in env var has priority over config file path
325+
Figment::from(Serialized::defaults(Configuration::default()))
326+
.merge(Toml::string(config_toml))
327+
.merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR))
328+
} else {
329+
Figment::from(Serialized::defaults(Configuration::default()))
330+
.merge(Toml::file(&info.config_toml_path))
331+
.merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR))
332+
};
321333

322334
let mut config: Configuration = figment.extract()?;
323335

@@ -449,11 +461,14 @@ mod tests {
449461

450462
#[test]
451463
fn configuration_should_use_the_default_values_when_an_empty_configuration_is_provided_by_the_user() {
452-
figment::Jail::expect_with(|_jail| {
464+
figment::Jail::expect_with(|jail| {
465+
jail.create_file("tracker.toml", "")?;
466+
453467
let empty_configuration = String::new();
454468

455469
let info = Info {
456-
tracker_toml: empty_configuration,
470+
config_toml: Some(empty_configuration),
471+
config_toml_path: "tracker.toml".to_string(),
457472
api_admin_token: None,
458473
};
459474

@@ -466,28 +481,59 @@ mod tests {
466481
}
467482

468483
#[test]
469-
fn configuration_should_be_loaded_from_a_toml_config_file() {
484+
fn default_configuration_could_be_overwritten_from_a_single_env_var_with_toml_contents() {
470485
figment::Jail::expect_with(|_jail| {
486+
let config_toml = r#"
487+
db_path = "OVERWRITTEN DEFAULT DB PATH"
488+
"#
489+
.to_string();
490+
471491
let info = Info {
472-
tracker_toml: default_config_toml(),
492+
config_toml: Some(config_toml),
493+
config_toml_path: String::new(),
473494
api_admin_token: None,
474495
};
475496

476497
let configuration = Configuration::load(&info).expect("Could not load configuration from file");
477498

478-
assert_eq!(configuration, Configuration::default());
499+
assert_eq!(configuration.core.db_path, "OVERWRITTEN DEFAULT DB PATH".to_string());
500+
501+
Ok(())
502+
});
503+
}
504+
505+
#[test]
506+
fn default_configuration_could_be_overwritten_from_a_toml_config_file() {
507+
figment::Jail::expect_with(|jail| {
508+
jail.create_file(
509+
"tracker.toml",
510+
r#"
511+
db_path = "OVERWRITTEN DEFAULT DB PATH"
512+
"#,
513+
)?;
514+
515+
let info = Info {
516+
config_toml: None,
517+
config_toml_path: "tracker.toml".to_string(),
518+
api_admin_token: None,
519+
};
520+
521+
let configuration = Configuration::load(&info).expect("Could not load configuration from file");
522+
523+
assert_eq!(configuration.core.db_path, "OVERWRITTEN DEFAULT DB PATH".to_string());
479524

480525
Ok(())
481526
});
482527
}
483528

484529
#[test]
485-
fn configuration_should_allow_to_overwrite_the_default_tracker_api_token_for_admin_with_env_var() {
530+
fn configuration_should_allow_to_overwrite_the_default_tracker_api_token_for_admin_with_an_env_var() {
486531
figment::Jail::expect_with(|jail| {
487-
jail.set_env("TORRUST_TRACKER__HTTP_API__ACCESS_TOKENS__ADMIN", "NewToken");
532+
jail.set_env("TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN", "NewToken");
488533

489534
let info = Info {
490-
tracker_toml: default_config_toml(),
535+
config_toml: Some(default_config_toml()),
536+
config_toml_path: String::new(),
491537
api_admin_token: None,
492538
};
493539

@@ -506,7 +552,8 @@ mod tests {
506552
fn configuration_should_allow_to_overwrite_the_default_tracker_api_token_for_admin_with_the_deprecated_env_var_name() {
507553
figment::Jail::expect_with(|_jail| {
508554
let info = Info {
509-
tracker_toml: default_config_toml(),
555+
config_toml: Some(default_config_toml()),
556+
config_toml_path: String::new(),
510557
api_admin_token: Some("NewToken".to_owned()),
511558
};
512559

0 commit comments

Comments
 (0)