configuration.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use crate::errors::{ Result, Error };
use clap::{ Parser };
use config::{ Config };
use tracing_subscriber::layer::{ SubscriberExt };
use tracing_subscriber::util::{ SubscriberInitExt };
use std::path::{ PathBuf };
use std::str::{ FromStr };
macro_rules! version_with_hash {
() => {
format!("{} (hash {})", env!("CARGO_PKG_VERSION"), env!("GIT_HASH"))
}
}
#[derive(Parser)]
#[command(author, version = version_with_hash!(), about)]
struct Arguments {
/// Additional configuration files to load (may be passed multiple times)
#[arg(id="configuration_file", short, long)]
configuration_files: Vec<String>,
/// Additional configuration parameters to use (may be passed multiple times; overrides configuration files)
#[arg(id="parameter", short, long)]
configuration_parameters: Vec<ConfigurationParameter>,
}
#[derive(Clone)]
struct ConfigurationParameter(String, String);
impl FromStr for ConfigurationParameter {
type Err = clap::Error;
fn from_str(s: &str) -> ::std::result::Result<ConfigurationParameter, clap::Error> {
let parts: Vec<&str> = s.splitn(2, '=').collect();
if parts.len() != 2 {
return Err(clap::Error::new(clap::error::ErrorKind::WrongNumberOfValues));
}
Ok(ConfigurationParameter(parts[0].to_string(), parts[1].to_string()))
}
}
pub(crate) struct Configuration {
pub(crate) content_metadata_file: String,
pub(crate) content_root: PathBuf,
pub(crate) email_from: String,
pub(crate) email_live: bool,
pub(crate) email_smtp_host: String,
pub(crate) email_smtp_port: u16,
pub(crate) network_host: String,
pub(crate) network_port: u16,
}
pub(crate) fn configure() -> Result<Configuration> {
// Configure tracing (aka logging)
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.init();
// Parse command line arguments
let arguments = Arguments::parse();
// Set up default configuration values
let mut builder = Config::builder()
.set_default("content.metadata_file", "metadata.json")?
.set_default("email.from", "web-pylon@example.com")?
.set_default("email.live", false)?
.set_default("email.smtp_host", "localhost")?
.set_default("email.smtp_port", 25)?
.set_default("network.host", "localhost")?
.set_default("network.port", 8080)?;
// Prepare the list of configuration files to read
if !arguments.configuration_files.is_empty() {
for path in arguments.configuration_files {
tracing::debug!("Found configuration file: {}", path);
builder = builder.add_source(config::File::with_name(&path));
}
} else {
tracing::warn!("No configuration files detected; using default settings");
}
// Apply configuration parameters specified on the command line
for parameter in arguments.configuration_parameters {
tracing::debug!("Overriding parameter {}: {}", parameter.0, parameter.1);
builder = builder.set_override(parameter.0, parameter.1)?;
}
// Actually read the configuration files
let config = builder.build()?;
// Perform any per-parameter validation
let email_smtp_port =
match config.get_int("email.smtp_port")?.try_into() {
Ok(port) => Ok(port),
Err(e) => Err(Error::InvalidPort(e)),
}?;
let network_port =
match config.get_int("network.port")?.try_into() {
Ok(port) => Ok(port),
Err(e) => Err(Error::InvalidPort(e)),
}?;
Ok(Configuration {
content_metadata_file: config.get_string("content.metadata_file")?,
content_root: config.get_string("content.root")?.into(),
email_from: config.get_string("email.from")?,
email_live: config.get_bool("email.live")?,
email_smtp_host: config.get_string("email.smtp_host")?,
email_smtp_port,
network_host: config.get_string("network.host")?,
network_port,
})
}