config_load/lib.rs
1/*!
2The conditional configuration loader for Rust applications that use the `config` crate.
3
4```rust
5use std::path::Path;
6
7use config_load::config::builder::DefaultState;
8use config_load::config::{ConfigBuilder, Environment};
9use config_load::{ConfigLoader, FileLocation, Load};
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Default, Deserialize, Serialize)]
13#[serde(default)]
14struct AppConfig {
15 enabled: bool,
16 title: String,
17 attempts: u32,
18}
19
20impl Load for AppConfig {
21 fn load(config_builder: ConfigBuilder<DefaultState>) -> config_load::Result<Self> {
22 let config = config_builder
23 .add_source(Environment::with_prefix("APP").separator("_"))
24 .build()?;
25 config.try_deserialize()
26 }
27}
28
29let config_file = None; // Parsed from command line arguments, for example
30
31let config: AppConfig = ConfigLoader::default()
32 .add(
33 FileLocation::first_some_path()
34 .from_env("APP_ROOT_CONFIG")
35 .from_home(Path::new(".example_app").join("AppConfig.toml")),
36 )
37 .exclude_not_exists() // Exclude file pathes that don't exist, especially when APP_ROOT_CONFIG is set to ""
38 .add(
39 FileLocation::first_some_path()
40 .from_file(config_file)
41 .from_cwd_and_parents_exists("AppConfig.toml"),
42 )
43 .load()
44 .expect("Failed to load config");
45```
46 */
47use std::marker::PhantomData;
48use std::path::PathBuf;
49
50pub use config;
51use config::builder::{BuilderState, DefaultState};
52use config::{Config, ConfigBuilder, ConfigError};
53use either::Either;
54
55pub use crate::load::Load;
56pub use crate::location::Location;
57pub use crate::location::file::FileLocation;
58
59pub mod load;
60pub mod location;
61
62pub type Result<T> = std::result::Result<T, ConfigError>;
63
64#[derive(Debug, Default, Clone)]
65pub struct ConfigLoader<S: BuilderState = DefaultState> {
66 config_paths: Vec<PathBuf>,
67 _state: PhantomData<S>,
68}
69
70impl<S: BuilderState> ConfigLoader<S> {
71 pub fn new() -> Self {
72 Self {
73 config_paths: Vec::new(),
74 _state: PhantomData,
75 }
76 }
77
78 pub fn add(mut self, location: impl Location) -> Self {
79 match location.try_into_path() {
80 Either::Left(path) => self.config_paths.push(path),
81 Either::Right(_) => (),
82 }
83 self
84 }
85
86 pub fn exclude_not_exists(mut self) -> Self {
87 self.config_paths.retain(|path| path.is_file());
88 self
89 }
90}
91
92impl ConfigLoader<DefaultState> {
93 pub fn builder(self) -> ConfigBuilder<DefaultState> {
94 let mut config_builder = Config::builder();
95 for path in self.config_paths {
96 config_builder = config_builder.add_source(config::File::from(path))
97 }
98 config_builder
99 }
100
101 pub fn load<T: Load<DefaultState>>(self) -> Result<T> {
102 let config_builder = self.builder();
103 T::load(config_builder)
104 }
105}