4个版本

0.1.3 2023年12月29日
0.1.2 2023年12月28日
0.1.1 2023年12月27日
0.1.0 2023年12月27日

配置 分类中排名 202

Download history 8/week @ 2024-04-16 63/week @ 2024-04-23 120/week @ 2024-04-30 48/week @ 2024-05-07 31/week @ 2024-05-14 14/week @ 2024-05-21 22/week @ 2024-05-28 18/week @ 2024-06-04 29/week @ 2024-06-11 66/week @ 2024-06-18 73/week @ 2024-06-25 81/week @ 2024-07-02 11/week @ 2024-07-09 6/week @ 2024-07-16 14/week @ 2024-07-23 20/week @ 2024-07-30

每月下载量 66

Apache-2.0

11KB
53

yacm

Yacm是另一个配置宏。

为什么?

鉴于“另一个”项目的悠久历史,甚至连它的名字也不是原创的。然而,我在配置宏中找不到我想要的东西,厌倦了重复编写相同的无聊代码来加载我随后传递给需要配置的东西的结构。

项目目标

  1. 干燥的配置代码
  2. 能够从异步源(例如AWS SSM参数)加载配置
  3. 配置错误时快速失败
  4. 使配置的加载方式高度可配置

总体思路

#[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())
    }
}

这将生成类似以下内容:

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

yacm会根据每个字段的指定或默认加载器生成代码来加载结构体的单个字段和整个结构体。其中加载函数应具有如下签名:

pubasyncfn foo_loader<T>(name: &str) -> Result<Option<T>, yacm::Error>

返回的yacm::Error应该是 yacm::Error::Readyacm::Error::Parse

传入加载函数的名称来自名称提供者或特定字段的名称。可以为单个字段或整个结构体指定名称提供者,其中yacm的默认值为 ::yacm::env::screaming_snake_case

名称提供者应具有如下签名:

pubasyncfn foo(field: &str, prefix: Option<&str>) -> Result<String, Box<dynstd::error::Error+ Sync + Send>>

名称是类型为 &str 的表达式,可以返回 Err(Box<dyn std::error::Error + Sync + Send>)

例如,名称可以是文字字符串 &str,例如 "FOO_BAR_SAMPLE",或者更复杂,例如 &format!("{}.sample",Config::load_env().await?)

结构体级别的属性

#[yacm(prefix= "foo",name_provider=path::custom_name_convention,default_loader=path::custom_loader)]

  • prefix:一个可选的 String,表示配置结构体的前缀,在生成用于加载配置时要传递给任何名称提供者。例如,可以指定 #[yacm(prefix = "sample")],这样字段 foobar 就可以从名为 SAMPLE_FOOSAMPLE_BAR 的环境变量中加载。

  • name_provider:一个可选的路径,用于每个结构体字段的默认名称提供者。

  • default_loader:一个可选的路径,用于覆盖 yacm 默认的 ::yacm::env::read_env

字段级别的属性

#[yacm(name= "bar",name_provider= ..,loader=path::custom_loader,default= 42,validator=path::custom_validator)]

  • name:一个可选的表达式,用于指定要使用的确切的 &str 作为名称

  • name_provider:一个可选的路径,用于每个结构体字段的默认名称提供者。

  • loader:一个可选的路径,用于覆盖结构体默认或 yacm 默认的 ::yacm::env::read_env

  • default:一个可选的默认值,其类型应与字段类型匹配

  • validator:一个可选的路径,用于验证器,其签名应类似于 fn custom_val(value: &FieldType) -> Result<(), Box<dyn std::error::error::Error + Sync + Send>>

路线图

  1. 在我对接口有一定信心之前,在我的几个自己的项目中使用它
  2. 添加测试
  3. 文档
  4. 也许可以将异步功能作为一个功能,而不是默认(我不需要非异步的,但有些人可能需要)

为什么你现在不应该使用它

它是全新的,我甚至还没有使用我打算使用的地方。即接口预期将非常主观。

欢迎反馈(是的,即使在早期阶段)

虽然我已经编程40年了,但我对 Rust 是新手,我对 Rust 中的元编程尤其不熟悉。任何从渐进改进的建议到链接到我应该使用而不是浪费时间在另一个配置宏上的 crate 的链接都是受欢迎的。

依赖关系

~0.3–1.1MB
~24K SLoC