Skip to content

Commit c708d44

Browse files
szarykottmatthiasbeyer
authored andcommitted
Reimplement the Config building mechanism
This patch rewrites the Config building mechanism using special objects for tracking the config building state. Transitions between states are done on the fly as required. This is required so that the async sources can be stored inside the configuration building objects, while keeping out the expenses in the non-async case, so a user of the crate has only to pay for what they are using (no async means no overhead for that). Signed-off-by: Matthias Beyer <[email protected]> Reviewed-by: Matthias Beyer <[email protected]>
1 parent 48e4a66 commit c708d44

File tree

3 files changed

+216
-25
lines changed

3 files changed

+216
-25
lines changed

src/builder.rs

Lines changed: 211 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::str::FromStr;
22
use std::{collections::HashMap, iter::IntoIterator};
33

44
use crate::error::Result;
5+
use crate::source::AsyncSource;
56
use crate::{config::Config, path::Expression, source::Source, value::Value};
67

78
/// A configuration builder
@@ -22,15 +23,24 @@ use crate::{config::Config, path::Expression, source::Source, value::Value};
2223
/// It happens on demand when [`build`](Self::build) (or its alternative) is called.
2324
/// Therefore all errors, related to any of the [`Source`] will only show up then.
2425
///
26+
/// # Sync and async builder
27+
///
28+
/// [`ConfigBuilder`] uses type parameter to keep track of builder state.
29+
///
30+
/// In [`DefaultState`] builder only supports [`Source`]s
31+
///
32+
/// In [`AsyncState`] it supports both [`Source`]s and [`AsyncSource`]s at the price of building using `async fn`.
33+
///
2534
/// # Examples
2635
///
2736
/// ```rust
2837
/// # use config::*;
2938
/// # use std::error::Error;
3039
/// # fn main() -> Result<(), Box<dyn Error>> {
31-
/// let mut builder = ConfigBuilder::default()
40+
/// let mut builder = Config::builder()
3241
/// .set_default("default", "1")?
3342
/// .add_source(File::new("config/settings", FileFormat::Json))
43+
/// // .add_async_source(...)
3444
/// .set_override("override", "1")?;
3545
///
3646
/// match builder.build() {
@@ -45,35 +55,102 @@ use crate::{config::Config, path::Expression, source::Source, value::Value};
4555
/// # }
4656
/// ```
4757
///
58+
/// If any [`AsyncSource`] is used, the builder will transition to [`AsyncState`].
59+
/// In such case, it is required to _await_ calls to [`build`](Self::build) and its non-consuming sibling.
60+
///
4861
/// Calls can be not chained as well
4962
/// ```rust
5063
/// # use std::error::Error;
5164
/// # use config::*;
5265
/// # fn main() -> Result<(), Box<dyn Error>> {
53-
/// let mut builder = ConfigBuilder::default();
66+
/// let mut builder = Config::builder();
5467
/// builder = builder.set_default("default", "1")?;
5568
/// builder = builder.add_source(File::new("config/settings", FileFormat::Json));
5669
/// builder = builder.add_source(File::new("config/settings.prod", FileFormat::Json));
5770
/// builder = builder.set_override("override", "1")?;
5871
/// # Ok(())
5972
/// # }
6073
/// ```
74+
///
75+
/// Calling [`Config::builder`](Config::builder) yields builder in the default state.
76+
/// If having an asynchronous state as the initial state is desired, _turbofish_ notation needs to be used.
77+
/// ```rust
78+
/// # use config::{*, builder::AsyncState};
79+
/// let mut builder = ConfigBuilder::<AsyncState>::default();
80+
/// ```
81+
///
82+
/// If for some reason acquiring builder in default state is required without calling [`Config::builder`](Config::builder)
83+
/// it can also be achieved.
84+
/// ```rust
85+
/// # use config::{*, builder::DefaultState};
86+
/// let mut builder = ConfigBuilder::<DefaultState>::default();
87+
/// ```
6188
#[derive(Debug, Clone, Default)]
62-
pub struct ConfigBuilder {
89+
pub struct ConfigBuilder<St: BuilderState> {
6390
defaults: HashMap<Expression, Value>,
6491
overrides: HashMap<Expression, Value>,
92+
state: St,
93+
}
94+
95+
/// Represents [`ConfigBuilder`] state.
96+
pub trait BuilderState {}
97+
98+
/// Represents data specific to builder in default, sychronous state, without support for async.
99+
#[derive(Debug, Default)]
100+
pub struct DefaultState {
65101
sources: Vec<Box<dyn Source + Send + Sync>>,
66102
}
67103

68-
impl ConfigBuilder {
104+
/// The asynchronous configuration builder.
105+
///
106+
/// Similar to a [`ConfigBuilder`] it maintains a set of defaults, a set of sources, and overrides.
107+
///
108+
/// Defaults do not override anything, sources override defaults, and overrides override anything else.
109+
/// Within those three groups order of adding them at call site matters - entities added later take precedence.
110+
///
111+
/// For more detailed description and examples see [`ConfigBuilder`].
112+
/// [`AsyncConfigBuilder`] is just an extension of it that takes async functions into account.
113+
///
114+
/// To obtain a [`Config`] call [`build`](AsyncConfigBuilder::build) or [`build_cloned`](AsyncConfigBuilder::build_cloned)
115+
///
116+
/// # Example
117+
/// Since this library does not implement any [`AsyncSource`] an example in rustdocs cannot be given.
118+
/// Detailed explanation about why such a source is not implemented is in [`AsyncSource`]'s documentation.
119+
///
120+
/// Refer to [`ConfigBuilder`] for similar API sample usage or to the examples folder of the crate, where such a source is implemented.
121+
#[derive(Debug, Clone, Default)]
122+
pub struct AsyncConfigBuilder {
123+
defaults: HashMap<Expression, Value>,
124+
overrides: HashMap<Expression, Value>,
125+
sources: Vec<SourceType>,
126+
}
127+
128+
/// Represents data specific to builder in asychronous state, with support for async.
129+
#[derive(Debug, Default)]
130+
pub struct AsyncState {
131+
sources: Vec<SourceType>,
132+
}
133+
134+
#[derive(Debug, Clone)]
135+
enum SourceType {
136+
Sync(Box<dyn Source + Send + Sync>),
137+
Async(Box<dyn AsyncSource + Send + Sync>),
138+
}
139+
140+
impl BuilderState for DefaultState {}
141+
impl BuilderState for AsyncState {}
142+
143+
impl<St: BuilderState> ConfigBuilder<St> {
144+
// operations allowed in any state
145+
69146
/// Set a default `value` at `key`
70147
///
71-
/// This value can be overwritten by any [`Source`] or override.
148+
/// This value can be overwritten by any [`Source`], [`AsyncSource`] or override.
72149
///
73150
/// # Errors
74151
///
75152
/// Fails if `Expression::from_str(key)` fails.
76-
pub fn set_default<S, T>(mut self, key: S, value: T) -> Result<ConfigBuilder>
153+
pub fn set_default<S, T>(mut self, key: S, value: T) -> Result<ConfigBuilder<St>>
77154
where
78155
S: AsRef<str>,
79156
T: Into<Value>,
@@ -83,25 +160,14 @@ impl ConfigBuilder {
83160
Ok(self)
84161
}
85162

86-
/// Registers new [`Source`] in this builder.
87-
///
88-
/// Calling this method does not invoke any I/O. [`Source`] is only saved in internal register for later use.
89-
pub fn add_source<T>(mut self, source: T) -> Self
90-
where
91-
T: Source + Send + Sync + 'static,
92-
{
93-
self.sources.push(Box::new(source));
94-
self
95-
}
96-
97163
/// Set an override
98164
///
99-
/// This function sets an overwrite value. It will not be altered by any default or [`Source`]
165+
/// This function sets an overwrite value. It will not be altered by any default, [`Source`] nor [`AsyncSource`]
100166
///
101167
/// # Errors
102168
///
103169
/// Fails if `Expression::from_str(key)` fails.
104-
pub fn set_override<S, T>(mut self, key: S, value: T) -> Result<ConfigBuilder>
170+
pub fn set_override<S, T>(mut self, key: S, value: T) -> Result<ConfigBuilder<St>>
105171
where
106172
S: AsRef<str>,
107173
T: Into<Value>,
@@ -110,6 +176,44 @@ impl ConfigBuilder {
110176
.insert(Expression::from_str(key.as_ref())?, value.into());
111177
Ok(self)
112178
}
179+
}
180+
181+
impl ConfigBuilder<DefaultState> {
182+
// operations allowed in sync state
183+
184+
/// Registers new [`Source`] in this builder.
185+
///
186+
/// Calling this method does not invoke any I/O. [`Source`] is only saved in internal register for later use.
187+
pub fn add_source<T>(mut self, source: T) -> Self
188+
where
189+
T: Source + Send + Sync + 'static,
190+
{
191+
self.state.sources.push(Box::new(source));
192+
self
193+
}
194+
195+
/// Registers new [`AsyncSource`] in this builder and forces transition to [`AsyncState`].
196+
///
197+
/// Calling this method does not invoke any I/O. [`AsyncSource`] is only saved in internal register for later use.
198+
pub fn add_async_source<T>(self, source: T) -> ConfigBuilder<AsyncState>
199+
where
200+
T: AsyncSource + Send + Sync + 'static,
201+
{
202+
let async_state = ConfigBuilder {
203+
state: AsyncState {
204+
sources: self
205+
.state
206+
.sources
207+
.into_iter()
208+
.map(|s| SourceType::Sync(s))
209+
.collect(),
210+
},
211+
defaults: self.defaults,
212+
overrides: self.overrides,
213+
};
214+
215+
async_state.add_async_source(source)
216+
}
113217

114218
/// Reads all registered [`Source`]s.
115219
///
@@ -120,7 +224,7 @@ impl ConfigBuilder {
120224
/// If source collection fails, be it technical reasons or related to inability to read data as `Config` for different reasons,
121225
/// this method returns error.
122226
pub fn build(self) -> Result<Config> {
123-
Self::build_internal(self.defaults, self.overrides, &self.sources)
227+
Self::build_internal(self.defaults, self.overrides, &self.state.sources)
124228
}
125229

126230
/// Reads all registered [`Source`]s.
@@ -132,7 +236,11 @@ impl ConfigBuilder {
132236
/// If source collection fails, be it technical reasons or related to inability to read data as `Config` for different reasons,
133237
/// this method returns error.
134238
pub fn build_cloned(&self) -> Result<Config> {
135-
Self::build_internal(self.defaults.clone(), self.overrides.clone(), &self.sources)
239+
Self::build_internal(
240+
self.defaults.clone(),
241+
self.overrides.clone(),
242+
&self.state.sources,
243+
)
136244
}
137245

138246
fn build_internal(
@@ -158,3 +266,85 @@ impl ConfigBuilder {
158266
Ok(Config::new(cache))
159267
}
160268
}
269+
270+
impl ConfigBuilder<AsyncState> {
271+
// operations allowed in async state
272+
273+
/// Registers new [`Source`] in this builder.
274+
///
275+
/// Calling this method does not invoke any I/O. [`Source`] is only saved in internal register for later use.
276+
pub fn add_source<T>(mut self, source: T) -> ConfigBuilder<AsyncState>
277+
where
278+
T: Source + Send + Sync + 'static,
279+
{
280+
self.state.sources.push(SourceType::Sync(Box::new(source)));
281+
self
282+
}
283+
284+
/// Registers new [`AsyncSource`] in this builder.
285+
///
286+
/// Calling this method does not invoke any I/O. [`AsyncSource`] is only saved in internal register for later use.
287+
pub fn add_async_source<T>(mut self, source: T) -> ConfigBuilder<AsyncState>
288+
where
289+
T: AsyncSource + Send + Sync + 'static,
290+
{
291+
self.state.sources.push(SourceType::Async(Box::new(source)));
292+
self
293+
}
294+
295+
/// Reads all registered defaults, [`Source`]s, [`AsyncSource`]s and overrides.
296+
///
297+
/// This is the method that invokes all I/O operations.
298+
/// For a non consuming alternative see [`build_cloned`](Self::build_cloned)
299+
///
300+
/// # Errors
301+
/// If source collection fails, be it technical reasons or related to inability to read data as `Config` for different reasons,
302+
/// this method returns error.
303+
pub async fn build(self) -> Result<Config> {
304+
Self::build_internal(self.defaults, self.overrides, &self.state.sources).await
305+
}
306+
307+
/// Reads all registered defaults, [`Source`]s, [`AsyncSource`]s and overrides.
308+
///
309+
/// Similar to [`build`](Self::build), but it does not take ownership of `ConfigBuilder` to allow later reuse.
310+
/// Internally it clones data to achieve it.
311+
///
312+
/// # Errors
313+
/// If source collection fails, be it technical reasons or related to inability to read data as `Config` for different reasons,
314+
/// this method returns error.
315+
pub async fn build_cloned(&self) -> Result<Config> {
316+
Self::build_internal(
317+
self.defaults.clone(),
318+
self.overrides.clone(),
319+
&self.state.sources,
320+
)
321+
.await
322+
}
323+
324+
async fn build_internal(
325+
defaults: HashMap<Expression, Value>,
326+
overrides: HashMap<Expression, Value>,
327+
sources: &[SourceType],
328+
) -> Result<Config> {
329+
let mut cache: Value = HashMap::<String, Value>::new().into();
330+
331+
// Add defaults
332+
for (key, val) in defaults.into_iter() {
333+
key.set(&mut cache, val);
334+
}
335+
336+
for source in sources.iter() {
337+
match source {
338+
SourceType::Sync(source) => source.collect_to(&mut cache)?,
339+
SourceType::Async(source) => source.collect_to(&mut cache).await?,
340+
}
341+
}
342+
343+
// Add overrides
344+
for (key, val) in overrides.into_iter() {
345+
key.set(&mut cache, val);
346+
}
347+
348+
Ok(Config::new(cache))
349+
}
350+
}

src/config.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::collections::HashMap;
22
use std::fmt::Debug;
33

4-
use crate::builder::ConfigBuilder;
4+
use crate::builder::{ConfigBuilder, DefaultState};
55
use serde::de::Deserialize;
66
use serde::ser::Serialize;
77

@@ -44,8 +44,8 @@ impl Config {
4444
}
4545

4646
/// Creates new [`ConfigBuilder`] instance
47-
pub fn builder() -> ConfigBuilder {
48-
ConfigBuilder::default()
47+
pub fn builder() -> ConfigBuilder<DefaultState> {
48+
ConfigBuilder::<DefaultState>::default()
4949
}
5050

5151
/// Merge in a configuration property source.

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ extern crate ron;
5353
#[cfg(feature = "json5")]
5454
extern crate json5_rs;
5555

56-
mod builder;
56+
pub mod builder;
5757
mod config;
5858
mod de;
5959
mod env;
@@ -64,6 +64,7 @@ mod ser;
6464
mod source;
6565
mod value;
6666

67+
pub use crate::builder::AsyncConfigBuilder;
6768
pub use crate::builder::ConfigBuilder;
6869
pub use crate::config::Config;
6970
pub use crate::env::Environment;

0 commit comments

Comments
 (0)