Crates.io | yacm |
lib.rs | yacm |
version | 0.1.3 |
source | src |
created_at | 2023-12-27 19:27:05.919231 |
updated_at | 2023-12-29 02:55:29.629248 |
description | An extensable framework for loading configurations |
homepage | |
repository | https://bitbucket.org/lodestonetechnologiesllc/yacm |
max_upload_size | |
id | 1081828 |
size | 19,857 |
Yacm is yet another config macro.
Given the Long history of Yet Another projects, not even it's name is original. However, I was not finding what I wanted in a config macro and was tired of rewriting the same dull code to load structures that I subsequently passed into things that need configured.
#[derive(Yacm)]
#[yacm(prefix = "derived", name_provider = yacm::name_provider::screaming_snake_case, default_loader = yacm::env::read_env)]
struct Test {
#[yacm(
name = "TEST_TEST_ONE",
default = 142u32,
validator = less_than_100
)]
pub test_1: u32,
#[yacm(
name = Test::load_test_1().await?,
loader = yacm::env::read_env,
)]
pub test_2: Option<String>,
pub test_3: Option<String>,
}
fn less_than_100(value: &u32) -> Result<(), Box<dyn std::error::Error + Sync + Send>> {
if *value < 100 {
Ok(())
} else {
Err("should be less than 100".into())
}
}
This would generate something like
impl Test {
pub async fn load_test_1() -> std::result::Result<u32,::yacm::Error> {
let name = "TEST_TEST_ONE";
let mut value = yacm::env::read_env(&name).await;
if let Ok(None) = value { value = Ok(Some(142u32.into())) }
if let Ok(v) = value.as_ref() {
if let Err(e) = less_than_100(v) {
return Err(::yacm::Error::ValidationError(name.to_string(), e));
}
};
match value {
Ok(Some(v)) => Ok(v),
Ok(None) => Err(::yacm::Error::NotFound(name.to_string())),
Err(e) => Err(e),
}
}
pub async fn load_test_2() -> std::result::Result<Option<String>, ::yacm::Error> {
let name = Test::load_test_1().await?;
let mut value = yacm::env::read_env(&name).await;
value
}
pub async fn load_test_3() -> std::result::Result<Option<String>, ::yacm::Error> {
let name = yacm::name_provider::screaming_snake_case("test_3", Some("derived")).await
.map_err(|e| ::yacm::Error::Read("test_3".to_string(), e))?;
let mut value = yacm::env::read_env(&name).await;
value
}
pub async fn load() -> Result<Self, ::yacm::Error> {
Ok(Self {
test_1: Self::load_test_1().await?,
test_2: Self::load_test_2().await?,
test_3: Self::load_test_3().await?
})
}
}
yacm derives code to load both individual fields of a struct and the entire struct based on the specified or default loader for each field. Where a load should have a signature like:
pub async fn foo_loader<T>(name: &str) -> Result<Option<T>, yacm::Error>
The returned yacm::Error should either be yacm::Error::Read
or yacm::Error::Parse
.
The name passed into the load come from either a name_provider or a field specific name. name_providers can
be specified for an individual field or for the entire struct, where the yacm default is
::yacm::env::screaming_snake_case
.
Name_providers should have a signature like:
pub async fn foo(field: &str, prefix: Option<&str>) -> Result<String, Box<dyn std::error::Error + Sync + Send>>
Names are expressions of type &str, which may optionally return Err(Box<dyn std::error::Error + Sync + Send>)
For example name be a literal &str such as "FOO_BAR_SAMPLE"
or can be a bit more complicated
such as &format!("{}.sample",Config::load_env().await?)
#[yacm(prefix = "foo", name_provider = path::custom_name_convention, default_loader = path::custom_loader )]
prefix
: An optional String
representing the prefix for the configuration struct, which would be passed
to any name providers when generating a name to use when loading the config. For example one might
specify #[yacm(prefix = "sample")]
so that fields foo
and bar
might be loaded from environment
variables named SAMPLE_FOO
AND SAMPLE_BAR
.
name_provider
: An optional path to a function used as the default name_provider for each field in the
struct.
default_loader
: An optional path overriding the yacm default of ::yacm::env::read_env
#[yacm(name = "bar", name_provider = .., loader = path::custom_loader, default = 42, validator = path::custom_validator )]
name
: An optional expression of the exact &str to use as name
name_provider
: An optional path to a function used as the default name_provider for each field in the
struct.
loader
: An optional path overriding the struct default or yacm default of ::yacm::env::read_env
default
: An optional default, which should have a type matching the field type
validator
: An optional path to a validator, which should have a signature like
fn custom_val(value: &FieldType) -> Result<(), Box<dyn std::error::Error + Sync + Send>>
It is brand spanking new and I have not even used it all the places I intend to yet. i.e. interface is expected to be highly volitional.
While I've been coding for 40 years, I'm new to Rust, and I'm especially new to Meta Programming in Rust. Anything from suggestions for incremental improvement, to links to crates I should be using instead of wasting my time on yet another config macro are welcome.