configulator/
configulator.rs1use std::marker::PhantomData;
2#[cfg(feature = "file")]
3use std::path::PathBuf;
4
5#[cfg(feature = "cli")]
6use crate::cli;
7use crate::defaults;
8#[cfg(feature = "env")]
9use crate::environment;
10use crate::error::ConfigulatorError;
11#[cfg(feature = "file")]
12use crate::file;
13#[cfg(feature = "file")]
14use crate::options::FileOptions;
15#[cfg(feature = "env")]
16use crate::options::EnvironmentVariableOptions;
17#[cfg(feature = "cli")]
18use crate::options::CLIFlagOptions;
19use crate::value_map::{merge_value_maps, ValueMap};
20#[cfg(all(feature = "cli", feature = "file"))]
21use crate::value_map::ConfigValue;
22use crate::{ConfigFields, FromValueMap, Validate};
23
24pub struct Configulator<C> {
30 #[cfg(feature = "file")]
31 file_opts: Option<FileOptions>,
32 #[cfg(feature = "env")]
33 env_opts: Option<EnvironmentVariableOptions>,
34 #[cfg(feature = "cli")]
35 cli_opts: Option<CLIFlagOptions>,
36 #[cfg(feature = "testing")]
37 cli_args: Option<Vec<String>>,
38 #[cfg(feature = "cli")]
39 cli_command: Option<clap::Command>,
40 _marker: PhantomData<C>,
41}
42
43impl<C: ConfigFields + FromValueMap + Default> Configulator<C> {
44 #[must_use]
46 pub fn new() -> Self {
47 Self {
48 #[cfg(feature = "file")]
49 file_opts: None,
50 #[cfg(feature = "env")]
51 env_opts: None,
52 #[cfg(feature = "cli")]
53 cli_opts: None,
54 #[cfg(feature = "testing")]
55 cli_args: None,
56 #[cfg(feature = "cli")]
57 cli_command: None,
58 _marker: PhantomData,
59 }
60 }
61
62 #[cfg(feature = "file")]
68 #[must_use]
69 pub fn with_file(mut self, opts: FileOptions) -> Self {
70 self.file_opts = Some(opts);
71 self
72 }
73
74 #[cfg(feature = "env")]
76 #[must_use]
77 pub fn with_environment_variables(mut self, opts: EnvironmentVariableOptions) -> Self {
78 self.env_opts = Some(opts);
79 self
80 }
81
82 #[cfg(feature = "cli")]
84 #[must_use]
85 pub fn with_cli_flags(mut self, opts: CLIFlagOptions) -> Self {
86 self.cli_opts = Some(opts);
87 self
88 }
89
90 #[cfg(feature = "testing")]
92 #[must_use]
93 pub fn with_cli_args(mut self, args: Vec<String>) -> Self {
94 self.cli_args = Some(args);
95 self
96 }
97
98 #[cfg(feature = "cli")]
107 #[must_use]
108 pub fn with_cli_command(mut self, cmd: clap::Command) -> Self {
109 self.cli_command = Some(cmd);
110 self
111 }
112
113 pub fn load(self) -> Result<C, ConfigulatorError>
115 where
116 C: Validate,
117 {
118 let config = self.load_without_validation()?;
119 config
120 .validate()
121 .map_err(ConfigulatorError::ValidationError)?;
122 Ok(config)
123 }
124
125 pub fn load_without_validation(self) -> Result<C, ConfigulatorError> {
127 let fields = C::configulator_fields();
128 let mut merged = ValueMap::new();
129
130 let defaults = defaults::load_defaults(&fields);
132 merge_value_maps(&mut merged, &defaults);
133
134 #[cfg(feature = "cli")]
136 let cli_values = if let Some(ref opts) = self.cli_opts {
137 let args = self.get_cli_args();
138 let has_file = {
139 #[cfg(feature = "file")]
140 { self.file_opts.is_some() }
141 #[cfg(not(feature = "file"))]
142 { false }
143 };
144 Some(cli::load_from_cli(opts, &fields, &args, has_file, self.cli_command.clone())?)
145 } else {
146 None
147 };
148 #[cfg(not(feature = "cli"))]
149 let cli_values = None::<ValueMap>;
150
151 #[cfg(feature = "file")]
153 {
154 let mut file_opts = self.file_opts;
155 #[cfg(feature = "cli")]
157 if let Some(ref mut opts) = file_opts {
158 if let Some(ref cli_vals) = cli_values {
159 if let Some(ConfigValue::Scalar(path)) = cli_vals.get("__config_file__") {
160 opts.paths.insert(0, PathBuf::from(path));
161 }
162 }
163 }
164 if let Some(ref opts) = file_opts {
165 let file_values = file::load_from_file(opts)?;
166 merge_value_maps(&mut merged, &file_values);
167 }
168 }
169
170 #[cfg(feature = "env")]
172 if let Some(ref opts) = self.env_opts {
173 let env_values = environment::load_from_env(opts, &fields);
174 merge_value_maps(&mut merged, &env_values);
175 }
176
177 #[cfg(feature = "cli")]
179 if let Some(mut cli_values) = cli_values {
180 cli_values.remove("__config_file__");
181 merge_value_maps(&mut merged, &cli_values);
182 }
183
184 C::from_value_map(&merged)
185 }
186
187 pub fn defaults_only() -> Result<C, ConfigulatorError> {
192 let fields = C::configulator_fields();
193 let defaults = defaults::load_defaults(&fields);
194 C::from_value_map(&defaults)
195 }
196
197 #[cfg(feature = "cli")]
198 fn get_cli_args(&self) -> Vec<String> {
199 #[cfg(feature = "testing")]
200 if let Some(args) = &self.cli_args {
201 return args.clone();
202 }
203 std::env::args().skip(1).collect()
204 }
205}
206
207impl<C: ConfigFields + FromValueMap + Default> Default for Configulator<C> {
208 fn default() -> Self {
209 Self::new()
210 }
211}