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
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 }; #[derive(Parser)] #[command(author, version, 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 struct Configuration { pub application_name: String, pub git_root: PathBuf, pub network_host: String, pub network_port: u16, } pub 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("application.name", "Gitten")? .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 network_port = match config.get_int("network.port")?.try_into() { Ok(port) => Ok(port), Err(e) => Err(Error::InvalidPort(e)), }?; Ok(Configuration { application_name: config.get_string("application.name")?, git_root: config.get_string("git.root")?.into(), network_host: config.get_string("network.host")?, network_port, }) }