Skip to content

Commit

Permalink
add support for envs in arrays (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
FaveroFerreira authored Sep 17, 2023
1 parent d7d6205 commit 085be25
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# 1.2.0

- Add support for environment variables in `Toml` arrays

# 1.1.0

- Add support for optional default in `Toml` files
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "konfiguration"
version = "1.1.0"
version = "1.2.0"
rust-version = "1.66.1"
description = "Toml configuration loader with environment variables support."
documentation = "https://docs.rs/konfiguration/"
Expand Down
20 changes: 18 additions & 2 deletions src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,25 @@ impl TryFrom<Seq<'_, '_>> for ConfigurationEntry {
type Error = serde_untagged::de::Error;

fn try_from(value: Seq) -> Result<Self, Self::Error> {
let array: TomlValue = value.deserialize()?;
let vec: Vec<TomlValue> = value.deserialize()?;

Ok(ConfigurationEntry::Simple(array))
let mut entries = Vec::new();

for toml in vec {
if let TomlValue::Table(t) = toml {
let str = toml::to_string(&t).map_err(|e| de::Error::custom(e.to_string()))?;

let entry = ConfigurationEntry::Table(
toml::from_str(&str).map_err(|e| de::Error::custom(e.to_string()))?,
);

entries.push(entry);
} else {
entries.push(ConfigurationEntry::Simple(toml));
}
}

Ok(ConfigurationEntry::Vec(entries))
}
}

Expand Down
64 changes: 49 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fs;
use serde::Deserialize;
use toml::de::ValueDeserializer;

use crate::error::KonfigurationResult;
use crate::error::{KonfigurationError, KonfigurationResult};
use crate::value::{ConfigurationEntry, ConfigurationManifest, TomlMap, TomlValue};

mod de;
Expand Down Expand Up @@ -55,26 +55,58 @@ impl Konfiguration {
}
}

/// Takes a configuration manifest and simplifies it into a TOML value.
fn simplify(manifest: ConfigurationManifest) -> KonfigurationResult<TomlMap> {
let mut map = TomlMap::new();
impl TryFrom<ConfigurationEntry> for Option<TomlValue> {
type Error = KonfigurationError;

for (key, config_entry) in manifest {
let value = match config_entry {
ConfigurationEntry::Simple(value) => value,
ConfigurationEntry::Env(env_value) => {
env_sanity_check(&env_value);
expand_with_retry(env_value)?
fn try_from(value: ConfigurationEntry) -> Result<Self, Self::Error> {
match value {
ConfigurationEntry::Simple(toml) => Ok(Some(toml)),
ConfigurationEntry::Env(env) => {
let toml = expand_with_retry(env)?;
Ok(Some(toml))
}
ConfigurationEntry::UnsetEnv => Ok(None),
ConfigurationEntry::Vec(vec) => {
let mut entries = Vec::new();

for entry in vec {
match Self::try_from(entry)? {
Some(entry) => entries.push(entry),
None => continue,
}
}

Ok(Some(TomlValue::Array(entries)))
}
ConfigurationEntry::UnsetEnv => continue,
ConfigurationEntry::Table(table) => {
let simplified = simplify(table)?;
let mut map = TomlMap::new();

for (key, entry) in table {
match Self::try_from(entry)? {
Some(entry) => {
map.insert(key, entry);
}
None => continue,
}
}

TomlValue::Table(simplified)
Ok(Some(TomlValue::Table(map)))
}
};
}
}
}

/// Takes a configuration manifest and simplifies it into a TOML value.
fn simplify(manifest: ConfigurationManifest) -> KonfigurationResult<TomlMap> {
let mut map = TomlMap::new();

map.insert(key, value);
for (key, entry) in manifest {
match Option::<TomlValue>::try_from(entry)? {
Some(entry) => {
map.insert(key, entry);
}
None => continue,
}
}

Ok(map)
Expand All @@ -92,6 +124,8 @@ fn env_sanity_check(env: &str) {
/// This is ugly because toml sometimes fails to deserialize a simple string
/// I will be looking into this later.
fn expand_with_retry(value: String) -> KonfigurationResult<TomlValue> {
env_sanity_check(&value);

match TomlValue::deserialize(ValueDeserializer::new(&value)) {
Ok(v) => Ok(v),
Err(_) => Ok(TomlValue::String(value)),
Expand Down
1 change: 1 addition & 0 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ pub enum ConfigurationEntry {
Simple(TomlValue),
Env(String),
UnsetEnv,
Vec(Vec<ConfigurationEntry>),
Table(HashMap<String, ConfigurationEntry>),
}
16 changes: 16 additions & 0 deletions test_files/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ hash_with_default = { env = "HASH_WITH_DEFAULT", default = { d = 4, e = 5, f = 6
hash_with_no_default_env_unset = { env = "HASH_WITH_NO_DEFAULT_ENV_UNSET" }
hash_with_no_default_env_set = { env = "HASH_WITH_NO_DEFAULT_ENV_SET" }

[[list]]
s = "im a string"
i = 42
f = 42.42
b = true
array = [1, 2, 3]
hash = { a = 1, b = 2, c = 3 }

[[list]]
s_with_no_default = { env = "S_WITH_NO_DEFAULT" }
i = 42
f = 42.42
b = true
array = [1, 2, 3]
hash = { a = 1, b = 2, c = 3 }

[nested]
s = "im a nested string"
i = 42
Expand Down
32 changes: 32 additions & 0 deletions tests/parse_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,23 @@ pub struct ConfigFileRepresentation {
hash_with_no_default_env_unset: Option<HashMap<String, i32>>,
hash_with_no_default_env_set: Option<HashMap<String, i32>>,

list: Vec<List>,

nested: Nested,
nested_do_not_exist: Option<Nested>,
}

#[derive(Debug, serde::Deserialize, PartialEq)]
pub struct List {
s: Option<String>,
s_with_no_default: Option<String>,
i: i32,
f: f32,
b: bool,
array: Vec<i32>,
hash: HashMap<String, i32>,
}

#[derive(Debug, serde::Deserialize, PartialEq)]
pub struct Nested {
s: String,
Expand Down Expand Up @@ -168,6 +181,25 @@ fn can_parse_configs() {
"array with no default env set failed"
);

assert_eq!(config.list[0].s, Some("im a string".to_string()));
assert_eq!(config.list[0].s_with_no_default, None);
assert_eq!(config.list[0].i, 42);
assert_eq!(config.list[0].f, 42.42);
assert_eq!(config.list[0].b, true);
assert_eq!(config.list[0].array, vec![1, 2, 3]);
assert_eq!(config.list[0].hash, config.hash);

assert_eq!(config.list[1].s, None);
assert_eq!(
config.list[1].s_with_no_default,
Some("im a string".to_string())
);
assert_eq!(config.list[1].i, 42);
assert_eq!(config.list[1].f, 42.42);
assert_eq!(config.list[1].b, true);
assert_eq!(config.list[1].array, vec![1, 2, 3]);
assert_eq!(config.list[1].hash, config.hash);

let mut hash = HashMap::new();
hash.insert("a".to_string(), 1);
hash.insert("b".to_string(), 2);
Expand Down

0 comments on commit 085be25

Please sign in to comment.