#![deny(missing_docs)]
mod error;
mod value;
use std::{fs, path::Path};
use serde::de::DeserializeOwned;
use serde_json::{Map, Value};
use error::deserialize_value;
pub use error::{ConfigError, Result};
use value::{insert_path, merge_maps, parse_scalar, prefixed_key_start};
#[derive(Clone, Debug, Default)]
pub struct Config {
values: Map<String, Value>,
}
impl Config {
pub fn new() -> Self {
Self::default()
}
pub fn from_pairs<K, V>(pairs: impl IntoIterator<Item = (K, V)>) -> Self
where
K: Into<String>,
V: AsRef<str>,
{
let values = pairs
.into_iter()
.map(|(key, value)| (key.into(), parse_scalar(value.as_ref())))
.collect();
Self { values }
}
pub fn from_value(value: Value) -> Result<Self> {
match value {
Value::Object(values) => Ok(Self { values }),
_ => Err(ConfigError::RootNotObject),
}
}
pub fn from_json_str(source: &str) -> Result<Self> {
let value = serde_json::from_str(source).map_err(ConfigError::Parse)?;
Self::from_value(value)
}
pub fn from_json_file(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let label = path.display().to_string();
let source = fs::read_to_string(path).map_err(|source| ConfigError::ReadFile {
path: label.clone(),
source,
})?;
let value = serde_json::from_str(&source).map_err(|source| ConfigError::ParseFile {
path: label.clone(),
source,
})?;
match value {
Value::Object(values) => Ok(Self { values }),
_ => Err(ConfigError::FileRootNotObject { path: label }),
}
}
pub fn from_env_prefix(prefix: &str) -> Self {
Self::from_prefixed_vars(prefix, std::env::vars())
}
pub fn from_prefixed_vars<K, V>(prefix: &str, vars: impl IntoIterator<Item = (K, V)>) -> Self
where
K: AsRef<str>,
V: AsRef<str>,
{
let prefix = prefixed_key_start(prefix);
let mut config = Self::new();
for (key, value) in vars {
let Some(raw_key) = key.as_ref().strip_prefix(&prefix) else {
continue;
};
if raw_key.is_empty() {
continue;
}
let path = raw_key
.split("__")
.filter(|segment| !segment.is_empty())
.map(|segment| segment.to_ascii_lowercase())
.collect::<Vec<_>>();
if !path.is_empty() {
insert_path(&mut config.values, &path, parse_scalar(value.as_ref()));
}
}
config
}
pub fn insert_value(&mut self, key: impl Into<String>, value: Value) {
self.values.insert(key.into(), value);
}
pub fn get(&self, key: &str) -> Option<&Value> {
self.values.get(key)
}
pub fn get_typed<T>(&self, key: &str) -> Result<Option<T>>
where
T: DeserializeOwned,
{
self.get(key)
.cloned()
.map(|value| deserialize_value(key.to_owned(), value))
.transpose()
}
pub fn get_required_typed<T>(&self, key: &str) -> Result<T>
where
T: DeserializeOwned,
{
self.get_typed(key)?
.ok_or_else(|| ConfigError::MissingValue {
path: key.to_owned(),
})
}
pub fn get_path<I, S>(&self, path: I) -> Option<&Value>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let mut path = path.into_iter();
let first = path.next()?;
let mut value = self.values.get(first.as_ref())?;
for segment in path {
let segment = segment.as_ref();
value = match value {
Value::Object(object) => object.get(segment)?,
Value::Array(array) => array.get(segment.parse::<usize>().ok()?)?,
_ => return None,
};
}
Some(value)
}
pub fn get_path_typed<I, S, T>(&self, path: I) -> Result<Option<T>>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
T: DeserializeOwned,
{
let path = path
.into_iter()
.map(|segment| segment.as_ref().to_owned())
.collect::<Vec<_>>();
let label = path.join(".");
self.get_path(path.iter().map(String::as_str))
.cloned()
.map(|value| deserialize_value(label, value))
.transpose()
}
pub fn get_required_path_typed<I, S, T>(&self, path: I) -> Result<T>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
T: DeserializeOwned,
{
let path = path
.into_iter()
.map(|segment| segment.as_ref().to_owned())
.collect::<Vec<_>>();
let label = path.join(".");
self.get_path_typed(path.iter().map(String::as_str))?
.ok_or(ConfigError::MissingValue { path: label })
}
pub fn merge(mut self, other: Self) -> Self {
self.merge_from(other);
self
}
pub fn merge_from(&mut self, other: Self) {
merge_maps(&mut self.values, other.values);
}
pub fn deserialize<T>(&self) -> Result<T>
where
T: DeserializeOwned,
{
serde_json::from_value(Value::Object(self.values.clone())).map_err(ConfigError::Deserialize)
}
}