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
//! File-based application configuration.
//!
//! # Quick start
//! ```no_run
//! use volga::{App, Config};
//! use serde::Deserialize;
//!
//! #[derive(Deserialize)]
//! struct Database { url: String }
//!
//! #[tokio::main]
//! async fn main() -> std::io::Result<()> {
//! let app = App::new()
//! .with_config(|cfg| cfg.with_file("app_config.toml").bind_section::<Database>("database"));
//! app.run().await
//! }
//! ```
pub(crate) mod builder;
pub(crate) mod extractor;
pub(crate) mod processing;
pub(crate) mod store;
pub use builder::ConfigBuilder;
pub use extractor::Config;
pub use store::{ConfigStore, SectionKind};
use crate::App;
impl App {
/// Loads configuration from the default file (`app_config.toml` or `app_config.json`).
///
/// Searches the current working directory in order: `app_config.toml`, then `app_config.json`.
///
/// **Strict:** panics at startup if neither file exists or if config processing fails.
/// If you want optional file-based config, use [`App::with_config`] directly.
///
/// # Panics
///
/// Panics if no default config file is found or if the config fails to load or parse.
pub fn with_default_config(self) -> Self {
let path = builder::get_default_file().unwrap_or_else(|| {
panic!(
"config: with_default_config() found neither app_config.toml nor app_config.json"
)
});
self.process_config(ConfigBuilder::from_file(path))
.unwrap_or_else(|e| panic!("config: {e}"))
}
/// Configures file-based configuration via a builder closure.
///
/// # Example
/// ```no_run
/// use volga::App;
/// use serde::Deserialize;
/// #[derive(Deserialize)] struct Database { url: String }
///
/// #[tokio::main]
/// async fn main() -> std::io::Result<()> {
/// let app = App::new().with_config(|cfg| {
/// cfg.with_file("config/prod.toml")
/// .bind_section::<Database>("database")
/// .reload_on_change()
/// });
/// app.run().await
/// }
/// ```
///
/// # Panics
///
/// Panics if the config file cannot be read, parsed, or if any required section is missing.
pub fn with_config<F>(self, f: F) -> Self
where
F: FnOnce(ConfigBuilder) -> ConfigBuilder,
{
self.process_config(f(ConfigBuilder::new()))
.unwrap_or_else(|e| panic!("config: {e}"))
}
/// Sets the file-based configuration
///
/// # Example
/// ```no_run
/// use volga::{App, ConfigBuilder};
/// use serde::Deserialize;
/// #[derive(Deserialize)] struct Database { url: String }
///
/// #[tokio::main]
/// async fn main() -> std::io::Result<()> {
/// let config = ConfigBuilder::from_file("config/prod.toml")
/// .bind_section::<Database>("database")
/// .reload_on_change();
///
/// let app = App::new().set_config(config);
/// app.run().await
/// }
/// ```
pub fn set_config(self, config: ConfigBuilder) -> Self {
self.process_config(config)
.unwrap_or_else(|e| panic!("config: {e}"))
}
}
#[cfg(test)]
mod tests {
use crate::App;
#[test]
#[should_panic(expected = "config:")]
fn with_default_config_panics_when_no_default_file() {
// This test relies on neither app_config.toml nor app_config.json
// existing in the current working directory during test runs.
App::new().with_default_config();
}
}