diff --git a/examples/async_source/main.rs b/examples/async_source.rs similarity index 87% rename from examples/async_source/main.rs rename to examples/async_source.rs index 9964b730..d8657ab2 100644 --- a/examples/async_source/main.rs +++ b/examples/async_source.rs @@ -1,3 +1,5 @@ +//! Example below presents sample configuration server and client. + use std::{error::Error, fmt::Debug}; use async_trait::async_trait; @@ -7,11 +9,6 @@ use config::{ use futures::{FutureExt, select}; use warp::Filter; -// Example below presents sample configuration server and client. -// -// Server serves simple configuration on HTTP endpoint. -// Client consumes it using custom HTTP AsyncSource built on top of reqwest. - #[tokio::main] async fn main() -> Result<(), Box> { select! { @@ -20,6 +17,7 @@ async fn main() -> Result<(), Box> { } } +/// Serve simple configuration on HTTP endpoint. async fn run_server() -> Result<(), Box> { let service = warp::path("configuration").map(|| r#"{ "value" : 123 }"#); @@ -30,6 +28,7 @@ async fn run_server() -> Result<(), Box> { Ok(()) } +/// Consumes the server's configuration using custom HTTP `AsyncSource` built on top of reqwest. async fn run_client() -> Result<(), Box> { // Good enough for an example to allow server to start tokio::time::sleep(tokio::time::Duration::from_secs(3)).await; @@ -47,8 +46,7 @@ async fn run_client() -> Result<(), Box> { Ok(()) } -// Actual implementation of AsyncSource can be found below - +/// `AsyncSource` to read configuration from an HTTP server #[derive(Debug)] struct HttpSource { uri: String, diff --git a/examples/custom_file_format/main.rs b/examples/custom_file_format/main.rs index 2650664b..e022416b 100644 --- a/examples/custom_file_format/main.rs +++ b/examples/custom_file_format/main.rs @@ -24,6 +24,7 @@ fn main() { // Deserialize the config object into your Settings struct: let settings: Settings = settings.try_deserialize().unwrap(); + println!("{settings:#?}"); } @@ -65,8 +66,7 @@ impl Format for PemFile { } } -// A slice of extensions associated to this format, when an extension -// is omitted from a file source, these will be tried implicitly: +// When an extension is omitted from a file source, these will be tried implicitly: impl FileStoredFormat for PemFile { fn file_extensions(&self) -> &'static [&'static str] { &["pem"] diff --git a/examples/custom_str_format/main.rs b/examples/custom_str_format.rs similarity index 70% rename from examples/custom_str_format/main.rs rename to examples/custom_str_format.rs index e78f2a9e..ccce440b 100644 --- a/examples/custom_str_format/main.rs +++ b/examples/custom_str_format.rs @@ -2,8 +2,8 @@ use config::{Config, File, FileStoredFormat, Format, Map, Value, ValueKind}; fn main() { let config = Config::builder() - .add_source(File::from_str("bad", MyFormat)) - .add_source(File::from_str("good", MyFormat)) + .add_source(File::from_str("bad", CustomFormat)) + .add_source(File::from_str("good", CustomFormat)) .build(); match config { @@ -13,9 +13,9 @@ fn main() { } #[derive(Debug, Clone)] -pub struct MyFormat; +pub struct CustomFormat; -impl Format for MyFormat { +impl Format for CustomFormat { fn parse( &self, uri: Option<&String>, @@ -40,11 +40,14 @@ impl Format for MyFormat { } } -// As strange as it seems for config sourced from a string, legacy demands its sacrifice -// It is only required for File source, custom sources can use Format without caring for extensions -static MY_FORMAT_EXT: Vec<&'static str> = vec![]; -impl FileStoredFormat for MyFormat { +impl FileStoredFormat for CustomFormat { fn file_extensions(&self) -> &'static [&'static str] { - &MY_FORMAT_EXT + &NO_EXTS } } + +/// In-memory format doesn't have any file extensions +/// +/// It is only required for File source, +/// custom sources can use Format without caring for extensions +static NO_EXTS: Vec<&'static str> = vec![]; diff --git a/examples/env-list/main.rs b/examples/env-list/main.rs deleted file mode 100644 index e7968ca2..00000000 --- a/examples/env-list/main.rs +++ /dev/null @@ -1,24 +0,0 @@ -use config::Config; - -#[derive(Debug, Default, serde::Deserialize, PartialEq, Eq)] -struct AppConfig { - list: Vec, -} - -fn main() { - // e.g. set `APP_LIST="Hello World" - - let config = Config::builder() - .add_source( - config::Environment::with_prefix("APP") - .try_parsing(true) - .separator("_") - .list_separator(" "), - ) - .build() - .unwrap(); - - let app: AppConfig = config.try_deserialize().unwrap(); - - assert_eq!(app.list, vec![String::from("Hello"), String::from("World")]); -} diff --git a/examples/glob/main.rs b/examples/glob/main.rs deleted file mode 100644 index 75780c8f..00000000 --- a/examples/glob/main.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::collections::HashMap; -use std::path::Path; - -use config::{Config, File}; -use glob::glob; - -fn main() { - // Option 1 - // -------- - // Gather all conf files from conf/ manually - let settings = Config::builder() - // File::with_name(..) is shorthand for File::from(Path::new(..)) - .add_source(File::with_name("examples/glob/conf/00-default.toml")) - .add_source(File::from(Path::new("examples/glob/conf/05-some.yml"))) - .add_source(File::from(Path::new("examples/glob/conf/99-extra.json"))) - .build() - .unwrap(); - - // Print out our settings (as a HashMap) - println!( - "\n{:?} \n\n-----------", - settings - .try_deserialize::>() - .unwrap() - ); - - // Option 2 - // -------- - // Gather all conf files from conf/ manually, but put in 1 merge call. - let settings = Config::builder() - .add_source(vec![ - File::with_name("examples/glob/conf/00-default.toml"), - File::from(Path::new("examples/glob/conf/05-some.yml")), - File::from(Path::new("examples/glob/conf/99-extra.json")), - ]) - .build() - .unwrap(); - - // Print out our settings (as a HashMap) - println!( - "\n{:?} \n\n-----------", - settings - .try_deserialize::>() - .unwrap() - ); - - // Option 3 - // -------- - // Gather all conf files from conf/ using glob and put in 1 merge call. - let settings = Config::builder() - .add_source( - glob("examples/glob/conf/*") - .unwrap() - .map(|path| File::from(path.unwrap())) - .collect::>(), - ) - .build() - .unwrap(); - - // Print out our settings (as a HashMap) - println!( - "\n{:?} \n\n-----------", - settings - .try_deserialize::>() - .unwrap() - ); -} diff --git a/examples/hierarchical-env/main.rs b/examples/hierarchical-env/main.rs deleted file mode 100644 index 08409494..00000000 --- a/examples/hierarchical-env/main.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod settings; - -use settings::Settings; - -fn main() { - let settings = Settings::new(); - - // Print out our settings - println!("{settings:?}"); -} diff --git a/examples/hierarchical-env/config/default.toml b/examples/modal/config/default.toml similarity index 100% rename from examples/hierarchical-env/config/default.toml rename to examples/modal/config/default.toml diff --git a/examples/hierarchical-env/config/development.toml b/examples/modal/config/development.toml similarity index 100% rename from examples/hierarchical-env/config/development.toml rename to examples/modal/config/development.toml diff --git a/examples/hierarchical-env/config/production.toml b/examples/modal/config/production.toml similarity index 100% rename from examples/hierarchical-env/config/production.toml rename to examples/modal/config/production.toml diff --git a/examples/hierarchical-env/settings.rs b/examples/modal/main.rs similarity index 92% rename from examples/hierarchical-env/settings.rs rename to examples/modal/main.rs index 6e6c859f..43421152 100644 --- a/examples/hierarchical-env/settings.rs +++ b/examples/modal/main.rs @@ -3,34 +3,11 @@ use std::env; use config::{Config, ConfigError, Environment, File}; use serde::Deserialize; -#[derive(Debug, Deserialize)] -#[allow(unused)] -struct Database { - url: String, -} +fn main() { + let settings = Settings::new(); -#[derive(Debug, Deserialize)] -#[allow(unused)] -struct Sparkpost { - key: String, - token: String, - url: String, - version: u8, -} - -#[derive(Debug, Deserialize)] -#[allow(unused)] -struct Twitter { - consumer_token: String, - consumer_secret: String, -} - -#[derive(Debug, Deserialize)] -#[allow(unused)] -struct Braintree { - merchant_id: String, - public_key: String, - private_key: String, + // Print out our settings + println!("{settings:?}"); } #[derive(Debug, Deserialize)] @@ -62,7 +39,7 @@ impl Settings { .add_source(File::with_name("examples/hierarchical-env/config/local").required(false)) // Add in settings from the environment (with a prefix of APP) // Eg.. `APP_DEBUG=1 ./target/app` would set the `debug` key - .add_source(Environment::with_prefix("app")) + .add_source(Environment::with_prefix("APP")) // You may also programmatically change settings .set_override("database.url", "postgres://")? .build()?; @@ -75,3 +52,33 @@ impl Settings { s.try_deserialize() } } + +#[derive(Debug, Deserialize)] +#[allow(unused)] +struct Database { + url: String, +} + +#[derive(Debug, Deserialize)] +#[allow(unused)] +struct Sparkpost { + key: String, + token: String, + url: String, + version: u8, +} + +#[derive(Debug, Deserialize)] +#[allow(unused)] +struct Twitter { + consumer_token: String, + consumer_secret: String, +} + +#[derive(Debug, Deserialize)] +#[allow(unused)] +struct Braintree { + merchant_id: String, + public_key: String, + private_key: String, +} diff --git a/examples/glob/conf/00-default.toml b/examples/priority/conf.d/00-default.toml similarity index 100% rename from examples/glob/conf/00-default.toml rename to examples/priority/conf.d/00-default.toml diff --git a/examples/glob/conf/05-some.yml b/examples/priority/conf.d/05-some.yml similarity index 100% rename from examples/glob/conf/05-some.yml rename to examples/priority/conf.d/05-some.yml diff --git a/examples/glob/conf/99-extra.json b/examples/priority/conf.d/99-extra.json similarity index 100% rename from examples/glob/conf/99-extra.json rename to examples/priority/conf.d/99-extra.json diff --git a/examples/priority/main.rs b/examples/priority/main.rs new file mode 100644 index 00000000..7a865dbf --- /dev/null +++ b/examples/priority/main.rs @@ -0,0 +1,23 @@ +//! Gather all conf files from conf.d/ using glob and put in 1 merge call. + +use std::collections::HashMap; + +use config::{Config, File}; +use glob::glob; + +fn main() { + // Glob results are sorted, ensuring user priority is preserved + let files = glob("examples/glob/conf.d/*") + .unwrap() + .map(|path| File::from(path.unwrap())) + .collect::>(); + let settings = Config::builder().add_source(files).build().unwrap(); + + // Print out our settings (as a HashMap) + println!( + "\n{:?} \n\n-----------", + settings + .try_deserialize::>() + .unwrap() + ); +} diff --git a/examples/simple/Settings.toml b/examples/settings.toml similarity index 100% rename from examples/simple/Settings.toml rename to examples/settings.toml diff --git a/examples/simple/main.rs b/examples/simple.rs similarity index 82% rename from examples/simple/main.rs rename to examples/simple.rs index f3fec111..ca0d2f2a 100644 --- a/examples/simple/main.rs +++ b/examples/simple.rs @@ -4,8 +4,8 @@ use config::Config; fn main() { let settings = Config::builder() - // Add in `./Settings.toml` - .add_source(config::File::with_name("examples/simple/Settings")) + // Add in `./settings.toml` + .add_source(config::File::with_name("examples/settings")) // Add in settings from the environment (with a prefix of APP) // Eg.. `APP_DEBUG=1 ./target/app` would set the `debug` key .add_source(config::Environment::with_prefix("APP")) diff --git a/examples/static_env.rs b/examples/static_env.rs index e95cd532..942741e9 100644 --- a/examples/static_env.rs +++ b/examples/static_env.rs @@ -1,24 +1,31 @@ +//! Use `config` as a higher-level API over `std::env::var_os` + use std::sync::OnceLock; use config::Config; +fn main() { + println!("APP_STRING={:?}", get::("string")); + println!("APP_INT={:?}", get::("int")); + println!("APP_STRLIST={:?}", get::>("strlist")); +} + +/// Get a configuration value from the environment +pub fn get<'a, T: serde::Deserialize<'a>>(path: &str) -> Option { + config().get::(path).ok() +} + fn config() -> &'static Config { static CONFIG: OnceLock = OnceLock::new(); CONFIG.get_or_init(|| { Config::builder() - .add_source(config::Environment::with_prefix("APP_NAME").separator("_")) + .add_source( + config::Environment::with_prefix("APP") + .try_parsing(true) + .separator("_") + .list_separator(","), + ) .build() .unwrap() }) } - -/// Get a configuration value from the static configuration object -pub fn get<'a, T: serde::Deserialize<'a>>(key: &str) -> T { - // You shouldn't probably do it like that and actually handle that error that might happen - // here, but for the sake of simplicity, we do it like this here - config().get::(key).unwrap() -} - -fn main() { - println!("{:?}", get::("foo")); -} diff --git a/examples/watch/main.rs b/examples/watch.rs similarity index 79% rename from examples/watch/main.rs rename to examples/watch.rs index 6b2cce33..f59c026f 100644 --- a/examples/watch/main.rs +++ b/examples/watch.rs @@ -8,36 +8,8 @@ use std::time::Duration; use config::{Config, File}; use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher}; -fn settings() -> &'static RwLock { - static CONFIG: OnceLock> = OnceLock::new(); - CONFIG.get_or_init(|| { - let settings = load(); - - RwLock::new(settings) - }) -} - -fn refresh() { - *settings().write().unwrap() = load(); -} - -fn load() -> Config { - Config::builder() - .add_source(File::with_name("examples/watch/Settings.toml")) - .build() - .unwrap() -} - -fn show() { - println!( - " * Settings :: \n\x1b[31m{:?}\x1b[0m", - settings() - .read() - .unwrap() - .clone() - .try_deserialize::>() - .unwrap() - ); +fn main() { + watch(); } fn watch() -> ! { @@ -55,12 +27,11 @@ fn watch() -> ! { // Add a path to be watched. All files and directories at that path and // below will be monitored for changes. watcher - .watch( - Path::new("examples/watch/Settings.toml"), - RecursiveMode::NonRecursive, - ) + .watch(Path::new(SETTINGS_PATH), RecursiveMode::NonRecursive) .unwrap(); + show(); + // This is a simple loop, but you may want to use more complex logic here, // for example to handle I/O. loop { @@ -69,7 +40,7 @@ fn watch() -> ! { kind: notify::event::EventKind::Modify(_), .. })) => { - println!(" * Settings.toml written; refreshing configuration ..."); + println!(" * settings.toml written; refreshing configuration ..."); refresh(); show(); } @@ -83,11 +54,36 @@ fn watch() -> ! { } } -fn main() { - // This is just an example of what could be done, today - // We do want this to be built-in to config-rs at some point - // Feel free to take a crack at a PR +fn show() { + println!( + " * Settings :: \n\x1b[31m{:?}\x1b[0m", + settings() + .read() + .unwrap() + .clone() + .try_deserialize::>() + .unwrap() + ); +} - show(); - watch(); +pub fn settings() -> &'static RwLock { + static CONFIG: OnceLock> = OnceLock::new(); + CONFIG.get_or_init(|| { + let settings = load(); + + RwLock::new(settings) + }) +} + +pub fn refresh() { + *settings().write().unwrap() = load(); } + +fn load() -> Config { + Config::builder() + .add_source(File::with_name(SETTINGS_PATH)) + .build() + .unwrap() +} + +static SETTINGS_PATH: &str = "examples/settings.toml"; diff --git a/examples/watch/Settings.toml b/examples/watch/Settings.toml deleted file mode 100644 index 1518068c..00000000 --- a/examples/watch/Settings.toml +++ /dev/null @@ -1,3 +0,0 @@ -debug = false -port = 3223 -host = "0.0.0.0" diff --git a/src/lib.rs b/src/lib.rs index 72114540..33061916 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ //! //! ```rust //! # #[cfg(feature = "toml")] { -#![doc = include_str!("../examples/simple/main.rs")] +#![doc = include_str!("../examples/simple.rs")] //! # } //! ``` //!