Skip to content

Commit

Permalink
Adding CustomFn layer (#34)
Browse files Browse the repository at this point in the history
* Deriving `Default` trait on a number of tests, so that `cargo test --all-features` test will pass

* Adding CustomFn layer, where any Fn type, (functions or immutable closures) can provide a values.  This is useful to support additional data sources, for example a database or an unsupported file format, e.g. hjson

* Converting from Fn to FnOnce, because the 'static lifetime bound requirement on the closure makes an Fn version no better than a static fn.
Fixing a wrong doc comment

* Feature-gating tests, depending on cfg, so `cargo test` should pass, regardless of which combination of features are enabled

* Adding mention of custom_fn feature in README
Synchronizing 3 copies of README.md in the repo
  • Loading branch information
luketpeterson authored Jun 26, 2023
1 parent 7ad38f3 commit 75b974a
Show file tree
Hide file tree
Showing 21 changed files with 160 additions and 53 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ For now it supports :

- Default settings (inside your codebase with `#[serde(default = ...)]` coming from [serde](https://serde.rs))
- Reading from `TOML`, `YAML`, `JSON`, `DHALL`, `INI` files
- Executing a custom function or closure to supply values via a [serde_json::Value]
- Reading from environment variables: it supports `HashMap` structure with `MY_VARIABLE="mykey=myvalue,mykey2=myvalue2"` and also array like `MY_VARIABLE=first,second` thanks to [envy](https://github.com/softprops/envy).
- All [serde](https://serde.rs) attributes can be used in your struct to customize your configuration as you wish
- Reading your configuration from your command line built with [clap](https://github.com/clap-rs/clap) (ATTENTION: if you're using version < v3 use the `twelf@0.1.8` version, if you're using `[email protected]` please use `[email protected]`)
- Reading your configuration from your command line built with [clap](https://github.com/clap-rs/clap) (ATTENTION: if you're using version < v3 use the `[email protected]` version)

# Usage

Expand All @@ -23,6 +24,7 @@ For now it supports :
use twelf::{config, Layer};
#[config]
#[derive(Default)]
struct Conf {
test: String,
another: usize,
Expand All @@ -49,7 +51,6 @@ struct Conf {
// Will generate global arguments for each of your fields inside your configuration struct
let app = clap::Command::new("test").args(&Conf::clap_args());
// If you're looking for how to use with clap derive feature, check in the examples directory (twelf/examples/clap_derive.rs)
// Init configuration with layers, each layers override only existing fields
let config = Conf::with_layers(&[
Expand All @@ -68,9 +69,13 @@ Check [here](./twelf/examples) for more examples.
Twelf supports crate features, if you only want support for `json`, `env` and `toml` then you just have to add this to your `Cargo.toml`

```toml
twelf = { version = "0.4", default-features = false, features = ["json", "toml", "env"] }
twelf = { version = "0.11", default-features = false, features = ["json", "toml", "env"] }
```

`default_trait` enables code for a layer that integrate fields derived with the [std::default::Default] trait.

`custom_fn` enables code for a layer that allows a custom closure to be executed.

Default features are `["env", "clap"]`

# Contributing
Expand Down
1 change: 1 addition & 0 deletions config-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@
toml = []
yaml = []
default_trait = []
custom_fn = []
10 changes: 8 additions & 2 deletions config-derive/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ For now it supports :

- Default settings (inside your codebase with `#[serde(default = ...)]` coming from [serde](https://serde.rs))
- Reading from `TOML`, `YAML`, `JSON`, `DHALL`, `INI` files
- Executing a custom function or closure to supply values via a [serde_json::Value]
- Reading from environment variables: it supports `HashMap` structure with `MY_VARIABLE="mykey=myvalue,mykey2=myvalue2"` and also array like `MY_VARIABLE=first,second` thanks to [envy](https://github.com/softprops/envy).
- All [serde](https://serde.rs) attributes can be used in your struct to customize your configuration as you wish
- Reading your configuration from your command line built with [clap](https://github.com/clap-rs/clap) (ATTENTION: if you're using version < v3 use the `twelf@1.8` version)
- Reading your configuration from your command line built with [clap](https://github.com/clap-rs/clap) (ATTENTION: if you're using version < v3 use the `twelf@0.8` version)

# Usage

Expand All @@ -23,6 +24,7 @@ For now it supports :
use twelf::{config, Layer};
#[config]
#[derive(Default)]
struct Conf {
test: String,
another: usize,
Expand Down Expand Up @@ -67,9 +69,13 @@ Check [here](./twelf/examples) for more examples.
Twelf supports crate features, if you only want support for `json`, `env` and `toml` then you just have to add this to your `Cargo.toml`

```toml
twelf = { version = "0.3", default-features = false, features = ["json", "toml", "env"] }
twelf = { version = "0.11", default-features = false, features = ["json", "toml", "env"] }
```

`default_trait` enables code for a layer that integrate fields derived with the [std::default::Default] trait.

`custom_fn` enables code for a layer that allows a custom closure to be executed.

Default features are `["env", "clap"]`

# Contributing
Expand Down
14 changes: 12 additions & 2 deletions config-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ pub fn config(_attrs: TokenStream, item: TokenStream) -> TokenStream {
let yaml_branch = build_yaml_branch();
let dhall_branch = build_dhall_branch();
let default_trait_branch = build_default_trait_branch();
let custom_fn_branch = build_custom_fn_branch();

#[cfg(not(feature = "ini"))]
let ini_branch = quote! {};
Expand All @@ -141,9 +142,9 @@ pub fn config(_attrs: TokenStream, item: TokenStream) -> TokenStream {
docs,
);

#[cfg(not(feature = "default_trait"))]
#[cfg(all(not(feature = "default_trait"), not(feature = "custom_fn")))]
let derive_serialize = quote! {};
#[cfg(feature = "default_trait")]
#[cfg(any(feature = "default_trait", feature = "custom_fn"))]
let derive_serialize = quote! { #[derive(::twelf::reexports::serde::Serialize)] };

let code = quote! {
Expand Down Expand Up @@ -192,6 +193,7 @@ pub fn config(_attrs: TokenStream, item: TokenStream) -> TokenStream {
#ini_branch
#clap_branch
#default_trait_branch
#custom_fn_branch
other => unimplemented!("{:?}", other)
};

Expand Down Expand Up @@ -327,3 +329,11 @@ fn build_default_trait_branch() -> proc_macro2::TokenStream {
let default_trait_branch = quote! {};
default_trait_branch
}

fn build_custom_fn_branch() -> proc_macro2::TokenStream {
#[cfg(feature = "custom_fn")]
let custom_branch = quote! { ::twelf::Layer::CustomFn(custom_fn) => (custom_fn.clone().0(),None), };
#[cfg(not(feature = "custom_fn"))]
let custom_branch = quote! {};
custom_branch
}
2 changes: 2 additions & 0 deletions twelf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
serde_yaml = { version = "0.8.23", optional = true }
thiserror = "1"
toml_rs = { version = "0.5.8", package = "toml", optional = true }
dyn-clone = { version = "1.0.0", optional = true }

[features]
clap = ["clap_rs", "config-derive/clap", "envy"]
Expand All @@ -34,3 +35,4 @@
toml = ["toml_rs", "config-derive/toml"]
yaml = ["serde_yaml", "config-derive/yaml"]
default_trait = ["config-derive/default_trait"]
custom_fn = ["dyn-clone", "config-derive/custom_fn"]
8 changes: 7 additions & 1 deletion twelf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ For now it supports :

- Default settings (inside your codebase with `#[serde(default = ...)]` coming from [serde](https://serde.rs))
- Reading from `TOML`, `YAML`, `JSON`, `DHALL`, `INI` files
- Executing a custom function or closure to supply values via a [serde_json::Value]
- Reading from environment variables: it supports `HashMap` structure with `MY_VARIABLE="mykey=myvalue,mykey2=myvalue2"` and also array like `MY_VARIABLE=first,second` thanks to [envy](https://github.com/softprops/envy).
- All [serde](https://serde.rs) attributes can be used in your struct to customize your configuration as you wish
- Reading your configuration from your command line built with [clap](https://github.com/clap-rs/clap) (ATTENTION: if you're using version < v3 use the `[email protected]` version)
Expand All @@ -23,6 +24,7 @@ For now it supports :
use twelf::{config, Layer};
#[config]
#[derive(Default)]
struct Conf {
test: String,
another: usize,
Expand Down Expand Up @@ -67,9 +69,13 @@ Check [here](./twelf/examples) for more examples.
Twelf supports crate features, if you only want support for `json`, `env` and `toml` then you just have to add this to your `Cargo.toml`

```toml
twelf = { version = "0.3", default-features = false, features = ["json", "toml", "env"] }
twelf = { version = "0.11", default-features = false, features = ["json", "toml", "env"] }
```

`default_trait` enables code for a layer that integrate fields derived with the [std::default::Default] trait.

`custom_fn` enables code for a layer that allows a custom closure to be executed.

Default features are `["env", "clap"]`

# Contributing
Expand Down
4 changes: 2 additions & 2 deletions twelf/examples/advanced_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ use twelf::reexports::serde::{Deserialize, Serialize};
use twelf::{config, Layer};

#[config]
#[derive(Debug)]
#[derive(Debug, Default)]
struct Config {
list: Vec<String>,
labels: HashMap<String, String>,
#[serde(flatten)]
nested: Nested,
}

#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Default, Deserialize, Serialize)]
struct Nested {
inner: String,
}
Expand Down
2 changes: 1 addition & 1 deletion twelf/examples/clap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use clap_rs as clap;
use twelf::{config, Layer};

#[config]
#[derive(Debug)]
#[derive(Debug, Default)]
struct Config {
/// Documentation inside clap, to specifiy db_host
db_host: String,
Expand Down
2 changes: 1 addition & 1 deletion twelf/examples/clap_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use clap_rs as clap;
use twelf::{config, Layer};

#[config]
#[derive(Parser, Debug)]
#[derive(Parser, Debug, Default)]
#[clap(author, version, about, long_about = None)]
struct Config {
#[clap(long, help = "Documentation inside clap, to specifiy db_host")]
Expand Down
2 changes: 1 addition & 1 deletion twelf/examples/default_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use twelf::{config, Layer};

#[config]
#[derive(Debug)]
#[derive(Debug, Default)]
struct Config {
db_host: String,
threads: usize,
Expand Down
2 changes: 1 addition & 1 deletion twelf/examples/simple_env_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use twelf::{config, Layer};

#[config]
#[derive(Debug)]
#[derive(Debug, Default)]
struct Config {
db_host: String,
threads: usize,
Expand Down
32 changes: 32 additions & 0 deletions twelf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,36 @@ pub enum Layer {
/// Default layer, using std::default::Default trait
#[cfg(feature = "default_trait")]
DefaultTrait,
/// Custom layer, takes any function or closure that outputs a [serde_json::Value].
#[cfg(feature = "custom_fn")]
CustomFn(custom_fn::CustomFn),
}

#[cfg(feature = "custom_fn")]
pub mod custom_fn {
use dyn_clone::{DynClone, clone_box};

pub struct CustomFn(pub Box<dyn CustomFnTrait>);

impl<T> From<T> for CustomFn where T: FnOnce() -> crate::reexports::serde_json::Value + Clone + 'static {
fn from(func: T) -> Self {
CustomFn(Box::new(func) as Box<dyn CustomFnTrait>)
}
}

pub trait CustomFnTrait: FnOnce() -> crate::reexports::serde_json::Value + DynClone {}

impl<T> CustomFnTrait for T where T: Clone + FnOnce() -> crate::reexports::serde_json::Value {}

impl Clone for CustomFn {
fn clone(&self) -> Self {
CustomFn(clone_box(&*self.0) as Box<dyn CustomFnTrait>)
}
}

impl core::fmt::Debug for CustomFn {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "CustomFn")
}
}
}
6 changes: 4 additions & 2 deletions twelf/tests/clap.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![cfg(feature = "clap")]

use std::collections::HashMap;

use clap_rs as clap;
Expand All @@ -10,7 +12,7 @@ const JSON_TEST_FILE: &str = "./tests/fixtures/test.json";
#[test]
fn clap_with_array_and_hashmap() {
#[config]
#[derive(Debug)]
#[derive(Debug, Default)]
struct Conf {
/// My custom documentation on elements-snake in clap
elements_snake: HashMap<String, String>,
Expand Down Expand Up @@ -50,7 +52,7 @@ fn clap_with_array_and_hashmap() {
#[test]
fn clap_derive_array() {
#[config]
#[derive(Parser,Debug)]
#[derive(Parser,Debug, Default)]
#[clap(author, version, about, long_about = None)]
struct Conf {
#[clap(long, default_value_t = 55)]
Expand Down
32 changes: 32 additions & 0 deletions twelf/tests/custom_fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#![cfg(feature = "custom_fn")]

use config_derive::config;
use twelf::Layer;

use serde_json::value::to_value;

#[test]
fn simple_custom_fn() {
#[config]
#[derive(Debug, Default)]
struct TestCfg {
test: String,
another: usize,
}

std::env::set_var("ANOTHER", "5");

let func = || {
to_value(TestCfg{test: "from func".to_string(), another: 25}).unwrap()
};

let prio = vec![Layer::CustomFn(func.into()), Layer::Env(None)];
let config = TestCfg::with_layers(&prio).unwrap();
assert_eq!(config.test, String::from("from func"));
assert_eq!(config.another, 5usize);

let prio = vec![Layer::Env(None), Layer::CustomFn(func.into())];
let config = TestCfg::with_layers(&prio).unwrap();
assert_eq!(config.test, String::from("from func"));
assert_eq!(config.another, 25usize);
}
2 changes: 1 addition & 1 deletion twelf/tests/default.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![cfg(features = "default_trait")]
#![cfg(feature = "default_trait")]
use config_derive::config;
use twelf::Layer;

Expand Down
12 changes: 7 additions & 5 deletions twelf/tests/dhall.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![cfg(feature = "dhall")]

use std::collections::HashMap;

use config_derive::config;
Expand All @@ -12,7 +14,7 @@ const DHALL_TEST_FILE: &str = "./tests/fixtures/test.dhall";
#[test]
fn dhall_simple_types() {
#[config]
#[derive(Debug)]
#[derive(Debug, Default)]
struct TestCfg {
test: String,
another: usize,
Expand All @@ -32,7 +34,7 @@ fn dhall_simple_types() {
#[test]
fn dhall_simple_with_prefix() {
#[config]
#[derive(Debug)]
#[derive(Debug, Default)]
struct Conf {
elements_def: HashMap<String, String>,
#[serde(default = "default_array")]
Expand All @@ -57,7 +59,7 @@ fn dhall_simple_with_prefix() {
#[test]
fn dhall_simple_with_option() {
#[config]
#[derive(Debug)]
#[derive(Debug, Default)]
struct Conf {
elements_def: Option<HashMap<String, String>>,
array_def: Option<Vec<String>>,
Expand All @@ -80,7 +82,7 @@ fn dhall_simple_with_option() {
#[test]
fn dhall_with_array_and_hashmap_string() {
#[config]
#[derive(Debug)]
#[derive(Debug, Default)]
struct Conf {
elements: HashMap<String, String>,
#[serde(default = "default_array")]
Expand Down Expand Up @@ -113,7 +115,7 @@ fn dhall_with_array_and_hashmap_string() {
#[test]
fn dhall_with_array_and_hashmap_with_default() {
#[config]
#[derive(Debug)]
#[derive(Debug, Default)]
struct Conf {
elements_def: HashMap<String, String>,
#[serde(default = "default_array")]
Expand Down
Loading

0 comments on commit 75b974a

Please sign in to comment.