#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(
anonymous_parameters,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
nonstandard_style,
rust_2018_idioms,
single_use_lifetimes,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unused_extern_crates,
unused_qualifications,
variant_size_differences
)]
#[cfg(feature = "enable_log")]
use log::*;
use std::collections::HashSet;
use std::hash::BuildHasher;
use std::hash::Hash;
#[cfg(test)]
#[macro_use(quickcheck)]
extern crate quickcheck_macros;
#[cfg(feature = "enable_derive")]
#[cfg_attr(docsrs, doc(cfg(feature = "enable_derive")))]
mod derive;
#[cfg(feature = "enable_derive")]
#[cfg_attr(docsrs, doc(cfg(feature = "enable_derive")))]
pub use crate::derive::{AutoDeriveFromEnvironment, DefaultSourceFromEnvironment};
#[cfg(feature = "enable_derive")]
#[cfg_attr(docsrs, doc(cfg(feature = "enable_derive")))]
pub use salak_derive::FromEnvironment;
mod err;
mod utils;
pub use crate::err::PropertyError;
pub use crate::utils::SalakStringUtil;
mod env;
pub(crate) use crate::env::factory::FactoryRegistry;
pub use crate::env::{
factory::{FacRef, Factory, FactoryScope, FromFactory},
placeholder::PlaceholderResolver,
registry::SourceRegistry,
salak::{Salak, SalakBuilder},
};
mod source;
#[cfg(feature = "enable_toml")]
#[cfg_attr(docsrs, doc(cfg(feature = "enable_toml")))]
pub use crate::source::toml::Toml;
#[cfg(feature = "enable_yaml")]
#[cfg_attr(docsrs, doc(cfg(feature = "enable_yaml")))]
pub use crate::source::yaml::Yaml;
pub use crate::source::{args::*, env::SysEnvPropertySource, map::MapPropertySource};
#[allow(unused)]
pub(crate) const NOT_POSSIBLE: &str = "Not possible";
#[derive(Clone, Debug)]
pub enum Property {
Str(String),
Int(i64),
Float(f64),
Bool(bool),
}
pub trait IntoProperty: Sized {
fn into_property(self) -> Property;
}
pub trait FromProperty: Sized {
fn from_property(_: Property) -> Result<Self, PropertyError>;
}
pub trait PropertySource: Sync + Send {
fn name(&self) -> String;
fn get_property(&self, key: &str) -> Option<Property>;
fn contains_property(&self, key: &str) -> bool {
self.get_property(key).is_some()
}
fn is_empty(&self) -> bool;
fn get_keys(&self, prefix: &str) -> Vec<String>;
fn load(&self) -> Result<Option<Box<dyn PropertySource>>, PropertyError>;
}
pub trait Environment: Sync + Send + Sized {
fn contains(&self, key: &str) -> bool {
self.require::<Property>(key).is_ok()
}
fn require<T: FromEnvironment>(&self, key: &str) -> Result<T, PropertyError>;
fn require_or<T: FromEnvironment>(&self, key: &str, default: T) -> Result<T, PropertyError> {
match self.require::<Option<T>>(key) {
Ok(Some(a)) => Ok(a),
Ok(None) => Ok(default),
Err(e) => Err(e),
}
}
fn get<T: FromEnvironment>(&self, key: &str) -> Option<T> {
self.require(key).ok()
}
fn get_or<T: FromEnvironment>(&self, key: &str, default: T) -> T {
self.get(key).unwrap_or(default)
}
fn resolve_placeholder(&self, value: String) -> Result<Option<Property>, PropertyError>;
#[cfg(feature = "enable_derive")]
#[cfg_attr(docsrs, doc(cfg(feature = "enable_derive")))]
fn load_config<T: DefaultSourceFromEnvironment>(&self) -> Result<T, PropertyError> {
self.require(T::prefix())
}
fn find_keys(&self, prefix: &str) -> Vec<String>;
fn reload(&mut self) -> Result<(), PropertyError>;
}
pub trait FromEnvironment: Sized {
fn from_env(
prefix: &str,
property: Option<Property>,
env: &impl Environment,
) -> Result<Self, PropertyError>;
fn check_is_empty(&self) -> bool {
false
}
#[doc(hidden)]
fn from_err(err: PropertyError) -> Result<Self, PropertyError> {
Err(err)
}
#[doc(hidden)]
#[cfg(feature = "enable_derive")]
fn load_default() -> Vec<(String, Property)> {
vec![]
}
}
#[cfg(feature = "enable_toml")]
#[cfg(feature = "enable_derive")]
#[cfg(test)]
mod tests {
use crate::*;
use std::collections::HashMap;
#[derive(FromEnvironment, Debug)]
struct DatabaseConfigObj {
hello: String,
world: Option<String>,
}
#[derive(FromEnvironment, Debug)]
struct DatabaseConfigDetail {
#[salak(default = "str")]
option_str: String,
#[salak(default = 1)]
option_i64: i64,
option_arr: Vec<i64>,
option_multi_arr: Vec<Vec<i64>>,
option_obj: Vec<DatabaseConfigObj>,
}
#[derive(FromEnvironment, Debug)]
#[salak(prefix = "database")]
struct DatabaseConfig {
url: String,
name: String,
#[salak(default = "${database.name}")]
username: String,
password: Option<String>,
description: String,
detail: DatabaseConfigDetail,
}
#[test]
fn integration_tests() {
let env = Salak::new()
.with_custom_args(vec![
(
"database.detail.option_arr[0]".to_owned(),
"10".into_property(),
),
("database.url".to_owned(), "localhost:5432".into_property()),
("database.name".to_owned(), "salak".into_property()),
(
"database.description".to_owned(),
"\\$\\{Hello\\}".into_property(),
),
])
.build();
let ret = env.load_config::<DatabaseConfig>();
assert_eq!(true, ret.is_ok());
let ret = ret.unwrap();
assert_eq!("localhost:5432", ret.url);
assert_eq!("salak", ret.name);
assert_eq!("salak", ret.username);
assert_eq!(None, ret.password);
assert_eq!("${Hello}", ret.description);
let ret = ret.detail;
assert_eq!("str", ret.option_str);
assert_eq!(1, ret.option_i64);
assert_eq!(5, ret.option_arr.len());
assert_eq!(10, ret.option_arr[0]);
assert_eq!(0, ret.option_multi_arr.len());
assert_eq!(2, ret.option_obj.len());
let ret = env.require::<HashMap<String, String>>("database");
assert_eq!(true, ret.is_ok());
let ret = ret.unwrap();
assert_eq!(3, ret.len());
}
#[derive(FromEnvironment, Debug)]
struct Options {
#[salak(default = "cidr")]
mode: String,
#[salak(default = "\t")]
sep: String,
#[salak(default = "false")]
count: bool,
}
#[test]
fn placeholder_tests() {
let env = Salak::new().build();
let ret = env.require::<Options>("");
assert_eq!(true, ret.is_ok());
let ret = ret.unwrap();
assert_eq!("cidr", ret.mode);
assert_eq!("\t", ret.sep);
assert_eq!(false, ret.count);
}
}