From 8688608ea2024cd1176f4c27b0909e2e3e2ba056 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 20 May 2022 08:56:25 +0200 Subject: [PATCH 01/18] Remove world, to start from a clean slate Signed-off-by: Matthias Beyer --- examples/async_source/main.rs | 74 -- examples/custom_format/main.rs | 50 - examples/env-list/main.rs | 25 - examples/glob/conf/00-default.toml | 1 - examples/glob/conf/05-some.yml | 2 - examples/glob/conf/99-extra.json | 5 - examples/glob/main.rs | 66 -- examples/global/main.rs | 23 - examples/hierarchical-env/config/default.toml | 17 - .../hierarchical-env/config/development.toml | 4 - .../hierarchical-env/config/production.toml | 13 - examples/hierarchical-env/main.rs | 10 - examples/hierarchical-env/settings.rs | 76 -- examples/simple/Settings.toml | 3 - examples/simple/main.rs | 21 - examples/watch/Settings.toml | 3 - examples/watch/main.rs | 70 -- src/builder.rs | 370 -------- src/config.rs | 218 ----- src/de.rs | 468 ---------- src/env.rs | 244 ----- src/error.rs | 244 ----- src/file/format/ini.rs | 37 - src/file/format/json.rs | 58 -- src/file/format/json5.rs | 66 -- src/file/format/mod.rs | 147 --- src/file/format/ron.rs | 66 -- src/file/format/toml.rs | 49 - src/file/format/yaml.rs | 108 --- src/file/mod.rs | 149 --- src/file/source/file.rs | 141 --- src/file/source/mod.rs | 38 - src/file/source/string.rs | 33 - src/format.rs | 23 - src/lib.rs | 44 - src/map.rs | 4 - src/path/mod.rs | 250 ----- src/path/parser.rs | 131 --- src/ser.rs | 720 --------------- src/source.rs | 145 --- src/value.rs | 864 ------------------ tests/Settings-invalid.hjson | 4 - tests/Settings-invalid.ini | 2 - tests/Settings-invalid.json | 4 - tests/Settings-invalid.json5 | 4 - tests/Settings-invalid.ron | 4 - tests/Settings-invalid.toml | 2 - tests/Settings-invalid.yaml | 2 - tests/Settings-production.toml | 8 - tests/Settings.hjson | 18 - tests/Settings.ini | 9 - tests/Settings.json | 19 - tests/Settings.json5 | 20 - tests/Settings.ron | 20 - tests/Settings.toml | 55 -- tests/Settings.yaml | 14 - tests/Settings2.default.ini | 9 - tests/async_builder.rs | 142 --- tests/datetime.rs | 110 --- tests/defaults.rs | 32 - tests/empty.rs | 20 - tests/env.rs | 537 ----------- tests/errors.rs | 155 ---- tests/file.rs | 70 -- tests/file_ini.rs | 68 -- tests/file_json.rs | 113 --- tests/file_json5.rs | 91 -- tests/file_ron.rs | 91 -- tests/file_toml.rs | 103 --- tests/file_yaml.rs | 93 -- tests/get.rs | 280 ------ tests/integer_range.rs | 35 - tests/legacy/datetime.rs | 134 --- tests/legacy/env.rs | 348 ------- tests/legacy/errors.rs | 134 --- tests/legacy/file.rs | 54 -- tests/legacy/file_ini.rs | 66 -- tests/legacy/file_json.rs | 113 --- tests/legacy/file_ron.rs | 90 -- tests/legacy/file_toml.rs | 102 --- tests/legacy/file_yaml.rs | 93 -- tests/legacy/get.rs | 280 ------ tests/legacy/merge.rs | 60 -- tests/legacy/mod.rs | 11 - tests/legacy/set.rs | 91 -- tests/legacy_tests.rs | 2 - tests/merge.rs | 57 -- tests/set.rs | 91 -- tests/types/i64.toml | 1 - tests/weird_keys.rs | 111 --- 90 files changed, 9052 deletions(-) delete mode 100644 examples/async_source/main.rs delete mode 100644 examples/custom_format/main.rs delete mode 100644 examples/env-list/main.rs delete mode 100644 examples/glob/conf/00-default.toml delete mode 100644 examples/glob/conf/05-some.yml delete mode 100644 examples/glob/conf/99-extra.json delete mode 100644 examples/glob/main.rs delete mode 100644 examples/global/main.rs delete mode 100644 examples/hierarchical-env/config/default.toml delete mode 100644 examples/hierarchical-env/config/development.toml delete mode 100644 examples/hierarchical-env/config/production.toml delete mode 100644 examples/hierarchical-env/main.rs delete mode 100644 examples/hierarchical-env/settings.rs delete mode 100644 examples/simple/Settings.toml delete mode 100644 examples/simple/main.rs delete mode 100644 examples/watch/Settings.toml delete mode 100644 examples/watch/main.rs delete mode 100644 src/builder.rs delete mode 100644 src/config.rs delete mode 100644 src/de.rs delete mode 100644 src/env.rs delete mode 100644 src/error.rs delete mode 100644 src/file/format/ini.rs delete mode 100644 src/file/format/json.rs delete mode 100644 src/file/format/json5.rs delete mode 100644 src/file/format/mod.rs delete mode 100644 src/file/format/ron.rs delete mode 100644 src/file/format/toml.rs delete mode 100644 src/file/format/yaml.rs delete mode 100644 src/file/mod.rs delete mode 100644 src/file/source/file.rs delete mode 100644 src/file/source/mod.rs delete mode 100644 src/file/source/string.rs delete mode 100644 src/format.rs delete mode 100644 src/lib.rs delete mode 100644 src/map.rs delete mode 100644 src/path/mod.rs delete mode 100644 src/path/parser.rs delete mode 100644 src/ser.rs delete mode 100644 src/source.rs delete mode 100644 src/value.rs delete mode 100644 tests/Settings-invalid.hjson delete mode 100644 tests/Settings-invalid.ini delete mode 100644 tests/Settings-invalid.json delete mode 100644 tests/Settings-invalid.json5 delete mode 100644 tests/Settings-invalid.ron delete mode 100644 tests/Settings-invalid.toml delete mode 100644 tests/Settings-invalid.yaml delete mode 100644 tests/Settings-production.toml delete mode 100644 tests/Settings.hjson delete mode 100644 tests/Settings.ini delete mode 100644 tests/Settings.json delete mode 100644 tests/Settings.json5 delete mode 100644 tests/Settings.ron delete mode 100644 tests/Settings.toml delete mode 100644 tests/Settings.yaml delete mode 100644 tests/Settings2.default.ini delete mode 100644 tests/async_builder.rs delete mode 100644 tests/datetime.rs delete mode 100644 tests/defaults.rs delete mode 100644 tests/empty.rs delete mode 100644 tests/env.rs delete mode 100644 tests/errors.rs delete mode 100644 tests/file.rs delete mode 100644 tests/file_ini.rs delete mode 100644 tests/file_json.rs delete mode 100644 tests/file_json5.rs delete mode 100644 tests/file_ron.rs delete mode 100644 tests/file_toml.rs delete mode 100644 tests/file_yaml.rs delete mode 100644 tests/get.rs delete mode 100644 tests/integer_range.rs delete mode 100644 tests/legacy/datetime.rs delete mode 100644 tests/legacy/env.rs delete mode 100644 tests/legacy/errors.rs delete mode 100644 tests/legacy/file.rs delete mode 100644 tests/legacy/file_ini.rs delete mode 100644 tests/legacy/file_json.rs delete mode 100644 tests/legacy/file_ron.rs delete mode 100644 tests/legacy/file_toml.rs delete mode 100644 tests/legacy/file_yaml.rs delete mode 100644 tests/legacy/get.rs delete mode 100644 tests/legacy/merge.rs delete mode 100644 tests/legacy/mod.rs delete mode 100644 tests/legacy/set.rs delete mode 100644 tests/legacy_tests.rs delete mode 100644 tests/merge.rs delete mode 100644 tests/set.rs delete mode 100644 tests/types/i64.toml delete mode 100644 tests/weird_keys.rs diff --git a/examples/async_source/main.rs b/examples/async_source/main.rs deleted file mode 100644 index f5459b91..00000000 --- a/examples/async_source/main.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::{error::Error, fmt::Debug}; - -use config::{ - builder::AsyncState, AsyncSource, ConfigBuilder, ConfigError, FileFormat, Format, Map, -}; - -use async_trait::async_trait; -use futures::{select, FutureExt}; -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! { - r = run_server().fuse() => r, - r = run_client().fuse() => r - } -} - -async fn run_server() -> Result<(), Box> { - let service = warp::path("configuration").map(|| r#"{ "value" : 123 }"#); - - println!("Running server on localhost:5001"); - - warp::serve(service).bind(([127, 0, 0, 1], 5001)).await; - - Ok(()) -} - -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; - - let config = ConfigBuilder::::default() - .add_async_source(HttpSource { - uri: "http://localhost:5001/configuration".into(), - format: FileFormat::Json, - }) - .build() - .await?; - - println!("Config value is {}", config.get::("value")?); - - Ok(()) -} - -// Actual implementation of AsyncSource can be found below - -#[derive(Debug)] -struct HttpSource { - uri: String, - format: F, -} - -#[async_trait] -impl AsyncSource for HttpSource { - async fn collect(&self) -> Result, ConfigError> { - reqwest::get(&self.uri) - .await - .map_err(|e| ConfigError::Foreign(Box::new(e)))? // error conversion is possible from custom AsyncSource impls - .text() - .await - .map_err(|e| ConfigError::Foreign(Box::new(e))) - .and_then(|text| { - self.format - .parse(Some(&self.uri), &text) - .map_err(|e| ConfigError::Foreign(e)) - }) - } -} diff --git a/examples/custom_format/main.rs b/examples/custom_format/main.rs deleted file mode 100644 index 4da2e9dc..00000000 --- a/examples/custom_format/main.rs +++ /dev/null @@ -1,50 +0,0 @@ -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)) - .build(); - - match config { - Ok(cfg) => println!("A config: {:#?}", cfg), - Err(e) => println!("An error: {}", e), - } -} - -#[derive(Debug, Clone)] -pub struct MyFormat; - -impl Format for MyFormat { - fn parse( - &self, - uri: Option<&String>, - text: &str, - ) -> Result, Box> { - // Let's assume our format is somewhat malformed, but this is fine - // In real life anything can be used here - nom, serde or other. - // - // For some more real-life examples refer to format implementation within the library code - let mut result = Map::new(); - - if text == "good" { - result.insert( - "key".to_string(), - Value::new(uri, ValueKind::String(text.into())), - ); - } else { - println!("Something went wrong in {:?}", uri); - } - - Ok(result) - } -} - -// 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 { - fn file_extensions(&self) -> &'static [&'static str] { - &MY_FORMAT_EXT - } -} diff --git a/examples/env-list/main.rs b/examples/env-list/main.rs deleted file mode 100644 index f567419b..00000000 --- a/examples/env-list/main.rs +++ /dev/null @@ -1,25 +0,0 @@ -use config::Config; -#[derive(Debug, Default, serde_derive::Deserialize, PartialEq)] -struct AppConfig { - list: Vec, -} - -fn main() { - std::env::set_var("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")]); - - std::env::remove_var("APP_LIST"); -} diff --git a/examples/glob/conf/00-default.toml b/examples/glob/conf/00-default.toml deleted file mode 100644 index 7b95e7a8..00000000 --- a/examples/glob/conf/00-default.toml +++ /dev/null @@ -1 +0,0 @@ -debug = false diff --git a/examples/glob/conf/05-some.yml b/examples/glob/conf/05-some.yml deleted file mode 100644 index 52555a0c..00000000 --- a/examples/glob/conf/05-some.yml +++ /dev/null @@ -1,2 +0,0 @@ -secret: THIS IS SECRET -debug: true diff --git a/examples/glob/conf/99-extra.json b/examples/glob/conf/99-extra.json deleted file mode 100644 index 26f908c4..00000000 --- a/examples/glob/conf/99-extra.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "that": 3, - "this": 1230, - "key": "sdgnjklsdjklgds" -} diff --git a/examples/glob/main.rs b/examples/glob/main.rs deleted file mode 100644 index d5225a00..00000000 --- a/examples/glob/main.rs +++ /dev/null @@ -1,66 +0,0 @@ -use config::{Config, File}; -use glob::glob; -use std::collections::HashMap; -use std::path::Path; - -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/global/main.rs b/examples/global/main.rs deleted file mode 100644 index 160d8817..00000000 --- a/examples/global/main.rs +++ /dev/null @@ -1,23 +0,0 @@ -#![allow(deprecated)] -use config::Config; -use lazy_static::lazy_static; -use std::error::Error; -use std::sync::RwLock; - -lazy_static! { - static ref SETTINGS: RwLock = RwLock::new(Config::default()); -} - -fn try_main() -> Result<(), Box> { - // Set property - SETTINGS.write()?.set("property", 42)?; - - // Get property - println!("property: {}", SETTINGS.read()?.get::("property")?); - - Ok(()) -} - -fn main() { - try_main().unwrap(); -} diff --git a/examples/hierarchical-env/config/default.toml b/examples/hierarchical-env/config/default.toml deleted file mode 100644 index dbb2f302..00000000 --- a/examples/hierarchical-env/config/default.toml +++ /dev/null @@ -1,17 +0,0 @@ -[database] -url = "postgres://postgres@localhost" - -[sparkpost] -key = "sparkpost-dev-key" -token = "sparkpost-dev-token" -url = "https://api.sparkpost.com" -version = 1 - -[twitter] -consumer_token = "twitter-dev-consumer-key" -consumer_secret = "twitter-dev-consumer-secret" - -[braintree] -merchant_id = "braintree-merchant-id" -public_key = "braintree-dev-public-key" -private_key = "braintree-dev-private-key" diff --git a/examples/hierarchical-env/config/development.toml b/examples/hierarchical-env/config/development.toml deleted file mode 100644 index f07dcc2b..00000000 --- a/examples/hierarchical-env/config/development.toml +++ /dev/null @@ -1,4 +0,0 @@ -debug = true - -[database] -echo = true diff --git a/examples/hierarchical-env/config/production.toml b/examples/hierarchical-env/config/production.toml deleted file mode 100644 index cd0c4cf4..00000000 --- a/examples/hierarchical-env/config/production.toml +++ /dev/null @@ -1,13 +0,0 @@ -debug = false - -[sparkpost] -key = "sparkpost-prod-key" -token = "sparkpost-prod-token" - -[twitter] -consumer_token = "twitter-prod-consumer-key" -consumer_secret = "twitter-prod-consumer-secret" - -[braintree] -public_key = "braintree-prod-public-key" -private_key = "braintree-prod-private-key" diff --git a/examples/hierarchical-env/main.rs b/examples/hierarchical-env/main.rs deleted file mode 100644 index ee1b69bd..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/settings.rs b/examples/hierarchical-env/settings.rs deleted file mode 100644 index 65b5f875..00000000 --- a/examples/hierarchical-env/settings.rs +++ /dev/null @@ -1,76 +0,0 @@ -use config::{Config, ConfigError, Environment, File}; -use serde_derive::Deserialize; -use std::env; - -#[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, -} - -#[derive(Debug, Deserialize)] -#[allow(unused)] -pub struct Settings { - debug: bool, - database: Database, - sparkpost: Sparkpost, - twitter: Twitter, - braintree: Braintree, -} - -impl Settings { - pub fn new() -> Result { - let run_mode = env::var("RUN_MODE").unwrap_or_else(|_| "development".into()); - - let s = Config::builder() - // Start off by merging in the "default" configuration file - .add_source(File::with_name("examples/hierarchical-env/config/default")) - // Add in the current environment file - // Default to 'development' env - // Note that this file is _optional_ - .add_source( - File::with_name(&format!("examples/hierarchical-env/config/{}", run_mode)) - .required(false), - ) - // Add in a local configuration file - // This file shouldn't be checked in to git - .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")) - // You may also programmatically change settings - .set_override("database.url", "postgres://")? - .build()?; - - // Now that we're done, let's access our configuration - println!("debug: {:?}", s.get_bool("debug")); - println!("database: {:?}", s.get::("database.url")); - - // You can deserialize (and thus freeze) the entire configuration as - s.try_deserialize() - } -} diff --git a/examples/simple/Settings.toml b/examples/simple/Settings.toml deleted file mode 100644 index fd6d3c6d..00000000 --- a/examples/simple/Settings.toml +++ /dev/null @@ -1,3 +0,0 @@ -debug = false -priority = 32 -key = "189rjfadoisfj8923fjio" diff --git a/examples/simple/main.rs b/examples/simple/main.rs deleted file mode 100644 index 55ae8acf..00000000 --- a/examples/simple/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -use config::Config; -use std::collections::HashMap; - -fn main() { - let settings = Config::builder() - // Add in `./Settings.toml` - .add_source(config::File::with_name("examples/simple/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")) - .build() - .unwrap(); - - // Print out our settings (as a HashMap) - println!( - "{:?}", - settings - .try_deserialize::>() - .unwrap() - ); -} 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/examples/watch/main.rs b/examples/watch/main.rs deleted file mode 100644 index 801ee4fd..00000000 --- a/examples/watch/main.rs +++ /dev/null @@ -1,70 +0,0 @@ -#![allow(deprecated)] -use config::{Config, File}; -use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher}; -use std::collections::HashMap; -use std::sync::mpsc::channel; -use std::sync::RwLock; -use std::time::Duration; - -lazy_static::lazy_static! { - static ref SETTINGS: RwLock = RwLock::new({ - let mut settings = Config::default(); - settings.merge(File::with_name("examples/watch/Settings.toml")).unwrap(); - - settings - }); -} - -fn show() { - println!( - " * Settings :: \n\x1b[31m{:?}\x1b[0m", - SETTINGS - .read() - .unwrap() - .clone() - .try_deserialize::>() - .unwrap() - ); -} - -fn watch() { - // Create a channel to receive the events. - let (tx, rx) = channel(); - - // Automatically select the best implementation for your platform. - // You can also access each implementation directly e.g. INotifyWatcher. - let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2)).unwrap(); - - // Add a path to be watched. All files and directories at that path and - // below will be monitored for changes. - watcher - .watch("examples/watch/Settings.toml", RecursiveMode::NonRecursive) - .unwrap(); - - // This is a simple loop, but you may want to use more complex logic here, - // for example to handle I/O. - loop { - match rx.recv() { - Ok(DebouncedEvent::Write(_)) => { - println!(" * Settings.toml written; refreshing configuration ..."); - SETTINGS.write().unwrap().refresh().unwrap(); - show(); - } - - Err(e) => println!("watch error: {:?}", e), - - _ => { - // Ignore event - } - } - } -} - -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 - - show(); - watch(); -} diff --git a/src/builder.rs b/src/builder.rs deleted file mode 100644 index 6f928c69..00000000 --- a/src/builder.rs +++ /dev/null @@ -1,370 +0,0 @@ -use std::iter::IntoIterator; -use std::str::FromStr; - -use crate::error::Result; -use crate::map::Map; -use crate::source::AsyncSource; -use crate::{config::Config, path::Expression, source::Source, value::Value}; - -/// A configuration builder -/// -/// It registers ordered sources of configuration to later build consistent [`Config`] from them. -/// Configuration sources it defines are defaults, [`Source`]s and overrides. -/// -/// Defaults are always loaded first and can be overwritten by any of two other sources. -/// Overrides are always loaded last, thus cannot be overridden. -/// Both can be only set explicitly key by key in code -/// using [`set_default`](Self::set_default) or [`set_override`](Self::set_override). -/// -/// An intermediate category, [`Source`], set groups of keys at once implicitly using data coming from external sources -/// like files, environment variables or others that one implements. Defining a [`Source`] is as simple as implementing -/// a trait for a struct. -/// -/// Adding sources, setting defaults and overrides does not invoke any I/O nor builds a config. -/// It happens on demand when [`build`](Self::build) (or its alternative) is called. -/// Therefore all errors, related to any of the [`Source`] will only show up then. -/// -/// # Sync and async builder -/// -/// [`ConfigBuilder`] uses type parameter to keep track of builder state. -/// -/// In [`DefaultState`] builder only supports [`Source`]s -/// -/// In [`AsyncState`] it supports both [`Source`]s and [`AsyncSource`]s at the price of building using `async fn`. -/// -/// # Examples -/// -/// ```rust -/// # use config::*; -/// # use std::error::Error; -/// # fn main() -> Result<(), Box> { -/// let mut builder = Config::builder() -/// .set_default("default", "1")? -/// .add_source(File::new("config/settings", FileFormat::Json)) -/// // .add_async_source(...) -/// .set_override("override", "1")?; -/// -/// match builder.build() { -/// Ok(config) => { -/// // use your config -/// }, -/// Err(e) => { -/// // something went wrong -/// } -/// } -/// # Ok(()) -/// # } -/// ``` -/// -/// If any [`AsyncSource`] is used, the builder will transition to [`AsyncState`]. -/// In such case, it is required to _await_ calls to [`build`](Self::build) and its non-consuming sibling. -/// -/// Calls can be not chained as well -/// ```rust -/// # use std::error::Error; -/// # use config::*; -/// # fn main() -> Result<(), Box> { -/// let mut builder = Config::builder(); -/// builder = builder.set_default("default", "1")?; -/// builder = builder.add_source(File::new("config/settings", FileFormat::Json)); -/// builder = builder.add_source(File::new("config/settings.prod", FileFormat::Json)); -/// builder = builder.set_override("override", "1")?; -/// # Ok(()) -/// # } -/// ``` -/// -/// Calling [`Config::builder`](Config::builder) yields builder in the default state. -/// If having an asynchronous state as the initial state is desired, _turbofish_ notation needs to be used. -/// ```rust -/// # use config::{*, builder::AsyncState}; -/// let mut builder = ConfigBuilder::::default(); -/// ``` -/// -/// If for some reason acquiring builder in default state is required without calling [`Config::builder`](Config::builder) -/// it can also be achieved. -/// ```rust -/// # use config::{*, builder::DefaultState}; -/// let mut builder = ConfigBuilder::::default(); -/// ``` -#[derive(Debug, Clone, Default)] -pub struct ConfigBuilder { - defaults: Map, - overrides: Map, - state: St, -} - -/// Represents [`ConfigBuilder`] state. -pub trait BuilderState {} - -/// Represents data specific to builder in default, sychronous state, without support for async. -#[derive(Debug, Default)] -pub struct DefaultState { - sources: Vec>, -} - -/// The asynchronous configuration builder. -/// -/// Similar to a [`ConfigBuilder`] it maintains a set of defaults, a set of sources, and overrides. -/// -/// Defaults do not override anything, sources override defaults, and overrides override anything else. -/// Within those three groups order of adding them at call site matters - entities added later take precedence. -/// -/// For more detailed description and examples see [`ConfigBuilder`]. -/// [`AsyncConfigBuilder`] is just an extension of it that takes async functions into account. -/// -/// To obtain a [`Config`] call [`build`](AsyncConfigBuilder::build) or [`build_cloned`](AsyncConfigBuilder::build_cloned) -/// -/// # Example -/// Since this library does not implement any [`AsyncSource`] an example in rustdocs cannot be given. -/// Detailed explanation about why such a source is not implemented is in [`AsyncSource`]'s documentation. -/// -/// Refer to [`ConfigBuilder`] for similar API sample usage or to the examples folder of the crate, where such a source is implemented. -#[derive(Debug, Clone, Default)] -pub struct AsyncConfigBuilder {} - -/// Represents data specific to builder in asychronous state, with support for async. -#[derive(Debug, Default)] -pub struct AsyncState { - sources: Vec, -} - -#[derive(Debug, Clone)] -enum SourceType { - Sync(Box), - Async(Box), -} - -impl BuilderState for DefaultState {} -impl BuilderState for AsyncState {} - -impl ConfigBuilder { - // operations allowed in any state - - /// Set a default `value` at `key` - /// - /// This value can be overwritten by any [`Source`], [`AsyncSource`] or override. - /// - /// # Errors - /// - /// Fails if `Expression::from_str(key)` fails. - pub fn set_default(mut self, key: S, value: T) -> Result - where - S: AsRef, - T: Into, - { - self.defaults - .insert(Expression::from_str(key.as_ref())?, value.into()); - Ok(self) - } - - /// Set an override - /// - /// This function sets an overwrite value. It will not be altered by any default, [`Source`] nor [`AsyncSource`] - /// - /// # Errors - /// - /// Fails if `Expression::from_str(key)` fails. - pub fn set_override(mut self, key: S, value: T) -> Result - where - S: AsRef, - T: Into, - { - self.overrides - .insert(Expression::from_str(key.as_ref())?, value.into()); - Ok(self) - } - - /// Sets an override if value is Some(_) - /// - /// This function sets an overwrite value if Some(_) is passed. If None is passed, this function does nothing. - /// It will not be altered by any default, [`Source`] nor [`AsyncSource`] - /// - /// # Errors - /// - /// Fails if `Expression::from_str(key)` fails. - pub fn set_override_option(mut self, key: S, value: Option) -> Result - where - S: AsRef, - T: Into, - { - if let Some(value) = value { - self.overrides - .insert(Expression::from_str(key.as_ref())?, value.into()); - } - Ok(self) - } -} - -impl ConfigBuilder { - // operations allowed in sync state - - /// Registers new [`Source`] in this builder. - /// - /// Calling this method does not invoke any I/O. [`Source`] is only saved in internal register for later use. - #[must_use] - pub fn add_source(mut self, source: T) -> Self - where - T: Source + Send + Sync + 'static, - { - self.state.sources.push(Box::new(source)); - self - } - - /// Registers new [`AsyncSource`] in this builder and forces transition to [`AsyncState`]. - /// - /// Calling this method does not invoke any I/O. [`AsyncSource`] is only saved in internal register for later use. - pub fn add_async_source(self, source: T) -> ConfigBuilder - where - T: AsyncSource + Send + Sync + 'static, - { - let async_state = ConfigBuilder { - state: AsyncState { - sources: self - .state - .sources - .into_iter() - .map(|s| SourceType::Sync(s)) - .collect(), - }, - defaults: self.defaults, - overrides: self.overrides, - }; - - async_state.add_async_source(source) - } - - /// Reads all registered [`Source`]s. - /// - /// This is the method that invokes all I/O operations. - /// For a non consuming alternative see [`build_cloned`](Self::build_cloned) - /// - /// # Errors - /// If source collection fails, be it technical reasons or related to inability to read data as `Config` for different reasons, - /// this method returns error. - pub fn build(self) -> Result { - Self::build_internal(self.defaults, self.overrides, &self.state.sources) - } - - /// Reads all registered [`Source`]s. - /// - /// Similar to [`build`](Self::build), but it does not take ownership of `ConfigBuilder` to allow later reuse. - /// Internally it clones data to achieve it. - /// - /// # Errors - /// If source collection fails, be it technical reasons or related to inability to read data as `Config` for different reasons, - /// this method returns error. - pub fn build_cloned(&self) -> Result { - Self::build_internal( - self.defaults.clone(), - self.overrides.clone(), - &self.state.sources, - ) - } - - fn build_internal( - defaults: Map, - overrides: Map, - sources: &[Box], - ) -> Result { - let mut cache: Value = Map::::new().into(); - - // Add defaults - for (key, val) in defaults { - key.set(&mut cache, val); - } - - // Add sources - sources.collect_to(&mut cache)?; - - // Add overrides - for (key, val) in overrides { - key.set(&mut cache, val); - } - - Ok(Config::new(cache)) - } -} - -impl ConfigBuilder { - // operations allowed in async state - - /// Registers new [`Source`] in this builder. - /// - /// Calling this method does not invoke any I/O. [`Source`] is only saved in internal register for later use. - #[must_use] - pub fn add_source(mut self, source: T) -> Self - where - T: Source + Send + Sync + 'static, - { - self.state.sources.push(SourceType::Sync(Box::new(source))); - self - } - - /// Registers new [`AsyncSource`] in this builder. - /// - /// Calling this method does not invoke any I/O. [`AsyncSource`] is only saved in internal register for later use. - #[must_use] - pub fn add_async_source(mut self, source: T) -> Self - where - T: AsyncSource + Send + Sync + 'static, - { - self.state.sources.push(SourceType::Async(Box::new(source))); - self - } - - /// Reads all registered defaults, [`Source`]s, [`AsyncSource`]s and overrides. - /// - /// This is the method that invokes all I/O operations. - /// For a non consuming alternative see [`build_cloned`](Self::build_cloned) - /// - /// # Errors - /// If source collection fails, be it technical reasons or related to inability to read data as `Config` for different reasons, - /// this method returns error. - pub async fn build(self) -> Result { - Self::build_internal(self.defaults, self.overrides, &self.state.sources).await - } - - /// Reads all registered defaults, [`Source`]s, [`AsyncSource`]s and overrides. - /// - /// Similar to [`build`](Self::build), but it does not take ownership of `ConfigBuilder` to allow later reuse. - /// Internally it clones data to achieve it. - /// - /// # Errors - /// If source collection fails, be it technical reasons or related to inability to read data as `Config` for different reasons, - /// this method returns error. - pub async fn build_cloned(&self) -> Result { - Self::build_internal( - self.defaults.clone(), - self.overrides.clone(), - &self.state.sources, - ) - .await - } - - async fn build_internal( - defaults: Map, - overrides: Map, - sources: &[SourceType], - ) -> Result { - let mut cache: Value = Map::::new().into(); - - // Add defaults - for (key, val) in defaults { - key.set(&mut cache, val); - } - - for source in sources.iter() { - match source { - SourceType::Sync(source) => source.collect_to(&mut cache)?, - SourceType::Async(source) => source.collect_to(&mut cache).await?, - } - } - - // Add overrides - for (key, val) in overrides { - key.set(&mut cache, val); - } - - Ok(Config::new(cache)) - } -} diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 27b318dd..00000000 --- a/src/config.rs +++ /dev/null @@ -1,218 +0,0 @@ -use std::fmt::Debug; - -use crate::builder::{ConfigBuilder, DefaultState}; -use serde::de::Deserialize; -use serde::ser::Serialize; - -use crate::error::{ConfigError, Result}; -use crate::map::Map; -use crate::path; -use crate::ser::ConfigSerializer; -use crate::source::Source; -use crate::value::{Table, Value}; - -/// A prioritized configuration repository. It maintains a set of -/// configuration sources, fetches values to populate those, and provides -/// them according to the source's priority. -#[derive(Clone, Debug)] -pub struct Config { - defaults: Map, - overrides: Map, - sources: Vec>, - - /// Root of the cached configuration. - pub cache: Value, -} - -impl Default for Config { - fn default() -> Self { - Self { - defaults: Default::default(), - overrides: Default::default(), - sources: Default::default(), - cache: Value::new(None, Table::new()), - } - } -} - -impl Config { - pub(crate) fn new(value: Value) -> Self { - Self { - cache: value, - ..Self::default() - } - } - - /// Creates new [`ConfigBuilder`] instance - pub fn builder() -> ConfigBuilder { - ConfigBuilder::::default() - } - - /// Merge in a configuration property source. - #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] - pub fn merge(&mut self, source: T) -> Result<&mut Self> - where - T: 'static, - T: Source + Send + Sync, - { - self.sources.push(Box::new(source)); - - #[allow(deprecated)] - self.refresh() - } - - /// Merge in a configuration property source. - #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] - pub fn with_merged(mut self, source: T) -> Result - where - T: 'static, - T: Source + Send + Sync, - { - self.sources.push(Box::new(source)); - - #[allow(deprecated)] - self.refresh()?; - Ok(self) - } - - /// Refresh the configuration cache with fresh - /// data from added sources. - /// - /// Configuration is automatically refreshed after a mutation - /// operation (`set`, `merge`, `set_default`, etc.). - #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] - pub fn refresh(&mut self) -> Result<&mut Self> { - self.cache = { - let mut cache: Value = Map::::new().into(); - - // Add defaults - for (key, val) in &self.defaults { - key.set(&mut cache, val.clone()); - } - - // Add sources - self.sources.collect_to(&mut cache)?; - - // Add overrides - for (key, val) in &self.overrides { - key.set(&mut cache, val.clone()); - } - - cache - }; - - Ok(self) - } - - /// Set a default `value` at `key` - #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] - pub fn set_default(&mut self, key: &str, value: T) -> Result<&mut Self> - where - T: Into, - { - self.defaults.insert(key.parse()?, value.into()); - - #[allow(deprecated)] - self.refresh() - } - - /// Set an overwrite - /// - /// This function sets an overwrite value. - /// The overwrite `value` is written to the `key` location on every `refresh()` - /// - /// # Warning - /// - /// Errors if config is frozen - #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] - pub fn set(&mut self, key: &str, value: T) -> Result<&mut Self> - where - T: Into, - { - self.overrides.insert(key.parse()?, value.into()); - - #[allow(deprecated)] - self.refresh() - } - - #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] - pub fn set_once(&mut self, key: &str, value: Value) -> Result<()> { - let expr: path::Expression = key.parse()?; - - // Traverse the cache using the path to (possibly) retrieve a value - if let Some(ref mut val) = expr.get_mut(&mut self.cache) { - **val = value; - } else { - expr.set(&mut self.cache, value); - } - Ok(()) - } - - pub fn get<'de, T: Deserialize<'de>>(&self, key: &str) -> Result { - // Parse the key into a path expression - let expr: path::Expression = key.parse()?; - - // Traverse the cache using the path to (possibly) retrieve a value - let value = expr.get(&self.cache).cloned(); - - match value { - Some(value) => { - // Deserialize the received value into the requested type - T::deserialize(value).map_err(|e| e.extend_with_key(key)) - } - - None => Err(ConfigError::NotFound(key.into())), - } - } - - pub fn get_string(&self, key: &str) -> Result { - self.get(key).and_then(Value::into_string) - } - - pub fn get_int(&self, key: &str) -> Result { - self.get(key).and_then(Value::into_int) - } - - pub fn get_float(&self, key: &str) -> Result { - self.get(key).and_then(Value::into_float) - } - - pub fn get_bool(&self, key: &str) -> Result { - self.get(key).and_then(Value::into_bool) - } - - pub fn get_table(&self, key: &str) -> Result> { - self.get(key).and_then(Value::into_table) - } - - pub fn get_array(&self, key: &str) -> Result> { - self.get(key).and_then(Value::into_array) - } - - /// Attempt to deserialize the entire configuration into the requested type. - pub fn try_deserialize<'de, T: Deserialize<'de>>(self) -> Result { - T::deserialize(self) - } - - /// Attempt to serialize the entire configuration from the given type. - pub fn try_from(from: &T) -> Result { - let mut serializer = ConfigSerializer::default(); - from.serialize(&mut serializer)?; - Ok(serializer.output) - } - - #[deprecated(since = "0.7.0", note = "please use 'try_deserialize' instead")] - pub fn deserialize<'de, T: Deserialize<'de>>(self) -> Result { - self.try_deserialize() - } -} - -impl Source for Config { - fn clone_into_box(&self) -> Box { - Box::new((*self).clone()) - } - - fn collect(&self) -> Result> { - self.cache.clone().into_table() - } -} diff --git a/src/de.rs b/src/de.rs deleted file mode 100644 index 9df347f0..00000000 --- a/src/de.rs +++ /dev/null @@ -1,468 +0,0 @@ -use std::collections::VecDeque; -use std::iter::Enumerate; - -use serde::de; - -use crate::config::Config; -use crate::error::{ConfigError, Result}; -use crate::map::Map; -use crate::value::{Table, Value, ValueKind}; - -impl<'de> de::Deserializer<'de> for Value { - type Error = ConfigError; - - #[inline] - fn deserialize_any(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - // Deserialize based on the underlying type - match self.kind { - ValueKind::Nil => visitor.visit_unit(), - ValueKind::I64(i) => visitor.visit_i64(i), - ValueKind::I128(i) => visitor.visit_i128(i), - ValueKind::U64(i) => visitor.visit_u64(i), - ValueKind::U128(i) => visitor.visit_u128(i), - ValueKind::Boolean(b) => visitor.visit_bool(b), - ValueKind::Float(f) => visitor.visit_f64(f), - ValueKind::String(s) => visitor.visit_string(s), - ValueKind::Array(values) => visitor.visit_seq(SeqAccess::new(values)), - ValueKind::Table(map) => visitor.visit_map(MapAccess::new(map)), - } - } - - #[inline] - fn deserialize_bool>(self, visitor: V) -> Result { - visitor.visit_bool(self.into_bool()?) - } - - #[inline] - fn deserialize_i8>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_i8(self.into_int()? as i8) - } - - #[inline] - fn deserialize_i16>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_i16(self.into_int()? as i16) - } - - #[inline] - fn deserialize_i32>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_i32(self.into_int()? as i32) - } - - #[inline] - fn deserialize_i64>(self, visitor: V) -> Result { - visitor.visit_i64(self.into_int()?) - } - - #[inline] - fn deserialize_u8>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_u8(self.into_int()? as u8) - } - - #[inline] - fn deserialize_u16>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_u16(self.into_int()? as u16) - } - - #[inline] - fn deserialize_u32>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_u32(self.into_int()? as u32) - } - - #[inline] - fn deserialize_u64>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_u64(self.into_int()? as u64) - } - - #[inline] - fn deserialize_f32>(self, visitor: V) -> Result { - visitor.visit_f32(self.into_float()? as f32) - } - - #[inline] - fn deserialize_f64>(self, visitor: V) -> Result { - visitor.visit_f64(self.into_float()?) - } - - #[inline] - fn deserialize_str>(self, visitor: V) -> Result { - visitor.visit_string(self.into_string()?) - } - - #[inline] - fn deserialize_string>(self, visitor: V) -> Result { - visitor.visit_string(self.into_string()?) - } - - #[inline] - fn deserialize_option(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - // Match an explicit nil as None and everything else as Some - match self.kind { - ValueKind::Nil => visitor.visit_none(), - _ => visitor.visit_some(self), - } - } - - fn deserialize_newtype_struct(self, _name: &'static str, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - visitor.visit_newtype_struct(self) - } - - fn deserialize_enum( - self, - name: &'static str, - variants: &'static [&'static str], - visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - visitor.visit_enum(EnumAccess { - value: self, - name, - variants, - }) - } - - serde::forward_to_deserialize_any! { - char seq - bytes byte_buf map struct unit - identifier ignored_any unit_struct tuple_struct tuple - } -} - -struct StrDeserializer<'a>(&'a str); - -impl<'de, 'a> de::Deserializer<'de> for StrDeserializer<'a> { - type Error = ConfigError; - - #[inline] - fn deserialize_any>(self, visitor: V) -> Result { - visitor.visit_str(self.0) - } - - serde::forward_to_deserialize_any! { - bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq - bytes byte_buf map struct unit enum newtype_struct - identifier ignored_any unit_struct tuple_struct tuple option - } -} - -struct SeqAccess { - elements: Enumerate<::std::vec::IntoIter>, -} - -impl SeqAccess { - fn new(elements: Vec) -> Self { - Self { - elements: elements.into_iter().enumerate(), - } - } -} - -impl<'de> de::SeqAccess<'de> for SeqAccess { - type Error = ConfigError; - - fn next_element_seed(&mut self, seed: T) -> Result> - where - T: de::DeserializeSeed<'de>, - { - match self.elements.next() { - Some((idx, value)) => seed - .deserialize(value) - .map(Some) - .map_err(|e| e.prepend_index(idx)), - None => Ok(None), - } - } - - fn size_hint(&self) -> Option { - match self.elements.size_hint() { - (lower, Some(upper)) if lower == upper => Some(upper), - _ => None, - } - } -} - -struct MapAccess { - elements: VecDeque<(String, Value)>, -} - -impl MapAccess { - fn new(table: Map) -> Self { - Self { - elements: table.into_iter().collect(), - } - } -} - -impl<'de> de::MapAccess<'de> for MapAccess { - type Error = ConfigError; - - fn next_key_seed(&mut self, seed: K) -> Result> - where - K: de::DeserializeSeed<'de>, - { - if let Some(&(ref key_s, _)) = self.elements.front() { - let key_de = Value::new(None, key_s as &str); - let key = de::DeserializeSeed::deserialize(seed, key_de)?; - - Ok(Some(key)) - } else { - Ok(None) - } - } - - fn next_value_seed(&mut self, seed: V) -> Result - where - V: de::DeserializeSeed<'de>, - { - let (key, value) = self.elements.pop_front().unwrap(); - de::DeserializeSeed::deserialize(seed, value).map_err(|e| e.prepend_key(&key)) - } -} - -struct EnumAccess { - value: Value, - name: &'static str, - variants: &'static [&'static str], -} - -impl EnumAccess { - fn variant_deserializer(&self, name: &str) -> Result { - self.variants - .iter() - .find(|&&s| s == name) - .map(|&s| StrDeserializer(s)) - .ok_or_else(|| self.no_constructor_error(name)) - } - - fn table_deserializer(&self, table: &Table) -> Result { - if table.len() == 1 { - self.variant_deserializer(table.iter().next().unwrap().0) - } else { - Err(self.structural_error()) - } - } - - fn no_constructor_error(&self, supposed_variant: &str) -> ConfigError { - ConfigError::Message(format!( - "enum {} does not have variant constructor {}", - self.name, supposed_variant - )) - } - - fn structural_error(&self) -> ConfigError { - ConfigError::Message(format!( - "value of enum {} should be represented by either string or table with exactly one key", - self.name - )) - } -} - -impl<'de> de::EnumAccess<'de> for EnumAccess { - type Error = ConfigError; - type Variant = Self; - - fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant)> - where - V: de::DeserializeSeed<'de>, - { - let value = { - let deserializer = match self.value.kind { - ValueKind::String(ref s) => self.variant_deserializer(s), - ValueKind::Table(ref t) => self.table_deserializer(t), - _ => Err(self.structural_error()), - }?; - seed.deserialize(deserializer)? - }; - - Ok((value, self)) - } -} - -impl<'de> de::VariantAccess<'de> for EnumAccess { - type Error = ConfigError; - - fn unit_variant(self) -> Result<()> { - Ok(()) - } - - fn newtype_variant_seed(self, seed: T) -> Result - where - T: de::DeserializeSeed<'de>, - { - match self.value.kind { - ValueKind::Table(t) => seed.deserialize(t.into_iter().next().unwrap().1), - _ => unreachable!(), - } - } - - fn tuple_variant(self, _len: usize, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - match self.value.kind { - ValueKind::Table(t) => { - de::Deserializer::deserialize_seq(t.into_iter().next().unwrap().1, visitor) - } - _ => unreachable!(), - } - } - - fn struct_variant(self, _fields: &'static [&'static str], visitor: V) -> Result - where - V: de::Visitor<'de>, - { - match self.value.kind { - ValueKind::Table(t) => { - de::Deserializer::deserialize_map(t.into_iter().next().unwrap().1, visitor) - } - _ => unreachable!(), - } - } -} - -impl<'de> de::Deserializer<'de> for Config { - type Error = ConfigError; - - #[inline] - fn deserialize_any(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - // Deserialize based on the underlying type - match self.cache.kind { - ValueKind::Nil => visitor.visit_unit(), - ValueKind::I64(i) => visitor.visit_i64(i), - ValueKind::I128(i) => visitor.visit_i128(i), - ValueKind::U64(i) => visitor.visit_u64(i), - ValueKind::U128(i) => visitor.visit_u128(i), - ValueKind::Boolean(b) => visitor.visit_bool(b), - ValueKind::Float(f) => visitor.visit_f64(f), - ValueKind::String(s) => visitor.visit_string(s), - ValueKind::Array(values) => visitor.visit_seq(SeqAccess::new(values)), - ValueKind::Table(map) => visitor.visit_map(MapAccess::new(map)), - } - } - - #[inline] - fn deserialize_bool>(self, visitor: V) -> Result { - visitor.visit_bool(self.cache.into_bool()?) - } - - #[inline] - fn deserialize_i8>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_i8(self.cache.into_int()? as i8) - } - - #[inline] - fn deserialize_i16>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_i16(self.cache.into_int()? as i16) - } - - #[inline] - fn deserialize_i32>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_i32(self.cache.into_int()? as i32) - } - - #[inline] - fn deserialize_i64>(self, visitor: V) -> Result { - visitor.visit_i64(self.cache.into_int()?) - } - - #[inline] - fn deserialize_u8>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_u8(self.cache.into_int()? as u8) - } - - #[inline] - fn deserialize_u16>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_u16(self.cache.into_int()? as u16) - } - - #[inline] - fn deserialize_u32>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_u32(self.cache.into_int()? as u32) - } - - #[inline] - fn deserialize_u64>(self, visitor: V) -> Result { - // FIXME: This should *fail* if the value does not fit in the requets integer type - visitor.visit_u64(self.cache.into_int()? as u64) - } - - #[inline] - fn deserialize_f32>(self, visitor: V) -> Result { - visitor.visit_f32(self.cache.into_float()? as f32) - } - - #[inline] - fn deserialize_f64>(self, visitor: V) -> Result { - visitor.visit_f64(self.cache.into_float()?) - } - - #[inline] - fn deserialize_str>(self, visitor: V) -> Result { - visitor.visit_string(self.cache.into_string()?) - } - - #[inline] - fn deserialize_string>(self, visitor: V) -> Result { - visitor.visit_string(self.cache.into_string()?) - } - - #[inline] - fn deserialize_option(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - // Match an explicit nil as None and everything else as Some - match self.cache.kind { - ValueKind::Nil => visitor.visit_none(), - _ => visitor.visit_some(self), - } - } - - fn deserialize_enum( - self, - name: &'static str, - variants: &'static [&'static str], - visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - visitor.visit_enum(EnumAccess { - value: self.cache, - name, - variants, - }) - } - - serde::forward_to_deserialize_any! { - char seq - bytes byte_buf map struct unit newtype_struct - identifier ignored_any unit_struct tuple_struct tuple - } -} diff --git a/src/env.rs b/src/env.rs deleted file mode 100644 index 432df2c8..00000000 --- a/src/env.rs +++ /dev/null @@ -1,244 +0,0 @@ -use std::env; - -use crate::error::Result; -use crate::map::Map; -use crate::source::Source; -use crate::value::{Value, ValueKind}; - -#[must_use] -#[derive(Clone, Debug, Default)] -pub struct Environment { - /// Optional prefix that will limit access to the environment to only keys that - /// begin with the defined prefix. - /// - /// A prefix with a separator of `_` is tested to be present on each key before its considered - /// to be part of the source environment. - /// - /// For example, the key `CONFIG_DEBUG` would become `DEBUG` with a prefix of `config`. - prefix: Option, - - /// Optional character sequence that separates the prefix from the rest of the key - prefix_separator: Option, - - /// Optional character sequence that separates each key segment in an environment key pattern. - /// Consider a nested configuration such as `redis.password`, a separator of `_` would allow - /// an environment key of `REDIS_PASSWORD` to match. - separator: Option, - - /// Optional character sequence that separates each env value into a vector. only works when try_parsing is set to true - /// Once set, you cannot have type String on the same environment, unless you set list_parse_keys. - list_separator: Option, - /// A list of keys which should always be parsed as a list. If not set you can have only Vec or String (not both) in one environment. - list_parse_keys: Option>, - - /// Ignore empty env values (treat as unset). - ignore_empty: bool, - - /// Parses booleans, integers and floats if they're detected (can be safely parsed). - try_parsing: bool, - - // Preserve the prefix while parsing - keep_prefix: bool, - - /// Alternate source for the environment. This can be used when you want to test your own code - /// using this source, without the need to change the actual system environment variables. - /// - /// ## Example - /// - /// ```rust - /// # use config::{Environment, Config}; - /// # use serde::Deserialize; - /// # use std::collections::HashMap; - /// # use std::convert::TryInto; - /// # - /// #[test] - /// fn test_config() -> Result<(), config::ConfigError> { - /// #[derive(Clone, Debug, Deserialize)] - /// struct MyConfig { - /// pub my_string: String, - /// } - /// - /// let source = Environment::default() - /// .source(Some({ - /// let mut env = HashMap::new(); - /// env.insert("MY_STRING".into(), "my-value".into()); - /// env - /// })); - /// - /// let config: MyConfig = Config::builder() - /// .add_source(source) - /// .build()? - /// .try_into()?; - /// assert_eq!(config.my_string, "my-value"); - /// - /// Ok(()) - /// } - /// ``` - source: Option>, -} - -impl Environment { - #[deprecated(since = "0.12.0", note = "please use 'Environment::default' instead")] - pub fn new() -> Self { - Self::default() - } - - pub fn with_prefix(s: &str) -> Self { - Self { - prefix: Some(s.into()), - ..Self::default() - } - } - - pub fn prefix(mut self, s: &str) -> Self { - self.prefix = Some(s.into()); - self - } - - pub fn prefix_separator(mut self, s: &str) -> Self { - self.prefix_separator = Some(s.into()); - self - } - - pub fn separator(mut self, s: &str) -> Self { - self.separator = Some(s.into()); - self - } - - /// When set and try_parsing is true, then all environment variables will be parsed as [`Vec`] instead of [`String`]. - /// See [`with_list_parse_key`] when you want to use [`Vec`] in combination with [`String`]. - pub fn list_separator(mut self, s: &str) -> Self { - self.list_separator = Some(s.into()); - self - } - - /// Add a key which should be parsed as a list when collecting [`Value`]s from the environment. - /// Once list_separator is set, the type for string is [`Vec`]. - /// To switch the default type back to type Strings you need to provide the keys which should be [`Vec`] using this function. - pub fn with_list_parse_key(mut self, key: &str) -> Self { - if self.list_parse_keys == None { - self.list_parse_keys = Some(vec![key.into()]) - } else { - self.list_parse_keys = self.list_parse_keys.map(|mut keys| { - keys.push(key.into()); - keys - }); - } - self - } - - pub fn ignore_empty(mut self, ignore: bool) -> Self { - self.ignore_empty = ignore; - self - } - - /// Note: enabling `try_parsing` can reduce performance it will try and parse - /// each environment variable 3 times (bool, i64, f64) - pub fn try_parsing(mut self, try_parsing: bool) -> Self { - self.try_parsing = try_parsing; - self - } - - pub fn keep_prefix(mut self, keep: bool) -> Self { - self.keep_prefix = keep; - self - } - - pub fn source(mut self, source: Option>) -> Self { - self.source = source; - self - } -} - -impl Source for Environment { - fn clone_into_box(&self) -> Box { - Box::new((*self).clone()) - } - - fn collect(&self) -> Result> { - let mut m = Map::new(); - let uri: String = "the environment".into(); - - let separator = self.separator.as_deref().unwrap_or(""); - let prefix_separator = match (self.prefix_separator.as_deref(), self.separator.as_deref()) { - (Some(pre), _) => pre, - (None, Some(sep)) => sep, - (None, None) => "_", - }; - - // Define a prefix pattern to test and exclude from keys - let prefix_pattern = self - .prefix - .as_ref() - .map(|prefix| format!("{}{}", prefix, prefix_separator).to_lowercase()); - - let collector = |(key, value): (String, String)| { - // Treat empty environment variables as unset - if self.ignore_empty && value.is_empty() { - return; - } - - let mut key = key.to_lowercase(); - - // Check for prefix - if let Some(ref prefix_pattern) = prefix_pattern { - if key.starts_with(prefix_pattern) { - if !self.keep_prefix { - // Remove this prefix from the key - key = key[prefix_pattern.len()..].to_string(); - } - } else { - // Skip this key - return; - } - } - - // If separator is given replace with `.` - if !separator.is_empty() { - key = key.replace(separator, "."); - } - - let value = if self.try_parsing { - // convert to lowercase because bool parsing expects all lowercase - if let Ok(parsed) = value.to_lowercase().parse::() { - ValueKind::Boolean(parsed) - } else if let Ok(parsed) = value.parse::() { - ValueKind::I64(parsed) - } else if let Ok(parsed) = value.parse::() { - ValueKind::Float(parsed) - } else if let Some(separator) = &self.list_separator { - if let Some(keys) = &self.list_parse_keys { - if keys.contains(&key) { - let v: Vec = value - .split(separator) - .map(|s| Value::new(Some(&uri), ValueKind::String(s.to_string()))) - .collect(); - ValueKind::Array(v) - } else { - ValueKind::String(value) - } - } else { - let v: Vec = value - .split(separator) - .map(|s| Value::new(Some(&uri), ValueKind::String(s.to_string()))) - .collect(); - ValueKind::Array(v) - } - } else { - ValueKind::String(value) - } - } else { - ValueKind::String(value) - }; - - m.insert(key, Value::new(Some(&uri), value)); - }; - - match &self.source { - Some(source) => source.clone().into_iter().for_each(collector), - None => env::vars().for_each(collector), - } - - Ok(m) - } -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 7957d2f1..00000000 --- a/src/error.rs +++ /dev/null @@ -1,244 +0,0 @@ -use std::error::Error; -use std::fmt; -use std::result; - -use serde::de; -use serde::ser; - -#[derive(Debug)] -pub enum Unexpected { - Bool(bool), - I64(i64), - I128(i128), - U64(u64), - U128(u128), - Float(f64), - Str(String), - Unit, - Seq, - Map, -} - -impl fmt::Display for Unexpected { - fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { - match *self { - Unexpected::Bool(b) => write!(f, "boolean `{}`", b), - Unexpected::I64(i) => write!(f, "64-bit integer `{}`", i), - Unexpected::I128(i) => write!(f, "128-bit integer `{}`", i), - Unexpected::U64(i) => write!(f, "64-bit unsigned integer `{}`", i), - Unexpected::U128(i) => write!(f, "128-bit unsigned integer `{}`", i), - Unexpected::Float(v) => write!(f, "floating point `{}`", v), - Unexpected::Str(ref s) => write!(f, "string {:?}", s), - Unexpected::Unit => write!(f, "unit value"), - Unexpected::Seq => write!(f, "sequence"), - Unexpected::Map => write!(f, "map"), - } - } -} - -/// Represents all possible errors that can occur when working with -/// configuration. -pub enum ConfigError { - /// Configuration is frozen and no further mutations can be made. - Frozen, - - /// Configuration property was not found - NotFound(String), - - /// Configuration path could not be parsed. - PathParse(nom::error::ErrorKind), - - /// Configuration could not be parsed from file. - FileParse { - /// The URI used to access the file (if not loaded from a string). - /// Example: `/path/to/config.json` - uri: Option, - - /// The captured error from attempting to parse the file in its desired format. - /// This is the actual error object from the library used for the parsing. - cause: Box, - }, - - /// Value could not be converted into the requested type. - Type { - /// The URI that references the source that the value came from. - /// Example: `/path/to/config.json` or `Environment` or `etcd://localhost` - // TODO: Why is this called Origin but FileParse has a uri field? - origin: Option, - - /// What we found when parsing the value - unexpected: Unexpected, - - /// What was expected when parsing the value - expected: &'static str, - - /// The key in the configuration hash of this value (if available where the - /// error is generated). - key: Option, - }, - - /// Custom message - Message(String), - - /// Unadorned error from a foreign origin. - Foreign(Box), -} - -impl ConfigError { - // FIXME: pub(crate) - #[doc(hidden)] - pub fn invalid_type( - origin: Option, - unexpected: Unexpected, - expected: &'static str, - ) -> Self { - Self::Type { - origin, - unexpected, - expected, - key: None, - } - } - - // Have a proper error fire if the root of a file is ever not a Table - // TODO: for now only json5 checked, need to finish others - #[doc(hidden)] - pub fn invalid_root(origin: Option<&String>, unexpected: Unexpected) -> Box { - Box::new(Self::Type { - origin: origin.cloned(), - unexpected, - expected: "a map", - key: None, - }) - } - - // FIXME: pub(crate) - #[doc(hidden)] - #[must_use] - pub fn extend_with_key(self, key: &str) -> Self { - match self { - Self::Type { - origin, - unexpected, - expected, - .. - } => Self::Type { - origin, - unexpected, - expected, - key: Some(key.into()), - }, - - _ => self, - } - } - - #[must_use] - fn prepend(self, segment: &str, add_dot: bool) -> Self { - let concat = |key: Option| { - let key = key.unwrap_or_default(); - let dot = if add_dot && key.as_bytes().get(0).unwrap_or(&b'[') != &b'[' { - "." - } else { - "" - }; - format!("{}{}{}", segment, dot, key) - }; - match self { - Self::Type { - origin, - unexpected, - expected, - key, - } => Self::Type { - origin, - unexpected, - expected, - key: Some(concat(key)), - }, - Self::NotFound(key) => Self::NotFound(concat(Some(key))), - _ => self, - } - } - - #[must_use] - pub(crate) fn prepend_key(self, key: &str) -> Self { - self.prepend(key, true) - } - - #[must_use] - pub(crate) fn prepend_index(self, idx: usize) -> Self { - self.prepend(&format!("[{}]", idx), false) - } -} - -/// Alias for a `Result` with the error type set to `ConfigError`. -pub type Result = result::Result; - -// Forward Debug to Display for readable panic! messages -impl fmt::Debug for ConfigError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", *self) - } -} - -impl fmt::Display for ConfigError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ConfigError::Frozen => write!(f, "configuration is frozen"), - - ConfigError::PathParse(ref kind) => write!(f, "{}", kind.description()), - - ConfigError::Message(ref s) => write!(f, "{}", s), - - ConfigError::Foreign(ref cause) => write!(f, "{}", cause), - - ConfigError::NotFound(ref key) => { - write!(f, "configuration property {:?} not found", key) - } - - ConfigError::Type { - ref origin, - ref unexpected, - expected, - ref key, - } => { - write!(f, "invalid type: {}, expected {}", unexpected, expected)?; - - if let Some(ref key) = *key { - write!(f, " for key `{}`", key)?; - } - - if let Some(ref origin) = *origin { - write!(f, " in {}", origin)?; - } - - Ok(()) - } - - ConfigError::FileParse { ref cause, ref uri } => { - write!(f, "{}", cause)?; - - if let Some(ref uri) = *uri { - write!(f, " in {}", uri)?; - } - - Ok(()) - } - } - } -} - -impl Error for ConfigError {} - -impl de::Error for ConfigError { - fn custom(msg: T) -> Self { - Self::Message(msg.to_string()) - } -} - -impl ser::Error for ConfigError { - fn custom(msg: T) -> Self { - Self::Message(msg.to_string()) - } -} diff --git a/src/file/format/ini.rs b/src/file/format/ini.rs deleted file mode 100644 index 9295e60e..00000000 --- a/src/file/format/ini.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::error::Error; - -use ini::Ini; - -use crate::map::Map; -use crate::value::{Value, ValueKind}; - -pub fn parse( - uri: Option<&String>, - text: &str, -) -> Result, Box> { - let mut map: Map = Map::new(); - let i = Ini::load_from_str(text)?; - for (sec, prop) in i.iter() { - match sec { - Some(sec) => { - let mut sec_map: Map = Map::new(); - for (k, v) in prop.iter() { - sec_map.insert( - k.to_owned(), - Value::new(uri, ValueKind::String(v.to_owned())), - ); - } - map.insert(sec.to_owned(), Value::new(uri, ValueKind::Table(sec_map))); - } - None => { - for (k, v) in prop.iter() { - map.insert( - k.to_owned(), - Value::new(uri, ValueKind::String(v.to_owned())), - ); - } - } - } - } - Ok(map) -} diff --git a/src/file/format/json.rs b/src/file/format/json.rs deleted file mode 100644 index 1720cb60..00000000 --- a/src/file/format/json.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::error::Error; - -use crate::map::Map; -use crate::value::{Value, ValueKind}; - -pub fn parse( - uri: Option<&String>, - text: &str, -) -> Result, Box> { - // Parse a JSON object value from the text - // TODO: Have a proper error fire if the root of a file is ever not a Table - let value = from_json_value(uri, &serde_json::from_str(text)?); - match value.kind { - ValueKind::Table(map) => Ok(map), - - _ => Ok(Map::new()), - } -} - -fn from_json_value(uri: Option<&String>, value: &serde_json::Value) -> Value { - match *value { - serde_json::Value::String(ref value) => Value::new(uri, ValueKind::String(value.clone())), - - serde_json::Value::Number(ref value) => { - if let Some(value) = value.as_i64() { - Value::new(uri, ValueKind::I64(value)) - } else if let Some(value) = value.as_f64() { - Value::new(uri, ValueKind::Float(value)) - } else { - unreachable!(); - } - } - - serde_json::Value::Bool(value) => Value::new(uri, ValueKind::Boolean(value)), - - serde_json::Value::Object(ref table) => { - let mut m = Map::new(); - - for (key, value) in table { - m.insert(key.clone(), from_json_value(uri, value)); - } - - Value::new(uri, ValueKind::Table(m)) - } - - serde_json::Value::Array(ref array) => { - let mut l = Vec::new(); - - for value in array { - l.push(from_json_value(uri, value)); - } - - Value::new(uri, ValueKind::Array(l)) - } - - serde_json::Value::Null => Value::new(uri, ValueKind::Nil), - } -} diff --git a/src/file/format/json5.rs b/src/file/format/json5.rs deleted file mode 100644 index 92aaa8f9..00000000 --- a/src/file/format/json5.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::error::Error; - -use crate::error::{ConfigError, Unexpected}; -use crate::map::Map; -use crate::value::{Value, ValueKind}; - -#[derive(serde::Deserialize, Debug)] -#[serde(untagged)] -pub enum Val { - Null, - Boolean(bool), - Integer(i64), - Float(f64), - String(String), - Array(Vec), - Object(Map), -} - -pub fn parse( - uri: Option<&String>, - text: &str, -) -> Result, Box> { - match json5_rs::from_str::(text)? { - Val::String(ref value) => Err(Unexpected::Str(value.clone())), - Val::Integer(value) => Err(Unexpected::I64(value)), - Val::Float(value) => Err(Unexpected::Float(value)), - Val::Boolean(value) => Err(Unexpected::Bool(value)), - Val::Array(_) => Err(Unexpected::Seq), - Val::Null => Err(Unexpected::Unit), - Val::Object(o) => match from_json5_value(uri, Val::Object(o)).kind { - ValueKind::Table(map) => Ok(map), - _ => Ok(Map::new()), - }, - } - .map_err(|err| ConfigError::invalid_root(uri, err)) - .map_err(|err| Box::new(err) as Box) -} - -fn from_json5_value(uri: Option<&String>, value: Val) -> Value { - let vk = match value { - Val::Null => ValueKind::Nil, - Val::String(v) => ValueKind::String(v), - Val::Integer(v) => ValueKind::I64(v), - Val::Float(v) => ValueKind::Float(v), - Val::Boolean(v) => ValueKind::Boolean(v), - Val::Object(table) => { - let m = table - .into_iter() - .map(|(k, v)| (k, from_json5_value(uri, v))) - .collect(); - - ValueKind::Table(m) - } - - Val::Array(array) => { - let l = array - .into_iter() - .map(|v| from_json5_value(uri, v)) - .collect(); - - ValueKind::Array(l) - } - }; - - Value::new(uri, vk) -} diff --git a/src/file/format/mod.rs b/src/file/format/mod.rs deleted file mode 100644 index 025e98a9..00000000 --- a/src/file/format/mod.rs +++ /dev/null @@ -1,147 +0,0 @@ -// If no features are used, there is an "unused mut" warning in `ALL_EXTENSIONS` -// BUG: ? For some reason this doesn't do anything if I try and function scope this -#![allow(unused_mut)] - -use lazy_static::lazy_static; -use std::collections::HashMap; -use std::error::Error; - -use crate::map::Map; -use crate::{file::FileStoredFormat, value::Value, Format}; - -#[cfg(feature = "toml")] -mod toml; - -#[cfg(feature = "json")] -mod json; - -#[cfg(feature = "yaml")] -mod yaml; - -#[cfg(feature = "ini")] -mod ini; - -#[cfg(feature = "ron")] -mod ron; - -#[cfg(feature = "json5")] -mod json5; - -/// File formats provided by the library. -/// -/// Although it is possible to define custom formats using [`Format`] trait it is recommended to use FileFormat if possible. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub enum FileFormat { - /// TOML (parsed with toml) - #[cfg(feature = "toml")] - Toml, - - /// JSON (parsed with serde_json) - #[cfg(feature = "json")] - Json, - - /// YAML (parsed with yaml_rust) - #[cfg(feature = "yaml")] - Yaml, - - /// INI (parsed with rust_ini) - #[cfg(feature = "ini")] - Ini, - - /// RON (parsed with ron) - #[cfg(feature = "ron")] - Ron, - - /// JSON5 (parsed with json5) - #[cfg(feature = "json5")] - Json5, -} - -lazy_static! { - #[doc(hidden)] - // #[allow(unused_mut)] ? - pub static ref ALL_EXTENSIONS: HashMap> = { - let mut formats: HashMap> = HashMap::new(); - - #[cfg(feature = "toml")] - formats.insert(FileFormat::Toml, vec!["toml"]); - - #[cfg(feature = "json")] - formats.insert(FileFormat::Json, vec!["json"]); - - #[cfg(feature = "yaml")] - formats.insert(FileFormat::Yaml, vec!["yaml", "yml"]); - - #[cfg(feature = "ini")] - formats.insert(FileFormat::Ini, vec!["ini"]); - - #[cfg(feature = "ron")] - formats.insert(FileFormat::Ron, vec!["ron"]); - - #[cfg(feature = "json5")] - formats.insert(FileFormat::Json5, vec!["json5"]); - - formats - }; -} - -impl FileFormat { - pub(crate) fn extensions(&self) -> &'static [&'static str] { - // It should not be possible for this to fail - // A FileFormat would need to be declared without being added to the - // ALL_EXTENSIONS map. - ALL_EXTENSIONS.get(self).unwrap() - } - - pub(crate) fn parse( - &self, - uri: Option<&String>, - text: &str, - ) -> Result, Box> { - match self { - #[cfg(feature = "toml")] - FileFormat::Toml => toml::parse(uri, text), - - #[cfg(feature = "json")] - FileFormat::Json => json::parse(uri, text), - - #[cfg(feature = "yaml")] - FileFormat::Yaml => yaml::parse(uri, text), - - #[cfg(feature = "ini")] - FileFormat::Ini => ini::parse(uri, text), - - #[cfg(feature = "ron")] - FileFormat::Ron => ron::parse(uri, text), - - #[cfg(feature = "json5")] - FileFormat::Json5 => json5::parse(uri, text), - - #[cfg(all( - not(feature = "toml"), - not(feature = "json"), - not(feature = "yaml"), - not(feature = "ini"), - not(feature = "ron"), - not(feature = "json5"), - ))] - _ => unreachable!("No features are enabled, this library won't work without features"), - } - } -} - -impl Format for FileFormat { - fn parse( - &self, - uri: Option<&String>, - text: &str, - ) -> Result, Box> { - self.parse(uri, text) - } -} - -impl FileStoredFormat for FileFormat { - fn file_extensions(&self) -> &'static [&'static str] { - self.extensions() - } -} diff --git a/src/file/format/ron.rs b/src/file/format/ron.rs deleted file mode 100644 index fb2b0636..00000000 --- a/src/file/format/ron.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::error::Error; - -use crate::map::Map; -use crate::value::{Value, ValueKind}; - -pub fn parse( - uri: Option<&String>, - text: &str, -) -> Result, Box> { - let value = from_ron_value(uri, ron::from_str(text)?)?; - match value.kind { - ValueKind::Table(map) => Ok(map), - - _ => Ok(Map::new()), - } -} - -fn from_ron_value( - uri: Option<&String>, - value: ron::Value, -) -> Result> { - let kind = match value { - ron::Value::Option(value) => match value { - Some(value) => from_ron_value(uri, *value)?.kind, - None => ValueKind::Nil, - }, - - ron::Value::Unit => ValueKind::Nil, - - ron::Value::Bool(value) => ValueKind::Boolean(value), - - ron::Value::Number(value) => match value { - ron::Number::Float(value) => ValueKind::Float(value.get()), - ron::Number::Integer(value) => ValueKind::I64(value), - }, - - ron::Value::Char(value) => ValueKind::String(value.to_string()), - - ron::Value::String(value) => ValueKind::String(value), - - ron::Value::Seq(values) => { - let array = values - .into_iter() - .map(|value| from_ron_value(uri, value)) - .collect::, _>>()?; - - ValueKind::Array(array) - } - - ron::Value::Map(values) => { - let map = values - .iter() - .map(|(key, value)| -> Result<_, Box> { - let key = key.clone().into_rust::()?; - let value = from_ron_value(uri, value.clone())?; - - Ok((key, value)) - }) - .collect::, _>>()?; - - ValueKind::Table(map) - } - }; - - Ok(Value::new(uri, kind)) -} diff --git a/src/file/format/toml.rs b/src/file/format/toml.rs deleted file mode 100644 index af21fc78..00000000 --- a/src/file/format/toml.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::error::Error; - -use crate::map::Map; -use crate::value::{Value, ValueKind}; - -pub fn parse( - uri: Option<&String>, - text: &str, -) -> Result, Box> { - // Parse a TOML value from the provided text - // TODO: Have a proper error fire if the root of a file is ever not a Table - let value = from_toml_value(uri, &toml::from_str(text)?); - match value.kind { - ValueKind::Table(map) => Ok(map), - - _ => Ok(Map::new()), - } -} - -fn from_toml_value(uri: Option<&String>, value: &toml::Value) -> Value { - match *value { - toml::Value::String(ref value) => Value::new(uri, value.to_string()), - toml::Value::Float(value) => Value::new(uri, value), - toml::Value::Integer(value) => Value::new(uri, value), - toml::Value::Boolean(value) => Value::new(uri, value), - - toml::Value::Table(ref table) => { - let mut m = Map::new(); - - for (key, value) in table { - m.insert(key.clone(), from_toml_value(uri, value)); - } - - Value::new(uri, m) - } - - toml::Value::Array(ref array) => { - let mut l = Vec::new(); - - for value in array { - l.push(from_toml_value(uri, value)); - } - - Value::new(uri, l) - } - - toml::Value::Datetime(ref datetime) => Value::new(uri, datetime.to_string()), - } -} diff --git a/src/file/format/yaml.rs b/src/file/format/yaml.rs deleted file mode 100644 index 2a76261c..00000000 --- a/src/file/format/yaml.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::error::Error; -use std::fmt; -use std::mem; - -use yaml_rust as yaml; - -use crate::map::Map; -use crate::value::{Value, ValueKind}; - -pub fn parse( - uri: Option<&String>, - text: &str, -) -> Result, Box> { - // Parse a YAML object from file - let mut docs = yaml::YamlLoader::load_from_str(text)?; - let root = match docs.len() { - 0 => yaml::Yaml::Hash(yaml::yaml::Hash::new()), - 1 => mem::replace(&mut docs[0], yaml::Yaml::Null), - n => { - return Err(Box::new(MultipleDocumentsError(n))); - } - }; - - // TODO: Have a proper error fire if the root of a file is ever not a Table - let value = from_yaml_value(uri, &root)?; - match value.kind { - ValueKind::Table(map) => Ok(map), - - _ => Ok(Map::new()), - } -} - -fn from_yaml_value( - uri: Option<&String>, - value: &yaml::Yaml, -) -> Result> { - match *value { - yaml::Yaml::String(ref value) => Ok(Value::new(uri, ValueKind::String(value.clone()))), - yaml::Yaml::Real(ref value) => { - // TODO: Figure out in what cases this can panic? - value - .parse::() - .map_err(|_| { - Box::new(FloatParsingError(value.to_string())) as Box<(dyn Error + Send + Sync)> - }) - .map(ValueKind::Float) - .map(|f| Value::new(uri, f)) - } - yaml::Yaml::Integer(value) => Ok(Value::new(uri, ValueKind::I64(value))), - yaml::Yaml::Boolean(value) => Ok(Value::new(uri, ValueKind::Boolean(value))), - yaml::Yaml::Hash(ref table) => { - let mut m = Map::new(); - for (key, value) in table { - if let Some(k) = key.as_str() { - m.insert(k.to_owned(), from_yaml_value(uri, value)?); - } - // TODO: should we do anything for non-string keys? - } - Ok(Value::new(uri, ValueKind::Table(m))) - } - yaml::Yaml::Array(ref array) => { - let mut l = Vec::new(); - - for value in array { - l.push(from_yaml_value(uri, value)?); - } - - Ok(Value::new(uri, ValueKind::Array(l))) - } - - // 1. Yaml NULL - // 2. BadValue – It shouldn't be possible to hit BadValue as this only happens when - // using the index trait badly or on a type error but we send back nil. - // 3. Alias – No idea what to do with this and there is a note in the lib that its - // not fully supported yet anyway - _ => Ok(Value::new(uri, ValueKind::Nil)), - } -} - -#[derive(Debug, Copy, Clone)] -struct MultipleDocumentsError(usize); - -impl fmt::Display for MultipleDocumentsError { - fn fmt(&self, format: &mut fmt::Formatter) -> fmt::Result { - write!(format, "Got {} YAML documents, expected 1", self.0) - } -} - -impl Error for MultipleDocumentsError { - fn description(&self) -> &str { - "More than one YAML document provided" - } -} - -#[derive(Debug, Clone)] -struct FloatParsingError(String); - -impl fmt::Display for FloatParsingError { - fn fmt(&self, format: &mut fmt::Formatter) -> fmt::Result { - write!(format, "Parsing {} as floating point number failed", self.0) - } -} - -impl Error for FloatParsingError { - fn description(&self) -> &str { - "Floating point number parsing failed" - } -} diff --git a/src/file/mod.rs b/src/file/mod.rs deleted file mode 100644 index 65f3fd65..00000000 --- a/src/file/mod.rs +++ /dev/null @@ -1,149 +0,0 @@ -mod format; -pub mod source; - -use std::fmt::Debug; -use std::path::{Path, PathBuf}; - -use crate::error::{ConfigError, Result}; -use crate::map::Map; -use crate::source::Source; -use crate::value::Value; -use crate::Format; - -pub use self::format::FileFormat; -use self::source::FileSource; - -pub use self::source::file::FileSourceFile; -pub use self::source::string::FileSourceString; - -/// A configuration source backed up by a file. -/// -/// It supports optional automatic file format discovery. -#[derive(Clone, Debug)] -pub struct File { - source: T, - - /// Format of file (which dictates what driver to use). - format: Option, - - /// A required File will error if it cannot be found - required: bool, -} - -/// An extension of [`Format`](crate::Format) trait. -/// -/// Associates format with file extensions, therefore linking storage-agnostic notion of format to a file system. -pub trait FileStoredFormat: Format { - /// Returns a vector of file extensions, for instance `[yml, yaml]`. - fn file_extensions(&self) -> &'static [&'static str]; -} - -impl File -where - F: FileStoredFormat + 'static, -{ - pub fn from_str(s: &str, format: F) -> Self { - Self { - format: Some(format), - required: true, - source: s.into(), - } - } -} - -impl File -where - F: FileStoredFormat + 'static, -{ - pub fn new(name: &str, format: F) -> Self { - Self { - format: Some(format), - required: true, - source: source::file::FileSourceFile::new(name.into()), - } - } -} - -impl File { - /// Given the basename of a file, will attempt to locate a file by setting its - /// extension to a registered format. - pub fn with_name(name: &str) -> Self { - Self { - format: None, - required: true, - source: source::file::FileSourceFile::new(name.into()), - } - } -} - -impl<'a> From<&'a Path> for File { - fn from(path: &'a Path) -> Self { - Self { - format: None, - required: true, - source: source::file::FileSourceFile::new(path.to_path_buf()), - } - } -} - -impl From for File { - fn from(path: PathBuf) -> Self { - Self { - format: None, - required: true, - source: source::file::FileSourceFile::new(path), - } - } -} - -impl File -where - F: FileStoredFormat + 'static, - T: FileSource, -{ - #[must_use] - pub fn format(mut self, format: F) -> Self { - self.format = Some(format); - self - } - - #[must_use] - pub fn required(mut self, required: bool) -> Self { - self.required = required; - self - } -} - -impl Source for File -where - F: FileStoredFormat + Debug + Clone + Send + Sync + 'static, - T: Sync + Send + FileSource + 'static, -{ - fn clone_into_box(&self) -> Box { - Box::new((*self).clone()) - } - - fn collect(&self) -> Result> { - // Coerce the file contents to a string - let (uri, contents, format) = match self - .source - .resolve(self.format.clone()) - .map_err(|err| ConfigError::Foreign(err)) - { - Ok(result) => (result.uri, result.content, result.format), - - Err(error) => { - if !self.required { - return Ok(Map::new()); - } - - return Err(error); - } - }; - - // Parse the string using the given format - format - .parse(uri.as_ref(), &contents) - .map_err(|cause| ConfigError::FileParse { uri, cause }) - } -} diff --git a/src/file/source/file.rs b/src/file/source/file.rs deleted file mode 100644 index 8ee5d315..00000000 --- a/src/file/source/file.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::env; -use std::error::Error; -use std::fs; -use std::io; -use std::path::PathBuf; - -use crate::file::{ - format::ALL_EXTENSIONS, source::FileSourceResult, FileSource, FileStoredFormat, Format, -}; - -/// Describes a file sourced from a file -#[derive(Clone, Debug)] -pub struct FileSourceFile { - /// Path of configuration file - name: PathBuf, -} - -impl FileSourceFile { - pub fn new(name: PathBuf) -> Self { - Self { name } - } - - fn find_file( - &self, - format_hint: Option, - ) -> Result<(PathBuf, Box), Box> - where - F: FileStoredFormat + Format + 'static, - { - let filename = if self.name.is_absolute() { - self.name.clone() - } else { - env::current_dir()?.as_path().join(&self.name) - }; - - // First check for an _exact_ match - if filename.is_file() { - return if let Some(format) = format_hint { - Ok((filename, Box::new(format))) - } else { - for (format, extensions) in ALL_EXTENSIONS.iter() { - if extensions.contains( - &filename - .extension() - .unwrap_or_default() - .to_string_lossy() - .as_ref(), - ) { - return Ok((filename, Box::new(*format))); - } - } - - Err(Box::new(io::Error::new( - io::ErrorKind::NotFound, - format!( - "configuration file \"{}\" is not of a registered file format", - filename.to_string_lossy() - ), - ))) - }; - } - // Adding a dummy extension will make sure we will not override secondary extensions, i.e. "file.local" - // This will make the following set_extension function calls to append the extension. - let mut filename = add_dummy_extension(filename); - - match format_hint { - Some(format) => { - for ext in format.file_extensions() { - filename.set_extension(ext); - - if filename.is_file() { - return Ok((filename, Box::new(format))); - } - } - } - - None => { - for format in ALL_EXTENSIONS.keys() { - for ext in format.extensions() { - filename.set_extension(ext); - - if filename.is_file() { - return Ok((filename, Box::new(*format))); - } - } - } - } - } - - Err(Box::new(io::Error::new( - io::ErrorKind::NotFound, - format!( - "configuration file \"{}\" not found", - self.name.to_string_lossy() - ), - ))) - } -} - -impl FileSource for FileSourceFile -where - F: Format + FileStoredFormat + 'static, -{ - fn resolve( - &self, - format_hint: Option, - ) -> Result> { - // Find file - let (filename, format) = self.find_file(format_hint)?; - - // Attempt to use a relative path for the URI - let uri = env::current_dir() - .ok() - .and_then(|base| pathdiff::diff_paths(&filename, base)) - .unwrap_or_else(|| filename.clone()); - - // Read contents from file - let text = fs::read_to_string(filename)?; - - Ok(FileSourceResult { - uri: Some(uri.to_string_lossy().into_owned()), - content: text, - format, - }) - } -} - -fn add_dummy_extension(mut filename: PathBuf) -> PathBuf { - match filename.extension() { - Some(extension) => { - let mut ext = extension.to_os_string(); - ext.push("."); - ext.push("dummy"); - filename.set_extension(ext); - } - None => { - filename.set_extension("dummy"); - } - } - filename -} diff --git a/src/file/source/mod.rs b/src/file/source/mod.rs deleted file mode 100644 index 3c3d10ca..00000000 --- a/src/file/source/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -pub mod file; -pub mod string; - -use std::error::Error; -use std::fmt::Debug; - -use crate::{file::FileStoredFormat, Format}; - -/// Describes where the file is sourced -pub trait FileSource: Debug + Clone -where - T: Format + FileStoredFormat, -{ - fn resolve( - &self, - format_hint: Option, - ) -> Result>; -} - -pub struct FileSourceResult { - pub(crate) uri: Option, - pub(crate) content: String, - pub(crate) format: Box, -} - -impl FileSourceResult { - pub fn uri(&self) -> &Option { - &self.uri - } - - pub fn content(&self) -> &str { - self.content.as_str() - } - - pub fn format(&self) -> &dyn Format { - self.format.as_ref() - } -} diff --git a/src/file/source/string.rs b/src/file/source/string.rs deleted file mode 100644 index 89300d4a..00000000 --- a/src/file/source/string.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::error::Error; - -use crate::{ - file::source::FileSourceResult, - file::{FileSource, FileStoredFormat}, - Format, -}; - -/// Describes a file sourced from a string -#[derive(Clone, Debug)] -pub struct FileSourceString(String); - -impl<'a> From<&'a str> for FileSourceString { - fn from(s: &'a str) -> Self { - Self(s.into()) - } -} - -impl FileSource for FileSourceString -where - F: Format + FileStoredFormat + 'static, -{ - fn resolve( - &self, - format_hint: Option, - ) -> Result> { - Ok(FileSourceResult { - uri: None, - content: self.0.clone(), - format: Box::new(format_hint.expect("from_str requires a set file format")), - }) - } -} diff --git a/src/format.rs b/src/format.rs deleted file mode 100644 index ab9e4fa2..00000000 --- a/src/format.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::error::Error; - -use crate::{map::Map, value::Value}; - -/// Describes a format of configuration source data -/// -/// Implementations of this trait can be used to convert [`File`](crate::File) sources to configuration data. -/// -/// There can be various formats, some of them provided by this library, such as JSON, Yaml and other. -/// This trait enables users of the library to easily define their own, even proprietary formats without -/// the need to alter library sources. -/// -/// What is more, it is recommended to use this trait with custom [`Source`](crate::Source)s and their async counterparts. -pub trait Format { - /// Parses provided content into configuration values understood by the library. - /// - /// It also allows specifying optional URI of the source associated with format instance that can facilitate debugging. - fn parse( - &self, - uri: Option<&String>, - text: &str, - ) -> Result, Box>; -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 589d2d51..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Config organizes hierarchical or layered configurations for Rust applications. -//! -//! Config lets you set a set of default parameters and then extend them via merging in -//! configuration from a variety of sources: -//! -//! - Environment variables -//! - String literals in well-known formats -//! - Another Config instance -//! - Files: TOML, JSON, YAML, INI, RON, JSON5 and custom ones defined with Format trait -//! - Manual, programmatic override (via a `.set` method on the Config instance) -//! -//! Additionally, Config supports: -//! -//! - Live watching and re-reading of configuration files -//! - Deep access into the merged configuration via a path syntax -//! - Deserialization via `serde` of the configuration or any subset defined via a path -//! -//! See the [examples](https://github.com/mehcode/config-rs/tree/master/examples) for -//! general usage information. -#![allow(unknown_lints)] -// #![warn(missing_docs)] - -pub mod builder; -mod config; -mod de; -mod env; -mod error; -mod file; -mod format; -mod map; -mod path; -mod ser; -mod source; -mod value; - -pub use crate::builder::{AsyncConfigBuilder, ConfigBuilder}; -pub use crate::config::Config; -pub use crate::env::Environment; -pub use crate::error::ConfigError; -pub use crate::file::{File, FileFormat, FileSourceFile, FileSourceString, FileStoredFormat}; -pub use crate::format::Format; -pub use crate::map::Map; -pub use crate::source::{AsyncSource, Source}; -pub use crate::value::{Value, ValueKind}; diff --git a/src/map.rs b/src/map.rs deleted file mode 100644 index 5873f0d2..00000000 --- a/src/map.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[cfg(not(feature = "preserve_order"))] -pub type Map = std::collections::HashMap; -#[cfg(feature = "preserve_order")] -pub type Map = indexmap::IndexMap; diff --git a/src/path/mod.rs b/src/path/mod.rs deleted file mode 100644 index 7456aa34..00000000 --- a/src/path/mod.rs +++ /dev/null @@ -1,250 +0,0 @@ -use std::str::FromStr; - -use crate::error::{ConfigError, Result}; -use crate::map::Map; -use crate::value::{Value, ValueKind}; - -mod parser; - -#[derive(Debug, Eq, PartialEq, Clone, Hash)] -pub enum Expression { - Identifier(String), - Child(Box, String), - Subscript(Box, isize), -} - -impl FromStr for Expression { - type Err = ConfigError; - - fn from_str(s: &str) -> Result { - parser::from_str(s).map_err(ConfigError::PathParse) - } -} - -fn sindex_to_uindex(index: isize, len: usize) -> usize { - if index >= 0 { - index as usize - } else { - len - (index.abs() as usize) - } -} - -impl Expression { - pub fn get(self, root: &Value) -> Option<&Value> { - match self { - Self::Identifier(id) => { - match root.kind { - // `x` access on a table is equivalent to: map[x] - ValueKind::Table(ref map) => map.get(&id), - - // all other variants return None - _ => None, - } - } - - Self::Child(expr, key) => { - match expr.get(root) { - Some(value) => { - match value.kind { - // Access on a table is identical to Identifier, it just forwards - ValueKind::Table(ref map) => map.get(&key), - - // all other variants return None - _ => None, - } - } - - _ => None, - } - } - - Self::Subscript(expr, index) => match expr.get(root) { - Some(value) => match value.kind { - ValueKind::Array(ref array) => { - let index = sindex_to_uindex(index, array.len()); - - if index >= array.len() { - None - } else { - Some(&array[index]) - } - } - - _ => None, - }, - - _ => None, - }, - } - } - - pub fn get_mut<'a>(&self, root: &'a mut Value) -> Option<&'a mut Value> { - match *self { - Self::Identifier(ref id) => match root.kind { - ValueKind::Table(ref mut map) => map.get_mut(id), - - _ => None, - }, - - Self::Child(ref expr, ref key) => match expr.get_mut(root) { - Some(value) => match value.kind { - ValueKind::Table(ref mut map) => map.get_mut(key), - - _ => None, - }, - - _ => None, - }, - - Self::Subscript(ref expr, index) => match expr.get_mut(root) { - Some(value) => match value.kind { - ValueKind::Array(ref mut array) => { - let index = sindex_to_uindex(index, array.len()); - - if index >= array.len() { - None - } else { - Some(&mut array[index]) - } - } - - _ => None, - }, - - _ => None, - }, - } - } - - pub fn get_mut_forcibly<'a>(&self, root: &'a mut Value) -> Option<&'a mut Value> { - match *self { - Self::Identifier(ref id) => match root.kind { - ValueKind::Table(ref mut map) => Some( - map.entry(id.clone()) - .or_insert_with(|| Value::new(None, ValueKind::Nil)), - ), - - _ => None, - }, - - Self::Child(ref expr, ref key) => match expr.get_mut_forcibly(root) { - Some(value) => { - if let ValueKind::Table(ref mut map) = value.kind { - Some( - map.entry(key.clone()) - .or_insert_with(|| Value::new(None, ValueKind::Nil)), - ) - } else { - *value = Map::::new().into(); - - if let ValueKind::Table(ref mut map) = value.kind { - Some( - map.entry(key.clone()) - .or_insert_with(|| Value::new(None, ValueKind::Nil)), - ) - } else { - unreachable!(); - } - } - } - - _ => None, - }, - - Self::Subscript(ref expr, index) => match expr.get_mut_forcibly(root) { - Some(value) => { - match value.kind { - ValueKind::Array(_) => (), - _ => *value = Vec::::new().into(), - } - - match value.kind { - ValueKind::Array(ref mut array) => { - let index = sindex_to_uindex(index, array.len()); - - if index >= array.len() { - array - .resize((index + 1) as usize, Value::new(None, ValueKind::Nil)); - } - - Some(&mut array[index]) - } - - _ => None, - } - } - _ => None, - }, - } - } - - pub fn set(&self, root: &mut Value, value: Value) { - match *self { - Self::Identifier(ref id) => { - // Ensure that root is a table - match root.kind { - ValueKind::Table(_) => {} - - _ => { - *root = Map::::new().into(); - } - } - - match value.kind { - ValueKind::Table(ref incoming_map) => { - // Pull out another table - let target = if let ValueKind::Table(ref mut map) = root.kind { - map.entry(id.clone()) - .or_insert_with(|| Map::::new().into()) - } else { - unreachable!(); - }; - - // Continue the deep merge - for (key, val) in incoming_map { - Self::Identifier(key.clone()).set(target, val.clone()); - } - } - - _ => { - if let ValueKind::Table(ref mut map) = root.kind { - // Just do a simple set - if let Some(existing) = map.get_mut(id) { - *existing = value; - } else { - map.insert(id.clone(), value); - } - } - } - } - } - - Self::Child(ref expr, ref key) => { - if let Some(parent) = expr.get_mut_forcibly(root) { - if !matches!(parent.kind, ValueKind::Table(_)) { - // Didn't find a table. Oh well. Make a table and do this anyway - *parent = Map::::new().into(); - } - Self::Identifier(key.clone()).set(parent, value); - } - } - - Self::Subscript(ref expr, index) => { - if let Some(parent) = expr.get_mut_forcibly(root) { - if !matches!(parent.kind, ValueKind::Array(_)) { - *parent = Vec::::new().into() - } - - if let ValueKind::Array(ref mut array) = parent.kind { - let uindex = sindex_to_uindex(index, array.len()); - if uindex >= array.len() { - array.resize((uindex + 1) as usize, Value::new(None, ValueKind::Nil)); - } - - array[uindex] = value; - } - } - } - } - } -} diff --git a/src/path/parser.rs b/src/path/parser.rs deleted file mode 100644 index 8378121b..00000000 --- a/src/path/parser.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::str::FromStr; - -use nom::{ - branch::alt, - bytes::complete::{is_a, tag}, - character::complete::{char, digit1, space0}, - combinator::{map, map_res, opt, recognize}, - error::ErrorKind, - sequence::{delimited, pair, preceded}, - Err, IResult, -}; - -use crate::path::Expression; - -fn raw_ident(i: &str) -> IResult<&str, String> { - map( - is_a( - "abcdefghijklmnopqrstuvwxyz \ - ABCDEFGHIJKLMNOPQRSTUVWXYZ \ - 0123456789 \ - _-", - ), - ToString::to_string, - )(i) -} - -fn integer(i: &str) -> IResult<&str, isize> { - map_res( - delimited(space0, recognize(pair(opt(tag("-")), digit1)), space0), - FromStr::from_str, - )(i) -} - -fn ident(i: &str) -> IResult<&str, Expression> { - map(raw_ident, Expression::Identifier)(i) -} - -fn postfix<'a>(expr: Expression) -> impl FnMut(&'a str) -> IResult<&'a str, Expression> { - let e2 = expr.clone(); - let child = map(preceded(tag("."), raw_ident), move |id| { - Expression::Child(Box::new(expr.clone()), id) - }); - - let subscript = map(delimited(char('['), integer, char(']')), move |num| { - Expression::Subscript(Box::new(e2.clone()), num) - }); - - alt((child, subscript)) -} - -pub fn from_str(input: &str) -> Result { - match ident(input) { - Ok((mut rem, mut expr)) => { - while !rem.is_empty() { - match postfix(expr)(rem) { - Ok((rem_, expr_)) => { - rem = rem_; - expr = expr_; - } - - // Forward Incomplete and Error - result => { - return result.map(|(_, o)| o).map_err(to_error_kind); - } - } - } - - Ok(expr) - } - - // Forward Incomplete and Error - result => result.map(|(_, o)| o).map_err(to_error_kind), - } -} - -pub fn to_error_kind(e: Err>) -> ErrorKind { - match e { - Err::Incomplete(_) => ErrorKind::Complete, - Err::Failure(e) | Err::Error(e) => e.code, - } -} - -#[cfg(test)] -mod test { - use super::Expression::*; - use super::*; - - #[test] - fn test_id() { - let parsed: Expression = from_str("abcd").unwrap(); - assert_eq!(parsed, Identifier("abcd".into())); - } - - #[test] - fn test_id_dash() { - let parsed: Expression = from_str("abcd-efgh").unwrap(); - assert_eq!(parsed, Identifier("abcd-efgh".into())); - } - - #[test] - fn test_child() { - let parsed: Expression = from_str("abcd.efgh").unwrap(); - let expected = Child(Box::new(Identifier("abcd".into())), "efgh".into()); - - assert_eq!(parsed, expected); - - let parsed: Expression = from_str("abcd.efgh.ijkl").unwrap(); - let expected = Child( - Box::new(Child(Box::new(Identifier("abcd".into())), "efgh".into())), - "ijkl".into(), - ); - - assert_eq!(parsed, expected); - } - - #[test] - fn test_subscript() { - let parsed: Expression = from_str("abcd[12]").unwrap(); - let expected = Subscript(Box::new(Identifier("abcd".into())), 12); - - assert_eq!(parsed, expected); - } - - #[test] - fn test_subscript_neg() { - let parsed: Expression = from_str("abcd[-1]").unwrap(); - let expected = Subscript(Box::new(Identifier("abcd".into())), -1); - - assert_eq!(parsed, expected); - } -} diff --git a/src/ser.rs b/src/ser.rs deleted file mode 100644 index 9ccf7c77..00000000 --- a/src/ser.rs +++ /dev/null @@ -1,720 +0,0 @@ -use std::fmt::Display; - -use serde::ser; - -use crate::error::{ConfigError, Result}; -use crate::value::{Value, ValueKind}; -use crate::Config; - -#[derive(Default, Debug)] -pub struct ConfigSerializer { - keys: Vec<(String, Option)>, - pub output: Config, -} - -impl ConfigSerializer { - fn serialize_primitive(&mut self, value: T) -> Result<()> - where - T: Into + Display, - { - let key = match self.last_key_index_pair() { - Some((key, Some(index))) => format!("{}[{}]", key, index), - Some((key, None)) => key.to_string(), - None => { - return Err(ConfigError::Message(format!( - "key is not found for value {}", - value - ))) - } - }; - - #[allow(deprecated)] - self.output.set(&key, value.into())?; - Ok(()) - } - - fn last_key_index_pair(&self) -> Option<(&str, Option)> { - let len = self.keys.len(); - if len > 0 { - self.keys - .get(len - 1) - .map(|&(ref key, opt)| (key.as_str(), opt)) - } else { - None - } - } - - fn inc_last_key_index(&mut self) -> Result<()> { - let len = self.keys.len(); - if len > 0 { - self.keys - .get_mut(len - 1) - .map(|pair| pair.1 = pair.1.map(|i| i + 1).or(Some(0))) - .ok_or_else(|| { - ConfigError::Message(format!("last key is not found in {} keys", len)) - }) - } else { - Err(ConfigError::Message("keys is empty".to_string())) - } - } - - fn make_full_key(&self, key: &str) -> String { - let len = self.keys.len(); - if len > 0 { - if let Some(&(ref prev_key, index)) = self.keys.get(len - 1) { - return if let Some(index) = index { - format!("{}[{}].{}", prev_key, index, key) - } else { - format!("{}.{}", prev_key, key) - }; - } - } - key.to_string() - } - - fn push_key(&mut self, key: &str) { - let full_key = self.make_full_key(key); - self.keys.push((full_key, None)); - } - - fn pop_key(&mut self) -> Option<(String, Option)> { - self.keys.pop() - } -} - -impl<'a> ser::Serializer for &'a mut ConfigSerializer { - type Ok = (); - type Error = ConfigError; - type SerializeSeq = Self; - type SerializeTuple = Self; - type SerializeTupleStruct = Self; - type SerializeTupleVariant = Self; - type SerializeMap = Self; - type SerializeStruct = Self; - type SerializeStructVariant = Self; - - fn serialize_bool(self, v: bool) -> Result { - self.serialize_primitive(v) - } - - fn serialize_i8(self, v: i8) -> Result { - self.serialize_i64(v.into()) - } - - fn serialize_i16(self, v: i16) -> Result { - self.serialize_i64(v.into()) - } - - fn serialize_i32(self, v: i32) -> Result { - self.serialize_i64(v.into()) - } - - fn serialize_i64(self, v: i64) -> Result { - self.serialize_primitive(v) - } - - fn serialize_u8(self, v: u8) -> Result { - self.serialize_u64(v.into()) - } - - fn serialize_u16(self, v: u16) -> Result { - self.serialize_u64(v.into()) - } - - fn serialize_u32(self, v: u32) -> Result { - self.serialize_u64(v.into()) - } - - fn serialize_u64(self, v: u64) -> Result { - if v > (i64::max_value() as u64) { - Err(ConfigError::Message(format!( - "value {} is greater than the max {}", - v, - i64::max_value() - ))) - } else { - self.serialize_i64(v as i64) - } - } - - fn serialize_f32(self, v: f32) -> Result { - self.serialize_f64(v.into()) - } - - fn serialize_f64(self, v: f64) -> Result { - self.serialize_primitive(v) - } - - fn serialize_char(self, v: char) -> Result { - self.serialize_primitive(v.to_string()) - } - - fn serialize_str(self, v: &str) -> Result { - self.serialize_primitive(v.to_string()) - } - - fn serialize_bytes(self, v: &[u8]) -> Result { - use serde::ser::SerializeSeq; - let mut seq = self.serialize_seq(Some(v.len()))?; - for byte in v { - seq.serialize_element(byte)?; - } - seq.end() - } - - fn serialize_none(self) -> Result { - self.serialize_unit() - } - - fn serialize_some(self, value: &T) -> Result - where - T: ?Sized + ser::Serialize, - { - value.serialize(self) - } - - fn serialize_unit(self) -> Result { - self.serialize_primitive(Value::from(ValueKind::Nil)) - } - - fn serialize_unit_struct(self, _name: &'static str) -> Result { - self.serialize_unit() - } - - fn serialize_unit_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - ) -> Result { - self.serialize_str(variant) - } - - fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result - where - T: ?Sized + ser::Serialize, - { - value.serialize(self) - } - - fn serialize_newtype_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - value: &T, - ) -> Result - where - T: ?Sized + ser::Serialize, - { - self.push_key(variant); - value.serialize(&mut *self)?; - self.pop_key(); - Ok(()) - } - - fn serialize_seq(self, _len: Option) -> Result { - Ok(self) - } - - fn serialize_tuple(self, len: usize) -> Result { - self.serialize_seq(Some(len)) - } - - fn serialize_tuple_struct( - self, - _name: &'static str, - len: usize, - ) -> Result { - self.serialize_seq(Some(len)) - } - - fn serialize_tuple_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - _len: usize, - ) -> Result { - self.push_key(variant); - Ok(self) - } - - fn serialize_map(self, _len: Option) -> Result { - Ok(self) - } - - fn serialize_struct(self, _name: &'static str, len: usize) -> Result { - self.serialize_map(Some(len)) - } - - fn serialize_struct_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - _len: usize, - ) -> Result { - self.push_key(variant); - Ok(self) - } -} - -impl<'a> ser::SerializeSeq for &'a mut ConfigSerializer { - type Ok = (); - type Error = ConfigError; - - fn serialize_element(&mut self, value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - self.inc_last_key_index()?; - value.serialize(&mut **self)?; - Ok(()) - } - - fn end(self) -> Result { - Ok(()) - } -} - -impl<'a> ser::SerializeTuple for &'a mut ConfigSerializer { - type Ok = (); - type Error = ConfigError; - - fn serialize_element(&mut self, value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - self.inc_last_key_index()?; - value.serialize(&mut **self)?; - Ok(()) - } - - fn end(self) -> Result { - Ok(()) - } -} - -impl<'a> ser::SerializeTupleStruct for &'a mut ConfigSerializer { - type Ok = (); - type Error = ConfigError; - - fn serialize_field(&mut self, value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - self.inc_last_key_index()?; - value.serialize(&mut **self)?; - Ok(()) - } - - fn end(self) -> Result { - Ok(()) - } -} - -impl<'a> ser::SerializeTupleVariant for &'a mut ConfigSerializer { - type Ok = (); - type Error = ConfigError; - - fn serialize_field(&mut self, value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - self.inc_last_key_index()?; - value.serialize(&mut **self)?; - Ok(()) - } - - fn end(self) -> Result { - self.pop_key(); - Ok(()) - } -} - -impl<'a> ser::SerializeMap for &'a mut ConfigSerializer { - type Ok = (); - type Error = ConfigError; - - fn serialize_key(&mut self, key: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - let key_serializer = StringKeySerializer; - let key = key.serialize(key_serializer)?; - self.push_key(&key); - Ok(()) - } - - fn serialize_value(&mut self, value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - value.serialize(&mut **self)?; - self.pop_key(); - Ok(()) - } - - fn end(self) -> Result { - Ok(()) - } -} - -impl<'a> ser::SerializeStruct for &'a mut ConfigSerializer { - type Ok = (); - type Error = ConfigError; - - fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - self.push_key(key); - value.serialize(&mut **self)?; - self.pop_key(); - Ok(()) - } - - fn end(self) -> Result { - Ok(()) - } -} - -impl<'a> ser::SerializeStructVariant for &'a mut ConfigSerializer { - type Ok = (); - type Error = ConfigError; - - fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - self.push_key(key); - value.serialize(&mut **self)?; - self.pop_key(); - Ok(()) - } - - fn end(self) -> Result { - self.pop_key(); - Ok(()) - } -} - -pub struct StringKeySerializer; - -impl ser::Serializer for StringKeySerializer { - type Ok = String; - type Error = ConfigError; - type SerializeSeq = Self; - type SerializeTuple = Self; - type SerializeTupleStruct = Self; - type SerializeTupleVariant = Self; - type SerializeMap = Self; - type SerializeStruct = Self; - type SerializeStructVariant = Self; - - fn serialize_bool(self, v: bool) -> Result { - Ok(v.to_string()) - } - - fn serialize_i8(self, v: i8) -> Result { - Ok(v.to_string()) - } - - fn serialize_i16(self, v: i16) -> Result { - Ok(v.to_string()) - } - - fn serialize_i32(self, v: i32) -> Result { - Ok(v.to_string()) - } - - fn serialize_i64(self, v: i64) -> Result { - Ok(v.to_string()) - } - - fn serialize_u8(self, v: u8) -> Result { - Ok(v.to_string()) - } - - fn serialize_u16(self, v: u16) -> Result { - Ok(v.to_string()) - } - - fn serialize_u32(self, v: u32) -> Result { - Ok(v.to_string()) - } - - fn serialize_u64(self, v: u64) -> Result { - Ok(v.to_string()) - } - - fn serialize_f32(self, v: f32) -> Result { - Ok(v.to_string()) - } - - fn serialize_f64(self, v: f64) -> Result { - Ok(v.to_string()) - } - - fn serialize_char(self, v: char) -> Result { - Ok(v.to_string()) - } - - fn serialize_str(self, v: &str) -> Result { - Ok(v.to_string()) - } - - fn serialize_bytes(self, v: &[u8]) -> Result { - Ok(String::from_utf8_lossy(v).to_string()) - } - - fn serialize_none(self) -> Result { - self.serialize_unit() - } - - fn serialize_some(self, value: &T) -> Result - where - T: ?Sized + ser::Serialize, - { - value.serialize(self) - } - - fn serialize_unit(self) -> Result { - Ok(String::new()) - } - - fn serialize_unit_struct(self, _name: &str) -> Result { - self.serialize_unit() - } - - fn serialize_unit_variant( - self, - _name: &str, - _variant_index: u32, - variant: &str, - ) -> Result { - Ok(variant.to_string()) - } - - fn serialize_newtype_struct(self, _name: &str, value: &T) -> Result - where - T: ?Sized + ser::Serialize, - { - value.serialize(self) - } - - fn serialize_newtype_variant( - self, - _name: &str, - _variant_index: u32, - _variant: &str, - value: &T, - ) -> Result - where - T: ?Sized + ser::Serialize, - { - value.serialize(self) - } - - fn serialize_seq(self, _len: Option) -> Result { - Err(ConfigError::Message( - "seq can't serialize to string key".to_string(), - )) - } - - fn serialize_tuple(self, _len: usize) -> Result { - Err(ConfigError::Message( - "tuple can't serialize to string key".to_string(), - )) - } - - fn serialize_tuple_struct(self, name: &str, _len: usize) -> Result { - Err(ConfigError::Message(format!( - "tuple struct {} can't serialize to string key", - name - ))) - } - - fn serialize_tuple_variant( - self, - name: &str, - _variant_index: u32, - variant: &str, - _len: usize, - ) -> Result { - Err(ConfigError::Message(format!( - "tuple variant {}::{} can't serialize to string key", - name, variant - ))) - } - - fn serialize_map(self, _len: Option) -> Result { - Err(ConfigError::Message( - "map can't serialize to string key".to_string(), - )) - } - - fn serialize_struct(self, name: &str, _len: usize) -> Result { - Err(ConfigError::Message(format!( - "struct {} can't serialize to string key", - name - ))) - } - - fn serialize_struct_variant( - self, - name: &str, - _variant_index: u32, - variant: &str, - _len: usize, - ) -> Result { - Err(ConfigError::Message(format!( - "struct variant {}::{} can't serialize to string key", - name, variant - ))) - } -} - -impl ser::SerializeSeq for StringKeySerializer { - type Ok = String; - type Error = ConfigError; - - fn serialize_element(&mut self, _value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - unreachable!() - } - - fn end(self) -> Result { - unreachable!() - } -} - -impl ser::SerializeTuple for StringKeySerializer { - type Ok = String; - type Error = ConfigError; - - fn serialize_element(&mut self, _value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - unreachable!() - } - - fn end(self) -> Result { - unreachable!() - } -} - -impl ser::SerializeTupleStruct for StringKeySerializer { - type Ok = String; - type Error = ConfigError; - - fn serialize_field(&mut self, _value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - unreachable!() - } - - fn end(self) -> Result { - unreachable!() - } -} - -impl ser::SerializeTupleVariant for StringKeySerializer { - type Ok = String; - type Error = ConfigError; - - fn serialize_field(&mut self, _value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - unreachable!() - } - - fn end(self) -> Result { - unreachable!() - } -} - -impl ser::SerializeMap for StringKeySerializer { - type Ok = String; - type Error = ConfigError; - - fn serialize_key(&mut self, _key: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - unreachable!() - } - - fn serialize_value(&mut self, _value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - unreachable!() - } - - fn end(self) -> Result { - unreachable!() - } -} - -impl ser::SerializeStruct for StringKeySerializer { - type Ok = String; - type Error = ConfigError; - - fn serialize_field(&mut self, _key: &'static str, _value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - unreachable!() - } - - fn end(self) -> Result { - unreachable!() - } -} - -impl ser::SerializeStructVariant for StringKeySerializer { - type Ok = String; - type Error = ConfigError; - - fn serialize_field(&mut self, _key: &'static str, _value: &T) -> Result<()> - where - T: ?Sized + ser::Serialize, - { - unreachable!() - } - - fn end(self) -> Result { - unreachable!() - } -} - -#[cfg(test)] -mod test { - use super::*; - use serde::{Deserialize, Serialize}; - - #[test] - fn test_struct() { - #[derive(Debug, Serialize, Deserialize, PartialEq)] - struct Test { - int: u32, - seq: Vec, - } - - let test = Test { - int: 1, - seq: vec!["a".to_string(), "b".to_string()], - }; - let config = Config::try_from(&test).unwrap(); - - let actual: Test = config.try_deserialize().unwrap(); - assert_eq!(test, actual); - } -} diff --git a/src/source.rs b/src/source.rs deleted file mode 100644 index 94f1c357..00000000 --- a/src/source.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::fmt::Debug; -use std::str::FromStr; - -use async_trait::async_trait; - -use crate::error::Result; -use crate::map::Map; -use crate::path; -use crate::value::{Value, ValueKind}; - -/// Describes a generic _source_ of configuration properties. -pub trait Source: Debug { - fn clone_into_box(&self) -> Box; - - /// Collect all configuration properties available from this source and return - /// a Map. - fn collect(&self) -> Result>; - - /// Collects all configuration properties to a provided cache. - fn collect_to(&self, cache: &mut Value) -> Result<()> { - self.collect()? - .iter() - .for_each(|(key, val)| set_value(cache, key, val)); - - Ok(()) - } -} - -fn set_value(cache: &mut Value, key: &str, value: &Value) { - match path::Expression::from_str(key) { - // Set using the path - Ok(expr) => expr.set(cache, value.clone()), - - // Set diretly anyway - _ => path::Expression::Identifier(key.to_string()).set(cache, value.clone()), - } -} - -/// Describes a generic _source_ of configuration properties capable of using an async runtime. -/// -/// At the moment this library does not implement it, although it allows using its implementations -/// within builders. Due to the scattered landscape of asynchronous runtimes, it is impossible to -/// cater to all needs with one implementation. Also, this trait might be most useful with remote -/// configuration sources, reachable via the network, probably using HTTP protocol. Numerous HTTP -/// libraries exist, making it even harder to find one implementation that rules them all. -/// -/// For those reasons, it is left to other crates to implement runtime-specific or proprietary -/// details. -/// -/// It is advised to use `async_trait` crate while implementing this trait. -/// -/// See examples for sample implementation. -#[async_trait] -pub trait AsyncSource: Debug + Sync { - // Sync is supertrait due to https://docs.rs/async-trait/0.1.50/async_trait/index.html#dyn-traits - - /// Collects all configuration properties available from this source and return - /// a Map as an async operations. - async fn collect(&self) -> Result>; - - /// Collects all configuration properties to a provided cache. - async fn collect_to(&self, cache: &mut Value) -> Result<()> { - self.collect() - .await? - .iter() - .for_each(|(key, val)| set_value(cache, key, val)); - - Ok(()) - } -} - -impl Clone for Box { - fn clone(&self) -> Self { - self.to_owned() - } -} - -impl Clone for Box { - fn clone(&self) -> Self { - self.clone_into_box() - } -} - -impl Source for Vec> { - fn clone_into_box(&self) -> Box { - Box::new((*self).clone()) - } - - fn collect(&self) -> Result> { - let mut cache: Value = Map::::new().into(); - - for source in self { - source.collect_to(&mut cache)?; - } - - if let ValueKind::Table(table) = cache.kind { - Ok(table) - } else { - unreachable!(); - } - } -} - -impl Source for [Box] { - fn clone_into_box(&self) -> Box { - Box::new(self.to_owned()) - } - - fn collect(&self) -> Result> { - let mut cache: Value = Map::::new().into(); - - for source in self { - source.collect_to(&mut cache)?; - } - - if let ValueKind::Table(table) = cache.kind { - Ok(table) - } else { - unreachable!(); - } - } -} - -impl Source for Vec -where - T: Source + Sync + Send + Clone + 'static, -{ - fn clone_into_box(&self) -> Box { - Box::new((*self).clone()) - } - - fn collect(&self) -> Result> { - let mut cache: Value = Map::::new().into(); - - for source in self { - source.collect_to(&mut cache)?; - } - - if let ValueKind::Table(table) = cache.kind { - Ok(table) - } else { - unreachable!(); - } - } -} diff --git a/src/value.rs b/src/value.rs deleted file mode 100644 index c3dad104..00000000 --- a/src/value.rs +++ /dev/null @@ -1,864 +0,0 @@ -use std::fmt; -use std::fmt::Display; - -use serde::de::{Deserialize, Deserializer, Visitor}; - -use crate::error::{ConfigError, Result, Unexpected}; -use crate::map::Map; - -/// Underlying kind of the configuration value. -/// -/// Standard operations on a `Value` by users of this crate do not require -/// knowledge of `ValueKind`. Introspection of underlying kind is only required -/// when the configuration values are unstructured or do not have known types. -#[derive(Debug, Clone, PartialEq)] -pub enum ValueKind { - Nil, - Boolean(bool), - I64(i64), - I128(i128), - U64(u64), - U128(u128), - Float(f64), - String(String), - Table(Table), - Array(Array), -} - -pub type Array = Vec; -pub type Table = Map; - -impl Default for ValueKind { - fn default() -> Self { - Self::Nil - } -} - -impl From> for ValueKind -where - T: Into, -{ - fn from(value: Option) -> Self { - match value { - Some(value) => value.into(), - None => Self::Nil, - } - } -} - -impl From for ValueKind { - fn from(value: String) -> Self { - Self::String(value) - } -} - -impl<'a> From<&'a str> for ValueKind { - fn from(value: &'a str) -> Self { - Self::String(value.into()) - } -} - -impl From for ValueKind { - fn from(value: i8) -> Self { - Self::I64(value.into()) - } -} - -impl From for ValueKind { - fn from(value: i16) -> Self { - Self::I64(value.into()) - } -} - -impl From for ValueKind { - fn from(value: i32) -> Self { - Self::I64(value.into()) - } -} - -impl From for ValueKind { - fn from(value: i64) -> Self { - Self::I64(value) - } -} - -impl From for ValueKind { - fn from(value: i128) -> Self { - Self::I128(value) - } -} - -impl From for ValueKind { - fn from(value: u8) -> Self { - Self::U64(value.into()) - } -} - -impl From for ValueKind { - fn from(value: u16) -> Self { - Self::U64(value.into()) - } -} - -impl From for ValueKind { - fn from(value: u32) -> Self { - Self::U64(value.into()) - } -} - -impl From for ValueKind { - fn from(value: u64) -> Self { - Self::U64(value) - } -} - -impl From for ValueKind { - fn from(value: u128) -> Self { - Self::U128(value) - } -} - -impl From for ValueKind { - fn from(value: f64) -> Self { - Self::Float(value) - } -} - -impl From for ValueKind { - fn from(value: bool) -> Self { - Self::Boolean(value) - } -} - -impl From> for ValueKind -where - T: Into, -{ - fn from(values: Map) -> Self { - let t = values.into_iter().map(|(k, v)| (k, v.into())).collect(); - Self::Table(t) - } -} - -impl From> for ValueKind -where - T: Into, -{ - fn from(values: Vec) -> Self { - Self::Array(values.into_iter().map(T::into).collect()) - } -} - -impl Display for ValueKind { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Self::String(ref value) => write!(f, "{}", value), - Self::Boolean(value) => write!(f, "{}", value), - Self::I64(value) => write!(f, "{}", value), - Self::I128(value) => write!(f, "{}", value), - Self::U64(value) => write!(f, "{}", value), - Self::U128(value) => write!(f, "{}", value), - Self::Float(value) => write!(f, "{}", value), - Self::Nil => write!(f, "nil"), - Self::Table(ref table) => write!(f, "{{ {} }}", { - table - .iter() - .map(|(k, v)| format!("{} => {}, ", k, v)) - .collect::() - }), - Self::Array(ref array) => write!(f, "{:?}", { - array.iter().map(|e| format!("{}, ", e)).collect::() - }), - } - } -} - -/// A configuration value. -#[derive(Default, Debug, Clone, PartialEq)] -pub struct Value { - /// A description of the original location of the value. - /// - /// A Value originating from a File might contain: - /// ```text - /// Settings.toml - /// ``` - /// - /// A Value originating from the environment would contain: - /// ```text - /// the envrionment - /// ``` - /// - /// A Value originating from a remote source might contain: - /// ```text - /// etcd+http://127.0.0.1:2379 - /// ``` - origin: Option, - - /// Underlying kind of the configuration value. - pub kind: ValueKind, -} - -impl Value { - /// Create a new value instance that will remember its source uri. - pub fn new(origin: Option<&String>, kind: V) -> Self - where - V: Into, - { - Self { - origin: origin.cloned(), - kind: kind.into(), - } - } - - /// Attempt to deserialize this value into the requested type. - pub fn try_deserialize<'de, T: Deserialize<'de>>(self) -> Result { - T::deserialize(self) - } - - /// Returns `self` as a bool, if possible. - // FIXME: Should this not be `try_into_*` ? - pub fn into_bool(self) -> Result { - match self.kind { - ValueKind::Boolean(value) => Ok(value), - ValueKind::I64(value) => Ok(value != 0), - ValueKind::I128(value) => Ok(value != 0), - ValueKind::U64(value) => Ok(value != 0), - ValueKind::U128(value) => Ok(value != 0), - ValueKind::Float(value) => Ok(value != 0.0), - - ValueKind::String(ref value) => { - match value.to_lowercase().as_ref() { - "1" | "true" | "on" | "yes" => Ok(true), - "0" | "false" | "off" | "no" => Ok(false), - - // Unexpected string value - s => Err(ConfigError::invalid_type( - self.origin.clone(), - Unexpected::Str(s.into()), - "a boolean", - )), - } - } - - // Unexpected type - ValueKind::Nil => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Unit, - "a boolean", - )), - ValueKind::Table(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Map, - "a boolean", - )), - ValueKind::Array(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Seq, - "a boolean", - )), - } - } - - /// Returns `self` into an i64, if possible. - // FIXME: Should this not be `try_into_*` ? - pub fn into_int(self) -> Result { - match self.kind { - ValueKind::I64(value) => Ok(value), - ValueKind::I128(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::I128(value), - "an signed 64 bit or less integer", - )), - ValueKind::U64(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::U64(value), - "an signed 64 bit or less integer", - )), - ValueKind::U128(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::U128(value), - "an signed 64 bit or less integer", - )), - - ValueKind::String(ref s) => { - match s.to_lowercase().as_ref() { - "true" | "on" | "yes" => Ok(1), - "false" | "off" | "no" => Ok(0), - _ => { - s.parse().map_err(|_| { - // Unexpected string - ConfigError::invalid_type( - self.origin.clone(), - Unexpected::Str(s.clone()), - "an integer", - ) - }) - } - } - } - - ValueKind::Boolean(value) => Ok(if value { 1 } else { 0 }), - ValueKind::Float(value) => Ok(value.round() as i64), - - // Unexpected type - ValueKind::Nil => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Unit, - "an integer", - )), - ValueKind::Table(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Map, - "an integer", - )), - ValueKind::Array(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Seq, - "an integer", - )), - } - } - - /// Returns `self` into an i128, if possible. - pub fn into_int128(self) -> Result { - match self.kind { - ValueKind::I64(value) => Ok(value.into()), - ValueKind::I128(value) => Ok(value), - ValueKind::U64(value) => Ok(value.into()), - ValueKind::U128(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::U128(value), - "an signed 128 bit integer", - )), - - ValueKind::String(ref s) => { - match s.to_lowercase().as_ref() { - "true" | "on" | "yes" => Ok(1), - "false" | "off" | "no" => Ok(0), - _ => { - s.parse().map_err(|_| { - // Unexpected string - ConfigError::invalid_type( - self.origin.clone(), - Unexpected::Str(s.clone()), - "an integer", - ) - }) - } - } - } - - ValueKind::Boolean(value) => Ok(if value { 1 } else { 0 }), - ValueKind::Float(value) => Ok(value.round() as i128), - - // Unexpected type - ValueKind::Nil => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Unit, - "an integer", - )), - ValueKind::Table(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Map, - "an integer", - )), - ValueKind::Array(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Seq, - "an integer", - )), - } - } - - /// Returns `self` into an u64, if possible. - // FIXME: Should this not be `try_into_*` ? - pub fn into_uint(self) -> Result { - match self.kind { - ValueKind::U64(value) => Ok(value), - ValueKind::U128(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::U128(value), - "an unsigned 64 bit or less integer", - )), - ValueKind::I64(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::I64(value), - "an unsigned 64 bit or less integer", - )), - ValueKind::I128(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::I128(value), - "an unsigned 64 bit or less integer", - )), - - ValueKind::String(ref s) => { - match s.to_lowercase().as_ref() { - "true" | "on" | "yes" => Ok(1), - "false" | "off" | "no" => Ok(0), - _ => { - s.parse().map_err(|_| { - // Unexpected string - ConfigError::invalid_type( - self.origin.clone(), - Unexpected::Str(s.clone()), - "an integer", - ) - }) - } - } - } - - ValueKind::Boolean(value) => Ok(if value { 1 } else { 0 }), - ValueKind::Float(value) => Ok(value.round() as u64), - - // Unexpected type - ValueKind::Nil => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Unit, - "an integer", - )), - ValueKind::Table(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Map, - "an integer", - )), - ValueKind::Array(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Seq, - "an integer", - )), - } - } - - /// Returns `self` into an u128, if possible. - pub fn into_uint128(self) -> Result { - match self.kind { - ValueKind::U64(value) => Ok(value.into()), - ValueKind::U128(value) => Ok(value), - ValueKind::I64(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::I64(value), - "an unsigned 128 bit or less integer", - )), - ValueKind::I128(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::I128(value), - "an unsigned 128 bit or less integer", - )), - - ValueKind::String(ref s) => { - match s.to_lowercase().as_ref() { - "true" | "on" | "yes" => Ok(1), - "false" | "off" | "no" => Ok(0), - _ => { - s.parse().map_err(|_| { - // Unexpected string - ConfigError::invalid_type( - self.origin.clone(), - Unexpected::Str(s.clone()), - "an integer", - ) - }) - } - } - } - - ValueKind::Boolean(value) => Ok(if value { 1 } else { 0 }), - ValueKind::Float(value) => Ok(value.round() as u128), - - // Unexpected type - ValueKind::Nil => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Unit, - "an integer", - )), - ValueKind::Table(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Map, - "an integer", - )), - ValueKind::Array(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Seq, - "an integer", - )), - } - } - - /// Returns `self` into a f64, if possible. - // FIXME: Should this not be `try_into_*` ? - pub fn into_float(self) -> Result { - match self.kind { - ValueKind::Float(value) => Ok(value), - - ValueKind::String(ref s) => { - match s.to_lowercase().as_ref() { - "true" | "on" | "yes" => Ok(1.0), - "false" | "off" | "no" => Ok(0.0), - _ => { - s.parse().map_err(|_| { - // Unexpected string - ConfigError::invalid_type( - self.origin.clone(), - Unexpected::Str(s.clone()), - "a floating point", - ) - }) - } - } - } - - ValueKind::I64(value) => Ok(value as f64), - ValueKind::I128(value) => Ok(value as f64), - ValueKind::U64(value) => Ok(value as f64), - ValueKind::U128(value) => Ok(value as f64), - ValueKind::Boolean(value) => Ok(if value { 1.0 } else { 0.0 }), - - // Unexpected type - ValueKind::Nil => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Unit, - "a floating point", - )), - ValueKind::Table(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Map, - "a floating point", - )), - ValueKind::Array(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Seq, - "a floating point", - )), - } - } - - /// Returns `self` into a string, if possible. - // FIXME: Should this not be `try_into_*` ? - pub fn into_string(self) -> Result { - match self.kind { - ValueKind::String(value) => Ok(value), - - ValueKind::Boolean(value) => Ok(value.to_string()), - ValueKind::I64(value) => Ok(value.to_string()), - ValueKind::I128(value) => Ok(value.to_string()), - ValueKind::U64(value) => Ok(value.to_string()), - ValueKind::U128(value) => Ok(value.to_string()), - ValueKind::Float(value) => Ok(value.to_string()), - - // Cannot convert - ValueKind::Nil => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Unit, - "a string", - )), - ValueKind::Table(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Map, - "a string", - )), - ValueKind::Array(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Seq, - "a string", - )), - } - } - - /// Returns `self` into an array, if possible - // FIXME: Should this not be `try_into_*` ? - pub fn into_array(self) -> Result> { - match self.kind { - ValueKind::Array(value) => Ok(value), - - // Cannot convert - ValueKind::Float(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Float(value), - "an array", - )), - ValueKind::String(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Str(value), - "an array", - )), - ValueKind::I64(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::I64(value), - "an array", - )), - ValueKind::I128(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::I128(value), - "an array", - )), - ValueKind::U64(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::U64(value), - "an array", - )), - ValueKind::U128(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::U128(value), - "an array", - )), - ValueKind::Boolean(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Bool(value), - "an array", - )), - ValueKind::Nil => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Unit, - "an array", - )), - ValueKind::Table(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Map, - "an array", - )), - } - } - - /// If the `Value` is a Table, returns the associated Map. - // FIXME: Should this not be `try_into_*` ? - pub fn into_table(self) -> Result> { - match self.kind { - ValueKind::Table(value) => Ok(value), - - // Cannot convert - ValueKind::Float(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Float(value), - "a map", - )), - ValueKind::String(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Str(value), - "a map", - )), - ValueKind::I64(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::I64(value), - "a map", - )), - ValueKind::I128(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::I128(value), - "a map", - )), - ValueKind::U64(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::U64(value), - "a map", - )), - ValueKind::U128(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::U128(value), - "a map", - )), - ValueKind::Boolean(value) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Bool(value), - "a map", - )), - ValueKind::Nil => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Unit, - "a map", - )), - ValueKind::Array(_) => Err(ConfigError::invalid_type( - self.origin, - Unexpected::Seq, - "a map", - )), - } - } -} - -impl<'de> Deserialize<'de> for Value { - #[inline] - fn deserialize(deserializer: D) -> ::std::result::Result - where - D: Deserializer<'de>, - { - struct ValueVisitor; - - impl<'de> Visitor<'de> for ValueVisitor { - type Value = Value; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("any valid configuration value") - } - - #[inline] - fn visit_bool(self, value: bool) -> ::std::result::Result { - Ok(value.into()) - } - - #[inline] - fn visit_i8(self, value: i8) -> ::std::result::Result { - Ok((i64::from(value)).into()) - } - - #[inline] - fn visit_i16(self, value: i16) -> ::std::result::Result { - Ok((i64::from(value)).into()) - } - - #[inline] - fn visit_i32(self, value: i32) -> ::std::result::Result { - Ok((i64::from(value)).into()) - } - - #[inline] - fn visit_i64(self, value: i64) -> ::std::result::Result { - Ok(value.into()) - } - - #[inline] - fn visit_i128(self, value: i128) -> ::std::result::Result { - Ok(value.into()) - } - - #[inline] - fn visit_u8(self, value: u8) -> ::std::result::Result { - Ok((i64::from(value)).into()) - } - - #[inline] - fn visit_u16(self, value: u16) -> ::std::result::Result { - Ok((i64::from(value)).into()) - } - - #[inline] - fn visit_u32(self, value: u32) -> ::std::result::Result { - Ok((i64::from(value)).into()) - } - - #[inline] - fn visit_u64(self, value: u64) -> ::std::result::Result { - // FIXME: This is bad - Ok((value as i64).into()) - } - - #[inline] - fn visit_u128(self, value: u128) -> ::std::result::Result { - // FIXME: This is bad - Ok((value as i128).into()) - } - - #[inline] - fn visit_f64(self, value: f64) -> ::std::result::Result { - Ok(value.into()) - } - - #[inline] - fn visit_str(self, value: &str) -> ::std::result::Result - where - E: ::serde::de::Error, - { - self.visit_string(String::from(value)) - } - - #[inline] - fn visit_string(self, value: String) -> ::std::result::Result { - Ok(value.into()) - } - - #[inline] - fn visit_none(self) -> ::std::result::Result { - Ok(Value::new(None, ValueKind::Nil)) - } - - #[inline] - fn visit_some(self, deserializer: D) -> ::std::result::Result - where - D: Deserializer<'de>, - { - Deserialize::deserialize(deserializer) - } - - #[inline] - fn visit_unit(self) -> ::std::result::Result { - Ok(Value::new(None, ValueKind::Nil)) - } - - #[inline] - fn visit_seq(self, mut visitor: V) -> ::std::result::Result - where - V: ::serde::de::SeqAccess<'de>, - { - let mut vec = Array::new(); - - while let Some(elem) = visitor.next_element()? { - vec.push(elem); - } - - Ok(vec.into()) - } - - fn visit_map(self, mut visitor: V) -> ::std::result::Result - where - V: ::serde::de::MapAccess<'de>, - { - let mut values = Table::new(); - - while let Some((key, value)) = visitor.next_entry()? { - values.insert(key, value); - } - - Ok(values.into()) - } - } - - deserializer.deserialize_any(ValueVisitor) - } -} - -impl From for Value -where - T: Into, -{ - fn from(value: T) -> Self { - Self { - origin: None, - kind: value.into(), - } - } -} - -impl Display for Value { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.kind) - } -} - -#[cfg(test)] -mod tests { - use super::ValueKind; - use crate::Config; - use crate::File; - use crate::FileFormat; - - #[test] - fn test_i64() { - let c = Config::builder() - .add_source(File::new("tests/types/i64.toml", FileFormat::Toml)) - .build() - .unwrap(); - - assert!(std::matches!(c.cache.kind, ValueKind::Table(_))); - let v = match c.cache.kind { - ValueKind::Table(t) => t, - _ => unreachable!(), - }; - - let value = v.get("value").unwrap(); - assert!( - std::matches!(value.kind, ValueKind::I64(120)), - "Is not a i64(120): {:?}", - value.kind - ); - } -} diff --git a/tests/Settings-invalid.hjson b/tests/Settings-invalid.hjson deleted file mode 100644 index 7e31ec3d..00000000 --- a/tests/Settings-invalid.hjson +++ /dev/null @@ -1,4 +0,0 @@ -{ - ok: true, - error -} diff --git a/tests/Settings-invalid.ini b/tests/Settings-invalid.ini deleted file mode 100644 index f2b8d9b0..00000000 --- a/tests/Settings-invalid.ini +++ /dev/null @@ -1,2 +0,0 @@ -ok : true, -error diff --git a/tests/Settings-invalid.json b/tests/Settings-invalid.json deleted file mode 100644 index ba2d7cba..00000000 --- a/tests/Settings-invalid.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "ok": true, - "error" -} diff --git a/tests/Settings-invalid.json5 b/tests/Settings-invalid.json5 deleted file mode 100644 index 7e97bc10..00000000 --- a/tests/Settings-invalid.json5 +++ /dev/null @@ -1,4 +0,0 @@ -{ - ok: true - error -} diff --git a/tests/Settings-invalid.ron b/tests/Settings-invalid.ron deleted file mode 100644 index 0f41c5cd..00000000 --- a/tests/Settings-invalid.ron +++ /dev/null @@ -1,4 +0,0 @@ -( - ok: true, - error -) diff --git a/tests/Settings-invalid.toml b/tests/Settings-invalid.toml deleted file mode 100644 index 4d159a4c..00000000 --- a/tests/Settings-invalid.toml +++ /dev/null @@ -1,2 +0,0 @@ -ok = true -error = tru diff --git a/tests/Settings-invalid.yaml b/tests/Settings-invalid.yaml deleted file mode 100644 index 070ff1b7..00000000 --- a/tests/Settings-invalid.yaml +++ /dev/null @@ -1,2 +0,0 @@ -ok: true -error false diff --git a/tests/Settings-production.toml b/tests/Settings-production.toml deleted file mode 100644 index 6545b4cc..00000000 --- a/tests/Settings-production.toml +++ /dev/null @@ -1,8 +0,0 @@ -debug = false -production = true - -[place] -rating = 4.9 - -[place.creator] -name = "Somebody New" diff --git a/tests/Settings.hjson b/tests/Settings.hjson deleted file mode 100644 index 9810e04d..00000000 --- a/tests/Settings.hjson +++ /dev/null @@ -1,18 +0,0 @@ -{ - debug: true - production: false - arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - place: { - name: Torre di Pisa - longitude: 43.7224985 - latitude: 10.3970522 - favorite: false - reviews: 3866 - rating: 4.5 - creator: { - name: John Smith - username: jsmith - email: jsmith@localhost - } - } -} diff --git a/tests/Settings.ini b/tests/Settings.ini deleted file mode 100644 index 16badd47..00000000 --- a/tests/Settings.ini +++ /dev/null @@ -1,9 +0,0 @@ -debug = true -production = false -[place] -name = Torre di Pisa -longitude = 43.7224985 -latitude = 10.3970522 -favorite = false -reviews = 3866 -rating = 4.5 diff --git a/tests/Settings.json b/tests/Settings.json deleted file mode 100644 index babb0ba9..00000000 --- a/tests/Settings.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "debug": true, - "debug_json": true, - "production": false, - "arr": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - "place": { - "name": "Torre di Pisa", - "longitude": 43.7224985, - "latitude": 10.3970522, - "favorite": false, - "reviews": 3866, - "rating": 4.5, - "creator": { - "name": "John Smith", - "username": "jsmith", - "email": "jsmith@localhost" - } - } -} diff --git a/tests/Settings.json5 b/tests/Settings.json5 deleted file mode 100644 index 4c93e421..00000000 --- a/tests/Settings.json5 +++ /dev/null @@ -1,20 +0,0 @@ -{ - // c - /* c */ - debug: true, - production: false, - arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10,], - place: { - name: 'Torre di Pisa', - longitude: 43.7224985, - latitude: 10.3970522, - favorite: false, - reviews: 3866, - rating: 4.5, - creator: { - name: "John Smith", - "username": "jsmith", - "email": "jsmith@localhost", - } - } -} diff --git a/tests/Settings.ron b/tests/Settings.ron deleted file mode 100644 index 7882840a..00000000 --- a/tests/Settings.ron +++ /dev/null @@ -1,20 +0,0 @@ -( - debug: true, - production: false, - arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - place: ( - initials: ('T', 'P'), - name: "Torre di Pisa", - longitude: 43.7224985, - latitude: 10.3970522, - favorite: false, - reviews: 3866, - rating: Some(4.5), - telephone: None, - creator: { - "name": "John Smith", - "username": "jsmith", - "email": "jsmith@localhost" - } - ) -) diff --git a/tests/Settings.toml b/tests/Settings.toml deleted file mode 100644 index bb538087..00000000 --- a/tests/Settings.toml +++ /dev/null @@ -1,55 +0,0 @@ -debug = true -debug_s = "true" -production = false -production_s = "false" - -code = 53 - -# errors -boolean_s_parse = "fals" - -arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -quarks = ["up", "down", "strange", "charm", "bottom", "top"] - -[diodes] -green = "off" - -[diodes.red] -brightness = 100 - -[diodes.blue] -blinking = [300, 700] - -[diodes.white.pattern] -name = "christmas" -inifinite = true - -[[items]] -name = "1" - -[[items]] -name = "2" - -[place] -number = 1 -name = "Torre di Pisa" -longitude = 43.7224985 -latitude = 10.3970522 -favorite = false -reviews = 3866 -rating = 4.5 - -[place.creator] -name = "John Smith" -username = "jsmith" -email = "jsmith@localhost" - -[proton] -up = 2 -down = 1 - -[divisors] -1 = 1 -2 = 2 -4 = 3 -5 = 2 diff --git a/tests/Settings.yaml b/tests/Settings.yaml deleted file mode 100644 index 8e79c29d..00000000 --- a/tests/Settings.yaml +++ /dev/null @@ -1,14 +0,0 @@ -debug: true -production: false -arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -place: - name: Torre di Pisa - longitude: 43.7224985 - latitude: 10.3970522 - favorite: false - reviews: 3866 - rating: 4.5 - creator: - name: John Smith - username: jsmith - email: jsmith@localhost diff --git a/tests/Settings2.default.ini b/tests/Settings2.default.ini deleted file mode 100644 index 16badd47..00000000 --- a/tests/Settings2.default.ini +++ /dev/null @@ -1,9 +0,0 @@ -debug = true -production = false -[place] -name = Torre di Pisa -longitude = 43.7224985 -latitude = 10.3970522 -favorite = false -reviews = 3866 -rating = 4.5 diff --git a/tests/async_builder.rs b/tests/async_builder.rs deleted file mode 100644 index bead9d38..00000000 --- a/tests/async_builder.rs +++ /dev/null @@ -1,142 +0,0 @@ -use async_trait::async_trait; -use config::{AsyncSource, Config, ConfigError, FileFormat, Format, Map, Value}; -use std::{env, fs, path, str::FromStr}; -use tokio::fs::read_to_string; - -#[derive(Debug)] -struct AsyncFile { - path: String, - format: FileFormat, -} - -/// This is a test only implementation to be used in tests -impl AsyncFile { - pub fn new(path: String, format: FileFormat) -> Self { - Self { path, format } - } -} - -#[async_trait] -impl AsyncSource for AsyncFile { - async fn collect(&self) -> Result, ConfigError> { - let mut path = env::current_dir().unwrap(); - let local = path::PathBuf::from_str(&self.path).unwrap(); - - path.extend(local.iter()); - let path = fs::canonicalize(path).map_err(|e| ConfigError::Foreign(Box::new(e)))?; - - let text = read_to_string(path) - .await - .map_err(|e| ConfigError::Foreign(Box::new(e)))?; - - self.format - .parse(Some(&self.path), &text) - .map_err(|e| ConfigError::Foreign(e)) - } -} - -#[tokio::test] -async fn test_single_async_file_source() { - let config = Config::builder() - .add_async_source(AsyncFile::new( - "tests/Settings.json".to_owned(), - FileFormat::Json, - )) - .build() - .await - .unwrap(); - - assert!(config.get::("debug").unwrap()); -} - -#[tokio::test] -async fn test_two_async_file_sources() { - let config = Config::builder() - .add_async_source(AsyncFile::new( - "tests/Settings.json".to_owned(), - FileFormat::Json, - )) - .add_async_source(AsyncFile::new( - "tests/Settings.toml".to_owned(), - FileFormat::Toml, - )) - .build() - .await - .unwrap(); - - assert_eq!("Torre di Pisa", config.get::("place.name").unwrap()); - assert!(config.get::("debug_json").unwrap()); - assert_eq!(1, config.get::("place.number").unwrap()); -} - -#[tokio::test] -async fn test_sync_to_async_file_sources() { - let config = Config::builder() - .add_source(config::File::new("tests/Settings", FileFormat::Json)) - .add_async_source(AsyncFile::new( - "tests/Settings.toml".to_owned(), - FileFormat::Toml, - )) - .build() - .await - .unwrap(); - - assert_eq!("Torre di Pisa", config.get::("place.name").unwrap()); - assert_eq!(1, config.get::("place.number").unwrap()); -} - -#[tokio::test] -async fn test_async_to_sync_file_sources() { - let config = Config::builder() - .add_async_source(AsyncFile::new( - "tests/Settings.toml".to_owned(), - FileFormat::Toml, - )) - .add_source(config::File::new("tests/Settings", FileFormat::Json)) - .build() - .await - .unwrap(); - - assert_eq!("Torre di Pisa", config.get::("place.name").unwrap()); - assert_eq!(1, config.get::("place.number").unwrap()); -} - -#[tokio::test] -async fn test_async_file_sources_with_defaults() { - let config = Config::builder() - .set_default("place.name", "Tower of London") - .unwrap() - .set_default("place.sky", "blue") - .unwrap() - .add_async_source(AsyncFile::new( - "tests/Settings.toml".to_owned(), - FileFormat::Toml, - )) - .build() - .await - .unwrap(); - - assert_eq!("Torre di Pisa", config.get::("place.name").unwrap()); - assert_eq!("blue", config.get::("place.sky").unwrap()); - assert_eq!(1, config.get::("place.number").unwrap()); -} - -#[tokio::test] -async fn test_async_file_sources_with_overrides() { - let config = Config::builder() - .set_override("place.name", "Tower of London") - .unwrap() - .add_async_source(AsyncFile::new( - "tests/Settings.toml".to_owned(), - FileFormat::Toml, - )) - .build() - .await - .unwrap(); - - assert_eq!( - "Tower of London", - config.get::("place.name").unwrap() - ); - assert_eq!(1, config.get::("place.number").unwrap()); -} diff --git a/tests/datetime.rs b/tests/datetime.rs deleted file mode 100644 index e15bdcec..00000000 --- a/tests/datetime.rs +++ /dev/null @@ -1,110 +0,0 @@ -#![cfg(all( - feature = "toml", - feature = "json", - feature = "yaml", - feature = "ini", - feature = "ron", -))] - -use chrono::{DateTime, TimeZone, Utc}; -use config::{Config, File, FileFormat}; - -fn make() -> Config { - Config::builder() - .add_source(File::from_str( - r#" - { - "json_datetime": "2017-05-10T02:14:53Z" - } - "#, - FileFormat::Json, - )) - .add_source(File::from_str( - r#" - yaml_datetime: 2017-06-12T10:58:30Z - "#, - FileFormat::Yaml, - )) - .add_source(File::from_str( - r#" - toml_datetime = 2017-05-11T14:55:15Z - "#, - FileFormat::Toml, - )) - .add_source(File::from_str( - r#" - ini_datetime = 2017-05-10T02:14:53Z - "#, - FileFormat::Ini, - )) - .add_source(File::from_str( - r#" - ( - ron_datetime: "2021-04-19T11:33:02Z" - ) - "#, - FileFormat::Ron, - )) - .build() - .unwrap() -} - -#[test] -fn test_datetime_string() { - let s = make(); - - // JSON - let date: String = s.get("json_datetime").unwrap(); - - assert_eq!(&date, "2017-05-10T02:14:53Z"); - - // TOML - let date: String = s.get("toml_datetime").unwrap(); - - assert_eq!(&date, "2017-05-11T14:55:15Z"); - - // YAML - let date: String = s.get("yaml_datetime").unwrap(); - - assert_eq!(&date, "2017-06-12T10:58:30Z"); - - // INI - let date: String = s.get("ini_datetime").unwrap(); - - assert_eq!(&date, "2017-05-10T02:14:53Z"); - - // RON - let date: String = s.get("ron_datetime").unwrap(); - - assert_eq!(&date, "2021-04-19T11:33:02Z"); -} - -#[test] -fn test_datetime() { - let s = make(); - - // JSON - let date: DateTime = s.get("json_datetime").unwrap(); - - assert_eq!(date, Utc.ymd(2017, 5, 10).and_hms(2, 14, 53)); - - // TOML - let date: DateTime = s.get("toml_datetime").unwrap(); - - assert_eq!(date, Utc.ymd(2017, 5, 11).and_hms(14, 55, 15)); - - // YAML - let date: DateTime = s.get("yaml_datetime").unwrap(); - - assert_eq!(date, Utc.ymd(2017, 6, 12).and_hms(10, 58, 30)); - - // INI - let date: DateTime = s.get("ini_datetime").unwrap(); - - assert_eq!(date, Utc.ymd(2017, 5, 10).and_hms(2, 14, 53)); - - // RON - let date: DateTime = s.get("ron_datetime").unwrap(); - - assert_eq!(date, Utc.ymd(2021, 4, 19).and_hms(11, 33, 2)); -} diff --git a/tests/defaults.rs b/tests/defaults.rs deleted file mode 100644 index f7402596..00000000 --- a/tests/defaults.rs +++ /dev/null @@ -1,32 +0,0 @@ -use serde_derive::{Deserialize, Serialize}; - -use config::Config; - -#[derive(Debug, Serialize, Deserialize)] -#[serde(default)] -pub struct Settings { - pub db_host: String, -} - -impl Default for Settings { - fn default() -> Self { - Self { - db_host: String::from("default"), - } - } -} - -#[test] -fn set_defaults() { - let c = Config::default(); - let s: Settings = c.try_deserialize().expect("Deserialization failed"); - - assert_eq!(s.db_host, "default"); -} - -#[test] -fn try_from_defaults() { - let c = Config::try_from(&Settings::default()).expect("Serialization failed"); - let s: Settings = c.try_deserialize().expect("Deserialization failed"); - assert_eq!(s.db_host, "default"); -} diff --git a/tests/empty.rs b/tests/empty.rs deleted file mode 100644 index bce4f921..00000000 --- a/tests/empty.rs +++ /dev/null @@ -1,20 +0,0 @@ -use serde_derive::{Deserialize, Serialize}; - -use config::Config; - -#[derive(Debug, Serialize, Deserialize)] -struct Settings { - #[serde(skip)] - foo: isize, - #[serde(skip)] - bar: u8, -} - -#[test] -fn empty_deserializes() { - let s: Settings = Config::default() - .try_deserialize() - .expect("Deserialization failed"); - assert_eq!(s.foo, 0); - assert_eq!(s.bar, 0); -} diff --git a/tests/env.rs b/tests/env.rs deleted file mode 100644 index ad252e41..00000000 --- a/tests/env.rs +++ /dev/null @@ -1,537 +0,0 @@ -use config::{Config, Environment, Source}; -use serde_derive::Deserialize; - -/// Reminder that tests using env variables need to use different env variable names, since -/// tests can be run in parallel - -#[test] -fn test_default() { - temp_env::with_var("A_B_C", Some("abc"), || { - let environment = Environment::default(); - - assert!(environment.collect().unwrap().contains_key("a_b_c")); - }) -} - -#[test] -fn test_prefix_is_removed_from_key() { - temp_env::with_var("B_A_C", Some("abc"), || { - let environment = Environment::with_prefix("B"); - - assert!(environment.collect().unwrap().contains_key("a_c")); - }) -} - -#[test] -fn test_prefix_with_variant_forms_of_spelling() { - temp_env::with_var("a_A_C", Some("abc"), || { - let environment = Environment::with_prefix("a"); - - assert!(environment.collect().unwrap().contains_key("a_c")); - }); - - temp_env::with_var("aB_A_C", Some("abc"), || { - let environment = Environment::with_prefix("aB"); - - assert!(environment.collect().unwrap().contains_key("a_c")); - }); - - temp_env::with_var("Ab_A_C", Some("abc"), || { - let environment = Environment::with_prefix("ab"); - - assert!(environment.collect().unwrap().contains_key("a_c")); - }); -} - -#[test] -fn test_separator_behavior() { - temp_env::with_var("C_B_A", Some("abc"), || { - let environment = Environment::with_prefix("C").separator("_"); - - assert!(environment.collect().unwrap().contains_key("b.a")); - }) -} - -#[test] -fn test_empty_value_is_ignored() { - temp_env::with_var("C_A_B", Some(""), || { - let environment = Environment::default().ignore_empty(true); - - assert!(!environment.collect().unwrap().contains_key("c_a_b")); - }) -} - -#[test] -fn test_keep_prefix() { - temp_env::with_var("C_A_B", Some(""), || { - // Do not keep the prefix - let environment = Environment::with_prefix("C"); - - assert!(environment.collect().unwrap().contains_key("a_b")); - - let environment = Environment::with_prefix("C").keep_prefix(false); - - assert!(environment.collect().unwrap().contains_key("a_b")); - - // Keep the prefix - let environment = Environment::with_prefix("C").keep_prefix(true); - - assert!(environment.collect().unwrap().contains_key("c_a_b")); - }) -} - -#[test] -fn test_custom_separator_behavior() { - temp_env::with_var("C.B.A", Some("abc"), || { - let environment = Environment::with_prefix("C").separator("."); - - assert!(environment.collect().unwrap().contains_key("b.a")); - }) -} - -#[test] -fn test_custom_prefix_separator_behavior() { - temp_env::with_var("C-B.A", Some("abc"), || { - let environment = Environment::with_prefix("C") - .separator(".") - .prefix_separator("-"); - - assert!(environment.collect().unwrap().contains_key("b.a")); - }) -} - -#[test] -fn test_parse_int() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestIntEnum { - Int(TestInt), - } - - #[derive(Deserialize, Debug)] - struct TestInt { - int_val: i32, - } - - temp_env::with_var("INT_VAL", Some("42"), || { - let environment = Environment::default().try_parsing(true); - - let config = Config::builder() - .set_default("tag", "Int") - .unwrap() - .add_source(environment) - .build() - .unwrap(); - - let config: TestIntEnum = config.try_deserialize().unwrap(); - - assert!(matches!(config, TestIntEnum::Int(TestInt { int_val: 42 }))); - }) -} - -#[test] -fn test_parse_float() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestFloatEnum { - Float(TestFloat), - } - - #[derive(Deserialize, Debug)] - struct TestFloat { - float_val: f64, - } - - temp_env::with_var("FLOAT_VAL", Some("42.3"), || { - let environment = Environment::default().try_parsing(true); - - let config = Config::builder() - .set_default("tag", "Float") - .unwrap() - .add_source(environment) - .build() - .unwrap(); - - let config: TestFloatEnum = config.try_deserialize().unwrap(); - - // can't use `matches!` because of float value - match config { - TestFloatEnum::Float(TestFloat { float_val }) => assert_eq!(float_val, 42.3), - } - }) -} - -#[test] -fn test_parse_bool() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestBoolEnum { - Bool(TestBool), - } - - #[derive(Deserialize, Debug)] - struct TestBool { - bool_val: bool, - } - - temp_env::with_var("BOOL_VAL", Some("true"), || { - let environment = Environment::default().try_parsing(true); - - let config = Config::builder() - .set_default("tag", "Bool") - .unwrap() - .add_source(environment) - .build() - .unwrap(); - - let config: TestBoolEnum = config.try_deserialize().unwrap(); - - assert!(matches!( - config, - TestBoolEnum::Bool(TestBool { bool_val: true }) - )); - }) -} - -#[test] -#[should_panic(expected = "invalid type: string \"42\", expected i32")] -fn test_parse_off_int() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestIntEnum { - Int(TestInt), - } - - #[derive(Deserialize, Debug)] - struct TestInt { - #[allow(dead_code)] - int_val_1: i32, - } - - temp_env::with_var("INT_VAL_1", Some("42"), || { - let environment = Environment::default().try_parsing(false); - - let config = Config::builder() - .set_default("tag", "Int") - .unwrap() - .add_source(environment) - .build() - .unwrap(); - - config.try_deserialize::().unwrap(); - }) -} - -#[test] -#[should_panic(expected = "invalid type: string \"42.3\", expected f64")] -fn test_parse_off_float() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestFloatEnum { - Float(TestFloat), - } - - #[derive(Deserialize, Debug)] - struct TestFloat { - #[allow(dead_code)] - float_val_1: f64, - } - - temp_env::with_var("FLOAT_VAL_1", Some("42.3"), || { - let environment = Environment::default().try_parsing(false); - - let config = Config::builder() - .set_default("tag", "Float") - .unwrap() - .add_source(environment) - .build() - .unwrap(); - - config.try_deserialize::().unwrap(); - }) -} - -#[test] -#[should_panic(expected = "invalid type: string \"true\", expected a boolean")] -fn test_parse_off_bool() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestBoolEnum { - Bool(TestBool), - } - - #[derive(Deserialize, Debug)] - struct TestBool { - #[allow(dead_code)] - bool_val_1: bool, - } - - temp_env::with_var("BOOL_VAL_1", Some("true"), || { - let environment = Environment::default().try_parsing(false); - - let config = Config::builder() - .set_default("tag", "Bool") - .unwrap() - .add_source(environment) - .build() - .unwrap(); - - config.try_deserialize::().unwrap(); - }) -} - -#[test] -#[should_panic(expected = "invalid type: string \"not an int\", expected i32")] -fn test_parse_int_fail() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestIntEnum { - Int(TestInt), - } - - #[derive(Deserialize, Debug)] - struct TestInt { - #[allow(dead_code)] - int_val_2: i32, - } - - temp_env::with_var("INT_VAL_2", Some("not an int"), || { - let environment = Environment::default().try_parsing(true); - - let config = Config::builder() - .set_default("tag", "Int") - .unwrap() - .add_source(environment) - .build() - .unwrap(); - - config.try_deserialize::().unwrap(); - }) -} - -#[test] -#[should_panic(expected = "invalid type: string \"not a float\", expected f64")] -fn test_parse_float_fail() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestFloatEnum { - Float(TestFloat), - } - - #[derive(Deserialize, Debug)] - struct TestFloat { - #[allow(dead_code)] - float_val_2: f64, - } - - temp_env::with_var("FLOAT_VAL_2", Some("not a float"), || { - let environment = Environment::default().try_parsing(true); - - let config = Config::builder() - .set_default("tag", "Float") - .unwrap() - .add_source(environment) - .build() - .unwrap(); - - config.try_deserialize::().unwrap(); - }) -} - -#[test] -#[should_panic(expected = "invalid type: string \"not a bool\", expected a boolean")] -fn test_parse_bool_fail() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestBoolEnum { - Bool(TestBool), - } - - #[derive(Deserialize, Debug)] - struct TestBool { - #[allow(dead_code)] - bool_val_2: bool, - } - - temp_env::with_var("BOOL_VAL_2", Some("not a bool"), || { - let environment = Environment::default().try_parsing(true); - - let config = Config::builder() - .set_default("tag", "Bool") - .unwrap() - .add_source(environment) - .build() - .unwrap(); - - config.try_deserialize::().unwrap(); - }) -} - -#[test] -fn test_parse_string_and_list() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestStringEnum { - String(TestString), - } - - #[derive(Deserialize, Debug)] - struct TestString { - string_val: String, - string_list: Vec, - } - - temp_env::with_vars( - vec![ - ("LIST_STRING_LIST", Some("test,string")), - ("LIST_STRING_VAL", Some("test,string")), - ], - || { - let environment = Environment::default() - .prefix("LIST") - .list_separator(",") - .with_list_parse_key("string_list") - .try_parsing(true); - - let config = Config::builder() - .set_default("tag", "String") - .unwrap() - .add_source(environment) - .build() - .unwrap(); - - let config: TestStringEnum = config.try_deserialize().unwrap(); - - match config { - TestStringEnum::String(TestString { - string_val, - string_list, - }) => { - assert_eq!(String::from("test,string"), string_val); - assert_eq!( - vec![String::from("test"), String::from("string")], - string_list - ); - } - } - }, - ) -} - -#[test] -fn test_parse_string() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestStringEnum { - String(TestString), - } - - #[derive(Deserialize, Debug)] - struct TestString { - string_val: String, - } - - temp_env::with_var("STRING_VAL", Some("test string"), || { - let environment = Environment::default().try_parsing(true); - - let config = Config::builder() - .set_default("tag", "String") - .unwrap() - .add_source(environment) - .build() - .unwrap(); - - let config: TestStringEnum = config.try_deserialize().unwrap(); - - let test_string = String::from("test string"); - - match config { - TestStringEnum::String(TestString { string_val }) => { - assert_eq!(test_string, string_val) - } - } - }) -} - -#[test] -fn test_parse_string_list() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestListEnum { - StringList(TestList), - } - - #[derive(Deserialize, Debug)] - struct TestList { - string_list: Vec, - } - - temp_env::with_var("STRING_LIST", Some("test string"), || { - let environment = Environment::default().try_parsing(true).list_separator(" "); - - let config = Config::builder() - .set_default("tag", "StringList") - .unwrap() - .add_source(environment) - .build() - .unwrap(); - - let config: TestListEnum = config.try_deserialize().unwrap(); - - let test_string = vec![String::from("test"), String::from("string")]; - - match config { - TestListEnum::StringList(TestList { string_list }) => { - assert_eq!(test_string, string_list) - } - } - }) -} - -#[test] -fn test_parse_off_string() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestStringEnum { - String(TestString), - } - - #[derive(Deserialize, Debug)] - struct TestString { - string_val_1: String, - } - - temp_env::with_var("STRING_VAL_1", Some("test string"), || { - let environment = Environment::default().try_parsing(false); - - let config = Config::builder() - .set_default("tag", "String") - .unwrap() - .add_source(environment) - .build() - .unwrap(); - - let config: TestStringEnum = config.try_deserialize().unwrap(); - - let test_string = String::from("test string"); - - match config { - TestStringEnum::String(TestString { string_val_1 }) => { - assert_eq!(test_string, string_val_1); - } - } - }) -} diff --git a/tests/errors.rs b/tests/errors.rs deleted file mode 100644 index 773fa469..00000000 --- a/tests/errors.rs +++ /dev/null @@ -1,155 +0,0 @@ -#![cfg(feature = "toml")] - -use serde_derive::Deserialize; - -use std::path::PathBuf; - -use config::{Config, ConfigError, File, FileFormat, Map, Value}; - -fn make() -> Config { - Config::builder() - .add_source(File::new("tests/Settings", FileFormat::Toml)) - .build() - .unwrap() -} - -#[test] -fn test_error_parse() { - let res = Config::builder() - .add_source(File::new("tests/Settings-invalid", FileFormat::Toml)) - .build(); - - let path: PathBuf = ["tests", "Settings-invalid.toml"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!( - "invalid TOML value, did you mean to use a quoted string? at line 2 column 9 in {}", - path.display() - ) - ); -} - -#[test] -fn test_error_type() { - let c = make(); - - let res = c.get::("boolean_s_parse"); - - let path: PathBuf = ["tests", "Settings.toml"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!( - "invalid type: string \"fals\", expected a boolean for key `boolean_s_parse` in {}", - path.display() - ) - ); -} - -#[test] -fn test_error_type_detached() { - let c = make(); - - let value = c.get::("boolean_s_parse").unwrap(); - let res = value.try_deserialize::(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - "invalid type: string \"fals\", expected a boolean".to_string() - ); -} - -#[test] -fn test_error_enum_de() { - #[derive(Debug, Deserialize, PartialEq)] - enum Diode { - Off, - Brightness(i32), - Blinking(i32, i32), - Pattern { name: String, inifinite: bool }, - } - - let on_v: Value = "on".into(); - let on_d = on_v.try_deserialize::(); - assert_eq!( - on_d.unwrap_err().to_string(), - "enum Diode does not have variant constructor on".to_string() - ); - - let array_v: Value = vec![100, 100].into(); - let array_d = array_v.try_deserialize::(); - assert_eq!( - array_d.unwrap_err().to_string(), - "value of enum Diode should be represented by either string or table with exactly one key" - ); - - let confused_v: Value = [ - ("Brightness".to_string(), 100.into()), - ("Blinking".to_string(), vec![300, 700].into()), - ] - .iter() - .cloned() - .collect::>() - .into(); - let confused_d = confused_v.try_deserialize::(); - assert_eq!( - confused_d.unwrap_err().to_string(), - "value of enum Diode should be represented by either string or table with exactly one key" - ); -} - -#[test] -fn error_with_path() { - #[derive(Debug, Deserialize)] - struct Inner { - #[allow(dead_code)] - test: i32, - } - - #[derive(Debug, Deserialize)] - struct Outer { - #[allow(dead_code)] - inner: Inner, - } - const CFG: &str = r#" -inner: - test: ABC -"#; - - let e = Config::builder() - .add_source(File::from_str(CFG, FileFormat::Yaml)) - .build() - .unwrap() - .try_deserialize::() - .unwrap_err(); - - if let ConfigError::Type { - key: Some(path), .. - } = e - { - assert_eq!(path, "inner.test"); - } else { - panic!("Wrong error {:?}", e); - } -} - -#[test] -fn test_error_root_not_table() { - match Config::builder() - .add_source(File::from_str(r#"false"#, FileFormat::Json5)) - .build() - { - Ok(_) => panic!("Should not merge if root is not a table"), - Err(e) => match e { - ConfigError::FileParse { cause, .. } => assert_eq!( - "invalid type: boolean `false`, expected a map", - format!("{}", cause) - ), - _ => panic!("Wrong error: {:?}", e), - }, - } -} diff --git a/tests/file.rs b/tests/file.rs deleted file mode 100644 index 9e4469ae..00000000 --- a/tests/file.rs +++ /dev/null @@ -1,70 +0,0 @@ -#![cfg(feature = "yaml")] - -use config::{Config, File, FileFormat}; - -#[test] -fn test_file_not_required() { - let res = Config::builder() - .add_source(File::new("tests/NoSettings", FileFormat::Yaml).required(false)) - .build(); - - assert!(res.is_ok()); -} - -#[test] -fn test_file_required_not_found() { - let res = Config::builder() - .add_source(File::new("tests/NoSettings", FileFormat::Yaml)) - .build(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - "configuration file \"tests/NoSettings\" not found".to_string() - ); -} - -#[test] -fn test_file_auto() { - let c = Config::builder() - .add_source(File::with_name("tests/Settings-production")) - .build() - .unwrap(); - - assert_eq!(c.get("debug").ok(), Some(false)); - assert_eq!(c.get("production").ok(), Some(true)); -} - -#[test] -fn test_file_auto_not_found() { - let res = Config::builder() - .add_source(File::with_name("tests/NoSettings")) - .build(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - "configuration file \"tests/NoSettings\" not found".to_string() - ); -} - -#[test] -fn test_file_ext() { - let c = Config::builder() - .add_source(File::with_name("tests/Settings.json")) - .build() - .unwrap(); - - assert_eq!(c.get("debug").ok(), Some(true)); - assert_eq!(c.get("production").ok(), Some(false)); -} -#[test] -fn test_file_second_ext() { - let c = Config::builder() - .add_source(File::with_name("tests/Settings2.default")) - .build() - .unwrap(); - - assert_eq!(c.get("debug").ok(), Some(true)); - assert_eq!(c.get("production").ok(), Some(false)); -} diff --git a/tests/file_ini.rs b/tests/file_ini.rs deleted file mode 100644 index 4ebf6e3c..00000000 --- a/tests/file_ini.rs +++ /dev/null @@ -1,68 +0,0 @@ -#![cfg(feature = "ini")] - -use serde_derive::Deserialize; - -use std::path::PathBuf; - -use config::{Config, File, FileFormat}; - -#[derive(Debug, Deserialize, PartialEq)] -struct Place { - name: String, - longitude: f64, - latitude: f64, - favorite: bool, - reviews: u64, - rating: Option, -} - -#[derive(Debug, Deserialize, PartialEq)] -struct Settings { - debug: f64, - place: Place, -} - -fn make() -> Config { - Config::builder() - .add_source(File::new("tests/Settings", FileFormat::Ini)) - .build() - .unwrap() -} - -#[test] -fn test_file() { - let c = make(); - let s: Settings = c.try_deserialize().unwrap(); - assert_eq!( - s, - Settings { - debug: 1.0, - place: Place { - name: String::from("Torre di Pisa"), - longitude: 43.722_498_5, - latitude: 10.397_052_2, - favorite: false, - reviews: 3866, - rating: Some(4.5), - }, - } - ); -} - -#[test] -fn test_error_parse() { - let res = Config::builder() - .add_source(File::new("tests/Settings-invalid", FileFormat::Ini)) - .build(); - - let path: PathBuf = ["tests", "Settings-invalid.ini"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!( - r#"2:0 expecting "[Some('='), Some(':')]" but found EOF. in {}"#, - path.display() - ) - ); -} diff --git a/tests/file_json.rs b/tests/file_json.rs deleted file mode 100644 index e6609976..00000000 --- a/tests/file_json.rs +++ /dev/null @@ -1,113 +0,0 @@ -#![cfg(feature = "json")] - -use serde_derive::Deserialize; - -use std::path::PathBuf; - -use config::{Config, File, FileFormat, Map, Value}; -use float_cmp::ApproxEqUlps; - -#[derive(Debug, Deserialize)] -struct Place { - name: String, - longitude: f64, - latitude: f64, - favorite: bool, - telephone: Option, - reviews: u64, - creator: Map, - rating: Option, -} - -#[derive(Debug, Deserialize)] -struct Settings { - debug: f64, - production: Option, - place: Place, - #[serde(rename = "arr")] - elements: Vec, -} - -fn make() -> Config { - Config::builder() - .add_source(File::new("tests/Settings", FileFormat::Json)) - .build() - .unwrap() -} - -#[test] -fn test_file() { - let c = make(); - - // Deserialize the entire file as single struct - let s: Settings = c.try_deserialize().unwrap(); - - assert!(s.debug.approx_eq_ulps(&1.0, 2)); - assert_eq!(s.production, Some("false".to_string())); - assert_eq!(s.place.name, "Torre di Pisa"); - assert!(s.place.longitude.approx_eq_ulps(&43.722_498_5, 2)); - assert!(s.place.latitude.approx_eq_ulps(&10.397_052_2, 2)); - assert!(!s.place.favorite); - assert_eq!(s.place.reviews, 3866); - assert_eq!(s.place.rating, Some(4.5)); - assert_eq!(s.place.telephone, None); - assert_eq!(s.elements.len(), 10); - assert_eq!(s.elements[3], "4".to_string()); - if cfg!(feature = "preserve_order") { - assert_eq!( - s.place - .creator - .into_iter() - .collect::>(), - vec![ - ("name".to_string(), "John Smith".into()), - ("username".into(), "jsmith".into()), - ("email".into(), "jsmith@localhost".into()), - ] - ); - } else { - assert_eq!( - s.place.creator["name"].clone().into_string().unwrap(), - "John Smith".to_string() - ); - } -} - -#[test] -fn test_error_parse() { - let res = Config::builder() - .add_source(File::new("tests/Settings-invalid", FileFormat::Json)) - .build(); - - let path_with_extension: PathBuf = ["tests", "Settings-invalid.json"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!( - "expected `:` at line 4 column 1 in {}", - path_with_extension.display() - ) - ); -} - -#[test] -fn test_json_vec() { - let c = Config::builder() - .add_source(File::from_str( - r#" - { - "WASTE": ["example_dir1", "example_dir2"] - } - "#, - FileFormat::Json, - )) - .build() - .unwrap(); - - let v = c.get_array("WASTE").unwrap(); - let mut vi = v.into_iter(); - assert_eq!(vi.next().unwrap().into_string().unwrap(), "example_dir1"); - assert_eq!(vi.next().unwrap().into_string().unwrap(), "example_dir2"); - assert!(vi.next().is_none()); -} diff --git a/tests/file_json5.rs b/tests/file_json5.rs deleted file mode 100644 index 4bc17d71..00000000 --- a/tests/file_json5.rs +++ /dev/null @@ -1,91 +0,0 @@ -#![cfg(feature = "json5")] - -use serde_derive::Deserialize; - -use config::{Config, File, FileFormat, Map, Value}; -use float_cmp::ApproxEqUlps; -use std::path::PathBuf; - -#[derive(Debug, Deserialize)] -struct Place { - name: String, - longitude: f64, - latitude: f64, - favorite: bool, - telephone: Option, - reviews: u64, - creator: Map, - rating: Option, -} - -#[derive(Debug, Deserialize)] -struct Settings { - debug: f64, - production: Option, - place: Place, - #[serde(rename = "arr")] - elements: Vec, -} - -fn make() -> Config { - Config::builder() - .add_source(File::new("tests/Settings", FileFormat::Json5)) - .build() - .unwrap() -} - -#[test] -fn test_file() { - let c = make(); - - // Deserialize the entire file as single struct - let s: Settings = c.try_deserialize().unwrap(); - - assert!(s.debug.approx_eq_ulps(&1.0, 2)); - assert_eq!(s.production, Some("false".to_string())); - assert_eq!(s.place.name, "Torre di Pisa"); - assert!(s.place.longitude.approx_eq_ulps(&43.722_498_5, 2)); - assert!(s.place.latitude.approx_eq_ulps(&10.397_052_2, 2)); - assert!(!s.place.favorite); - assert_eq!(s.place.reviews, 3866); - assert_eq!(s.place.rating, Some(4.5)); - assert_eq!(s.place.telephone, None); - assert_eq!(s.elements.len(), 10); - assert_eq!(s.elements[3], "4".to_string()); - if cfg!(feature = "preserve_order") { - assert_eq!( - s.place - .creator - .into_iter() - .collect::>(), - vec![ - ("name".to_string(), "John Smith".into()), - ("username".into(), "jsmith".into()), - ("email".into(), "jsmith@localhost".into()), - ] - ); - } else { - assert_eq!( - s.place.creator["name"].clone().into_string().unwrap(), - "John Smith".to_string() - ); - } -} - -#[test] -fn test_error_parse() { - let res = Config::builder() - .add_source(File::new("tests/Settings-invalid", FileFormat::Json5)) - .build(); - - let path_with_extension: PathBuf = ["tests", "Settings-invalid.json5"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!( - " --> 2:7\n |\n2 | ok: true␊\n | ^---\n |\n = expected null in {}", - path_with_extension.display() - ) - ); -} diff --git a/tests/file_ron.rs b/tests/file_ron.rs deleted file mode 100644 index 64e2caec..00000000 --- a/tests/file_ron.rs +++ /dev/null @@ -1,91 +0,0 @@ -#![cfg(feature = "ron")] - -use serde_derive::Deserialize; - -use std::path::PathBuf; - -use config::{Config, File, FileFormat, Map, Value}; -use float_cmp::ApproxEqUlps; - -#[derive(Debug, Deserialize)] -struct Place { - initials: (char, char), - name: String, - longitude: f64, - latitude: f64, - favorite: bool, - telephone: Option, - reviews: u64, - creator: Map, - rating: Option, -} - -#[derive(Debug, Deserialize)] -struct Settings { - debug: f64, - production: Option, - place: Place, - #[serde(rename = "arr")] - elements: Vec, -} - -fn make() -> Config { - Config::builder() - .add_source(File::new("tests/Settings", FileFormat::Ron)) - .build() - .unwrap() -} - -#[test] -fn test_file() { - let c = make(); - - // Deserialize the entire file as single struct - let s: Settings = c.try_deserialize().unwrap(); - - assert!(s.debug.approx_eq_ulps(&1.0, 2)); - assert_eq!(s.production, Some("false".to_string())); - assert_eq!(s.place.initials, ('T', 'P')); - assert_eq!(s.place.name, "Torre di Pisa"); - assert!(s.place.longitude.approx_eq_ulps(&43.722_498_5, 2)); - assert!(s.place.latitude.approx_eq_ulps(&10.397_052_2, 2)); - assert!(!s.place.favorite); - assert_eq!(s.place.reviews, 3866); - assert_eq!(s.place.rating, Some(4.5)); - assert_eq!(s.place.telephone, None); - assert_eq!(s.elements.len(), 10); - assert_eq!(s.elements[3], "4".to_string()); - if cfg!(feature = "preserve_order") { - assert_eq!( - s.place - .creator - .into_iter() - .collect::>(), - vec![ - ("name".to_string(), "John Smith".into()), - ("username".into(), "jsmith".into()), - ("email".into(), "jsmith@localhost".into()), - ] - ); - } else { - assert_eq!( - s.place.creator["name"].clone().into_string().unwrap(), - "John Smith".to_string() - ); - } -} - -#[test] -fn test_error_parse() { - let res = Config::builder() - .add_source(File::new("tests/Settings-invalid", FileFormat::Ron)) - .build(); - - let path_with_extension: PathBuf = ["tests", "Settings-invalid.ron"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!("4:1: Expected colon in {}", path_with_extension.display()) - ); -} diff --git a/tests/file_toml.rs b/tests/file_toml.rs deleted file mode 100644 index 9eed7887..00000000 --- a/tests/file_toml.rs +++ /dev/null @@ -1,103 +0,0 @@ -#![cfg(feature = "toml")] - -use serde_derive::Deserialize; - -use std::path::PathBuf; - -use config::{Config, File, FileFormat, Map, Value}; -use float_cmp::ApproxEqUlps; - -#[derive(Debug, Deserialize)] -struct Place { - number: PlaceNumber, - name: String, - longitude: f64, - latitude: f64, - favorite: bool, - telephone: Option, - reviews: u64, - creator: Map, - rating: Option, -} - -#[derive(Debug, Deserialize, PartialEq)] -struct PlaceNumber(u8); - -#[derive(Debug, Deserialize, PartialEq)] -struct AsciiCode(i8); - -#[derive(Debug, Deserialize)] -struct Settings { - debug: f64, - production: Option, - code: AsciiCode, - place: Place, - #[serde(rename = "arr")] - elements: Vec, -} - -#[cfg(test)] -fn make() -> Config { - Config::builder() - .add_source(File::new("tests/Settings", FileFormat::Toml)) - .build() - .unwrap() -} - -#[test] -fn test_file() { - let c = make(); - - // Deserialize the entire file as single struct - let s: Settings = c.try_deserialize().unwrap(); - - assert!(s.debug.approx_eq_ulps(&1.0, 2)); - assert_eq!(s.production, Some("false".to_string())); - assert_eq!(s.code, AsciiCode(53)); - assert_eq!(s.place.number, PlaceNumber(1)); - assert_eq!(s.place.name, "Torre di Pisa"); - assert!(s.place.longitude.approx_eq_ulps(&43.722_498_5, 2)); - assert!(s.place.latitude.approx_eq_ulps(&10.397_052_2, 2)); - assert!(!s.place.favorite); - assert_eq!(s.place.reviews, 3866); - assert_eq!(s.place.rating, Some(4.5)); - assert_eq!(s.place.telephone, None); - assert_eq!(s.elements.len(), 10); - assert_eq!(s.elements[3], "4".to_string()); - if cfg!(feature = "preserve_order") { - assert_eq!( - s.place - .creator - .into_iter() - .collect::>(), - vec![ - ("name".to_string(), "John Smith".into()), - ("username".into(), "jsmith".into()), - ("email".into(), "jsmith@localhost".into()), - ] - ); - } else { - assert_eq!( - s.place.creator["name"].clone().into_string().unwrap(), - "John Smith".to_string() - ); - } -} - -#[test] -fn test_error_parse() { - let res = Config::builder() - .add_source(File::new("tests/Settings-invalid", FileFormat::Toml)) - .build(); - - let path_with_extension: PathBuf = ["tests", "Settings-invalid.toml"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!( - "invalid TOML value, did you mean to use a quoted string? at line 2 column 9 in {}", - path_with_extension.display() - ) - ); -} diff --git a/tests/file_yaml.rs b/tests/file_yaml.rs deleted file mode 100644 index 233b92cf..00000000 --- a/tests/file_yaml.rs +++ /dev/null @@ -1,93 +0,0 @@ -#![cfg(feature = "yaml")] - -use serde_derive::Deserialize; - -use std::path::PathBuf; - -use config::{Config, File, FileFormat, Map, Value}; -use float_cmp::ApproxEqUlps; - -#[derive(Debug, Deserialize)] -struct Place { - name: String, - longitude: f64, - latitude: f64, - favorite: bool, - telephone: Option, - reviews: u64, - creator: Map, - rating: Option, -} - -#[derive(Debug, Deserialize)] -struct Settings { - debug: f64, - production: Option, - place: Place, - #[serde(rename = "arr")] - elements: Vec, -} - -fn make() -> Config { - Config::builder() - .add_source(File::new("tests/Settings", FileFormat::Yaml)) - .build() - .unwrap() -} - -#[test] -fn test_file() { - let c = make(); - - // Deserialize the entire file as single struct - let s: Settings = c.try_deserialize().unwrap(); - - assert!(s.debug.approx_eq_ulps(&1.0, 2)); - assert_eq!(s.production, Some("false".to_string())); - assert_eq!(s.place.name, "Torre di Pisa"); - assert!(s.place.longitude.approx_eq_ulps(&43.722_498_5, 2)); - assert!(s.place.latitude.approx_eq_ulps(&10.397_052_2, 2)); - assert!(!s.place.favorite); - assert_eq!(s.place.reviews, 3866); - assert_eq!(s.place.rating, Some(4.5)); - assert_eq!(s.place.telephone, None); - assert_eq!(s.elements.len(), 10); - assert_eq!(s.elements[3], "4".to_string()); - if cfg!(feature = "preserve_order") { - assert_eq!( - s.place - .creator - .into_iter() - .collect::>(), - vec![ - ("name".to_string(), "John Smith".into()), - ("username".into(), "jsmith".into()), - ("email".into(), "jsmith@localhost".into()), - ] - ); - } else { - assert_eq!( - s.place.creator["name"].clone().into_string().unwrap(), - "John Smith".to_string() - ); - } -} - -#[test] -fn test_error_parse() { - let res = Config::builder() - .add_source(File::new("tests/Settings-invalid", FileFormat::Yaml)) - .build(); - - let path_with_extension: PathBuf = ["tests", "Settings-invalid.yaml"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!( - "while parsing a block mapping, did not find expected key at \ - line 2 column 1 in {}", - path_with_extension.display() - ) - ); -} diff --git a/tests/get.rs b/tests/get.rs deleted file mode 100644 index fe66d34c..00000000 --- a/tests/get.rs +++ /dev/null @@ -1,280 +0,0 @@ -#![cfg(feature = "toml")] - -use serde_derive::Deserialize; - -use std::collections::HashSet; - -use config::{Config, File, FileFormat, Map, Value}; -use float_cmp::ApproxEqUlps; - -#[derive(Debug, Deserialize)] -struct Place { - name: String, - longitude: f64, - latitude: f64, - favorite: bool, - telephone: Option, - reviews: u64, - rating: Option, -} - -#[derive(Debug, Deserialize)] -struct Settings { - debug: f64, - production: Option, - place: Place, -} - -fn make() -> Config { - Config::builder() - .add_source(File::new("tests/Settings", FileFormat::Toml)) - .build() - .unwrap() -} - -#[test] -fn test_not_found() { - let c = make(); - let res = c.get::("not_found"); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - "configuration property \"not_found\" not found".to_string() - ); -} - -#[test] -fn test_scalar() { - let c = make(); - - assert_eq!(c.get("debug").ok(), Some(true)); - assert_eq!(c.get("production").ok(), Some(false)); -} - -#[test] -fn test_scalar_type_loose() { - let c = make(); - - assert_eq!(c.get("debug").ok(), Some(true)); - assert_eq!(c.get("debug").ok(), Some("true".to_string())); - assert_eq!(c.get("debug").ok(), Some(1)); - assert_eq!(c.get("debug").ok(), Some(1.0)); - - assert_eq!(c.get("debug_s").ok(), Some(true)); - assert_eq!(c.get("debug_s").ok(), Some("true".to_string())); - assert_eq!(c.get("debug_s").ok(), Some(1)); - assert_eq!(c.get("debug_s").ok(), Some(1.0)); - - assert_eq!(c.get("production").ok(), Some(false)); - assert_eq!(c.get("production").ok(), Some("false".to_string())); - assert_eq!(c.get("production").ok(), Some(0)); - assert_eq!(c.get("production").ok(), Some(0.0)); - - assert_eq!(c.get("production_s").ok(), Some(false)); - assert_eq!(c.get("production_s").ok(), Some("false".to_string())); - assert_eq!(c.get("production_s").ok(), Some(0)); - assert_eq!(c.get("production_s").ok(), Some(0.0)); -} - -#[test] -fn test_get_scalar_path() { - let c = make(); - - assert_eq!(c.get("place.favorite").ok(), Some(false)); - assert_eq!( - c.get("place.creator.name").ok(), - Some("John Smith".to_string()) - ); -} - -#[test] -fn test_get_scalar_path_subscript() { - let c = make(); - - assert_eq!(c.get("arr[2]").ok(), Some(3)); - assert_eq!(c.get("items[0].name").ok(), Some("1".to_string())); - assert_eq!(c.get("items[1].name").ok(), Some("2".to_string())); - assert_eq!(c.get("items[-1].name").ok(), Some("2".to_string())); - assert_eq!(c.get("items[-2].name").ok(), Some("1".to_string())); -} - -#[test] -fn test_map() { - let c = make(); - let m: Map = c.get("place").unwrap(); - - assert_eq!(m.len(), 8); - assert_eq!( - m["name"].clone().into_string().unwrap(), - "Torre di Pisa".to_string() - ); - assert_eq!(m["reviews"].clone().into_int().unwrap(), 3866); -} - -#[test] -fn test_map_str() { - let c = make(); - let m: Map = c.get("place.creator").unwrap(); - - if cfg!(feature = "preserve_order") { - assert_eq!( - m.into_iter().collect::>(), - vec![ - ("name".to_string(), "John Smith".to_string()), - ("username".to_string(), "jsmith".to_string()), - ("email".to_string(), "jsmith@localhost".to_string()), - ] - ); - } else { - assert_eq!(m.len(), 3); - assert_eq!(m["name"], "John Smith".to_string()); - } -} - -#[test] -fn test_map_struct() { - #[derive(Debug, Deserialize)] - struct Settings { - place: Map, - } - - let c = make(); - let s: Settings = c.try_deserialize().unwrap(); - - assert_eq!(s.place.len(), 8); - assert_eq!( - s.place["name"].clone().into_string().unwrap(), - "Torre di Pisa".to_string() - ); - assert_eq!(s.place["reviews"].clone().into_int().unwrap(), 3866); -} - -#[test] -fn test_file_struct() { - let c = make(); - - // Deserialize the entire file as single struct - let s: Settings = c.try_deserialize().unwrap(); - - assert!(s.debug.approx_eq_ulps(&1.0, 2)); - assert_eq!(s.production, Some("false".to_string())); - assert_eq!(s.place.name, "Torre di Pisa"); - assert!(s.place.longitude.approx_eq_ulps(&43.722_498_5, 2)); - assert!(s.place.latitude.approx_eq_ulps(&10.397_052_2, 2)); - assert!(!s.place.favorite); - assert_eq!(s.place.reviews, 3866); - assert_eq!(s.place.rating, Some(4.5)); - assert_eq!(s.place.telephone, None); -} - -#[test] -fn test_scalar_struct() { - let c = make(); - - // Deserialize a scalar struct that has lots of different - // data types - let p: Place = c.get("place").unwrap(); - - assert_eq!(p.name, "Torre di Pisa"); - assert!(p.longitude.approx_eq_ulps(&43.722_498_5, 2)); - assert!(p.latitude.approx_eq_ulps(&10.397_052_2, 2)); - assert!(!p.favorite); - assert_eq!(p.reviews, 3866); - assert_eq!(p.rating, Some(4.5)); - assert_eq!(p.telephone, None); -} - -#[test] -fn test_array_scalar() { - let c = make(); - let arr: Vec = c.get("arr").unwrap(); - - assert_eq!(arr.len(), 10); - assert_eq!(arr[3], 4); -} - -#[test] -fn test_struct_array() { - #[derive(Debug, Deserialize)] - struct Settings { - #[serde(rename = "arr")] - elements: Vec, - } - - let c = make(); - let s: Settings = c.try_deserialize().unwrap(); - - assert_eq!(s.elements.len(), 10); - assert_eq!(s.elements[3], "4".to_string()); -} - -#[test] -fn test_enum() { - #[derive(Debug, Deserialize, PartialEq)] - #[serde(rename_all = "lowercase")] - enum Diode { - Off, - Brightness(i32), - Blinking(i32, i32), - Pattern { name: String, inifinite: bool }, - } - #[derive(Debug, Deserialize)] - struct Settings { - diodes: Map, - } - - let c = make(); - let s: Settings = c.try_deserialize().unwrap(); - - assert_eq!(s.diodes["green"], Diode::Off); - assert_eq!(s.diodes["red"], Diode::Brightness(100)); - assert_eq!(s.diodes["blue"], Diode::Blinking(300, 700)); - assert_eq!( - s.diodes["white"], - Diode::Pattern { - name: "christmas".into(), - inifinite: true, - } - ); -} - -#[test] -fn test_enum_key() { - #[derive(Debug, Deserialize, PartialEq, Eq, Hash)] - #[serde(rename_all = "lowercase")] - enum Quark { - Up, - Down, - Strange, - Charm, - Bottom, - Top, - } - - #[derive(Debug, Deserialize)] - struct Settings { - proton: Map, - // Just to make sure that set keys work too. - quarks: HashSet, - } - - let c = make(); - let s: Settings = c.try_deserialize().unwrap(); - - assert_eq!(s.proton[&Quark::Up], 2); - assert_eq!(s.quarks.len(), 6); -} - -#[test] -fn test_int_key() { - #[derive(Debug, Deserialize, PartialEq)] - struct Settings { - divisors: Map, - } - - let c = make(); - let s: Settings = c.try_deserialize().unwrap(); - assert_eq!(s.divisors[&4], 3); - assert_eq!(s.divisors.len(), 4); -} diff --git a/tests/integer_range.rs b/tests/integer_range.rs deleted file mode 100644 index c3e88393..00000000 --- a/tests/integer_range.rs +++ /dev/null @@ -1,35 +0,0 @@ -use config::Config; - -#[test] -fn wrapping_u16() { - let c = Config::builder() - .add_source(config::File::from_str( - r#" - [settings] - port = 66000 - "#, - config::FileFormat::Toml, - )) - .build() - .unwrap(); - - let port: u16 = c.get("settings.port").unwrap(); - assert_eq!(port, 464); -} - -#[test] -fn nonwrapping_u32() { - let c = Config::builder() - .add_source(config::File::from_str( - r#" - [settings] - port = 66000 - "#, - config::FileFormat::Toml, - )) - .build() - .unwrap(); - - let port: u32 = c.get("settings.port").unwrap(); - assert_eq!(port, 66000); -} diff --git a/tests/legacy/datetime.rs b/tests/legacy/datetime.rs deleted file mode 100644 index 7e63e689..00000000 --- a/tests/legacy/datetime.rs +++ /dev/null @@ -1,134 +0,0 @@ -#![cfg(all( - feature = "toml", - feature = "json", - feature = "hjson", - feature = "yaml", - feature = "ini", - feature = "ron", -))] - -use self::chrono::{DateTime, TimeZone, Utc}; -use self::config::*; - -fn make() -> Config { - Config::default() - .merge(File::from_str( - r#" - { - "json_datetime": "2017-05-10T02:14:53Z" - } - "#, - FileFormat::Json, - )) - .unwrap() - .merge(File::from_str( - r#" - yaml_datetime: 2017-06-12T10:58:30Z - "#, - FileFormat::Yaml, - )) - .unwrap() - .merge(File::from_str( - r#" - toml_datetime = 2017-05-11T14:55:15Z - "#, - FileFormat::Toml, - )) - .unwrap() - .merge(File::from_str( - r#" - { - "hjson_datetime": "2017-05-10T02:14:53Z" - } - "#, - FileFormat::Hjson, - )) - .unwrap() - .merge(File::from_str( - r#" - ini_datetime = 2017-05-10T02:14:53Z - "#, - FileFormat::Ini, - )) - .unwrap() - .merge(File::from_str( - r#" - ( - ron_datetime: "2021-04-19T11:33:02Z" - ) - "#, - FileFormat::Ron, - )) - .unwrap() - .clone() -} - -#[test] -fn test_datetime_string() { - let s = make(); - - // JSON - let date: String = s.get("json_datetime").unwrap(); - - assert_eq!(&date, "2017-05-10T02:14:53Z"); - - // TOML - let date: String = s.get("toml_datetime").unwrap(); - - assert_eq!(&date, "2017-05-11T14:55:15Z"); - - // YAML - let date: String = s.get("yaml_datetime").unwrap(); - - assert_eq!(&date, "2017-06-12T10:58:30Z"); - - // HJSON - let date: String = s.get("hjson_datetime").unwrap(); - - assert_eq!(&date, "2017-05-10T02:14:53Z"); - - // INI - let date: String = s.get("ini_datetime").unwrap(); - - assert_eq!(&date, "2017-05-10T02:14:53Z"); - - // RON - let date: String = s.get("ron_datetime").unwrap(); - - assert_eq!(&date, "2021-04-19T11:33:02Z"); -} - -#[test] -fn test_datetime() { - let s = make(); - - // JSON - let date: DateTime = s.get("json_datetime").unwrap(); - - assert_eq!(date, Utc.ymd(2017, 5, 10).and_hms(2, 14, 53)); - - // TOML - let date: DateTime = s.get("toml_datetime").unwrap(); - - assert_eq!(date, Utc.ymd(2017, 5, 11).and_hms(14, 55, 15)); - - // YAML - let date: DateTime = s.get("yaml_datetime").unwrap(); - - assert_eq!(date, Utc.ymd(2017, 6, 12).and_hms(10, 58, 30)); - - // HJSON - let date: DateTime = s.get("hjson_datetime").unwrap(); - - assert_eq!(date, Utc.ymd(2017, 5, 10).and_hms(2, 14, 53)); - - // INI - let date: DateTime = s.get("ini_datetime").unwrap(); - - assert_eq!(date, Utc.ymd(2017, 5, 10).and_hms(2, 14, 53)); - - // RON - let date: DateTime = s.get("ron_datetime").unwrap(); - - assert_eq!(date, Utc.ymd(2021, 4, 19).and_hms(11, 33, 2)); -} diff --git a/tests/legacy/env.rs b/tests/legacy/env.rs deleted file mode 100644 index cde14821..00000000 --- a/tests/legacy/env.rs +++ /dev/null @@ -1,348 +0,0 @@ - -use config::{Config}; -use serde_derive::Deserialize; -use std::env; - -/// Reminder that tests using env variables need to use different env variable names, since -/// tests can be run in parallel - - -#[test] -fn test_parse_int() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestIntEnum { - Int(TestInt), - } - - #[derive(Deserialize, Debug)] - struct TestInt { - int_val: i32, - } - - env::set_var("INT_VAL", "42"); - - let environment = Environment::new().try_parsing(true); - let mut config = Config::default(); - - config.set("tag", "Int").unwrap(); - - config.merge(environment).unwrap(); - - let config: TestIntEnum = config.try_deserialize().unwrap(); - - assert!(matches!(config, TestIntEnum::Int(TestInt { int_val: 42 }))); - - env::remove_var("INT_VAL"); -} - -#[test] -fn test_parse_float() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestFloatEnum { - Float(TestFloat), - } - - #[derive(Deserialize, Debug)] - struct TestFloat { - float_val: f64, - } - - env::set_var("FLOAT_VAL", "42.3"); - - let environment = Environment::new().try_parsing(true); - let mut config = Config::default(); - - config.set("tag", "Float").unwrap(); - - config.merge(environment).unwrap(); - - let config: TestFloatEnum = config.try_deserialize().unwrap(); - - // can't use `matches!` because of float value - match config { - TestFloatEnum::Float(TestFloat { float_val }) => assert_eq!(float_val, 42.3), - } - - env::remove_var("FLOAT_VAL"); -} - -#[test] -fn test_parse_bool() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestBoolEnum { - Bool(TestBool), - } - - #[derive(Deserialize, Debug)] - struct TestBool { - bool_val: bool, - } - - env::set_var("BOOL_VAL", "true"); - - let environment = Environment::new().try_parsing(true); - let mut config = Config::default(); - - config.set("tag", "Bool").unwrap(); - - config.merge(environment).unwrap(); - - let config: TestBoolEnum = config.try_deserialize().unwrap(); - - assert!(matches!( - config, - TestBoolEnum::Bool(TestBool { bool_val: true }), - )); - - env::remove_var("BOOL_VAL"); -} - -#[test] -#[should_panic(expected = "invalid type: string \"42\", expected i32")] -fn test_parse_off_int() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestIntEnum { - Int(TestInt), - } - - #[derive(Deserialize, Debug)] - struct TestInt { - int_val_1: i32, - } - - env::set_var("INT_VAL_1", "42"); - - let environment = Environment::new().try_parsing(false); - let mut config = Config::default(); - - config.set("tag", "Int").unwrap(); - - config.merge(environment).unwrap(); - - env::remove_var("INT_VAL_1"); - - config.try_deserialize::().unwrap(); -} - -#[test] -#[should_panic(expected = "invalid type: string \"42.3\", expected f64")] -fn test_parse_off_float() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestFloatEnum { - Float(TestFloat), - } - - #[derive(Deserialize, Debug)] - struct TestFloat { - float_val_1: f64, - } - - env::set_var("FLOAT_VAL_1", "42.3"); - - let environment = Environment::new().try_parsing(false); - let mut config = Config::default(); - - config.set("tag", "Float").unwrap(); - - config.merge(environment).unwrap(); - - env::remove_var("FLOAT_VAL_1"); - - config.try_deserialize::().unwrap(); -} - -#[test] -#[should_panic(expected = "invalid type: string \"true\", expected a boolean")] -fn test_parse_off_bool() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestBoolEnum { - Bool(TestBool), - } - - #[derive(Deserialize, Debug)] - struct TestBool { - bool_val_1: bool, - } - - env::set_var("BOOL_VAL_1", "true"); - - let environment = Environment::new().try_parsing(false); - let mut config = Config::default(); - - config.set("tag", "Bool").unwrap(); - - config.merge(environment).unwrap(); - - env::remove_var("BOOL_VAL_1"); - - config.try_deserialize::().unwrap(); -} - -#[test] -#[should_panic(expected = "invalid type: string \"not an int\", expected i32")] -fn test_parse_int_fail() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestIntEnum { - Int(TestInt), - } - - #[derive(Deserialize, Debug)] - struct TestInt { - int_val_2: i32, - } - - env::set_var("INT_VAL_2", "not an int"); - - let environment = Environment::new().try_parsing(true); - let mut config = Config::default(); - - config.set("tag", "Int").unwrap(); - - config.merge(environment).unwrap(); - - env::remove_var("INT_VAL_2"); - - config.try_deserialize::().unwrap(); -} - -#[test] -#[should_panic(expected = "invalid type: string \"not a float\", expected f64")] -fn test_parse_float_fail() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestFloatEnum { - Float(TestFloat), - } - - #[derive(Deserialize, Debug)] - struct TestFloat { - float_val_2: f64, - } - - env::set_var("FLOAT_VAL_2", "not a float"); - - let environment = Environment::new().try_parsing(true); - let mut config = Config::default(); - - config.set("tag", "Float").unwrap(); - - config.merge(environment).unwrap(); - - env::remove_var("FLOAT_VAL_2"); - - config.try_deserialize::().unwrap(); -} - -#[test] -#[should_panic(expected = "invalid type: string \"not a bool\", expected a boolean")] -fn test_parse_bool_fail() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestBoolEnum { - Bool(TestBool), - } - - #[derive(Deserialize, Debug)] - struct TestBool { - bool_val_2: bool, - } - - env::set_var("BOOL_VAL_2", "not a bool"); - - let environment = Environment::new().try_parsing(true); - let mut config = Config::default(); - - config.set("tag", "Bool").unwrap(); - - config.merge(environment).unwrap(); - - env::remove_var("BOOL_VAL_2"); - - config.try_deserialize::().unwrap(); -} - -#[test] -fn test_parse_string() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestStringEnum { - String(TestString), - } - - #[derive(Deserialize, Debug)] - struct TestString { - string_val: String, - } - - env::set_var("STRING_VAL", "test string"); - - let environment = Environment::new().try_parsing(true); - let mut config = Config::default(); - - config.set("tag", "String").unwrap(); - - config.merge(environment).unwrap(); - - let config: TestStringEnum = config.try_deserialize().unwrap(); - - let test_string = String::from("test string"); - - match config { - TestStringEnum::String(TestString { string_val }) => assert_eq!(test_string, string_val), - } - - env::remove_var("STRING_VAL"); -} - -#[test] -fn test_parse_off_string() { - // using a struct in an enum here to make serde use `deserialize_any` - #[derive(Deserialize, Debug)] - #[serde(tag = "tag")] - enum TestStringEnum { - String(TestString), - } - - #[derive(Deserialize, Debug)] - struct TestString { - string_val_1: String, - } - - env::set_var("STRING_VAL_1", "test string"); - - let environment = Environment::new().try_parsing(false); - let mut config = Config::default(); - - config.set("tag", "String").unwrap(); - - config.merge(environment).unwrap(); - - let config: TestStringEnum = config.try_deserialize().unwrap(); - - let test_string = String::from("test string"); - - match config { - TestStringEnum::String(TestString { string_val_1 }) => { - assert_eq!(test_string, string_val_1) - } - } - - env::remove_var("STRING_VAL_1"); -} diff --git a/tests/legacy/errors.rs b/tests/legacy/errors.rs deleted file mode 100644 index 885580f3..00000000 --- a/tests/legacy/errors.rs +++ /dev/null @@ -1,134 +0,0 @@ -#![cfg(feature = "toml")] - -use std::path::PathBuf; - -use serde_derive::Deserialize; - -use config::{Config, ConfigError, File, FileFormat, Map, Value}; - -fn make() -> Config { - let mut c = Config::default(); - c.merge(File::new("tests/Settings", FileFormat::Toml)) - .unwrap(); - - c -} - -#[test] -fn test_error_parse() { - let mut c = Config::default(); - let res = c.merge(File::new("tests/Settings-invalid", FileFormat::Toml)); - - let path: PathBuf = ["tests", "Settings-invalid.toml"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!( - "invalid TOML value, did you mean to use a quoted string? at line 2 column 9 in {}", - path.display() - ) - ); -} - -#[test] -fn test_error_type() { - let c = make(); - - let res = c.get::("boolean_s_parse"); - - let path: PathBuf = ["tests", "Settings.toml"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!( - "invalid type: string \"fals\", expected a boolean for key `boolean_s_parse` in {}", - path.display() - ) - ); -} - -#[test] -fn test_error_type_detached() { - let c = make(); - - let value = c.get::("boolean_s_parse").unwrap(); - let res = value.try_deserialize::(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - "invalid type: string \"fals\", expected a boolean".to_string() - ); -} - -#[test] -fn test_error_enum_de() { - #[derive(Debug, Deserialize, PartialEq)] - enum Diode { - Off, - Brightness(i32), - Blinking(i32, i32), - Pattern { name: String, inifinite: bool }, - } - - let on_v: Value = "on".into(); - let on_d = on_v.try_deserialize::(); - assert_eq!( - on_d.unwrap_err().to_string(), - "enum Diode does not have variant constructor on".to_string() - ); - - let array_v: Value = vec![100, 100].into(); - let array_d = array_v.try_deserialize::(); - assert_eq!( - array_d.unwrap_err().to_string(), - "value of enum Diode should be represented by either string or table with exactly one key" - ); - - let confused_v: Value = [ - ("Brightness".to_string(), 100.into()), - ("Blinking".to_string(), vec![300, 700].into()), - ] - .iter() - .cloned() - .collect::>() - .into(); - let confused_d = confused_v.try_deserialize::(); - assert_eq!( - confused_d.unwrap_err().to_string(), - "value of enum Diode should be represented by either string or table with exactly one key" - ); -} - -#[test] -fn error_with_path() { - #[derive(Debug, Deserialize)] - struct Inner { - #[allow(dead_code)] - test: i32, - } - - #[derive(Debug, Deserialize)] - struct Outer { - #[allow(dead_code)] - inner: Inner, - } - const CFG: &str = r#" -inner: - test: ABC -"#; - - let mut cfg = Config::default(); - cfg.merge(File::from_str(CFG, FileFormat::Yaml)).unwrap(); - let e = cfg.try_deserialize::().unwrap_err(); - if let ConfigError::Type { - key: Some(path), .. - } = e - { - assert_eq!(path, "inner.test"); - } else { - panic!("Wrong error {:?}", e); - } -} diff --git a/tests/legacy/file.rs b/tests/legacy/file.rs deleted file mode 100644 index 59006465..00000000 --- a/tests/legacy/file.rs +++ /dev/null @@ -1,54 +0,0 @@ -#![cfg(feature = "yaml")] - -use config::{Config, File, FileFormat}; - -#[test] -fn test_file_not_required() { - let mut c = Config::default(); - let res = c.merge(File::new("tests/NoSettings", FileFormat::Yaml).required(false)); - - assert!(res.is_ok()); -} - -#[test] -fn test_file_required_not_found() { - let mut c = Config::default(); - let res = c.merge(File::new("tests/NoSettings", FileFormat::Yaml)); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - "configuration file \"tests/NoSettings\" not found".to_string() - ); -} - -#[test] -fn test_file_auto() { - let mut c = Config::default(); - c.merge(File::with_name("tests/Settings-production")) - .unwrap(); - - assert_eq!(c.get("debug").ok(), Some(false)); - assert_eq!(c.get("production").ok(), Some(true)); -} - -#[test] -fn test_file_auto_not_found() { - let mut c = Config::default(); - let res = c.merge(File::with_name("tests/NoSettings")); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - "configuration file \"tests/NoSettings\" not found".to_string() - ); -} - -#[test] -fn test_file_ext() { - let mut c = Config::default(); - c.merge(File::with_name("tests/Settings.json")).unwrap(); - - assert_eq!(c.get("debug").ok(), Some(true)); - assert_eq!(c.get("production").ok(), Some(false)); -} diff --git a/tests/legacy/file_ini.rs b/tests/legacy/file_ini.rs deleted file mode 100644 index 5cbf63d4..00000000 --- a/tests/legacy/file_ini.rs +++ /dev/null @@ -1,66 +0,0 @@ -#![cfg(feature = "ini")] - -use serde_derive::Deserialize; -use std::path::PathBuf; - -use config::{Config, File, FileFormat}; - -#[derive(Debug, Deserialize, PartialEq)] -struct Place { - name: String, - longitude: f64, - latitude: f64, - favorite: bool, - reviews: u64, - rating: Option, -} - -#[derive(Debug, Deserialize, PartialEq)] -struct Settings { - debug: f64, - place: Place, -} - -fn make() -> Config { - let mut c = Config::default(); - c.merge(File::new("tests/Settings", FileFormat::Ini)) - .unwrap(); - c -} - -#[test] -fn test_file() { - let c = make(); - let s: Settings = c.try_deserialize().unwrap(); - assert_eq!( - s, - Settings { - debug: 1.0, - place: Place { - name: String::from("Torre di Pisa"), - longitude: 43.722_498_5, - latitude: 10.397_052_2, - favorite: false, - reviews: 3866, - rating: Some(4.5), - }, - } - ); -} - -#[test] -fn test_error_parse() { - let mut c = Config::default(); - let res = c.merge(File::new("tests/Settings-invalid", FileFormat::Ini)); - - let path: PathBuf = ["tests", "Settings-invalid.ini"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!( - r#"2:0 expecting "[Some('='), Some(':')]" but found EOF. in {}"#, - path.display() - ) - ); -} diff --git a/tests/legacy/file_json.rs b/tests/legacy/file_json.rs deleted file mode 100644 index 72fd5ebb..00000000 --- a/tests/legacy/file_json.rs +++ /dev/null @@ -1,113 +0,0 @@ -#![cfg(feature = "json")] - -use serde_derive::Deserialize; - -use std::path::PathBuf; - -use config::{Config, File, FileFormat, Map, Value}; -use float_cmp::ApproxEqUlps; - -#[derive(Debug, Deserialize)] -struct Place { - name: String, - longitude: f64, - latitude: f64, - favorite: bool, - telephone: Option, - reviews: u64, - creator: Map, - rating: Option, -} - -#[derive(Debug, Deserialize)] -struct Settings { - debug: f64, - production: Option, - place: Place, - #[serde(rename = "arr")] - elements: Vec, -} - -fn make() -> Config { - let mut c = Config::default(); - c.merge(File::new("tests/Settings", FileFormat::Json)) - .unwrap(); - - c -} - -#[test] -fn test_file() { - let c = make(); - - // Deserialize the entire file as single struct - let s: Settings = c.try_deserialize().unwrap(); - - assert!(s.debug.approx_eq_ulps(&1.0, 2)); - assert_eq!(s.production, Some("false".to_string())); - assert_eq!(s.place.name, "Torre di Pisa"); - assert!(s.place.longitude.approx_eq_ulps(&43.722_498_5, 2)); - assert!(s.place.latitude.approx_eq_ulps(&10.397_052_2, 2)); - assert!(!s.place.favorite); - assert_eq!(s.place.reviews, 3866); - assert_eq!(s.place.rating, Some(4.5)); - assert_eq!(s.place.telephone, None); - assert_eq!(s.elements.len(), 10); - assert_eq!(s.elements[3], "4".to_string()); - if cfg!(feature = "preserve_order") { - assert_eq!( - s.place - .creator - .into_iter() - .collect::>(), - vec![ - ("name".to_string(), "John Smith".into()), - ("username".into(), "jsmith".into()), - ("email".into(), "jsmith@localhost".into()), - ] - ); - } else { - assert_eq!( - s.place.creator["name"].clone().into_string().unwrap(), - "John Smith".to_string() - ); - } -} - -#[test] -fn test_error_parse() { - let mut c = Config::default(); - let res = c.merge(File::new("tests/Settings-invalid", FileFormat::Json)); - - let path_with_extension: PathBuf = ["tests", "Settings-invalid.json"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!( - "expected `:` at line 4 column 1 in {}", - path_with_extension.display() - ) - ); -} - -#[test] -fn test_json_vec() { - let c = Config::default() - .merge(File::from_str( - r#" - { - "WASTE": ["example_dir1", "example_dir2"] - } - "#, - FileFormat::Json, - )) - .unwrap() - .clone(); - - let v = c.get_array("WASTE").unwrap(); - let mut vi = v.into_iter(); - assert_eq!(vi.next().unwrap().into_string().unwrap(), "example_dir1"); - assert_eq!(vi.next().unwrap().into_string().unwrap(), "example_dir2"); - assert!(vi.next().is_none()); -} diff --git a/tests/legacy/file_ron.rs b/tests/legacy/file_ron.rs deleted file mode 100644 index 7f31c0e9..00000000 --- a/tests/legacy/file_ron.rs +++ /dev/null @@ -1,90 +0,0 @@ -#![cfg(feature = "ron")] - -use serde_derive::Deserialize; -use std::path::PathBuf; - -use config::{Config, File, FileFormat, Map, Value}; -use float_cmp::ApproxEqUlps; - -#[derive(Debug, Deserialize)] -struct Place { - initials: (char, char), - name: String, - longitude: f64, - latitude: f64, - favorite: bool, - telephone: Option, - reviews: u64, - creator: Map, - rating: Option, -} - -#[derive(Debug, Deserialize)] -struct Settings { - debug: f64, - production: Option, - place: Place, - #[serde(rename = "arr")] - elements: Vec, -} - -fn make() -> Config { - let mut c = Config::default(); - c.merge(File::new("tests/Settings", FileFormat::Ron)) - .unwrap(); - - c -} - -#[test] -fn test_file() { - let c = make(); - - // Deserialize the entire file as single struct - let s: Settings = c.try_deserialize().unwrap(); - - assert!(s.debug.approx_eq_ulps(&1.0, 2)); - assert_eq!(s.production, Some("false".to_string())); - assert_eq!(s.place.initials, ('T', 'P')); - assert_eq!(s.place.name, "Torre di Pisa"); - assert!(s.place.longitude.approx_eq_ulps(&43.722_498_5, 2)); - assert!(s.place.latitude.approx_eq_ulps(&10.397_052_2, 2)); - assert!(!s.place.favorite); - assert_eq!(s.place.reviews, 3866); - assert_eq!(s.place.rating, Some(4.5)); - assert_eq!(s.place.telephone, None); - assert_eq!(s.elements.len(), 10); - assert_eq!(s.elements[3], "4".to_string()); - if cfg!(feature = "preserve_order") { - assert_eq!( - s.place - .creator - .into_iter() - .collect::>(), - vec![ - ("name".to_string(), "John Smith".into()), - ("username".into(), "jsmith".into()), - ("email".into(), "jsmith@localhost".into()), - ] - ); - } else { - assert_eq!( - s.place.creator["name"].clone().into_string().unwrap(), - "John Smith".to_string() - ); - } -} - -#[test] -fn test_error_parse() { - let mut c = Config::default(); - let res = c.merge(File::new("tests/Settings-invalid", FileFormat::Ron)); - - let path_with_extension: PathBuf = ["tests", "Settings-invalid.ron"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!("4:1: Expected colon in {}", path_with_extension.display()) - ); -} diff --git a/tests/legacy/file_toml.rs b/tests/legacy/file_toml.rs deleted file mode 100644 index 795f1ab6..00000000 --- a/tests/legacy/file_toml.rs +++ /dev/null @@ -1,102 +0,0 @@ -#![cfg(feature = "toml")] - -use serde_derive::Deserialize; -use std::path::PathBuf; - -use config::{Config, File, FileFormat, Map, Value}; -use float_cmp::ApproxEqUlps; - -#[derive(Debug, Deserialize)] -struct Place { - number: PlaceNumber, - name: String, - longitude: f64, - latitude: f64, - favorite: bool, - telephone: Option, - reviews: u64, - creator: Map, - rating: Option, -} - -#[derive(Debug, Deserialize, PartialEq)] -struct PlaceNumber(u8); - -#[derive(Debug, Deserialize, PartialEq)] -struct AsciiCode(i8); - -#[derive(Debug, Deserialize)] -struct Settings { - debug: f64, - production: Option, - code: AsciiCode, - place: Place, - #[serde(rename = "arr")] - elements: Vec, -} - -#[cfg(test)] -fn make() -> Config { - let mut c = Config::default(); - c.merge(File::new("tests/Settings", FileFormat::Toml)) - .unwrap(); - - c -} - -#[test] -fn test_file() { - let c = make(); - - // Deserialize the entire file as single struct - let s: Settings = c.try_deserialize().unwrap(); - - assert!(s.debug.approx_eq_ulps(&1.0, 2)); - assert_eq!(s.production, Some("false".to_string())); - assert_eq!(s.code, AsciiCode(53)); - assert_eq!(s.place.number, PlaceNumber(1)); - assert_eq!(s.place.name, "Torre di Pisa"); - assert!(s.place.longitude.approx_eq_ulps(&43.722_498_5, 2)); - assert!(s.place.latitude.approx_eq_ulps(&10.397_052_2, 2)); - assert!(!s.place.favorite); - assert_eq!(s.place.reviews, 3866); - assert_eq!(s.place.rating, Some(4.5)); - assert_eq!(s.place.telephone, None); - assert_eq!(s.elements.len(), 10); - assert_eq!(s.elements[3], "4".to_string()); - if cfg!(feature = "preserve_order") { - assert_eq!( - s.place - .creator - .into_iter() - .collect::>(), - vec![ - ("name".to_string(), "John Smith".into()), - ("username".into(), "jsmith".into()), - ("email".into(), "jsmith@localhost".into()), - ] - ); - } else { - assert_eq!( - s.place.creator["name"].clone().into_string().unwrap(), - "John Smith".to_string() - ); - } -} - -#[test] -fn test_error_parse() { - let mut c = Config::default(); - let res = c.merge(File::new("tests/Settings-invalid", FileFormat::Toml)); - - let path_with_extension: PathBuf = ["tests", "Settings-invalid.toml"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!( - "invalid TOML value, did you mean to use a quoted string? at line 2 column 9 in {}", - path_with_extension.display() - ) - ); -} diff --git a/tests/legacy/file_yaml.rs b/tests/legacy/file_yaml.rs deleted file mode 100644 index 21d41384..00000000 --- a/tests/legacy/file_yaml.rs +++ /dev/null @@ -1,93 +0,0 @@ -#![cfg(feature = "yaml")] - -use serde_derive::Deserialize; - -use std::path::PathBuf; - -use config::{Config, File, FileFormat, Map, Value}; -use float_cmp::ApproxEqUlps; - -#[derive(Debug, Deserialize)] -struct Place { - name: String, - longitude: f64, - latitude: f64, - favorite: bool, - telephone: Option, - reviews: u64, - creator: Map, - rating: Option, -} - -#[derive(Debug, Deserialize)] -struct Settings { - debug: f64, - production: Option, - place: Place, - #[serde(rename = "arr")] - elements: Vec, -} - -fn make() -> Config { - let mut c = Config::default(); - c.merge(File::new("tests/Settings", FileFormat::Yaml)) - .unwrap(); - - c -} - -#[test] -fn test_file() { - let c = make(); - - // Deserialize the entire file as single struct - let s: Settings = c.try_deserialize().unwrap(); - - assert!(s.debug.approx_eq_ulps(&1.0, 2)); - assert_eq!(s.production, Some("false".to_string())); - assert_eq!(s.place.name, "Torre di Pisa"); - assert!(s.place.longitude.approx_eq_ulps(&43.722_498_5, 2)); - assert!(s.place.latitude.approx_eq_ulps(&10.397_052_2, 2)); - assert!(!s.place.favorite); - assert_eq!(s.place.reviews, 3866); - assert_eq!(s.place.rating, Some(4.5)); - assert_eq!(s.place.telephone, None); - assert_eq!(s.elements.len(), 10); - assert_eq!(s.elements[3], "4".to_string()); - if cfg!(feature = "preserve_order") { - assert_eq!( - s.place - .creator - .into_iter() - .collect::>(), - vec![ - ("name".to_string(), "John Smith".into()), - ("username".into(), "jsmith".into()), - ("email".into(), "jsmith@localhost".into()), - ] - ); - } else { - assert_eq!( - s.place.creator["name"].clone().into_string().unwrap(), - "John Smith".to_string() - ); - } -} - -#[test] -fn test_error_parse() { - let mut c = Config::default(); - let res = c.merge(File::new("tests/Settings-invalid", FileFormat::Yaml)); - - let path_with_extension: PathBuf = ["tests", "Settings-invalid.yaml"].iter().collect(); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - format!( - "while parsing a block mapping, did not find expected key at \ - line 2 column 1 in {}", - path_with_extension.display() - ) - ); -} diff --git a/tests/legacy/get.rs b/tests/legacy/get.rs deleted file mode 100644 index 24f0d352..00000000 --- a/tests/legacy/get.rs +++ /dev/null @@ -1,280 +0,0 @@ -#![cfg(feature = "toml")] - -use serde_derive::Deserialize; -use std::collections::HashSet; - -use config::{Config, File, FileFormat, Map, Value}; -use float_cmp::ApproxEqUlps; - -#[derive(Debug, Deserialize)] -struct Place { - name: String, - longitude: f64, - latitude: f64, - favorite: bool, - telephone: Option, - reviews: u64, - rating: Option, -} - -#[derive(Debug, Deserialize)] -struct Settings { - debug: f64, - production: Option, - place: Place, -} - -fn make() -> Config { - let mut c = Config::default(); - c.merge(File::new("tests/Settings", FileFormat::Toml)) - .unwrap(); - - c -} - -#[test] -fn test_not_found() { - let c = make(); - let res = c.get::("not_found"); - - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - "configuration property \"not_found\" not found".to_string() - ); -} - -#[test] -fn test_scalar() { - let c = make(); - - assert_eq!(c.get("debug").ok(), Some(true)); - assert_eq!(c.get("production").ok(), Some(false)); -} - -#[test] -fn test_scalar_type_loose() { - let c = make(); - - assert_eq!(c.get("debug").ok(), Some(true)); - assert_eq!(c.get("debug").ok(), Some("true".to_string())); - assert_eq!(c.get("debug").ok(), Some(1)); - assert_eq!(c.get("debug").ok(), Some(1.0)); - - assert_eq!(c.get("debug_s").ok(), Some(true)); - assert_eq!(c.get("debug_s").ok(), Some("true".to_string())); - assert_eq!(c.get("debug_s").ok(), Some(1)); - assert_eq!(c.get("debug_s").ok(), Some(1.0)); - - assert_eq!(c.get("production").ok(), Some(false)); - assert_eq!(c.get("production").ok(), Some("false".to_string())); - assert_eq!(c.get("production").ok(), Some(0)); - assert_eq!(c.get("production").ok(), Some(0.0)); - - assert_eq!(c.get("production_s").ok(), Some(false)); - assert_eq!(c.get("production_s").ok(), Some("false".to_string())); - assert_eq!(c.get("production_s").ok(), Some(0)); - assert_eq!(c.get("production_s").ok(), Some(0.0)); -} - -#[test] -fn test_get_scalar_path() { - let c = make(); - - assert_eq!(c.get("place.favorite").ok(), Some(false)); - assert_eq!( - c.get("place.creator.name").ok(), - Some("John Smith".to_string()) - ); -} - -#[test] -fn test_get_scalar_path_subscript() { - let c = make(); - - assert_eq!(c.get("arr[2]").ok(), Some(3)); - assert_eq!(c.get("items[0].name").ok(), Some("1".to_string())); - assert_eq!(c.get("items[1].name").ok(), Some("2".to_string())); - assert_eq!(c.get("items[-1].name").ok(), Some("2".to_string())); - assert_eq!(c.get("items[-2].name").ok(), Some("1".to_string())); -} - -#[test] -fn test_map() { - let c = make(); - let m: Map = c.get("place").unwrap(); - - assert_eq!(m.len(), 8); - assert_eq!( - m["name"].clone().into_string().unwrap(), - "Torre di Pisa".to_string() - ); - assert_eq!(m["reviews"].clone().into_int().unwrap(), 3866); -} - -#[test] -fn test_map_str() { - let c = make(); - let m: Map = c.get("place.creator").unwrap(); - - if cfg!(feature = "preserve_order") { - assert_eq!( - m.into_iter().collect::>(), - vec![ - ("name".to_string(), "John Smith".to_string()), - ("username".to_string(), "jsmith".to_string()), - ("email".to_string(), "jsmith@localhost".to_string()), - ] - ); - } else { - assert_eq!(m.len(), 3); - assert_eq!(m["name"], "John Smith".to_string()); - } -} - -#[test] -fn test_map_struct() { - #[derive(Debug, Deserialize)] - struct Settings { - place: Map, - } - - let c = make(); - let s: Settings = c.try_deserialize().unwrap(); - - assert_eq!(s.place.len(), 8); - assert_eq!( - s.place["name"].clone().into_string().unwrap(), - "Torre di Pisa".to_string() - ); - assert_eq!(s.place["reviews"].clone().into_int().unwrap(), 3866); -} - -#[test] -fn test_file_struct() { - let c = make(); - - // Deserialize the entire file as single struct - let s: Settings = c.try_deserialize().unwrap(); - - assert!(s.debug.approx_eq_ulps(&1.0, 2)); - assert_eq!(s.production, Some("false".to_string())); - assert_eq!(s.place.name, "Torre di Pisa"); - assert!(s.place.longitude.approx_eq_ulps(&43.722_498_5, 2)); - assert!(s.place.latitude.approx_eq_ulps(&10.397_052_2, 2)); - assert!(!s.place.favorite); - assert_eq!(s.place.reviews, 3866); - assert_eq!(s.place.rating, Some(4.5)); - assert_eq!(s.place.telephone, None); -} - -#[test] -fn test_scalar_struct() { - let c = make(); - - // Deserialize a scalar struct that has lots of different - // data types - let p: Place = c.get("place").unwrap(); - - assert_eq!(p.name, "Torre di Pisa"); - assert!(p.longitude.approx_eq_ulps(&43.722_498_5, 2)); - assert!(p.latitude.approx_eq_ulps(&10.397_052_2, 2)); - assert!(!p.favorite); - assert_eq!(p.reviews, 3866); - assert_eq!(p.rating, Some(4.5)); - assert_eq!(p.telephone, None); -} - -#[test] -fn test_array_scalar() { - let c = make(); - let arr: Vec = c.get("arr").unwrap(); - - assert_eq!(arr.len(), 10); - assert_eq!(arr[3], 4); -} - -#[test] -fn test_struct_array() { - #[derive(Debug, Deserialize)] - struct Settings { - #[serde(rename = "arr")] - elements: Vec, - } - - let c = make(); - let s: Settings = c.try_deserialize().unwrap(); - - assert_eq!(s.elements.len(), 10); - assert_eq!(s.elements[3], "4".to_string()); -} - -#[test] -fn test_enum() { - #[derive(Debug, Deserialize, PartialEq)] - #[serde(rename_all = "lowercase")] - enum Diode { - Off, - Brightness(i32), - Blinking(i32, i32), - Pattern { name: String, inifinite: bool }, - } - #[derive(Debug, Deserialize)] - struct Settings { - diodes: Map, - } - - let c = make(); - let s: Settings = c.try_deserialize().unwrap(); - - assert_eq!(s.diodes["green"], Diode::Off); - assert_eq!(s.diodes["red"], Diode::Brightness(100)); - assert_eq!(s.diodes["blue"], Diode::Blinking(300, 700)); - assert_eq!( - s.diodes["white"], - Diode::Pattern { - name: "christmas".into(), - inifinite: true, - } - ); -} - -#[test] -fn test_enum_key() { - #[derive(Debug, Deserialize, PartialEq, Eq, Hash)] - #[serde(rename_all = "lowercase")] - enum Quark { - Up, - Down, - Strange, - Charm, - Bottom, - Top, - } - - #[derive(Debug, Deserialize)] - struct Settings { - proton: Map, - // Just to make sure that set keys work too. - quarks: HashSet, - } - - let c = make(); - let s: Settings = c.try_deserialize().unwrap(); - - assert_eq!(s.proton[&Quark::Up], 2); - assert_eq!(s.quarks.len(), 6); -} - -#[test] -fn test_int_key() { - #[derive(Debug, Deserialize, PartialEq)] - struct Settings { - divisors: Map, - } - - let c = make(); - let s: Settings = c.try_deserialize().unwrap(); - assert_eq!(s.divisors[&4], 3); - assert_eq!(s.divisors.len(), 4); -} diff --git a/tests/legacy/merge.rs b/tests/legacy/merge.rs deleted file mode 100644 index 74463d66..00000000 --- a/tests/legacy/merge.rs +++ /dev/null @@ -1,60 +0,0 @@ -#![cfg(feature = "toml")] - -use config::{Config, File, FileFormat, Map}; - -fn make() -> Config { - let mut c = Config::default(); - c.merge(File::new("tests/Settings", FileFormat::Toml)) - .unwrap(); - - c.merge(File::new("tests/Settings-production", FileFormat::Toml)) - .unwrap(); - - c -} - -#[test] -fn test_merge() { - let c = make(); - - assert_eq!(c.get("debug").ok(), Some(false)); - assert_eq!(c.get("production").ok(), Some(true)); - assert_eq!(c.get("place.rating").ok(), Some(4.9)); - - if cfg!(feature = "preserve_order") { - let m: Map = c.get("place.creator").unwrap(); - assert_eq!( - m.into_iter().collect::>(), - vec![ - ("name".to_string(), "Somebody New".to_string()), - ("username".to_string(), "jsmith".to_string()), - ("email".to_string(), "jsmith@localhost".to_string()), - ] - ); - } else { - assert_eq!( - c.get("place.creator.name").ok(), - Some("Somebody New".to_string()) - ); - } -} - -#[test] -fn test_merge_whole_config() { - let mut c1 = Config::default(); - let mut c2 = Config::default(); - - c1.set("x", 10).unwrap(); - c2.set("y", 25).unwrap(); - - assert_eq!(c1.get("x").ok(), Some(10)); - assert_eq!(c2.get::<()>("x").ok(), None); - - assert_eq!(c2.get("y").ok(), Some(25)); - assert_eq!(c1.get::<()>("y").ok(), None); - - c1.merge(c2).unwrap(); - - assert_eq!(c1.get("x").ok(), Some(10)); - assert_eq!(c1.get("y").ok(), Some(25)); -} diff --git a/tests/legacy/mod.rs b/tests/legacy/mod.rs deleted file mode 100644 index cd0a16a5..00000000 --- a/tests/legacy/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod datetime; -pub mod errors; -pub mod file; -pub mod file_ini; -pub mod file_json; -pub mod file_ron; -pub mod file_toml; -pub mod file_yaml; -pub mod get; -pub mod merge; -pub mod set; diff --git a/tests/legacy/set.rs b/tests/legacy/set.rs deleted file mode 100644 index 169e462a..00000000 --- a/tests/legacy/set.rs +++ /dev/null @@ -1,91 +0,0 @@ -use config::{Config, File, FileFormat}; - -#[test] -fn test_set_scalar() { - let mut c = Config::default(); - - c.set("value", true).unwrap(); - - assert_eq!(c.get("value").ok(), Some(true)); -} - -#[cfg(feature = "toml")] -#[test] -fn test_set_scalar_default() { - let mut c = Config::default(); - - c.merge(File::new("tests/Settings", FileFormat::Toml)) - .unwrap(); - - c.set_default("debug", false).unwrap(); - c.set_default("staging", false).unwrap(); - - assert_eq!(c.get("debug").ok(), Some(true)); - assert_eq!(c.get("staging").ok(), Some(false)); -} - -#[cfg(feature = "toml")] -#[test] -fn test_set_scalar_path() { - let mut c = Config::default(); - - c.set("first.second.third", true).unwrap(); - - assert_eq!(c.get("first.second.third").ok(), Some(true)); - - c.merge(File::new("tests/Settings", FileFormat::Toml)) - .unwrap(); - - c.set_default("place.favorite", true).unwrap(); - c.set_default("place.blocked", true).unwrap(); - - assert_eq!(c.get("place.favorite").ok(), Some(false)); - assert_eq!(c.get("place.blocked").ok(), Some(true)); -} - -#[cfg(feature = "toml")] -#[test] -fn test_set_arr_path() { - let mut c = Config::default(); - - c.set("items[0].name", "Ivan").unwrap(); - - assert_eq!(c.get("items[0].name").ok(), Some("Ivan".to_string())); - - c.set("data[0].things[1].name", "foo").unwrap(); - c.set("data[0].things[1].value", 42).unwrap(); - c.set("data[1]", 0).unwrap(); - - assert_eq!( - c.get("data[0].things[1].name").ok(), - Some("foo".to_string()) - ); - assert_eq!(c.get("data[0].things[1].value").ok(), Some(42)); - assert_eq!(c.get("data[1]").ok(), Some(0)); - - c.merge(File::new("tests/Settings", FileFormat::Toml)) - .unwrap(); - - c.set("items[0].name", "John").unwrap(); - - assert_eq!(c.get("items[0].name").ok(), Some("John".to_string())); - - c.set("items[2]", "George").unwrap(); - - assert_eq!(c.get("items[2]").ok(), Some("George".to_string())); -} - -#[cfg(feature = "toml")] -#[test] -fn test_set_capital() { - let mut c = Config::default(); - - c.set_default("this", false).unwrap(); - c.set("ThAt", true).unwrap(); - c.merge(File::from_str("{\"logLevel\": 5}", FileFormat::Json)) - .unwrap(); - - assert_eq!(c.get("this").ok(), Some(false)); - assert_eq!(c.get("ThAt").ok(), Some(true)); - assert_eq!(c.get("logLevel").ok(), Some(5)); -} diff --git a/tests/legacy_tests.rs b/tests/legacy_tests.rs deleted file mode 100644 index ce288387..00000000 --- a/tests/legacy_tests.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[allow(deprecated)] -pub mod legacy; diff --git a/tests/merge.rs b/tests/merge.rs deleted file mode 100644 index 0469064f..00000000 --- a/tests/merge.rs +++ /dev/null @@ -1,57 +0,0 @@ -#![cfg(feature = "toml")] - -use config::{Config, File, FileFormat, Map}; - -fn make() -> Config { - Config::builder() - .add_source(File::new("tests/Settings", FileFormat::Toml)) - .add_source(File::new("tests/Settings-production", FileFormat::Toml)) - .build() - .unwrap() -} - -#[test] -fn test_merge() { - let c = make(); - - assert_eq!(c.get("debug").ok(), Some(false)); - assert_eq!(c.get("production").ok(), Some(true)); - assert_eq!(c.get("place.rating").ok(), Some(4.9)); - - if cfg!(feature = "preserve_order") { - let m: Map = c.get("place.creator").unwrap(); - assert_eq!( - m.into_iter().collect::>(), - vec![ - ("name".to_string(), "Somebody New".to_string()), - ("username".to_string(), "jsmith".to_string()), - ("email".to_string(), "jsmith@localhost".to_string()), - ] - ); - } else { - assert_eq!( - c.get("place.creator.name").ok(), - Some("Somebody New".to_string()) - ); - } -} - -#[test] -fn test_merge_whole_config() { - let builder1 = Config::builder().set_override("x", 10).unwrap(); - let builder2 = Config::builder().set_override("y", 25).unwrap(); - - let config1 = builder1.build_cloned().unwrap(); - let config2 = builder2.build_cloned().unwrap(); - - assert_eq!(config1.get("x").ok(), Some(10)); - assert_eq!(config2.get::<()>("x").ok(), None); - - assert_eq!(config2.get("y").ok(), Some(25)); - assert_eq!(config1.get::<()>("y").ok(), None); - - let config3 = builder1.add_source(config2).build().unwrap(); - - assert_eq!(config3.get("x").ok(), Some(10)); - assert_eq!(config3.get("y").ok(), Some(25)); -} diff --git a/tests/set.rs b/tests/set.rs deleted file mode 100644 index 59b5b849..00000000 --- a/tests/set.rs +++ /dev/null @@ -1,91 +0,0 @@ -use config::{Config, File, FileFormat}; - -#[test] -fn test_set_override_scalar() { - let config = Config::builder() - .set_override("value", true) - .and_then(|b| b.build()) - .unwrap(); - - assert_eq!(config.get("value").ok(), Some(true)); -} - -#[cfg(feature = "toml")] -#[test] -fn test_set_scalar_default() { - let config = Config::builder() - .add_source(File::new("tests/Settings", FileFormat::Toml)) - .set_default("debug", false) - .unwrap() - .set_default("staging", false) - .unwrap() - .build() - .unwrap(); - - assert_eq!(config.get("debug").ok(), Some(true)); - assert_eq!(config.get("staging").ok(), Some(false)); -} - -#[cfg(feature = "toml")] -#[test] -fn test_set_scalar_path() { - let config = Config::builder() - .set_override("first.second.third", true) - .unwrap() - .add_source(File::new("tests/Settings", FileFormat::Toml)) - .set_default("place.favorite", true) - .unwrap() - .set_default("place.blocked", true) - .unwrap() - .build() - .unwrap(); - - assert_eq!(config.get("first.second.third").ok(), Some(true)); - assert_eq!(config.get("place.favorite").ok(), Some(false)); - assert_eq!(config.get("place.blocked").ok(), Some(true)); -} - -#[cfg(feature = "toml")] -#[test] -fn test_set_arr_path() { - let config = Config::builder() - .set_override("items[0].name", "Ivan") - .unwrap() - .set_override("data[0].things[1].name", "foo") - .unwrap() - .set_override("data[0].things[1].value", 42) - .unwrap() - .set_override("data[1]", 0) - .unwrap() - .add_source(File::new("tests/Settings", FileFormat::Toml)) - .set_override("items[2]", "George") - .unwrap() - .build() - .unwrap(); - - assert_eq!(config.get("items[0].name").ok(), Some("Ivan".to_string())); - assert_eq!( - config.get("data[0].things[1].name").ok(), - Some("foo".to_string()) - ); - assert_eq!(config.get("data[0].things[1].value").ok(), Some(42)); - assert_eq!(config.get("data[1]").ok(), Some(0)); - assert_eq!(config.get("items[2]").ok(), Some("George".to_string())); -} - -#[cfg(feature = "toml")] -#[test] -fn test_set_capital() { - let config = Config::builder() - .set_default("this", false) - .unwrap() - .set_override("ThAt", true) - .unwrap() - .add_source(File::from_str("{\"logLevel\": 5}", FileFormat::Json)) - .build() - .unwrap(); - - assert_eq!(config.get("this").ok(), Some(false)); - assert_eq!(config.get("ThAt").ok(), Some(true)); - assert_eq!(config.get("logLevel").ok(), Some(5)); -} diff --git a/tests/types/i64.toml b/tests/types/i64.toml deleted file mode 100644 index 4ad173a1..00000000 --- a/tests/types/i64.toml +++ /dev/null @@ -1 +0,0 @@ -value = 120 diff --git a/tests/weird_keys.rs b/tests/weird_keys.rs deleted file mode 100644 index c997fe05..00000000 --- a/tests/weird_keys.rs +++ /dev/null @@ -1,111 +0,0 @@ -// Please note: This file is named "weird" keys because these things are normally not keys, not -// because your software is weird if it expects these keys in the config file. -// -// Please don't be offended! -// - -use serde_derive::{Deserialize, Serialize}; - -use config::{File, FileFormat}; - -/// Helper fn to test the different deserializations -fn test_config_as<'a, T>(config: &str, format: FileFormat) -> T -where - T: serde::Deserialize<'a> + std::fmt::Debug, -{ - let cfg = config::Config::builder() - .add_source(File::from_str(config, format)) - .build(); - - assert!(cfg.is_ok(), "Config could not be built: {:?}", cfg); - let cfg = cfg.unwrap().try_deserialize(); - - assert!(cfg.is_ok(), "Config could not be transformed: {:?}", cfg); - let cfg: T = cfg.unwrap(); - cfg -} - -#[derive(Debug, Serialize, Deserialize)] -struct SettingsColon { - #[serde(rename = "foo:foo")] - foo: u8, - - bar: u8, -} - -#[test] -fn test_colon_key_toml() { - let config = r#" - "foo:foo" = 8 - bar = 12 - "#; - - let cfg = test_config_as::(config, FileFormat::Toml); - assert_eq!(cfg.foo, 8); - assert_eq!(cfg.bar, 12); -} - -#[test] -fn test_colon_key_json() { - let config = r#" {"foo:foo": 8, "bar": 12 } "#; - - let cfg = test_config_as::(config, FileFormat::Json); - assert_eq!(cfg.foo, 8); - assert_eq!(cfg.bar, 12); -} - -#[derive(Debug, Serialize, Deserialize)] -struct SettingsSlash { - #[serde(rename = "foo/foo")] - foo: u8, - bar: u8, -} - -#[test] -fn test_slash_key_toml() { - let config = r#" - "foo/foo" = 8 - bar = 12 - "#; - - let cfg = test_config_as::(config, FileFormat::Toml); - assert_eq!(cfg.foo, 8); - assert_eq!(cfg.bar, 12); -} - -#[test] -fn test_slash_key_json() { - let config = r#" {"foo/foo": 8, "bar": 12 } "#; - - let cfg = test_config_as::(config, FileFormat::Json); - assert_eq!(cfg.foo, 8); - assert_eq!(cfg.bar, 12); -} - -#[derive(Debug, Serialize, Deserialize)] -struct SettingsDoubleBackslash { - #[serde(rename = "foo\\foo")] - foo: u8, - bar: u8, -} - -#[test] -fn test_doublebackslash_key_toml() { - let config = r#" - "foo\\foo" = 8 - bar = 12 - "#; - - let cfg = test_config_as::(config, FileFormat::Toml); - assert_eq!(cfg.foo, 8); - assert_eq!(cfg.bar, 12); -} - -#[test] -fn test_doublebackslash_key_json() { - let config = r#" {"foo\\foo": 8, "bar": 12 } "#; - - let cfg = test_config_as::(config, FileFormat::Json); - assert_eq!(cfg.foo, 8); - assert_eq!(cfg.bar, 12); -} From 9d6be62c3d5531579631ee99052071707684712e Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 20 May 2022 10:06:20 +0200 Subject: [PATCH 02/18] Add first mockup Signed-off-by: Matthias Beyer --- Cargo.toml | 40 +++----------- src/accessor.rs | 22 ++++++++ src/config.rs | 130 ++++++++++++++++++++++++++++++++++++++++++++ src/description.rs | 11 ++++ src/element/json.rs | 55 +++++++++++++++++++ src/element/mod.rs | 34 ++++++++++++ src/element/toml.rs | 55 +++++++++++++++++++ src/lib.rs | 9 +++ src/object/mod.rs | 19 +++++++ src/source.rs | 7 +++ 10 files changed, 350 insertions(+), 32 deletions(-) create mode 100644 src/accessor.rs create mode 100644 src/config.rs create mode 100644 src/description.rs create mode 100644 src/element/json.rs create mode 100644 src/element/mod.rs create mode 100644 src/element/toml.rs create mode 100644 src/lib.rs create mode 100644 src/object/mod.rs create mode 100644 src/source.rs diff --git a/Cargo.toml b/Cargo.toml index 79a3fc85..53018d94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,40 +14,16 @@ edition = "2018" [badges] maintenance = { status = "actively-developed" } -[features] -default = ["toml", "json", "yaml", "ini", "ron", "json5"] -json = ["serde_json"] -yaml = ["yaml-rust"] -ini = ["rust-ini"] -json5 = ["json5_rs"] -preserve_order = ["indexmap", "toml/preserve_order", "serde_json/preserve_order", "ron/indexmap"] - [dependencies] -async-trait = "0.1.50" -lazy_static = "1.0" -serde = "1.0.8" -nom = "7" +serde = { version = "1.0.8", features = ["derive"] } +thiserror = "1" +url = "2.2" -toml = { version = "0.5", optional = true } serde_json = { version = "1.0.2", optional = true } -yaml-rust = { version = "0.4", optional = true } -rust-ini = { version = "0.18", optional = true } -ron = { version = "0.7", optional = true } -json5_rs = { version = "0.4", optional = true, package = "json5" } -indexmap = { version = "1.7.0", features = ["serde-1"], optional = true} -pathdiff = "0.2" +toml = { version = "0.5", optional = true } -[dev-dependencies] -serde_derive = "1.0.8" -float-cmp = "0.9" -chrono = { version = "0.4", features = ["serde"] } -tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs", "io-util", "time"]} -warp = "=0.3.1" -futures = "0.3.15" -reqwest = "0.11.10" +[features] +default = ["json", "toml"] +json = ["serde_json"] +toml = ["dep:toml"] -serde = "1.0" -glob = "0.3" -lazy_static = "1" -notify = "^4.0.0" -temp-env = "0.2.0" diff --git a/src/accessor.rs b/src/accessor.rs new file mode 100644 index 00000000..719be2ee --- /dev/null +++ b/src/accessor.rs @@ -0,0 +1,22 @@ +pub trait ParsableAccessor { + fn parse(&self) -> Result; +} + +impl ParsableAccessor for &str { + fn parse(&self) -> Result { + unimplemented!() + } +} + +impl ParsableAccessor for String { + fn parse(&self) -> Result { + unimplemented!() + } +} + +pub struct Accessor; + +#[derive(Debug, thiserror::Error)] +pub enum AccessorParseError { +} + diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..d9b5a64b --- /dev/null +++ b/src/config.rs @@ -0,0 +1,130 @@ +use crate::element::ConfigElement; +use crate::object::ConfigObject; +use crate::source::ConfigSource; +use crate::accessor::Accessor; +use crate::accessor::ParsableAccessor; + +#[derive(Debug)] +pub struct Config<'a> { + layers: Vec>, +} + +impl<'a> Config<'a> { + pub fn builder() -> ConfigBuilder<'a> { + ConfigBuilder::new() + } + + /// Access the configuration at a specific position + /// + /// Use an object of a type implementing the `ParsableAccessor` trait for accessing the + /// configuration at a certain position. + /// As `ParsableAccessor` is implemented by [`&str`] and [`String`], passing those directly + /// works. + /// + /// # Note + /// + /// Each time, [`Config::get`] is called, the `ParsableAccessor::parse()` function is called. + /// If that is a unbearable overhead (especially in cases where the accessor is hard-coded), + /// [`Config::get_with_accessor`] can be used to prevent that overhead. + /// + /// # Examples + /// + /// ```no_run + /// # use crate::config::Config; + /// let config: Config = { //... + /// # unimplemented!() + /// }; + /// + /// config.get("foo") + /// // ... + /// # ; + /// ``` + pub fn get(&self, accessor: A) -> Result>, ConfigError> + where A: ParsableAccessor + { + let accessor = accessor.parse()?; + self.get_with_accessor(accessor) + } + + /// Access the configuration at a specific position + /// + /// See [`Config::get`] + pub fn get_with_accessor(&self, accessor: Accessor) -> Result>, ConfigError> { + for layer in self.layers.iter() { + if let Some(value) = layer.get(&accessor)? { + return Ok(Some(value)) + } + } + + Ok(None) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ConfigError { + #[error("Accessor parser error")] + AccessorParseError(#[from] crate::accessor::AccessorParseError), + + #[error("Config object access error")] + ConfigObjectAccessError(#[from] crate::object::ConfigObjectAccessError), +} + +#[derive(Debug)] +pub struct ConfigBuilder<'a> { + layers: Vec>, + defaults: Vec>, + overwrites: Vec>, +} + +#[derive(Debug, thiserror::Error)] +pub enum ConfigBuilderError { + Wrapped(E), +} + +impl<'a> ConfigBuilder<'a> { + pub(crate) fn new() -> Self { + ConfigBuilder { + layers: Vec::new(), + defaults: Vec::new(), + overwrites: Vec::new(), + } + } + + pub fn load(mut self, source: &'a CS) -> Result> + where CS: ConfigSource, + E: std::error::Error, + { + let object = source.load().map_err(ConfigBuilderError::Wrapped)?; + self.layers.push(object); + Ok(self) + } + + pub fn load_default(mut self, source: &'a CS) -> Result> + where CS: ConfigSource, + E: std::error::Error, + { + let object = source.load().map_err(ConfigBuilderError::Wrapped)?; + self.defaults.push(object); + Ok(self) + } + + pub fn load_overwrite(mut self, source: &'a CS) -> Result> + where CS: ConfigSource, + E: std::error::Error, + { + let object = source.load().map_err(ConfigBuilderError::Wrapped)?; + self.overwrites.push(object); + Ok(self) + } + + pub fn build(mut self) -> Config<'a> { + let mut layers = self.overwrites; + layers.append(&mut self.layers); + layers.append(&mut self.defaults); + + Config { + layers + } + } +} + diff --git a/src/description.rs b/src/description.rs new file mode 100644 index 00000000..dfb01b4e --- /dev/null +++ b/src/description.rs @@ -0,0 +1,11 @@ +#[derive(Debug)] +#[non_exhaustive] +pub enum ConfigSourceDescription { + Unknown, + Default, + Overwrite, + Path(std::path::PathBuf), + Uri(url::Url), + Custom(String), +} + diff --git a/src/element/json.rs b/src/element/json.rs new file mode 100644 index 00000000..0e9a9083 --- /dev/null +++ b/src/element/json.rs @@ -0,0 +1,55 @@ +use std::collections::HashMap; + +use crate::element::AsConfigElement; +use crate::element::ConfigElement; + +#[derive(Debug, thiserror::Error)] +pub enum JsonIntoConfigElementError {} + +impl AsConfigElement for serde_json::Value { + type Error = JsonIntoConfigElementError; + + fn as_config_element<'a>(&'a self) -> Result, Self::Error> { + match self { + serde_json::Value::Null => Ok(ConfigElement::Null), + serde_json::Value::Bool(b) => Ok(ConfigElement::Bool(*b)), + serde_json::Value::Number(num) => num + .as_i64() + .map(ConfigElement::I64) + .or_else(|| num.as_u64().map(ConfigElement::U64)) + .or_else(|| num.as_f64().map(ConfigElement::F64)) + .ok_or_else(|| unimplemented!()), + serde_json::Value::String(s) => Ok(ConfigElement::Str(&s)), + serde_json::Value::Array(vec) => vec + .iter() + .map(serde_json::Value::as_config_element) + .collect::, Self::Error>>() + .map(ConfigElement::List), + serde_json::Value::Object(obj) => obj + .iter() + .map(|(k, v)| v.as_config_element().map(|v| (k.as_ref(), v))) + .collect::>, JsonIntoConfigElementError>>() + .map(|map| ConfigElement::Map(map)), + } + } +} + +#[cfg(test)] +mod tests { + use crate::element::ConfigElement; + + #[test] + fn deser_json_1() { + let s = r#" + { "key": "value" } + "#; + + let e: ConfigElement = serde_json::from_str(s).unwrap(); + match e { + ConfigElement::Map(map) => { + assert_eq!(*map.get("key").unwrap(), ConfigElement::Str("value")); + } + _ => panic!("Not a map"), + } + } +} diff --git a/src/element/mod.rs b/src/element/mod.rs new file mode 100644 index 00000000..3425bed5 --- /dev/null +++ b/src/element/mod.rs @@ -0,0 +1,34 @@ +use std::collections::HashMap; + +#[derive(Debug, PartialEq, serde::Deserialize)] +#[serde(untagged)] +pub enum ConfigElement<'a> { + Null, + Bool(bool), + I8(i8), + I16(i16), + I32(i32), + I64(i64), + U8(u8), + U16(u16), + U32(u32), + U64(u64), + F32(f32), + F64(f64), + Str(&'a str), + List(Vec>), + Map(HashMap<&'a str, ConfigElement<'a>>), +} + +pub trait AsConfigElement { + type Error: std::error::Error; + + fn as_config_element<'a>(&'a self) -> Result, Self::Error>; +} + +#[cfg(feature = "json")] +mod json; + +#[cfg(feature = "toml")] +mod toml; + diff --git a/src/element/toml.rs b/src/element/toml.rs new file mode 100644 index 00000000..d7094699 --- /dev/null +++ b/src/element/toml.rs @@ -0,0 +1,55 @@ +use std::collections::HashMap; + +use crate::element::AsConfigElement; +use crate::element::ConfigElement; + +#[derive(Debug, thiserror::Error)] +pub enum TomlIntoConfigElementError {} + +impl AsConfigElement for toml::Value { + type Error = TomlIntoConfigElementError; + + fn as_config_element<'a>(&'a self) -> Result, Self::Error> { + match self { + toml::Value::String(s) => Ok(ConfigElement::Str(&s)), + toml::Value::Integer(i) => Ok(ConfigElement::I64(*i)), + toml::Value::Float(f) => Ok(ConfigElement::F64(*f)), + toml::Value::Boolean(b) => Ok(ConfigElement::Bool(*b)), + toml::Value::Datetime(_) => unimplemented!(), // TODO + toml::Value::Array(ary) => { + ary.into_iter() + .map(toml::Value::as_config_element) + .collect::, Self::Error>>() + .map(ConfigElement::List) + }, + toml::Value::Table(table) => { + table.into_iter() + .map(|(k, v)| v.as_config_element().map(|v| (k.as_ref(), v))) + .collect::>, Self::Error>>() + .map(ConfigElement::Map) + } + + } + } +} + +#[cfg(test)] +mod tests { + use crate::element::ConfigElement; + + #[test] + fn deser_toml_1() { + let s = r#" + key = "value" + "#; + + let e: ConfigElement = toml::from_str(s).unwrap(); + match e { + ConfigElement::Map(map) => { + assert_eq!(*map.get("key").unwrap(), ConfigElement::Str("value")); + } + _ => panic!("Not a map"), + } + } +} + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..7102d4a4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +mod accessor; +mod config; +mod description; +mod element; +mod object; +mod source; + +pub use crate::config::Config; +pub use crate::source::ConfigSource; diff --git a/src/object/mod.rs b/src/object/mod.rs new file mode 100644 index 00000000..345e2746 --- /dev/null +++ b/src/object/mod.rs @@ -0,0 +1,19 @@ +use crate::element::ConfigElement; +use crate::description::ConfigSourceDescription; +use crate::accessor::Accessor; + +#[derive(Debug)] +pub struct ConfigObject<'a> { + element: ConfigElement<'a>, + source: ConfigSourceDescription, +} + +impl<'a> ConfigObject<'a> { + pub(crate) fn get(&self, accessor: &Accessor) -> Result>, ConfigObjectAccessError> { + unimplemented!() + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ConfigObjectAccessError { +} diff --git a/src/source.rs b/src/source.rs new file mode 100644 index 00000000..d3ff3230 --- /dev/null +++ b/src/source.rs @@ -0,0 +1,7 @@ +use crate::object::ConfigObject; + +pub trait ConfigSource: std::fmt::Debug { + type Error: std::error::Error; + + fn load<'a>(&'a self) -> Result, Self::Error>; +} From 140d740750f8a9de6d67715f37991dcb0dee22ca Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 21 Jun 2022 09:25:22 +0200 Subject: [PATCH 03/18] Add ConfigObject::new() for creating config objects in tests Signed-off-by: Matthias Beyer --- src/object/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/object/mod.rs b/src/object/mod.rs index 345e2746..e8b76906 100644 --- a/src/object/mod.rs +++ b/src/object/mod.rs @@ -9,6 +9,10 @@ pub struct ConfigObject<'a> { } impl<'a> ConfigObject<'a> { + pub(crate) fn new(element: ConfigElement<'a>, source: ConfigSourceDescription) -> Self { + Self { element, source } + } + pub(crate) fn get(&self, accessor: &Accessor) -> Result>, ConfigObjectAccessError> { unimplemented!() } From 9e2a8457ca24bfb6a75efa54a75dec96bb6132ad Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 21 Jun 2022 09:26:18 +0200 Subject: [PATCH 04/18] Add tests for loading sources Signed-off-by: Matthias Beyer --- src/config.rs | 28 ++++++++++++++++++++++++++++ src/source.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/config.rs b/src/config.rs index d9b5a64b..6d581d1f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -128,3 +128,31 @@ impl<'a> ConfigBuilder<'a> { } } +#[cfg(test)] +mod tests { + use super::*; + use crate::element::AsConfigElement; + + #[test] + fn test_compile_loading() { + let _c = Config::builder() + .load(&crate::source::test_source::TestSource(|| ConfigElement::Null)) + .unwrap() + .build(); + } + + #[test] + #[cfg(feature = "json")] + fn test_load_json() { + let json: serde_json::Value = serde_json::from_str(r#" + { "key": "value" } + "#).unwrap(); + let json = std::sync::Arc::new(json); + + let _c = Config::builder() + .load(&crate::source::test_source::TestSource(|| json.as_config_element().unwrap())) + .unwrap() + .build(); + } + +} diff --git a/src/source.rs b/src/source.rs index d3ff3230..485da8ab 100644 --- a/src/source.rs +++ b/src/source.rs @@ -5,3 +5,33 @@ pub trait ConfigSource: std::fmt::Debug { fn load<'a>(&'a self) -> Result, Self::Error>; } + + +#[cfg(test)] +pub(crate) mod test_source { + use crate::source::ConfigSource; + use crate::object::ConfigObject; + use crate::element::ConfigElement; + use crate::description::ConfigSourceDescription; + + pub(crate) struct TestSource<'a, G>(pub(crate) G) + where G: Fn() -> ConfigElement<'a>; + + impl<'g, G> std::fmt::Debug for TestSource<'g, G> + where G: Fn() -> ConfigElement<'g> + { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Ok(()) + } + } + + impl<'g, G> ConfigSource for TestSource<'g, G> + where G: Fn() -> ConfigElement<'g> + { + type Error = std::convert::Infallible; // can never happen + + fn load<'a>(&'a self) -> Result, Self::Error> { + Ok(ConfigObject::new(self.0(), ConfigSourceDescription::Unknown)) + } + } +} From f1c766259290b94cd11535f360263639a0b08539 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 21 Jun 2022 10:14:47 +0200 Subject: [PATCH 05/18] Add Accessor implementation Signed-off-by: Matthias Beyer --- src/accessor.rs | 36 ++++++++++- src/config.rs | 22 +++++++ src/object/mod.rs | 162 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 213 insertions(+), 7 deletions(-) diff --git a/src/accessor.rs b/src/accessor.rs index 719be2ee..06b25d59 100644 --- a/src/accessor.rs +++ b/src/accessor.rs @@ -4,17 +4,47 @@ pub trait ParsableAccessor { impl ParsableAccessor for &str { fn parse(&self) -> Result { - unimplemented!() + use std::str::FromStr; + + // TODO: Make this non-trivial and bulletproof + + let accessor = self.split('.') + .map(|s| { + match usize::from_str(s) { + Ok(u) => AccessType::Index(u), + Err(_) => AccessType::Key(s.to_string()) + } + }) + .collect(); + + Ok(Accessor(accessor)) } } impl ParsableAccessor for String { fn parse(&self) -> Result { - unimplemented!() + let s: &str = self; + ParsableAccessor::parse(&s) } } -pub struct Accessor; +pub struct Accessor(Vec); + +pub(crate) enum AccessType { + Key(String), + Index(usize), +} + +impl Accessor { + pub(crate) fn head(&self) -> Option<&AccessType> { + self.0.get(0) + } + + pub fn pop(mut self) -> Accessor { + let _ = self.0.remove(0); + Accessor(self.0) + } +} #[derive(Debug, thiserror::Error)] pub enum AccessorParseError { diff --git a/src/config.rs b/src/config.rs index 6d581d1f..f7965cf6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -155,4 +155,26 @@ mod tests { .build(); } + #[test] + #[cfg(feature = "json")] + fn test_load_json_get_value() { + let json: serde_json::Value = serde_json::from_str(r#" + { "key": "value" } + "#).unwrap(); + let json = std::sync::Arc::new(json); + + let source = crate::source::test_source::TestSource(|| json.as_config_element().unwrap()); + + let c = Config::builder() + .load(&source) + .unwrap() + .build(); + + let r = c.get("key"); + assert!(r.is_ok()); + let r = r.unwrap(); + assert!(r.is_some()); + let r = r.unwrap(); + assert!(std::matches!(r, ConfigElement::Str("value"))); + } } diff --git a/src/object/mod.rs b/src/object/mod.rs index e8b76906..05adfb9b 100644 --- a/src/object/mod.rs +++ b/src/object/mod.rs @@ -1,6 +1,7 @@ -use crate::element::ConfigElement; -use crate::description::ConfigSourceDescription; use crate::accessor::Accessor; +use crate::description::ConfigSourceDescription; +use crate::element::ConfigElement; +use crate::accessor::AccessType; #[derive(Debug)] pub struct ConfigObject<'a> { @@ -13,11 +14,164 @@ impl<'a> ConfigObject<'a> { Self { element, source } } - pub(crate) fn get(&self, accessor: &Accessor) -> Result>, ConfigObjectAccessError> { - unimplemented!() + pub(crate) fn get( + &self, + accessor: &Accessor, + ) -> Result>, ConfigObjectAccessError> { + match (accessor.head(), &self.element) { + (Some(AccessType::Key(k)), ConfigElement::Null) => { + Err(ConfigObjectAccessError::AccessWithKeyOnNull(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::Bool(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnBool(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::I8(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnI8(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::I16(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnI16(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::I32(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnI32(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::I64(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnI64(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::U8(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnU8(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::U16(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnU16(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::U32(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnU32(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::U64(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnU64(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::F32(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnF32(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::F64(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnF64(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::Str(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnStr(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::List(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnList(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::Map(hm)) => Ok(hm.get(k.as_str())), + + (Some(AccessType::Index(u)), ConfigElement::Null) => { + Err(ConfigObjectAccessError::AccessWithIndexOnNull(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::Bool(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnBool(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::I8(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnI8(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::I16(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnI16(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::I32(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnI32(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::I64(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnI64(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::U8(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnU8(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::U16(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnU16(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::U32(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnU32(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::U64(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnU64(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::F32(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnF32(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::F64(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnF64(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::Str(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnStr(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::List(v)) => Ok(v.get(*u)), + (Some(AccessType::Index(u)), ConfigElement::Map(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnMap(*u)) + } + + (None, _) => Err(ConfigObjectAccessError::NoAccessor), + } } } #[derive(Debug, thiserror::Error)] pub enum ConfigObjectAccessError { + #[error("Tried to access with no accessor")] + NoAccessor, + + #[error("Accessed Null with key '{0}'")] + AccessWithKeyOnNull(String), + #[error("Accessed Bool with key '{0}'")] + AccessWithKeyOnBool(String), + #[error("Accessed i8 with key '{0}'")] + AccessWithKeyOnI8(String), + #[error("Accessed i16 with key '{0}'")] + AccessWithKeyOnI16(String), + #[error("Accessed i32 with key '{0}'")] + AccessWithKeyOnI32(String), + #[error("Accessed i64 with key '{0}'")] + AccessWithKeyOnI64(String), + #[error("Accessed u8 with key '{0}'")] + AccessWithKeyOnU8(String), + #[error("Accessed u16 with key '{0}'")] + AccessWithKeyOnU16(String), + #[error("Accessed u32 with key '{0}'")] + AccessWithKeyOnU32(String), + #[error("Accessed u64 with key '{0}'")] + AccessWithKeyOnU64(String), + #[error("Accessed f32 with key '{0}'")] + AccessWithKeyOnF32(String), + #[error("Accessed f64 with key '{0}'")] + AccessWithKeyOnF64(String), + #[error("Accessed String with key '{0}'")] + AccessWithKeyOnStr(String), + #[error("Accessed List with key '{0}'")] + AccessWithKeyOnList(String), + + #[error("Accessed Null with index '{0}'")] + AccessWithIndexOnNull(usize), + #[error("Accessed Bool with index '{0}'")] + AccessWithIndexOnBool(usize), + #[error("Accessed i8 with index '{0}'")] + AccessWithIndexOnI8(usize), + #[error("Accessed i16 with index '{0}'")] + AccessWithIndexOnI16(usize), + #[error("Accessed i32 with index '{0}'")] + AccessWithIndexOnI32(usize), + #[error("Accessed i64 with index '{0}'")] + AccessWithIndexOnI64(usize), + #[error("Accessed u8 with index '{0}'")] + AccessWithIndexOnU8(usize), + #[error("Accessed u16 with index '{0}'")] + AccessWithIndexOnU16(usize), + #[error("Accessed u32 with index '{0}'")] + AccessWithIndexOnU32(usize), + #[error("Accessed u64 with index '{0}'")] + AccessWithIndexOnU64(usize), + #[error("Accessed f32 with index '{0}'")] + AccessWithIndexOnF32(usize), + #[error("Accessed f64 with index '{0}'")] + AccessWithIndexOnF64(usize), + #[error("Accessed usize with index '{0}'")] + AccessWithIndexOnStr(usize), + #[error("Accessed Map with index '{0}'")] + AccessWithIndexOnMap(usize), } From b2ba820ad8f93654bf03900362d20fc0e35a43a4 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 21 Jun 2022 11:04:46 +0200 Subject: [PATCH 06/18] Add StringSource This patch adds a type for a String buffer as a source for the configuration as well as format parser interfaces. Signed-off-by: Matthias Beyer --- src/element/mod.rs | 2 +- src/lib.rs | 1 + src/source/format.rs | 23 +++++++++++++++ src/{source.rs => source/mod.rs} | 21 ++++++++++++++ src/source/string.rs | 49 ++++++++++++++++++++++++++++++++ 5 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/source/format.rs rename src/{source.rs => source/mod.rs} (66%) create mode 100644 src/source/string.rs diff --git a/src/element/mod.rs b/src/element/mod.rs index 3425bed5..6059651c 100644 --- a/src/element/mod.rs +++ b/src/element/mod.rs @@ -27,7 +27,7 @@ pub trait AsConfigElement { } #[cfg(feature = "json")] -mod json; +pub mod json; #[cfg(feature = "toml")] mod toml; diff --git a/src/lib.rs b/src/lib.rs index 7102d4a4..4ddb7a94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,3 +7,4 @@ mod source; pub use crate::config::Config; pub use crate::source::ConfigSource; +pub use crate::source::StringSource; diff --git a/src/source/format.rs b/src/source/format.rs new file mode 100644 index 00000000..b2e6551b --- /dev/null +++ b/src/source/format.rs @@ -0,0 +1,23 @@ +use crate::element::AsConfigElement; + +use super::SourceError; + +pub trait FormatParser: std::fmt::Debug { + type Output: AsConfigElement + std::fmt::Debug + Sized; + + fn parse(buffer: &str) -> Result; +} + +#[cfg(feature = "json")] +#[derive(Debug)] +pub struct JsonFormatParser; + +#[cfg(feature = "json")] +impl FormatParser for JsonFormatParser { + type Output = serde_json::Value; + + fn parse(buffer: &str) -> Result { + serde_json::from_str(buffer).map_err(SourceError::JsonParserError) + } +} + diff --git a/src/source.rs b/src/source/mod.rs similarity index 66% rename from src/source.rs rename to src/source/mod.rs index 485da8ab..3b2dd877 100644 --- a/src/source.rs +++ b/src/source/mod.rs @@ -1,11 +1,31 @@ use crate::object::ConfigObject; +mod format; +mod string; + +pub use crate::source::string::StringSource; +pub use crate::source::format::FormatParser; +pub use crate::source::format::JsonFormatParser; + pub trait ConfigSource: std::fmt::Debug { type Error: std::error::Error; fn load<'a>(&'a self) -> Result, Self::Error>; } +#[derive(Debug, thiserror::Error)] +pub enum SourceError { + #[error("IO Error")] + Io(#[from] std::io::Error), + + #[cfg(feature = "json")] + #[error("JSON Parser error")] + JsonParserError(#[from] serde_json::Error), + + #[cfg(feature = "json")] + #[error("JSON load error")] + JsonLoadError(#[from] crate::element::json::JsonIntoConfigElementError), +} #[cfg(test)] pub(crate) mod test_source { @@ -35,3 +55,4 @@ pub(crate) mod test_source { } } } + diff --git a/src/source/string.rs b/src/source/string.rs new file mode 100644 index 00000000..e09e7f2f --- /dev/null +++ b/src/source/string.rs @@ -0,0 +1,49 @@ +use crate::ConfigSource; +use crate::description::ConfigSourceDescription; +use crate::element::AsConfigElement; +use crate::object::ConfigObject; +use crate::source::format::FormatParser; + +use super::SourceError; + +#[derive(Debug)] +pub struct StringSource { + data: P::Output, +} + +impl StringSource

{ + pub fn new(buffer: &str) -> Result { + Ok(StringSource { + data: P::parse(buffer)?, + }) + } +} + +impl ConfigSource for StringSource

+ where SourceError: From<<

::Output as AsConfigElement>::Error> +{ + type Error = SourceError; + + fn load<'a>(&'a self) -> Result, Self::Error> { + let element = self.data + .as_config_element()?; + + let desc = ConfigSourceDescription::Custom("String".to_string()); + Ok(ConfigObject::new(element, desc)) + } +} + +#[cfg(test)] +mod test_source_impl { + #[cfg(feature = "json")] + #[test] + fn test_json_string_source() { + use super::*; + + let source = "{}"; + + let source = StringSource::::new(source).unwrap(); + let _object = source.load().unwrap(); + } +} + From 65561dae1c3c425d224eedb292f9d7c20159e903 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 21 Jun 2022 11:11:46 +0200 Subject: [PATCH 07/18] Add TOML parser backend Signed-off-by: Matthias Beyer --- src/element/mod.rs | 2 +- src/source/format.rs | 13 +++++++++++++ src/source/mod.rs | 8 ++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/element/mod.rs b/src/element/mod.rs index 6059651c..b70b5df2 100644 --- a/src/element/mod.rs +++ b/src/element/mod.rs @@ -30,5 +30,5 @@ pub trait AsConfigElement { pub mod json; #[cfg(feature = "toml")] -mod toml; +pub mod toml; diff --git a/src/source/format.rs b/src/source/format.rs index b2e6551b..47f48002 100644 --- a/src/source/format.rs +++ b/src/source/format.rs @@ -21,3 +21,16 @@ impl FormatParser for JsonFormatParser { } } + +#[cfg(feature = "toml")] +#[derive(Debug)] +pub struct TomlFormatParser; + +#[cfg(feature = "toml")] +impl FormatParser for TomlFormatParser { + type Output = toml::Value; + + fn parse(buffer: &str) -> Result { + toml::from_str(buffer).map_err(SourceError::TomlParserError) + } +} diff --git a/src/source/mod.rs b/src/source/mod.rs index 3b2dd877..452d500b 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -25,6 +25,14 @@ pub enum SourceError { #[cfg(feature = "json")] #[error("JSON load error")] JsonLoadError(#[from] crate::element::json::JsonIntoConfigElementError), + + #[cfg(feature = "toml")] + #[error("TOML Parser error")] + TomlParserError(#[from] toml::de::Error), + + #[cfg(feature = "toml")] + #[error("TOML load error")] + TomlLoadError(#[from] crate::element::toml::TomlIntoConfigElementError), } #[cfg(test)] From db442a6d2c38026e900420ce1b044a42994f00f7 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 21 Jun 2022 11:20:52 +0200 Subject: [PATCH 08/18] Add tests for layered configuration Signed-off-by: Matthias Beyer --- src/config.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/config.rs b/src/config.rs index f7965cf6..182ae7e3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -177,4 +177,81 @@ mod tests { let r = r.unwrap(); assert!(std::matches!(r, ConfigElement::Str("value"))); } + + #[test] + #[cfg(feature = "json")] + fn test_layered_json_config() { + let json1: serde_json::Value = serde_json::from_str(r#" + { "key1": "value1" } + "#).unwrap(); + let json1 = std::sync::Arc::new(json1); + + let json2: serde_json::Value = serde_json::from_str(r#" + { "key1": "value2", "key2": "value3" } + "#).unwrap(); + let json2 = std::sync::Arc::new(json2); + + let source1 = crate::source::test_source::TestSource(|| json1.as_config_element().unwrap()); + let source2 = crate::source::test_source::TestSource(|| json2.as_config_element().unwrap()); + + let c = Config::builder() + .load(&source1) + .unwrap() + .load(&source2) + .unwrap() + .build(); + + let r = c.get("key1"); + assert!(r.is_ok()); + let r = r.unwrap(); + assert!(r.is_some()); + let r = r.unwrap(); + assert!(std::matches!(r, ConfigElement::Str("value1"))); + + let r = c.get("key2"); + assert!(r.is_ok()); + let r = r.unwrap(); + assert!(r.is_some()); + let r = r.unwrap(); + assert!(std::matches!(r, ConfigElement::Str("value3"))); + } + + #[test] + #[cfg(all(feature = "json", feature = "toml"))] + fn test_layered_json_toml_config() { + let json: serde_json::Value = serde_json::from_str(r#" + { "key1": "value1" } + "#).unwrap(); + let json = std::sync::Arc::new(json); + + let toml: toml::Value = toml::from_str(r#" + key1 = "value2" + key2 = "value3" + "#).unwrap(); + let toml = std::sync::Arc::new(toml); + + let source1 = crate::source::test_source::TestSource(|| json.as_config_element().unwrap()); + let source2 = crate::source::test_source::TestSource(|| toml.as_config_element().unwrap()); + + let c = Config::builder() + .load(&source1) + .unwrap() + .load(&source2) + .unwrap() + .build(); + + let r = c.get("key1"); + assert!(r.is_ok()); + let r = r.unwrap(); + assert!(r.is_some()); + let r = r.unwrap(); + assert!(std::matches!(r, ConfigElement::Str("value1"))); + + let r = c.get("key2"); + assert!(r.is_ok()); + let r = r.unwrap(); + assert!(r.is_some()); + let r = r.unwrap(); + assert!(std::matches!(r, ConfigElement::Str("value3"))); + } } From 87a620528ef775c702da2d3074eab2637210f990 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 21 Jun 2022 11:35:47 +0200 Subject: [PATCH 09/18] Make Accessor type an index helper This patch changes the Accessor type to be a helper over a list of AccessType objects. Signed-off-by: Matthias Beyer --- src/accessor.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/accessor.rs b/src/accessor.rs index 06b25d59..5e1f30f3 100644 --- a/src/accessor.rs +++ b/src/accessor.rs @@ -17,7 +17,7 @@ impl ParsableAccessor for &str { }) .collect(); - Ok(Accessor(accessor)) + Ok(Accessor::new(accessor)) } } @@ -28,7 +28,19 @@ impl ParsableAccessor for String { } } -pub struct Accessor(Vec); +pub struct Accessor { + stack: Vec, + index: usize, +} + +impl Accessor { + pub(crate) fn new(stack: Vec) -> Self { + Self { + stack, + index: 0 + } + } +} pub(crate) enum AccessType { Key(String), @@ -36,13 +48,12 @@ pub(crate) enum AccessType { } impl Accessor { - pub(crate) fn head(&self) -> Option<&AccessType> { - self.0.get(0) + pub(crate) fn current(&self) -> Option<&AccessType> { + self.stack.get(self.index) } - pub fn pop(mut self) -> Accessor { - let _ = self.0.remove(0); - Accessor(self.0) + pub fn advance(&mut self) { + self.index += 1; } } From 0daf2b033633c145ff8caa76dc58fa97278a10dc Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 21 Jun 2022 11:36:45 +0200 Subject: [PATCH 10/18] Move traversal to ConfigElement impl Signed-off-by: Matthias Beyer --- src/config.rs | 4 +- src/element/mod.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++++ src/object/mod.rs | 95 +------------------------------------------- 3 files changed, 102 insertions(+), 95 deletions(-) diff --git a/src/config.rs b/src/config.rs index 182ae7e3..2b2cc52b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -49,9 +49,9 @@ impl<'a> Config<'a> { /// Access the configuration at a specific position /// /// See [`Config::get`] - pub fn get_with_accessor(&self, accessor: Accessor) -> Result>, ConfigError> { + pub fn get_with_accessor(&self, mut accessor: Accessor) -> Result>, ConfigError> { for layer in self.layers.iter() { - if let Some(value) = layer.get(&accessor)? { + if let Some(value) = layer.get(&mut accessor)? { return Ok(Some(value)) } } diff --git a/src/element/mod.rs b/src/element/mod.rs index b70b5df2..a7568afc 100644 --- a/src/element/mod.rs +++ b/src/element/mod.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use crate::{accessor::{AccessType, Accessor}, object::ConfigObjectAccessError}; + #[derive(Debug, PartialEq, serde::Deserialize)] #[serde(untagged)] pub enum ConfigElement<'a> { @@ -20,6 +22,102 @@ pub enum ConfigElement<'a> { Map(HashMap<&'a str, ConfigElement<'a>>), } +impl<'a> ConfigElement<'a> { + pub(crate) fn get(&self, accessor: &mut Accessor) -> Result>, ConfigObjectAccessError> { + match (accessor.current(), &self) { + (Some(AccessType::Key(k)), ConfigElement::Null) => { + Err(ConfigObjectAccessError::AccessWithKeyOnNull(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::Bool(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnBool(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::I8(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnI8(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::I16(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnI16(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::I32(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnI32(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::I64(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnI64(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::U8(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnU8(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::U16(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnU16(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::U32(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnU32(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::U64(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnU64(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::F32(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnF32(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::F64(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnF64(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::Str(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnStr(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::List(_)) => { + Err(ConfigObjectAccessError::AccessWithKeyOnList(k.to_string())) + } + (Some(AccessType::Key(k)), ConfigElement::Map(hm)) => Ok(hm.get(k.as_str())), + + (Some(AccessType::Index(u)), ConfigElement::Null) => { + Err(ConfigObjectAccessError::AccessWithIndexOnNull(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::Bool(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnBool(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::I8(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnI8(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::I16(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnI16(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::I32(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnI32(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::I64(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnI64(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::U8(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnU8(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::U16(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnU16(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::U32(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnU32(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::U64(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnU64(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::F32(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnF32(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::F64(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnF64(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::Str(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnStr(*u)) + } + (Some(AccessType::Index(u)), ConfigElement::List(v)) => Ok(v.get(*u)), + (Some(AccessType::Index(u)), ConfigElement::Map(_)) => { + Err(ConfigObjectAccessError::AccessWithIndexOnMap(*u)) + } + + (None, _) => Err(ConfigObjectAccessError::NoAccessor), + } + } +} + pub trait AsConfigElement { type Error: std::error::Error; diff --git a/src/object/mod.rs b/src/object/mod.rs index 05adfb9b..c40329cb 100644 --- a/src/object/mod.rs +++ b/src/object/mod.rs @@ -1,7 +1,6 @@ use crate::accessor::Accessor; use crate::description::ConfigSourceDescription; use crate::element::ConfigElement; -use crate::accessor::AccessType; #[derive(Debug)] pub struct ConfigObject<'a> { @@ -16,99 +15,9 @@ impl<'a> ConfigObject<'a> { pub(crate) fn get( &self, - accessor: &Accessor, + accessor: &mut Accessor, ) -> Result>, ConfigObjectAccessError> { - match (accessor.head(), &self.element) { - (Some(AccessType::Key(k)), ConfigElement::Null) => { - Err(ConfigObjectAccessError::AccessWithKeyOnNull(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::Bool(_)) => { - Err(ConfigObjectAccessError::AccessWithKeyOnBool(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::I8(_)) => { - Err(ConfigObjectAccessError::AccessWithKeyOnI8(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::I16(_)) => { - Err(ConfigObjectAccessError::AccessWithKeyOnI16(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::I32(_)) => { - Err(ConfigObjectAccessError::AccessWithKeyOnI32(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::I64(_)) => { - Err(ConfigObjectAccessError::AccessWithKeyOnI64(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::U8(_)) => { - Err(ConfigObjectAccessError::AccessWithKeyOnU8(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::U16(_)) => { - Err(ConfigObjectAccessError::AccessWithKeyOnU16(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::U32(_)) => { - Err(ConfigObjectAccessError::AccessWithKeyOnU32(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::U64(_)) => { - Err(ConfigObjectAccessError::AccessWithKeyOnU64(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::F32(_)) => { - Err(ConfigObjectAccessError::AccessWithKeyOnF32(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::F64(_)) => { - Err(ConfigObjectAccessError::AccessWithKeyOnF64(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::Str(_)) => { - Err(ConfigObjectAccessError::AccessWithKeyOnStr(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::List(_)) => { - Err(ConfigObjectAccessError::AccessWithKeyOnList(k.to_string())) - } - (Some(AccessType::Key(k)), ConfigElement::Map(hm)) => Ok(hm.get(k.as_str())), - - (Some(AccessType::Index(u)), ConfigElement::Null) => { - Err(ConfigObjectAccessError::AccessWithIndexOnNull(*u)) - } - (Some(AccessType::Index(u)), ConfigElement::Bool(_)) => { - Err(ConfigObjectAccessError::AccessWithIndexOnBool(*u)) - } - (Some(AccessType::Index(u)), ConfigElement::I8(_)) => { - Err(ConfigObjectAccessError::AccessWithIndexOnI8(*u)) - } - (Some(AccessType::Index(u)), ConfigElement::I16(_)) => { - Err(ConfigObjectAccessError::AccessWithIndexOnI16(*u)) - } - (Some(AccessType::Index(u)), ConfigElement::I32(_)) => { - Err(ConfigObjectAccessError::AccessWithIndexOnI32(*u)) - } - (Some(AccessType::Index(u)), ConfigElement::I64(_)) => { - Err(ConfigObjectAccessError::AccessWithIndexOnI64(*u)) - } - (Some(AccessType::Index(u)), ConfigElement::U8(_)) => { - Err(ConfigObjectAccessError::AccessWithIndexOnU8(*u)) - } - (Some(AccessType::Index(u)), ConfigElement::U16(_)) => { - Err(ConfigObjectAccessError::AccessWithIndexOnU16(*u)) - } - (Some(AccessType::Index(u)), ConfigElement::U32(_)) => { - Err(ConfigObjectAccessError::AccessWithIndexOnU32(*u)) - } - (Some(AccessType::Index(u)), ConfigElement::U64(_)) => { - Err(ConfigObjectAccessError::AccessWithIndexOnU64(*u)) - } - (Some(AccessType::Index(u)), ConfigElement::F32(_)) => { - Err(ConfigObjectAccessError::AccessWithIndexOnF32(*u)) - } - (Some(AccessType::Index(u)), ConfigElement::F64(_)) => { - Err(ConfigObjectAccessError::AccessWithIndexOnF64(*u)) - } - (Some(AccessType::Index(u)), ConfigElement::Str(_)) => { - Err(ConfigObjectAccessError::AccessWithIndexOnStr(*u)) - } - (Some(AccessType::Index(u)), ConfigElement::List(v)) => Ok(v.get(*u)), - (Some(AccessType::Index(u)), ConfigElement::Map(_)) => { - Err(ConfigObjectAccessError::AccessWithIndexOnMap(*u)) - } - - (None, _) => Err(ConfigObjectAccessError::NoAccessor), - } + self.element.get(accessor) } } From f1293c064adb52b05535f8a7c4c6a9b9db587f62 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 21 Jun 2022 11:38:27 +0200 Subject: [PATCH 11/18] Add test for nested TOML configuration Signed-off-by: Matthias Beyer --- src/element/mod.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/element/mod.rs b/src/element/mod.rs index a7568afc..a92de51b 100644 --- a/src/element/mod.rs +++ b/src/element/mod.rs @@ -67,7 +67,18 @@ impl<'a> ConfigElement<'a> { (Some(AccessType::Key(k)), ConfigElement::List(_)) => { Err(ConfigObjectAccessError::AccessWithKeyOnList(k.to_string())) } - (Some(AccessType::Key(k)), ConfigElement::Map(hm)) => Ok(hm.get(k.as_str())), + (Some(AccessType::Key(k)), ConfigElement::Map(hm)) => { + if let Some(value) = hm.get(k.as_str()) { + accessor.advance(); + if accessor.current().is_none() { + return Ok(Some(value)) + } else { + value.get(accessor) + } + } else { + Ok(None) + } + }, (Some(AccessType::Index(u)), ConfigElement::Null) => { Err(ConfigObjectAccessError::AccessWithIndexOnNull(*u)) @@ -130,3 +141,42 @@ pub mod json; #[cfg(feature = "toml")] pub mod toml; +#[cfg(test)] +mod tests { + #[test] + #[cfg(feature = "toml")] + fn test_nested_toml_config() { + use crate::Config; + use crate::element::AsConfigElement; + use crate::element::ConfigElement; + + let toml: toml::Value = toml::from_str(r#" + key1 = "value2" + + [table] + key2 = "value3" + "#).unwrap(); + let toml = std::sync::Arc::new(toml); + + let source = crate::source::test_source::TestSource(|| toml.as_config_element().unwrap()); + + let c = Config::builder() + .load(&source) + .unwrap() + .build(); + + let r = c.get("key1"); + assert!(r.is_ok()); + let r = r.unwrap(); + assert!(r.is_some()); + let r = r.unwrap(); + assert!(std::matches!(r, ConfigElement::Str("value2"))); + + let r = c.get("table.key2"); + assert!(r.is_ok()); + let r = r.unwrap(); + assert!(r.is_some()); + let r = r.unwrap(); + assert!(std::matches!(r, ConfigElement::Str("value3")), "{:?} != value3", r); + } +} From 024d97e0591274f6635e5d053ffd19439e8096ab Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 21 Jun 2022 11:38:27 +0200 Subject: [PATCH 12/18] Add test for array-nested TOML configuration Signed-off-by: Matthias Beyer --- src/element/mod.rs | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/element/mod.rs b/src/element/mod.rs index a92de51b..a8e4443f 100644 --- a/src/element/mod.rs +++ b/src/element/mod.rs @@ -119,7 +119,18 @@ impl<'a> ConfigElement<'a> { (Some(AccessType::Index(u)), ConfigElement::Str(_)) => { Err(ConfigObjectAccessError::AccessWithIndexOnStr(*u)) } - (Some(AccessType::Index(u)), ConfigElement::List(v)) => Ok(v.get(*u)), + (Some(AccessType::Index(u)), ConfigElement::List(v)) => { + if let Some(value) = v.get(*u) { + accessor.advance(); + if accessor.current().is_none() { + return Ok(Some(value)) + } else { + value.get(accessor) + } + } else { + Ok(None) + } + }, (Some(AccessType::Index(u)), ConfigElement::Map(_)) => { Err(ConfigObjectAccessError::AccessWithIndexOnMap(*u)) } @@ -179,4 +190,35 @@ mod tests { let r = r.unwrap(); assert!(std::matches!(r, ConfigElement::Str("value3")), "{:?} != value3", r); } + + #[test] + #[cfg(feature = "toml")] + fn test_nested_toml_config_with_index() { + use crate::Config; + use crate::element::AsConfigElement; + use crate::element::ConfigElement; + + let toml: toml::Value = toml::from_str(r#" + [[key]] + k = "a" + + [[key]] + k = "b" + "#).unwrap(); + let toml = std::sync::Arc::new(toml); + + let source = crate::source::test_source::TestSource(|| toml.as_config_element().unwrap()); + + let c = Config::builder() + .load(&source) + .unwrap() + .build(); + + let r = c.get("key.0.k"); + assert!(r.is_ok()); + let r = r.unwrap(); + assert!(r.is_some()); + let r = r.unwrap(); + assert!(std::matches!(r, ConfigElement::Str("a")), "{:?} != a", r); + } } From 9bf609fb0b80a674ef2293cb1812b427c5cf4fca Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Wed, 22 Jun 2022 08:06:55 +0200 Subject: [PATCH 13/18] Split config into several submodules Signed-off-by: Matthias Beyer --- src/config/builder.rs | 58 +++++++++++++ src/config/config.rs | 63 +++++++++++++++ src/config/error.rs | 13 +++ src/{config.rs => config/mod.rs} | 135 ++----------------------------- 4 files changed, 141 insertions(+), 128 deletions(-) create mode 100644 src/config/builder.rs create mode 100644 src/config/config.rs create mode 100644 src/config/error.rs rename src/{config.rs => config/mod.rs} (50%) diff --git a/src/config/builder.rs b/src/config/builder.rs new file mode 100644 index 00000000..2d81a4c4 --- /dev/null +++ b/src/config/builder.rs @@ -0,0 +1,58 @@ +use crate::config::Config; +use crate::config::ConfigBuilderError; +use crate::object::ConfigObject; +use crate::source::ConfigSource; + +#[derive(Debug)] +pub struct ConfigBuilder<'a> { + layers: Vec>, + defaults: Vec>, + overwrites: Vec>, +} + +impl<'a> ConfigBuilder<'a> { + pub(crate) fn new() -> Self { + ConfigBuilder { + layers: Vec::new(), + defaults: Vec::new(), + overwrites: Vec::new(), + } + } + + pub fn load(mut self, source: &'a CS) -> Result> + where CS: ConfigSource, + E: std::error::Error, + { + let object = source.load().map_err(ConfigBuilderError::Wrapped)?; + self.layers.push(object); + Ok(self) + } + + pub fn load_default(mut self, source: &'a CS) -> Result> + where CS: ConfigSource, + E: std::error::Error, + { + let object = source.load().map_err(ConfigBuilderError::Wrapped)?; + self.defaults.push(object); + Ok(self) + } + + pub fn load_overwrite(mut self, source: &'a CS) -> Result> + where CS: ConfigSource, + E: std::error::Error, + { + let object = source.load().map_err(ConfigBuilderError::Wrapped)?; + self.overwrites.push(object); + Ok(self) + } + + pub fn build(mut self) -> Config<'a> { + let mut layers = self.overwrites; + layers.append(&mut self.layers); + layers.append(&mut self.defaults); + + Config { + layers + } + } +} diff --git a/src/config/config.rs b/src/config/config.rs new file mode 100644 index 00000000..2c97fe9a --- /dev/null +++ b/src/config/config.rs @@ -0,0 +1,63 @@ +use crate::accessor::Accessor; +use crate::accessor::ParsableAccessor; +use crate::config::ConfigBuilder; +use crate::config::ConfigError; +use crate::element::ConfigElement; +use crate::object::ConfigObject; + +#[derive(Debug)] +pub struct Config<'a> { + pub(super) layers: Vec>, +} + +impl<'a> Config<'a> { + pub fn builder() -> ConfigBuilder<'a> { + ConfigBuilder::new() + } + + /// Access the configuration at a specific position + /// + /// Use an object of a type implementing the `ParsableAccessor` trait for accessing the + /// configuration at a certain position. + /// As `ParsableAccessor` is implemented by [`&str`] and [`String`], passing those directly + /// works. + /// + /// # Note + /// + /// Each time, [`Config::get`] is called, the `ParsableAccessor::parse()` function is called. + /// If that is a unbearable overhead (especially in cases where the accessor is hard-coded), + /// [`Config::get_with_accessor`] can be used to prevent that overhead. + /// + /// # Examples + /// + /// ```no_run + /// # use crate::config::Config; + /// let config: Config = { //... + /// # unimplemented!() + /// }; + /// + /// config.get("foo") + /// // ... + /// # ; + /// ``` + pub fn get(&self, accessor: A) -> Result>, ConfigError> + where A: ParsableAccessor + { + let accessor = accessor.parse()?; + self.get_with_accessor(accessor) + } + + /// Access the configuration at a specific position + /// + /// See [`Config::get`] + pub fn get_with_accessor(&self, mut accessor: Accessor) -> Result>, ConfigError> { + for layer in self.layers.iter() { + if let Some(value) = layer.get(&mut accessor)? { + return Ok(Some(value)) + } + } + + Ok(None) + } +} + diff --git a/src/config/error.rs b/src/config/error.rs new file mode 100644 index 00000000..250d9fb4 --- /dev/null +++ b/src/config/error.rs @@ -0,0 +1,13 @@ +#[derive(Debug, thiserror::Error)] +pub enum ConfigError { + #[error("Accessor parser error")] + AccessorParseError(#[from] crate::accessor::AccessorParseError), + + #[error("Config object access error")] + ConfigObjectAccessError(#[from] crate::object::ConfigObjectAccessError), +} + +#[derive(Debug, thiserror::Error)] +pub enum ConfigBuilderError { + Wrapped(E), +} diff --git a/src/config.rs b/src/config/mod.rs similarity index 50% rename from src/config.rs rename to src/config/mod.rs index 2b2cc52b..678af1a7 100644 --- a/src/config.rs +++ b/src/config/mod.rs @@ -1,137 +1,16 @@ -use crate::element::ConfigElement; -use crate::object::ConfigObject; -use crate::source::ConfigSource; -use crate::accessor::Accessor; -use crate::accessor::ParsableAccessor; +mod builder; +mod config; +mod error; -#[derive(Debug)] -pub struct Config<'a> { - layers: Vec>, -} - -impl<'a> Config<'a> { - pub fn builder() -> ConfigBuilder<'a> { - ConfigBuilder::new() - } - - /// Access the configuration at a specific position - /// - /// Use an object of a type implementing the `ParsableAccessor` trait for accessing the - /// configuration at a certain position. - /// As `ParsableAccessor` is implemented by [`&str`] and [`String`], passing those directly - /// works. - /// - /// # Note - /// - /// Each time, [`Config::get`] is called, the `ParsableAccessor::parse()` function is called. - /// If that is a unbearable overhead (especially in cases where the accessor is hard-coded), - /// [`Config::get_with_accessor`] can be used to prevent that overhead. - /// - /// # Examples - /// - /// ```no_run - /// # use crate::config::Config; - /// let config: Config = { //... - /// # unimplemented!() - /// }; - /// - /// config.get("foo") - /// // ... - /// # ; - /// ``` - pub fn get(&self, accessor: A) -> Result>, ConfigError> - where A: ParsableAccessor - { - let accessor = accessor.parse()?; - self.get_with_accessor(accessor) - } - - /// Access the configuration at a specific position - /// - /// See [`Config::get`] - pub fn get_with_accessor(&self, mut accessor: Accessor) -> Result>, ConfigError> { - for layer in self.layers.iter() { - if let Some(value) = layer.get(&mut accessor)? { - return Ok(Some(value)) - } - } - - Ok(None) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum ConfigError { - #[error("Accessor parser error")] - AccessorParseError(#[from] crate::accessor::AccessorParseError), - - #[error("Config object access error")] - ConfigObjectAccessError(#[from] crate::object::ConfigObjectAccessError), -} - -#[derive(Debug)] -pub struct ConfigBuilder<'a> { - layers: Vec>, - defaults: Vec>, - overwrites: Vec>, -} - -#[derive(Debug, thiserror::Error)] -pub enum ConfigBuilderError { - Wrapped(E), -} - -impl<'a> ConfigBuilder<'a> { - pub(crate) fn new() -> Self { - ConfigBuilder { - layers: Vec::new(), - defaults: Vec::new(), - overwrites: Vec::new(), - } - } - - pub fn load(mut self, source: &'a CS) -> Result> - where CS: ConfigSource, - E: std::error::Error, - { - let object = source.load().map_err(ConfigBuilderError::Wrapped)?; - self.layers.push(object); - Ok(self) - } - - pub fn load_default(mut self, source: &'a CS) -> Result> - where CS: ConfigSource, - E: std::error::Error, - { - let object = source.load().map_err(ConfigBuilderError::Wrapped)?; - self.defaults.push(object); - Ok(self) - } - - pub fn load_overwrite(mut self, source: &'a CS) -> Result> - where CS: ConfigSource, - E: std::error::Error, - { - let object = source.load().map_err(ConfigBuilderError::Wrapped)?; - self.overwrites.push(object); - Ok(self) - } - - pub fn build(mut self) -> Config<'a> { - let mut layers = self.overwrites; - layers.append(&mut self.layers); - layers.append(&mut self.defaults); - - Config { - layers - } - } -} +pub use crate::config::builder::*; +pub use crate::config::config::*; +pub use crate::config::error::*; #[cfg(test)] mod tests { use super::*; use crate::element::AsConfigElement; + use crate::element::ConfigElement; #[test] fn test_compile_loading() { From 4866d794243102c3ead4d3ee2721021bb34f8a03 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 26 Jul 2022 12:53:28 +0200 Subject: [PATCH 14/18] Add async config builder This patch also removes the associated error type from the ConfigSource trait, as we figured out that this can be simply a SourceError. Signed-off-by: Matthias Beyer --- Cargo.toml | 5 ++- src/config/builder.rs | 74 ++++++++++++++++++++++++++++++++++--------- src/config/config.rs | 7 ++++ src/description.rs | 2 +- src/element/mod.rs | 2 +- src/object/mod.rs | 2 +- src/source/mod.rs | 8 +++-- src/source/string.rs | 4 +-- 8 files changed, 80 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 53018d94..b3dfe7a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,9 @@ serde = { version = "1.0.8", features = ["derive"] } thiserror = "1" url = "2.2" +async-trait = { version = "0.1", optional = true } +itertools = { version = "0.10", optional = true } +futures = { version = "0.3", optional = true } serde_json = { version = "1.0.2", optional = true } toml = { version = "0.5", optional = true } @@ -26,4 +29,4 @@ toml = { version = "0.5", optional = true } default = ["json", "toml"] json = ["serde_json"] toml = ["dep:toml"] - +async = ["async-trait", "futures", "itertools"] diff --git a/src/config/builder.rs b/src/config/builder.rs index 2d81a4c4..209cc6bb 100644 --- a/src/config/builder.rs +++ b/src/config/builder.rs @@ -1,7 +1,10 @@ use crate::config::Config; use crate::config::ConfigBuilderError; use crate::object::ConfigObject; +#[cfg(feature = "async")] +use crate::source::AsyncConfigSource; use crate::source::ConfigSource; +use crate::source::SourceError; #[derive(Debug)] pub struct ConfigBuilder<'a> { @@ -19,40 +22,81 @@ impl<'a> ConfigBuilder<'a> { } } - pub fn load(mut self, source: &'a CS) -> Result> - where CS: ConfigSource, - E: std::error::Error, + pub fn load(mut self, source: &'a CS) -> Result> + where + CS: ConfigSource, { let object = source.load().map_err(ConfigBuilderError::Wrapped)?; self.layers.push(object); Ok(self) } - pub fn load_default(mut self, source: &'a CS) -> Result> - where CS: ConfigSource, - E: std::error::Error, + pub fn load_default(mut self, source: &'a CS) -> Result> + where + CS: ConfigSource, { let object = source.load().map_err(ConfigBuilderError::Wrapped)?; self.defaults.push(object); Ok(self) } - pub fn load_overwrite(mut self, source: &'a CS) -> Result> - where CS: ConfigSource, - E: std::error::Error, + pub fn load_overwrite(mut self, source: &'a CS) -> Result> + where + CS: ConfigSource, { let object = source.load().map_err(ConfigBuilderError::Wrapped)?; self.overwrites.push(object); Ok(self) } - pub fn build(mut self) -> Config<'a> { - let mut layers = self.overwrites; - layers.append(&mut self.layers); - layers.append(&mut self.defaults); + pub fn build(&self) -> Config<'a> { + let mut layers = self.overwrites.clone(); + layers.append(&mut self.layers.clone()); + layers.append(&mut self.defaults.clone()); - Config { - layers + Config { layers } + } +} + +#[cfg(feature = "async")] +#[derive(Debug)] +pub struct AsyncConfigBuilder { + sources: Vec>, +} + +#[cfg(feature = "async")] +impl AsyncConfigBuilder { + pub(crate) fn new() -> Self { + Self { + sources: Vec::new(), + } + } + + /// Register a AsyncConfigSource with the builder, but don't poll it + pub fn load(mut self, source: Box) -> Self { + self.sources.push(source); + self + } + + pub async fn build<'a>(&'a self) -> Result, Vec> { + use futures::stream::FuturesUnordered; + use futures::stream::StreamExt; + use itertools::Itertools; + + let (layers, errs) = self + .sources + .iter() + .map(|cs| async move { cs.load().await }) + .collect::>() + .collect::, SourceError>>>() + .await + .into_iter() + .partition_result::>, Vec, ConfigObject<'a>, SourceError>(); + + if errs.is_empty() { + Ok(Config { layers }) + } else { + Err(errs) } } } diff --git a/src/config/config.rs b/src/config/config.rs index 2c97fe9a..d2a37309 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -1,5 +1,7 @@ use crate::accessor::Accessor; use crate::accessor::ParsableAccessor; +#[cfg(feature = "async")] +use crate::config::AsyncConfigBuilder; use crate::config::ConfigBuilder; use crate::config::ConfigError; use crate::element::ConfigElement; @@ -15,6 +17,11 @@ impl<'a> Config<'a> { ConfigBuilder::new() } + #[cfg(feature = "async")] + pub fn async_builder() -> AsyncConfigBuilder { + AsyncConfigBuilder::new() + } + /// Access the configuration at a specific position /// /// Use an object of a type implementing the `ParsableAccessor` trait for accessing the diff --git a/src/description.rs b/src/description.rs index dfb01b4e..01311e75 100644 --- a/src/description.rs +++ b/src/description.rs @@ -1,4 +1,4 @@ -#[derive(Debug)] +#[derive(Clone, Debug)] #[non_exhaustive] pub enum ConfigSourceDescription { Unknown, diff --git a/src/element/mod.rs b/src/element/mod.rs index a8e4443f..0127df69 100644 --- a/src/element/mod.rs +++ b/src/element/mod.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::{accessor::{AccessType, Accessor}, object::ConfigObjectAccessError}; -#[derive(Debug, PartialEq, serde::Deserialize)] +#[derive(Clone, Debug, PartialEq, serde::Deserialize)] #[serde(untagged)] pub enum ConfigElement<'a> { Null, diff --git a/src/object/mod.rs b/src/object/mod.rs index c40329cb..ac8c12e8 100644 --- a/src/object/mod.rs +++ b/src/object/mod.rs @@ -2,7 +2,7 @@ use crate::accessor::Accessor; use crate::description::ConfigSourceDescription; use crate::element::ConfigElement; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ConfigObject<'a> { element: ConfigElement<'a>, source: ConfigSourceDescription, diff --git a/src/source/mod.rs b/src/source/mod.rs index 452d500b..616298dd 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -8,9 +8,13 @@ pub use crate::source::format::FormatParser; pub use crate::source::format::JsonFormatParser; pub trait ConfigSource: std::fmt::Debug { - type Error: std::error::Error; + fn load<'a>(&'a self) -> Result, SourceError>; +} - fn load<'a>(&'a self) -> Result, Self::Error>; +#[cfg(feature = "async")] +#[async_trait::async_trait] +pub trait AsyncConfigSource: std::fmt::Debug { + async fn load<'a>(&'a self) -> Result, SourceError>; } #[derive(Debug, thiserror::Error)] diff --git a/src/source/string.rs b/src/source/string.rs index e09e7f2f..d485fbee 100644 --- a/src/source/string.rs +++ b/src/source/string.rs @@ -22,9 +22,7 @@ impl StringSource

{ impl ConfigSource for StringSource

where SourceError: From<<

::Output as AsConfigElement>::Error> { - type Error = SourceError; - - fn load<'a>(&'a self) -> Result, Self::Error> { + fn load<'a>(&'a self) -> Result, SourceError> { let element = self.data .as_config_element()?; From a234c0823122e019c94f68d03ee8f63649ed4fe1 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Mon, 1 Aug 2022 08:50:29 +0200 Subject: [PATCH 15/18] Rewrite to store loaders _and_ loaded config Signed-off-by: Matthias Beyer --- src/config/builder.rs | 139 ++++++++++++++++++++++++------------------ src/config/config.rs | 83 +++++++++++++++++++++---- src/config/error.rs | 9 +++ src/element/json.rs | 4 +- src/element/mod.rs | 4 +- src/element/toml.rs | 4 +- src/source/format.rs | 14 ++--- src/source/mod.rs | 8 +-- src/source/string.rs | 23 ++++--- 9 files changed, 191 insertions(+), 97 deletions(-) diff --git a/src/config/builder.rs b/src/config/builder.rs index 209cc6bb..3748921a 100644 --- a/src/config/builder.rs +++ b/src/config/builder.rs @@ -1,102 +1,123 @@ use crate::config::Config; -use crate::config::ConfigBuilderError; use crate::object::ConfigObject; #[cfg(feature = "async")] use crate::source::AsyncConfigSource; use crate::source::ConfigSource; use crate::source::SourceError; +use super::ConfigError; + #[derive(Debug)] -pub struct ConfigBuilder<'a> { - layers: Vec>, - defaults: Vec>, - overwrites: Vec>, +pub struct ConfigBuilder<'source> { + layers_builders: Vec>>, + defaults_builders: Vec>>, + overwrites_builders: Vec>>, } -impl<'a> ConfigBuilder<'a> { +impl<'source> ConfigBuilder<'source> { pub(crate) fn new() -> Self { ConfigBuilder { - layers: Vec::new(), - defaults: Vec::new(), - overwrites: Vec::new(), + layers_builders: Vec::new(), + defaults_builders: Vec::new(), + overwrites_builders: Vec::new(), } } - pub fn load(mut self, source: &'a CS) -> Result> - where - CS: ConfigSource, - { - let object = source.load().map_err(ConfigBuilderError::Wrapped)?; - self.layers.push(object); - Ok(self) + pub fn load(mut self, source: Box>) -> Self { + self.layers_builders.push(source); + self } - pub fn load_default(mut self, source: &'a CS) -> Result> - where - CS: ConfigSource, - { - let object = source.load().map_err(ConfigBuilderError::Wrapped)?; - self.defaults.push(object); - Ok(self) + pub fn load_default(mut self, source: Box>) -> Self { + self.defaults_builders.push(source); + self } - pub fn load_overwrite(mut self, source: &'a CS) -> Result> - where - CS: ConfigSource, - { - let object = source.load().map_err(ConfigBuilderError::Wrapped)?; - self.overwrites.push(object); - Ok(self) + pub fn load_overwrite(mut self, source: Box>) -> Self { + self.overwrites_builders.push(source); + self } - pub fn build(&self) -> Config<'a> { - let mut layers = self.overwrites.clone(); - layers.append(&mut self.layers.clone()); - layers.append(&mut self.defaults.clone()); + pub fn build(self) -> Result, ConfigError> { + Config::build_from_builder(self) + } - Config { layers } + pub(crate) fn reload(&'source self) -> Result>, SourceError> { + self.overwrites_builders + .iter() + .map(|cs| cs.load()) + .chain(self.layers_builders.iter().map(|cs| cs.load())) + .chain(self.defaults_builders.iter().map(|cs| cs.load())) + .collect() } } #[cfg(feature = "async")] #[derive(Debug)] -pub struct AsyncConfigBuilder { - sources: Vec>, +pub struct AsyncConfigBuilder<'source> { + layers_builders: Vec>>, + defaults_builders: Vec>>, + overwrites_builders: Vec>>, } #[cfg(feature = "async")] -impl AsyncConfigBuilder { +impl<'source> AsyncConfigBuilder<'source> { pub(crate) fn new() -> Self { Self { - sources: Vec::new(), + layers_builders: Vec::new(), + defaults_builders: Vec::new(), + overwrites_builders: Vec::new(), } } /// Register a AsyncConfigSource with the builder, but don't poll it - pub fn load(mut self, source: Box) -> Self { - self.sources.push(source); + pub fn load(mut self, source: Box>) -> Self { + self.layers_builders.push(source); self } - pub async fn build<'a>(&'a self) -> Result, Vec> { - use futures::stream::FuturesUnordered; - use futures::stream::StreamExt; - use itertools::Itertools; + /// Register a AsyncConfigSource with the builder, but don't poll it + pub fn load_default(mut self, source: Box>) -> Self { + self.defaults_builders.push(source); + self + } - let (layers, errs) = self - .sources - .iter() - .map(|cs| async move { cs.load().await }) - .collect::>() - .collect::, SourceError>>>() - .await - .into_iter() - .partition_result::>, Vec, ConfigObject<'a>, SourceError>(); - - if errs.is_empty() { - Ok(Config { layers }) - } else { - Err(errs) + /// Register a AsyncConfigSource with the builder, but don't poll it + pub fn load_overwrite(mut self, source: Box>) -> Self { + self.overwrites_builders.push(source); + self + } + + pub async fn build(self) -> Result, ConfigError> { + Config::build_from_async_builder(self).await + } + + pub(crate) async fn reload(&'source self) -> Result>, SourceError> { + async fn do_load<'source>(builders: &'source Vec>>) -> Result>, SourceError> { + let mut v = Vec::with_capacity(builders.len()); + for cs in builders.iter() { + v.push(cs.load().await?); + } + Ok(v) } + + let overwrites = do_load(&self.overwrites_builders); + let layers = do_load(&self.layers_builders); + let defaults = do_load(&self.defaults_builders); + + let (mut overwrites, mut layers, mut defaults) = + futures::try_join!(overwrites, layers, defaults)?; + + let mut v = Vec::with_capacity({ + self.layers_builders.len() + + self.defaults_builders.len() + + self.overwrites_builders.len() + }); + + v.append(&mut overwrites); + v.append(&mut layers); + v.append(&mut defaults); + + Ok(v) } } diff --git a/src/config/config.rs b/src/config/config.rs index d2a37309..568b3faa 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -1,3 +1,5 @@ +use std::sync::RwLock; + use crate::accessor::Accessor; use crate::accessor::ParsableAccessor; #[cfg(feature = "async")] @@ -8,17 +10,67 @@ use crate::element::ConfigElement; use crate::object::ConfigObject; #[derive(Debug)] -pub struct Config<'a> { - pub(super) layers: Vec>, +pub struct Config<'source> { + builder: Builder<'source>, + layers: RwLock>>>, +} + +#[derive(Debug)] +enum Builder<'source> { + Sync(ConfigBuilder<'source>), + + #[cfg(feature = "async")] + Async(AsyncConfigBuilder<'source>), } -impl<'a> Config<'a> { - pub fn builder() -> ConfigBuilder<'a> { +impl<'source> Config<'source> { + pub fn builder() -> ConfigBuilder<'source> { ConfigBuilder::new() } + pub(super) fn build_from_builder(builder: ConfigBuilder<'source>) -> Result { + let config = Config { + layers: RwLock::new(None), + builder: Builder::Sync(builder), + }; + + { + let mut layers = config.layers.write().unwrap(); + #[allow(irrefutable_let_patterns)] + if let Builder::Sync(builder) = &config.builder { + *layers = Some(builder.reload()?); + } else { + unreachable!() + } + } + + Ok(config) + } + + #[cfg(feature = "async")] + pub(super) async fn build_from_async_builder( + builder: AsyncConfigBuilder<'source>, + ) -> Result, ConfigError> { + let config = Config { + layers: RwLock::new(None), + builder: Builder::Async(builder), + }; + + { + let l = match config.builder { + Builder::Sync(ref builder) => builder.reload()?, + Builder::Async(ref builder) => builder.reload().await?, + }; + + let mut layers = config.layers.write().unwrap(); + *layers = Some(l); + } + + Ok(config) + } + #[cfg(feature = "async")] - pub fn async_builder() -> AsyncConfigBuilder { + pub fn async_builder() -> AsyncConfigBuilder<'source> { AsyncConfigBuilder::new() } @@ -47,8 +99,9 @@ impl<'a> Config<'a> { /// // ... /// # ; /// ``` - pub fn get(&self, accessor: A) -> Result>, ConfigError> - where A: ParsableAccessor + pub fn get(&self, accessor: A) -> Result>, ConfigError> + where + A: ParsableAccessor, { let accessor = accessor.parse()?; self.get_with_accessor(accessor) @@ -57,14 +110,22 @@ impl<'a> Config<'a> { /// Access the configuration at a specific position /// /// See [`Config::get`] - pub fn get_with_accessor(&self, mut accessor: Accessor) -> Result>, ConfigError> { - for layer in self.layers.iter() { + pub fn get_with_accessor( + &self, + mut accessor: Accessor, + ) -> Result>, ConfigError> { + let layers = self + .layers + .read() + .map_err(|_| ConfigError::InternalRwLockPoisioned)? + .ok_or_else(|| ConfigError::NotLoaded)?; + + for layer in layers.iter() { if let Some(value) = layer.get(&mut accessor)? { - return Ok(Some(value)) + return Ok(Some(value)); } } Ok(None) } } - diff --git a/src/config/error.rs b/src/config/error.rs index 250d9fb4..a82acb11 100644 --- a/src/config/error.rs +++ b/src/config/error.rs @@ -5,6 +5,15 @@ pub enum ConfigError { #[error("Config object access error")] ConfigObjectAccessError(#[from] crate::object::ConfigObjectAccessError), + + #[error("Error loading Source")] + SourceError(#[from] crate::source::SourceError), + + #[error("RwLock poisoned")] + InternalRwLockPoisioned, + + #[error("Configuration is not loaded")] + NotLoaded, } #[derive(Debug, thiserror::Error)] diff --git a/src/element/json.rs b/src/element/json.rs index 0e9a9083..2655741a 100644 --- a/src/element/json.rs +++ b/src/element/json.rs @@ -6,10 +6,10 @@ use crate::element::ConfigElement; #[derive(Debug, thiserror::Error)] pub enum JsonIntoConfigElementError {} -impl AsConfigElement for serde_json::Value { +impl<'source> AsConfigElement<'source> for serde_json::Value { type Error = JsonIntoConfigElementError; - fn as_config_element<'a>(&'a self) -> Result, Self::Error> { + fn as_config_element(&'source self) -> Result, Self::Error> { match self { serde_json::Value::Null => Ok(ConfigElement::Null), serde_json::Value::Bool(b) => Ok(ConfigElement::Bool(*b)), diff --git a/src/element/mod.rs b/src/element/mod.rs index 0127df69..056758a4 100644 --- a/src/element/mod.rs +++ b/src/element/mod.rs @@ -140,10 +140,10 @@ impl<'a> ConfigElement<'a> { } } -pub trait AsConfigElement { +pub trait AsConfigElement<'source> { type Error: std::error::Error; - fn as_config_element<'a>(&'a self) -> Result, Self::Error>; + fn as_config_element(&'source self) -> Result, Self::Error>; } #[cfg(feature = "json")] diff --git a/src/element/toml.rs b/src/element/toml.rs index d7094699..ca33381d 100644 --- a/src/element/toml.rs +++ b/src/element/toml.rs @@ -6,10 +6,10 @@ use crate::element::ConfigElement; #[derive(Debug, thiserror::Error)] pub enum TomlIntoConfigElementError {} -impl AsConfigElement for toml::Value { +impl<'source> AsConfigElement<'source> for toml::Value { type Error = TomlIntoConfigElementError; - fn as_config_element<'a>(&'a self) -> Result, Self::Error> { + fn as_config_element(&'source self) -> Result, Self::Error> { match self { toml::Value::String(s) => Ok(ConfigElement::Str(&s)), toml::Value::Integer(i) => Ok(ConfigElement::I64(*i)), diff --git a/src/source/format.rs b/src/source/format.rs index 47f48002..b3d9b466 100644 --- a/src/source/format.rs +++ b/src/source/format.rs @@ -2,10 +2,10 @@ use crate::element::AsConfigElement; use super::SourceError; -pub trait FormatParser: std::fmt::Debug { - type Output: AsConfigElement + std::fmt::Debug + Sized; +pub trait FormatParser<'source>: std::fmt::Debug { + type Output: AsConfigElement<'source> + std::fmt::Debug + Sized; - fn parse(buffer: &str) -> Result; + fn parse(buffer: &'source str) -> Result; } #[cfg(feature = "json")] @@ -13,10 +13,10 @@ pub trait FormatParser: std::fmt::Debug { pub struct JsonFormatParser; #[cfg(feature = "json")] -impl FormatParser for JsonFormatParser { +impl<'source> FormatParser<'source> for JsonFormatParser { type Output = serde_json::Value; - fn parse(buffer: &str) -> Result { + fn parse(buffer: &'source str) -> Result { serde_json::from_str(buffer).map_err(SourceError::JsonParserError) } } @@ -27,10 +27,10 @@ impl FormatParser for JsonFormatParser { pub struct TomlFormatParser; #[cfg(feature = "toml")] -impl FormatParser for TomlFormatParser { +impl<'source> FormatParser<'source> for TomlFormatParser { type Output = toml::Value; - fn parse(buffer: &str) -> Result { + fn parse(buffer: &'source str) -> Result { toml::from_str(buffer).map_err(SourceError::TomlParserError) } } diff --git a/src/source/mod.rs b/src/source/mod.rs index 616298dd..c34c281a 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -7,14 +7,14 @@ pub use crate::source::string::StringSource; pub use crate::source::format::FormatParser; pub use crate::source::format::JsonFormatParser; -pub trait ConfigSource: std::fmt::Debug { - fn load<'a>(&'a self) -> Result, SourceError>; +pub trait ConfigSource<'source>: std::fmt::Debug { + fn load(&'source self) -> Result, SourceError>; } #[cfg(feature = "async")] #[async_trait::async_trait] -pub trait AsyncConfigSource: std::fmt::Debug { - async fn load<'a>(&'a self) -> Result, SourceError>; +pub trait AsyncConfigSource<'source>: std::fmt::Debug { + async fn load(&'source self) -> Result, SourceError>; } #[derive(Debug, thiserror::Error)] diff --git a/src/source/string.rs b/src/source/string.rs index d485fbee..3e095366 100644 --- a/src/source/string.rs +++ b/src/source/string.rs @@ -7,24 +7,27 @@ use crate::source::format::FormatParser; use super::SourceError; #[derive(Debug)] -pub struct StringSource { - data: P::Output, +pub struct StringSource<'source, P: FormatParser<'source> + std::fmt::Debug> { + source: &'source str, + _pd: std::marker::PhantomData

, } -impl StringSource

{ - pub fn new(buffer: &str) -> Result { +impl<'source, P: FormatParser<'source>> StringSource<'source, P> { + pub fn new(source: &'source str) -> Result { Ok(StringSource { - data: P::parse(buffer)?, + source, + _pd: std::marker::PhantomData }) } } -impl ConfigSource for StringSource

- where SourceError: From<<

::Output as AsConfigElement>::Error> +impl<'source, P> ConfigSource<'source> for StringSource<'source, P> + where P: FormatParser<'source> + std::fmt::Debug + 'source, + SourceError: From<<

>::Output as AsConfigElement<'source>>::Error> { - fn load<'a>(&'a self) -> Result, SourceError> { - let element = self.data - .as_config_element()?; + fn load(&'source self) -> Result, SourceError> { + let element = P::parse(self.source)?; + let element = element.as_config_element()?; let desc = ConfigSourceDescription::Custom("String".to_string()); Ok(ConfigObject::new(element, desc)) From 6de1c4be7f6bde2d98a8098d5ee766e9a8f81dad Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Mon, 1 Aug 2022 10:44:20 +0200 Subject: [PATCH 16/18] Remove asyncness This patch removes the async loading. The async loading makes everything awefully complicated and in fact it should rather be solved on the user side of things, not within this crate. Signed-off-by: Matthias Beyer --- src/config/builder.rs | 91 +++++-------------------------------------- src/config/config.rs | 75 +++++------------------------------ src/config/mod.rs | 79 ++++++++++++++++++++----------------- src/element/json.rs | 22 +++++------ src/element/mod.rs | 53 ++++++++++++++----------- src/element/toml.rs | 22 +++++------ src/object/mod.rs | 10 ++--- src/source/format.rs | 16 ++++---- src/source/mod.rs | 32 ++++----------- src/source/string.rs | 24 ++++++------ 10 files changed, 147 insertions(+), 277 deletions(-) diff --git a/src/config/builder.rs b/src/config/builder.rs index 3748921a..86529bb1 100644 --- a/src/config/builder.rs +++ b/src/config/builder.rs @@ -1,20 +1,18 @@ use crate::config::Config; use crate::object::ConfigObject; -#[cfg(feature = "async")] -use crate::source::AsyncConfigSource; use crate::source::ConfigSource; use crate::source::SourceError; use super::ConfigError; #[derive(Debug)] -pub struct ConfigBuilder<'source> { - layers_builders: Vec>>, - defaults_builders: Vec>>, - overwrites_builders: Vec>>, +pub struct ConfigBuilder { + layers_builders: Vec>, + defaults_builders: Vec>, + overwrites_builders: Vec>, } -impl<'source> ConfigBuilder<'source> { +impl ConfigBuilder { pub(crate) fn new() -> Self { ConfigBuilder { layers_builders: Vec::new(), @@ -23,26 +21,26 @@ impl<'source> ConfigBuilder<'source> { } } - pub fn load(mut self, source: Box>) -> Self { + pub fn load(mut self, source: Box) -> Self { self.layers_builders.push(source); self } - pub fn load_default(mut self, source: Box>) -> Self { + pub fn load_default(mut self, source: Box) -> Self { self.defaults_builders.push(source); self } - pub fn load_overwrite(mut self, source: Box>) -> Self { + pub fn load_overwrite(mut self, source: Box) -> Self { self.overwrites_builders.push(source); self } - pub fn build(self) -> Result, ConfigError> { + pub fn build(&self) -> Result { Config::build_from_builder(self) } - pub(crate) fn reload(&'source self) -> Result>, SourceError> { + pub(crate) fn reload(&self) -> Result, SourceError> { self.overwrites_builders .iter() .map(|cs| cs.load()) @@ -52,72 +50,3 @@ impl<'source> ConfigBuilder<'source> { } } -#[cfg(feature = "async")] -#[derive(Debug)] -pub struct AsyncConfigBuilder<'source> { - layers_builders: Vec>>, - defaults_builders: Vec>>, - overwrites_builders: Vec>>, -} - -#[cfg(feature = "async")] -impl<'source> AsyncConfigBuilder<'source> { - pub(crate) fn new() -> Self { - Self { - layers_builders: Vec::new(), - defaults_builders: Vec::new(), - overwrites_builders: Vec::new(), - } - } - - /// Register a AsyncConfigSource with the builder, but don't poll it - pub fn load(mut self, source: Box>) -> Self { - self.layers_builders.push(source); - self - } - - /// Register a AsyncConfigSource with the builder, but don't poll it - pub fn load_default(mut self, source: Box>) -> Self { - self.defaults_builders.push(source); - self - } - - /// Register a AsyncConfigSource with the builder, but don't poll it - pub fn load_overwrite(mut self, source: Box>) -> Self { - self.overwrites_builders.push(source); - self - } - - pub async fn build(self) -> Result, ConfigError> { - Config::build_from_async_builder(self).await - } - - pub(crate) async fn reload(&'source self) -> Result>, SourceError> { - async fn do_load<'source>(builders: &'source Vec>>) -> Result>, SourceError> { - let mut v = Vec::with_capacity(builders.len()); - for cs in builders.iter() { - v.push(cs.load().await?); - } - Ok(v) - } - - let overwrites = do_load(&self.overwrites_builders); - let layers = do_load(&self.layers_builders); - let defaults = do_load(&self.defaults_builders); - - let (mut overwrites, mut layers, mut defaults) = - futures::try_join!(overwrites, layers, defaults)?; - - let mut v = Vec::with_capacity({ - self.layers_builders.len() - + self.defaults_builders.len() - + self.overwrites_builders.len() - }); - - v.append(&mut overwrites); - v.append(&mut layers); - v.append(&mut defaults); - - Ok(v) - } -} diff --git a/src/config/config.rs b/src/config/config.rs index 568b3faa..ca1f93b2 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -1,79 +1,28 @@ -use std::sync::RwLock; - use crate::accessor::Accessor; use crate::accessor::ParsableAccessor; -#[cfg(feature = "async")] -use crate::config::AsyncConfigBuilder; use crate::config::ConfigBuilder; use crate::config::ConfigError; use crate::element::ConfigElement; use crate::object::ConfigObject; #[derive(Debug)] -pub struct Config<'source> { - builder: Builder<'source>, - layers: RwLock>>>, -} - -#[derive(Debug)] -enum Builder<'source> { - Sync(ConfigBuilder<'source>), - - #[cfg(feature = "async")] - Async(AsyncConfigBuilder<'source>), +pub struct Config { + layers: Vec, } -impl<'source> Config<'source> { - pub fn builder() -> ConfigBuilder<'source> { +impl Config { + pub fn builder() -> ConfigBuilder { ConfigBuilder::new() } - pub(super) fn build_from_builder(builder: ConfigBuilder<'source>) -> Result { - let config = Config { - layers: RwLock::new(None), - builder: Builder::Sync(builder), - }; - - { - let mut layers = config.layers.write().unwrap(); - #[allow(irrefutable_let_patterns)] - if let Builder::Sync(builder) = &config.builder { - *layers = Some(builder.reload()?); - } else { - unreachable!() - } - } - - Ok(config) - } - - #[cfg(feature = "async")] - pub(super) async fn build_from_async_builder( - builder: AsyncConfigBuilder<'source>, - ) -> Result, ConfigError> { + pub(super) fn build_from_builder(builder: &ConfigBuilder) -> Result { let config = Config { - layers: RwLock::new(None), - builder: Builder::Async(builder), + layers: builder.reload()?, }; - { - let l = match config.builder { - Builder::Sync(ref builder) => builder.reload()?, - Builder::Async(ref builder) => builder.reload().await?, - }; - - let mut layers = config.layers.write().unwrap(); - *layers = Some(l); - } - Ok(config) } - #[cfg(feature = "async")] - pub fn async_builder() -> AsyncConfigBuilder<'source> { - AsyncConfigBuilder::new() - } - /// Access the configuration at a specific position /// /// Use an object of a type implementing the `ParsableAccessor` trait for accessing the @@ -99,7 +48,7 @@ impl<'source> Config<'source> { /// // ... /// # ; /// ``` - pub fn get(&self, accessor: A) -> Result>, ConfigError> + pub fn get(&self, accessor: A) -> Result, ConfigError> where A: ParsableAccessor, { @@ -113,14 +62,8 @@ impl<'source> Config<'source> { pub fn get_with_accessor( &self, mut accessor: Accessor, - ) -> Result>, ConfigError> { - let layers = self - .layers - .read() - .map_err(|_| ConfigError::InternalRwLockPoisioned)? - .ok_or_else(|| ConfigError::NotLoaded)?; - - for layer in layers.iter() { + ) -> Result, ConfigError> { + for layer in self.layers.iter() { if let Some(value) = layer.get(&mut accessor)? { return Ok(Some(value)); } diff --git a/src/config/mod.rs b/src/config/mod.rs index 678af1a7..a56d6cba 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -9,15 +9,15 @@ pub use crate::config::error::*; #[cfg(test)] mod tests { use super::*; - use crate::element::AsConfigElement; + use crate::element::IntoConfigElement; use crate::element::ConfigElement; #[test] fn test_compile_loading() { let _c = Config::builder() - .load(&crate::source::test_source::TestSource(|| ConfigElement::Null)) - .unwrap() - .build(); + .load(Box::new(crate::source::test_source::TestSource(ConfigElement::Null))) + .build() + .unwrap(); } #[test] @@ -26,12 +26,11 @@ mod tests { let json: serde_json::Value = serde_json::from_str(r#" { "key": "value" } "#).unwrap(); - let json = std::sync::Arc::new(json); let _c = Config::builder() - .load(&crate::source::test_source::TestSource(|| json.as_config_element().unwrap())) - .unwrap() - .build(); + .load(Box::new(crate::source::test_source::TestSource(json.into_config_element().unwrap()))) + .build() + .unwrap(); } #[test] @@ -40,21 +39,23 @@ mod tests { let json: serde_json::Value = serde_json::from_str(r#" { "key": "value" } "#).unwrap(); - let json = std::sync::Arc::new(json); - let source = crate::source::test_source::TestSource(|| json.as_config_element().unwrap()); + let source = crate::source::test_source::TestSource(json.into_config_element().unwrap()); let c = Config::builder() - .load(&source) - .unwrap() - .build(); + .load(Box::new(source)) + .build() + .unwrap(); let r = c.get("key"); assert!(r.is_ok()); let r = r.unwrap(); assert!(r.is_some()); let r = r.unwrap(); - assert!(std::matches!(r, ConfigElement::Str("value"))); + match r { + ConfigElement::Str(s) => assert_eq!(s, "value"), + _ => panic!(), + } } #[test] @@ -63,36 +64,39 @@ mod tests { let json1: serde_json::Value = serde_json::from_str(r#" { "key1": "value1" } "#).unwrap(); - let json1 = std::sync::Arc::new(json1); let json2: serde_json::Value = serde_json::from_str(r#" { "key1": "value2", "key2": "value3" } "#).unwrap(); - let json2 = std::sync::Arc::new(json2); - let source1 = crate::source::test_source::TestSource(|| json1.as_config_element().unwrap()); - let source2 = crate::source::test_source::TestSource(|| json2.as_config_element().unwrap()); + let source1 = crate::source::test_source::TestSource(json1.into_config_element().unwrap()); + let source2 = crate::source::test_source::TestSource(json2.into_config_element().unwrap()); let c = Config::builder() - .load(&source1) - .unwrap() - .load(&source2) - .unwrap() - .build(); + .load(Box::new(source1)) + .load(Box::new(source2)) + .build() + .unwrap(); let r = c.get("key1"); assert!(r.is_ok()); let r = r.unwrap(); assert!(r.is_some()); let r = r.unwrap(); - assert!(std::matches!(r, ConfigElement::Str("value1"))); + match r { + ConfigElement::Str(s) => assert_eq!(s, "value1"), + _ => panic!(), + } let r = c.get("key2"); assert!(r.is_ok()); let r = r.unwrap(); assert!(r.is_some()); let r = r.unwrap(); - assert!(std::matches!(r, ConfigElement::Str("value3"))); + match r { + ConfigElement::Str(s) => assert_eq!(s, "value3"), + _ => panic!(), + } } #[test] @@ -101,36 +105,39 @@ mod tests { let json: serde_json::Value = serde_json::from_str(r#" { "key1": "value1" } "#).unwrap(); - let json = std::sync::Arc::new(json); let toml: toml::Value = toml::from_str(r#" key1 = "value2" key2 = "value3" "#).unwrap(); - let toml = std::sync::Arc::new(toml); - let source1 = crate::source::test_source::TestSource(|| json.as_config_element().unwrap()); - let source2 = crate::source::test_source::TestSource(|| toml.as_config_element().unwrap()); + let source1 = crate::source::test_source::TestSource(json.into_config_element().unwrap()); + let source2 = crate::source::test_source::TestSource(toml.into_config_element().unwrap()); let c = Config::builder() - .load(&source1) - .unwrap() - .load(&source2) - .unwrap() - .build(); + .load(Box::new(source1)) + .load(Box::new(source2)) + .build() + .unwrap(); let r = c.get("key1"); assert!(r.is_ok()); let r = r.unwrap(); assert!(r.is_some()); let r = r.unwrap(); - assert!(std::matches!(r, ConfigElement::Str("value1"))); + match r { + ConfigElement::Str(s) => assert_eq!(s, "value1"), + _ => panic!(), + } let r = c.get("key2"); assert!(r.is_ok()); let r = r.unwrap(); assert!(r.is_some()); let r = r.unwrap(); - assert!(std::matches!(r, ConfigElement::Str("value3"))); + match r { + ConfigElement::Str(s) => assert_eq!(s, "value3"), + _ => panic!(), + } } } diff --git a/src/element/json.rs b/src/element/json.rs index 2655741a..1072c450 100644 --- a/src/element/json.rs +++ b/src/element/json.rs @@ -1,34 +1,34 @@ use std::collections::HashMap; -use crate::element::AsConfigElement; +use crate::element::IntoConfigElement; use crate::element::ConfigElement; #[derive(Debug, thiserror::Error)] pub enum JsonIntoConfigElementError {} -impl<'source> AsConfigElement<'source> for serde_json::Value { +impl IntoConfigElement for serde_json::Value { type Error = JsonIntoConfigElementError; - fn as_config_element(&'source self) -> Result, Self::Error> { + fn into_config_element(self) -> Result { match self { serde_json::Value::Null => Ok(ConfigElement::Null), - serde_json::Value::Bool(b) => Ok(ConfigElement::Bool(*b)), + serde_json::Value::Bool(b) => Ok(ConfigElement::Bool(b)), serde_json::Value::Number(num) => num .as_i64() .map(ConfigElement::I64) .or_else(|| num.as_u64().map(ConfigElement::U64)) .or_else(|| num.as_f64().map(ConfigElement::F64)) .ok_or_else(|| unimplemented!()), - serde_json::Value::String(s) => Ok(ConfigElement::Str(&s)), + serde_json::Value::String(s) => Ok(ConfigElement::Str(s)), serde_json::Value::Array(vec) => vec - .iter() - .map(serde_json::Value::as_config_element) + .into_iter() + .map(|v| v.into_config_element()) .collect::, Self::Error>>() .map(ConfigElement::List), serde_json::Value::Object(obj) => obj - .iter() - .map(|(k, v)| v.as_config_element().map(|v| (k.as_ref(), v))) - .collect::>, JsonIntoConfigElementError>>() + .into_iter() + .map(|(k, v)| v.into_config_element().map(|v| (k.to_string(), v))) + .collect::, JsonIntoConfigElementError>>() .map(|map| ConfigElement::Map(map)), } } @@ -47,7 +47,7 @@ mod tests { let e: ConfigElement = serde_json::from_str(s).unwrap(); match e { ConfigElement::Map(map) => { - assert_eq!(*map.get("key").unwrap(), ConfigElement::Str("value")); + assert_eq!(*map.get("key").unwrap(), ConfigElement::Str("value".to_string())); } _ => panic!("Not a map"), } diff --git a/src/element/mod.rs b/src/element/mod.rs index 056758a4..1711ce18 100644 --- a/src/element/mod.rs +++ b/src/element/mod.rs @@ -4,7 +4,7 @@ use crate::{accessor::{AccessType, Accessor}, object::ConfigObjectAccessError}; #[derive(Clone, Debug, PartialEq, serde::Deserialize)] #[serde(untagged)] -pub enum ConfigElement<'a> { +pub enum ConfigElement { Null, Bool(bool), I8(i8), @@ -17,13 +17,13 @@ pub enum ConfigElement<'a> { U64(u64), F32(f32), F64(f64), - Str(&'a str), - List(Vec>), - Map(HashMap<&'a str, ConfigElement<'a>>), + Str(String), + List(Vec), + Map(HashMap), } -impl<'a> ConfigElement<'a> { - pub(crate) fn get(&self, accessor: &mut Accessor) -> Result>, ConfigObjectAccessError> { +impl ConfigElement { + pub(crate) fn get(&self, accessor: &mut Accessor) -> Result, ConfigObjectAccessError> { match (accessor.current(), &self) { (Some(AccessType::Key(k)), ConfigElement::Null) => { Err(ConfigObjectAccessError::AccessWithKeyOnNull(k.to_string())) @@ -140,10 +140,10 @@ impl<'a> ConfigElement<'a> { } } -pub trait AsConfigElement<'source> { +pub trait IntoConfigElement { type Error: std::error::Error; - fn as_config_element(&'source self) -> Result, Self::Error>; + fn into_config_element(self) -> Result; } #[cfg(feature = "json")] @@ -158,7 +158,7 @@ mod tests { #[cfg(feature = "toml")] fn test_nested_toml_config() { use crate::Config; - use crate::element::AsConfigElement; + use crate::element::IntoConfigElement; use crate::element::ConfigElement; let toml: toml::Value = toml::from_str(r#" @@ -167,35 +167,40 @@ mod tests { [table] key2 = "value3" "#).unwrap(); - let toml = std::sync::Arc::new(toml); - let source = crate::source::test_source::TestSource(|| toml.as_config_element().unwrap()); + let source = crate::source::test_source::TestSource(toml.into_config_element().unwrap()); let c = Config::builder() - .load(&source) - .unwrap() - .build(); + .load(Box::new(source)) + .build() + .unwrap(); let r = c.get("key1"); assert!(r.is_ok()); let r = r.unwrap(); assert!(r.is_some()); let r = r.unwrap(); - assert!(std::matches!(r, ConfigElement::Str("value2"))); + match r { + ConfigElement::Str(s) => assert_eq!(s, "value2"), + _ => panic!(), + } let r = c.get("table.key2"); assert!(r.is_ok()); let r = r.unwrap(); assert!(r.is_some()); let r = r.unwrap(); - assert!(std::matches!(r, ConfigElement::Str("value3")), "{:?} != value3", r); + match r { + ConfigElement::Str(s) => assert_eq!(s, "value3"), + _ => panic!(), + } } #[test] #[cfg(feature = "toml")] fn test_nested_toml_config_with_index() { use crate::Config; - use crate::element::AsConfigElement; + use crate::element::IntoConfigElement; use crate::element::ConfigElement; let toml: toml::Value = toml::from_str(r#" @@ -205,20 +210,22 @@ mod tests { [[key]] k = "b" "#).unwrap(); - let toml = std::sync::Arc::new(toml); - let source = crate::source::test_source::TestSource(|| toml.as_config_element().unwrap()); + let source = crate::source::test_source::TestSource(toml.into_config_element().unwrap()); let c = Config::builder() - .load(&source) - .unwrap() - .build(); + .load(Box::new(source)) + .build() + .unwrap(); let r = c.get("key.0.k"); assert!(r.is_ok()); let r = r.unwrap(); assert!(r.is_some()); let r = r.unwrap(); - assert!(std::matches!(r, ConfigElement::Str("a")), "{:?} != a", r); + match r { + ConfigElement::Str(s) => assert_eq!(s, "a"), + _ => panic!(), + } } } diff --git a/src/element/toml.rs b/src/element/toml.rs index ca33381d..e80d8bf1 100644 --- a/src/element/toml.rs +++ b/src/element/toml.rs @@ -1,31 +1,31 @@ use std::collections::HashMap; -use crate::element::AsConfigElement; +use crate::element::IntoConfigElement; use crate::element::ConfigElement; #[derive(Debug, thiserror::Error)] pub enum TomlIntoConfigElementError {} -impl<'source> AsConfigElement<'source> for toml::Value { +impl IntoConfigElement for toml::Value { type Error = TomlIntoConfigElementError; - fn as_config_element(&'source self) -> Result, Self::Error> { + fn into_config_element(self) -> Result { match self { - toml::Value::String(s) => Ok(ConfigElement::Str(&s)), - toml::Value::Integer(i) => Ok(ConfigElement::I64(*i)), - toml::Value::Float(f) => Ok(ConfigElement::F64(*f)), - toml::Value::Boolean(b) => Ok(ConfigElement::Bool(*b)), + toml::Value::String(s) => Ok(ConfigElement::Str(s)), + toml::Value::Integer(i) => Ok(ConfigElement::I64(i)), + toml::Value::Float(f) => Ok(ConfigElement::F64(f)), + toml::Value::Boolean(b) => Ok(ConfigElement::Bool(b)), toml::Value::Datetime(_) => unimplemented!(), // TODO toml::Value::Array(ary) => { ary.into_iter() - .map(toml::Value::as_config_element) + .map(|e| e.into_config_element()) .collect::, Self::Error>>() .map(ConfigElement::List) }, toml::Value::Table(table) => { table.into_iter() - .map(|(k, v)| v.as_config_element().map(|v| (k.as_ref(), v))) - .collect::>, Self::Error>>() + .map(|(k, v)| v.into_config_element().map(|v| (k.to_string(), v))) + .collect::, Self::Error>>() .map(ConfigElement::Map) } @@ -46,7 +46,7 @@ mod tests { let e: ConfigElement = toml::from_str(s).unwrap(); match e { ConfigElement::Map(map) => { - assert_eq!(*map.get("key").unwrap(), ConfigElement::Str("value")); + assert_eq!(*map.get("key").unwrap(), ConfigElement::Str("value".to_string())); } _ => panic!("Not a map"), } diff --git a/src/object/mod.rs b/src/object/mod.rs index ac8c12e8..ef597793 100644 --- a/src/object/mod.rs +++ b/src/object/mod.rs @@ -3,20 +3,20 @@ use crate::description::ConfigSourceDescription; use crate::element::ConfigElement; #[derive(Clone, Debug)] -pub struct ConfigObject<'a> { - element: ConfigElement<'a>, +pub struct ConfigObject { + element: ConfigElement, source: ConfigSourceDescription, } -impl<'a> ConfigObject<'a> { - pub(crate) fn new(element: ConfigElement<'a>, source: ConfigSourceDescription) -> Self { +impl ConfigObject { + pub(crate) fn new(element: ConfigElement, source: ConfigSourceDescription) -> Self { Self { element, source } } pub(crate) fn get( &self, accessor: &mut Accessor, - ) -> Result>, ConfigObjectAccessError> { + ) -> Result, ConfigObjectAccessError> { self.element.get(accessor) } } diff --git a/src/source/format.rs b/src/source/format.rs index b3d9b466..6ed26366 100644 --- a/src/source/format.rs +++ b/src/source/format.rs @@ -1,11 +1,11 @@ -use crate::element::AsConfigElement; +use crate::element::IntoConfigElement; use super::SourceError; -pub trait FormatParser<'source>: std::fmt::Debug { - type Output: AsConfigElement<'source> + std::fmt::Debug + Sized; +pub trait FormatParser: std::fmt::Debug { + type Output: IntoConfigElement + std::fmt::Debug + Sized; - fn parse(buffer: &'source str) -> Result; + fn parse(buffer: &str) -> Result; } #[cfg(feature = "json")] @@ -13,10 +13,10 @@ pub trait FormatParser<'source>: std::fmt::Debug { pub struct JsonFormatParser; #[cfg(feature = "json")] -impl<'source> FormatParser<'source> for JsonFormatParser { +impl FormatParser for JsonFormatParser { type Output = serde_json::Value; - fn parse(buffer: &'source str) -> Result { + fn parse(buffer: &str) -> Result { serde_json::from_str(buffer).map_err(SourceError::JsonParserError) } } @@ -27,10 +27,10 @@ impl<'source> FormatParser<'source> for JsonFormatParser { pub struct TomlFormatParser; #[cfg(feature = "toml")] -impl<'source> FormatParser<'source> for TomlFormatParser { +impl FormatParser for TomlFormatParser { type Output = toml::Value; - fn parse(buffer: &'source str) -> Result { + fn parse(buffer: &str) -> Result { toml::from_str(buffer).map_err(SourceError::TomlParserError) } } diff --git a/src/source/mod.rs b/src/source/mod.rs index c34c281a..4410ef12 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -7,14 +7,8 @@ pub use crate::source::string::StringSource; pub use crate::source::format::FormatParser; pub use crate::source::format::JsonFormatParser; -pub trait ConfigSource<'source>: std::fmt::Debug { - fn load(&'source self) -> Result, SourceError>; -} - -#[cfg(feature = "async")] -#[async_trait::async_trait] -pub trait AsyncConfigSource<'source>: std::fmt::Debug { - async fn load(&'source self) -> Result, SourceError>; +pub trait ConfigSource: std::fmt::Debug { + fn load(&self) -> Result; } #[derive(Debug, thiserror::Error)] @@ -46,24 +40,14 @@ pub(crate) mod test_source { use crate::element::ConfigElement; use crate::description::ConfigSourceDescription; - pub(crate) struct TestSource<'a, G>(pub(crate) G) - where G: Fn() -> ConfigElement<'a>; - - impl<'g, G> std::fmt::Debug for TestSource<'g, G> - where G: Fn() -> ConfigElement<'g> - { - fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Ok(()) - } - } + use super::SourceError; - impl<'g, G> ConfigSource for TestSource<'g, G> - where G: Fn() -> ConfigElement<'g> - { - type Error = std::convert::Infallible; // can never happen + #[derive(Debug)] + pub(crate) struct TestSource(pub(crate) ConfigElement); - fn load<'a>(&'a self) -> Result, Self::Error> { - Ok(ConfigObject::new(self.0(), ConfigSourceDescription::Unknown)) + impl ConfigSource for TestSource { + fn load(&self) -> Result { + Ok(ConfigObject::new(self.0.clone(), ConfigSourceDescription::Unknown)) } } } diff --git a/src/source/string.rs b/src/source/string.rs index 3e095366..87437a5b 100644 --- a/src/source/string.rs +++ b/src/source/string.rs @@ -1,19 +1,19 @@ use crate::ConfigSource; use crate::description::ConfigSourceDescription; -use crate::element::AsConfigElement; +use crate::element::IntoConfigElement; use crate::object::ConfigObject; use crate::source::format::FormatParser; use super::SourceError; #[derive(Debug)] -pub struct StringSource<'source, P: FormatParser<'source> + std::fmt::Debug> { - source: &'source str, +pub struct StringSource { + source: String, _pd: std::marker::PhantomData

, } -impl<'source, P: FormatParser<'source>> StringSource<'source, P> { - pub fn new(source: &'source str) -> Result { +impl StringSource

{ + pub fn new(source: String) -> Result { Ok(StringSource { source, _pd: std::marker::PhantomData @@ -21,13 +21,13 @@ impl<'source, P: FormatParser<'source>> StringSource<'source, P> { } } -impl<'source, P> ConfigSource<'source> for StringSource<'source, P> - where P: FormatParser<'source> + std::fmt::Debug + 'source, - SourceError: From<<

>::Output as AsConfigElement<'source>>::Error> +impl

ConfigSource for StringSource

+ where P: FormatParser + std::fmt::Debug, + SourceError: From<<

::Output as IntoConfigElement>::Error> { - fn load(&'source self) -> Result, SourceError> { - let element = P::parse(self.source)?; - let element = element.as_config_element()?; + fn load(&self) -> Result { + let element = P::parse(&self.source)?; + let element = element.into_config_element()?; let desc = ConfigSourceDescription::Custom("String".to_string()); Ok(ConfigObject::new(element, desc)) @@ -43,7 +43,7 @@ mod test_source_impl { let source = "{}"; - let source = StringSource::::new(source).unwrap(); + let source = StringSource::::new(source.to_string()).unwrap(); let _object = source.load().unwrap(); } } From c45b2136fcc3c5f5fcd17a0a0d12c06efb691cd8 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 17 Sep 2022 15:32:47 +0200 Subject: [PATCH 17/18] cargo-fmt Signed-off-by: Matthias Beyer --- src/accessor.rs | 20 ++++++--------- src/config/builder.rs | 1 - src/config/mod.rs | 57 ++++++++++++++++++++++++++++--------------- src/description.rs | 1 - src/element/json.rs | 7 ++++-- src/element/mod.rs | 50 ++++++++++++++++++++----------------- src/element/toml.rs | 31 ++++++++++++----------- src/source/format.rs | 1 - src/source/mod.rs | 14 ++++++----- src/source/string.rs | 13 +++++----- 10 files changed, 108 insertions(+), 87 deletions(-) diff --git a/src/accessor.rs b/src/accessor.rs index 5e1f30f3..10cc8caa 100644 --- a/src/accessor.rs +++ b/src/accessor.rs @@ -8,12 +8,11 @@ impl ParsableAccessor for &str { // TODO: Make this non-trivial and bulletproof - let accessor = self.split('.') - .map(|s| { - match usize::from_str(s) { - Ok(u) => AccessType::Index(u), - Err(_) => AccessType::Key(s.to_string()) - } + let accessor = self + .split('.') + .map(|s| match usize::from_str(s) { + Ok(u) => AccessType::Index(u), + Err(_) => AccessType::Key(s.to_string()), }) .collect(); @@ -35,10 +34,7 @@ pub struct Accessor { impl Accessor { pub(crate) fn new(stack: Vec) -> Self { - Self { - stack, - index: 0 - } + Self { stack, index: 0 } } } @@ -58,6 +54,4 @@ impl Accessor { } #[derive(Debug, thiserror::Error)] -pub enum AccessorParseError { -} - +pub enum AccessorParseError {} diff --git a/src/config/builder.rs b/src/config/builder.rs index 86529bb1..ea5462c7 100644 --- a/src/config/builder.rs +++ b/src/config/builder.rs @@ -49,4 +49,3 @@ impl ConfigBuilder { .collect() } } - diff --git a/src/config/mod.rs b/src/config/mod.rs index a56d6cba..133a424e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -9,13 +9,15 @@ pub use crate::config::error::*; #[cfg(test)] mod tests { use super::*; - use crate::element::IntoConfigElement; use crate::element::ConfigElement; + use crate::element::IntoConfigElement; #[test] fn test_compile_loading() { let _c = Config::builder() - .load(Box::new(crate::source::test_source::TestSource(ConfigElement::Null))) + .load(Box::new(crate::source::test_source::TestSource( + ConfigElement::Null, + ))) .build() .unwrap(); } @@ -23,12 +25,17 @@ mod tests { #[test] #[cfg(feature = "json")] fn test_load_json() { - let json: serde_json::Value = serde_json::from_str(r#" + let json: serde_json::Value = serde_json::from_str( + r#" { "key": "value" } - "#).unwrap(); + "#, + ) + .unwrap(); let _c = Config::builder() - .load(Box::new(crate::source::test_source::TestSource(json.into_config_element().unwrap()))) + .load(Box::new(crate::source::test_source::TestSource( + json.into_config_element().unwrap(), + ))) .build() .unwrap(); } @@ -36,16 +43,16 @@ mod tests { #[test] #[cfg(feature = "json")] fn test_load_json_get_value() { - let json: serde_json::Value = serde_json::from_str(r#" + let json: serde_json::Value = serde_json::from_str( + r#" { "key": "value" } - "#).unwrap(); + "#, + ) + .unwrap(); let source = crate::source::test_source::TestSource(json.into_config_element().unwrap()); - let c = Config::builder() - .load(Box::new(source)) - .build() - .unwrap(); + let c = Config::builder().load(Box::new(source)).build().unwrap(); let r = c.get("key"); assert!(r.is_ok()); @@ -61,13 +68,19 @@ mod tests { #[test] #[cfg(feature = "json")] fn test_layered_json_config() { - let json1: serde_json::Value = serde_json::from_str(r#" + let json1: serde_json::Value = serde_json::from_str( + r#" { "key1": "value1" } - "#).unwrap(); + "#, + ) + .unwrap(); - let json2: serde_json::Value = serde_json::from_str(r#" + let json2: serde_json::Value = serde_json::from_str( + r#" { "key1": "value2", "key2": "value3" } - "#).unwrap(); + "#, + ) + .unwrap(); let source1 = crate::source::test_source::TestSource(json1.into_config_element().unwrap()); let source2 = crate::source::test_source::TestSource(json2.into_config_element().unwrap()); @@ -102,14 +115,20 @@ mod tests { #[test] #[cfg(all(feature = "json", feature = "toml"))] fn test_layered_json_toml_config() { - let json: serde_json::Value = serde_json::from_str(r#" + let json: serde_json::Value = serde_json::from_str( + r#" { "key1": "value1" } - "#).unwrap(); + "#, + ) + .unwrap(); - let toml: toml::Value = toml::from_str(r#" + let toml: toml::Value = toml::from_str( + r#" key1 = "value2" key2 = "value3" - "#).unwrap(); + "#, + ) + .unwrap(); let source1 = crate::source::test_source::TestSource(json.into_config_element().unwrap()); let source2 = crate::source::test_source::TestSource(toml.into_config_element().unwrap()); diff --git a/src/description.rs b/src/description.rs index 01311e75..0b2a8abd 100644 --- a/src/description.rs +++ b/src/description.rs @@ -8,4 +8,3 @@ pub enum ConfigSourceDescription { Uri(url::Url), Custom(String), } - diff --git a/src/element/json.rs b/src/element/json.rs index 1072c450..c2636eb0 100644 --- a/src/element/json.rs +++ b/src/element/json.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; -use crate::element::IntoConfigElement; use crate::element::ConfigElement; +use crate::element::IntoConfigElement; #[derive(Debug, thiserror::Error)] pub enum JsonIntoConfigElementError {} @@ -47,7 +47,10 @@ mod tests { let e: ConfigElement = serde_json::from_str(s).unwrap(); match e { ConfigElement::Map(map) => { - assert_eq!(*map.get("key").unwrap(), ConfigElement::Str("value".to_string())); + assert_eq!( + *map.get("key").unwrap(), + ConfigElement::Str("value".to_string()) + ); } _ => panic!("Not a map"), } diff --git a/src/element/mod.rs b/src/element/mod.rs index 1711ce18..2f13147c 100644 --- a/src/element/mod.rs +++ b/src/element/mod.rs @@ -1,6 +1,9 @@ use std::collections::HashMap; -use crate::{accessor::{AccessType, Accessor}, object::ConfigObjectAccessError}; +use crate::{ + accessor::{AccessType, Accessor}, + object::ConfigObjectAccessError, +}; #[derive(Clone, Debug, PartialEq, serde::Deserialize)] #[serde(untagged)] @@ -23,7 +26,10 @@ pub enum ConfigElement { } impl ConfigElement { - pub(crate) fn get(&self, accessor: &mut Accessor) -> Result, ConfigObjectAccessError> { + pub(crate) fn get( + &self, + accessor: &mut Accessor, + ) -> Result, ConfigObjectAccessError> { match (accessor.current(), &self) { (Some(AccessType::Key(k)), ConfigElement::Null) => { Err(ConfigObjectAccessError::AccessWithKeyOnNull(k.to_string())) @@ -71,14 +77,14 @@ impl ConfigElement { if let Some(value) = hm.get(k.as_str()) { accessor.advance(); if accessor.current().is_none() { - return Ok(Some(value)) + return Ok(Some(value)); } else { value.get(accessor) } } else { Ok(None) } - }, + } (Some(AccessType::Index(u)), ConfigElement::Null) => { Err(ConfigObjectAccessError::AccessWithIndexOnNull(*u)) @@ -123,14 +129,14 @@ impl ConfigElement { if let Some(value) = v.get(*u) { accessor.advance(); if accessor.current().is_none() { - return Ok(Some(value)) + return Ok(Some(value)); } else { value.get(accessor) } } else { Ok(None) } - }, + } (Some(AccessType::Index(u)), ConfigElement::Map(_)) => { Err(ConfigObjectAccessError::AccessWithIndexOnMap(*u)) } @@ -157,23 +163,23 @@ mod tests { #[test] #[cfg(feature = "toml")] fn test_nested_toml_config() { - use crate::Config; - use crate::element::IntoConfigElement; use crate::element::ConfigElement; + use crate::element::IntoConfigElement; + use crate::Config; - let toml: toml::Value = toml::from_str(r#" + let toml: toml::Value = toml::from_str( + r#" key1 = "value2" [table] key2 = "value3" - "#).unwrap(); + "#, + ) + .unwrap(); let source = crate::source::test_source::TestSource(toml.into_config_element().unwrap()); - let c = Config::builder() - .load(Box::new(source)) - .build() - .unwrap(); + let c = Config::builder().load(Box::new(source)).build().unwrap(); let r = c.get("key1"); assert!(r.is_ok()); @@ -199,24 +205,24 @@ mod tests { #[test] #[cfg(feature = "toml")] fn test_nested_toml_config_with_index() { - use crate::Config; - use crate::element::IntoConfigElement; use crate::element::ConfigElement; + use crate::element::IntoConfigElement; + use crate::Config; - let toml: toml::Value = toml::from_str(r#" + let toml: toml::Value = toml::from_str( + r#" [[key]] k = "a" [[key]] k = "b" - "#).unwrap(); + "#, + ) + .unwrap(); let source = crate::source::test_source::TestSource(toml.into_config_element().unwrap()); - let c = Config::builder() - .load(Box::new(source)) - .build() - .unwrap(); + let c = Config::builder().load(Box::new(source)).build().unwrap(); let r = c.get("key.0.k"); assert!(r.is_ok()); diff --git a/src/element/toml.rs b/src/element/toml.rs index e80d8bf1..663e4e1b 100644 --- a/src/element/toml.rs +++ b/src/element/toml.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; -use crate::element::IntoConfigElement; use crate::element::ConfigElement; +use crate::element::IntoConfigElement; #[derive(Debug, thiserror::Error)] pub enum TomlIntoConfigElementError {} @@ -16,19 +16,16 @@ impl IntoConfigElement for toml::Value { toml::Value::Float(f) => Ok(ConfigElement::F64(f)), toml::Value::Boolean(b) => Ok(ConfigElement::Bool(b)), toml::Value::Datetime(_) => unimplemented!(), // TODO - toml::Value::Array(ary) => { - ary.into_iter() - .map(|e| e.into_config_element()) - .collect::, Self::Error>>() - .map(ConfigElement::List) - }, - toml::Value::Table(table) => { - table.into_iter() - .map(|(k, v)| v.into_config_element().map(|v| (k.to_string(), v))) - .collect::, Self::Error>>() - .map(ConfigElement::Map) - } - + toml::Value::Array(ary) => ary + .into_iter() + .map(|e| e.into_config_element()) + .collect::, Self::Error>>() + .map(ConfigElement::List), + toml::Value::Table(table) => table + .into_iter() + .map(|(k, v)| v.into_config_element().map(|v| (k.to_string(), v))) + .collect::, Self::Error>>() + .map(ConfigElement::Map), } } } @@ -46,10 +43,12 @@ mod tests { let e: ConfigElement = toml::from_str(s).unwrap(); match e { ConfigElement::Map(map) => { - assert_eq!(*map.get("key").unwrap(), ConfigElement::Str("value".to_string())); + assert_eq!( + *map.get("key").unwrap(), + ConfigElement::Str("value".to_string()) + ); } _ => panic!("Not a map"), } } } - diff --git a/src/source/format.rs b/src/source/format.rs index 6ed26366..6486f806 100644 --- a/src/source/format.rs +++ b/src/source/format.rs @@ -21,7 +21,6 @@ impl FormatParser for JsonFormatParser { } } - #[cfg(feature = "toml")] #[derive(Debug)] pub struct TomlFormatParser; diff --git a/src/source/mod.rs b/src/source/mod.rs index 4410ef12..1bee2cc2 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -3,9 +3,9 @@ use crate::object::ConfigObject; mod format; mod string; -pub use crate::source::string::StringSource; pub use crate::source::format::FormatParser; pub use crate::source::format::JsonFormatParser; +pub use crate::source::string::StringSource; pub trait ConfigSource: std::fmt::Debug { fn load(&self) -> Result; @@ -35,10 +35,10 @@ pub enum SourceError { #[cfg(test)] pub(crate) mod test_source { - use crate::source::ConfigSource; - use crate::object::ConfigObject; - use crate::element::ConfigElement; use crate::description::ConfigSourceDescription; + use crate::element::ConfigElement; + use crate::object::ConfigObject; + use crate::source::ConfigSource; use super::SourceError; @@ -47,8 +47,10 @@ pub(crate) mod test_source { impl ConfigSource for TestSource { fn load(&self) -> Result { - Ok(ConfigObject::new(self.0.clone(), ConfigSourceDescription::Unknown)) + Ok(ConfigObject::new( + self.0.clone(), + ConfigSourceDescription::Unknown, + )) } } } - diff --git a/src/source/string.rs b/src/source/string.rs index 87437a5b..b4e7efdf 100644 --- a/src/source/string.rs +++ b/src/source/string.rs @@ -1,8 +1,8 @@ -use crate::ConfigSource; use crate::description::ConfigSourceDescription; use crate::element::IntoConfigElement; use crate::object::ConfigObject; use crate::source::format::FormatParser; +use crate::ConfigSource; use super::SourceError; @@ -16,14 +16,15 @@ impl StringSource

{ pub fn new(source: String) -> Result { Ok(StringSource { source, - _pd: std::marker::PhantomData + _pd: std::marker::PhantomData, }) } } impl

ConfigSource for StringSource

- where P: FormatParser + std::fmt::Debug, - SourceError: From<<

::Output as IntoConfigElement>::Error> +where + P: FormatParser + std::fmt::Debug, + SourceError: From<<

::Output as IntoConfigElement>::Error>, { fn load(&self) -> Result { let element = P::parse(&self.source)?; @@ -43,8 +44,8 @@ mod test_source_impl { let source = "{}"; - let source = StringSource::::new(source.to_string()).unwrap(); + let source = + StringSource::::new(source.to_string()).unwrap(); let _object = source.load().unwrap(); } } - From 42a2b2635fe445cbb5f0ce876c67bc975cf72f7f Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 17 Sep 2022 15:39:00 +0200 Subject: [PATCH 18/18] Rework visibility and export types that are required for working with the crate Signed-off-by: Matthias Beyer --- src/accessor.rs | 6 +++--- src/lib.rs | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/accessor.rs b/src/accessor.rs index 10cc8caa..4a6e23b9 100644 --- a/src/accessor.rs +++ b/src/accessor.rs @@ -33,12 +33,12 @@ pub struct Accessor { } impl Accessor { - pub(crate) fn new(stack: Vec) -> Self { + pub fn new(stack: Vec) -> Self { Self { stack, index: 0 } } } -pub(crate) enum AccessType { +pub enum AccessType { Key(String), Index(usize), } @@ -48,7 +48,7 @@ impl Accessor { self.stack.get(self.index) } - pub fn advance(&mut self) { + pub(crate) fn advance(&mut self) { self.index += 1; } } diff --git a/src/lib.rs b/src/lib.rs index 4ddb7a94..ae51325f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,13 @@ mod element; mod object; mod source; +pub use crate::accessor::Accessor; +pub use crate::accessor::AccessType; +pub use crate::accessor::ParsableAccessor; pub use crate::config::Config; +pub use crate::config::ConfigBuilder; +pub use crate::description::ConfigSourceDescription; +pub use crate::element::ConfigElement; +pub use crate::object::ConfigObject; pub use crate::source::ConfigSource; pub use crate::source::StringSource;