#derive #macro-derive #json-configuration #uclicious

uclicious_derive

从 uclicious 包派生出 FromObject 特质的进程宏

5 个版本

0.1.7 2021年11月11日
0.1.5 2020年4月20日
0.1.3 2020年4月4日
0.1.1 2020年3月16日
0.1.0 2020年3月15日

#27 in #json-configuration

Download history 19/week @ 2024-03-31

每月 121 次下载
uclicious 中使用

BSD-2-Clause

43KB
1K SLoC

Uclicious 构建状态 codecov docs.rs Crates.io

什么是 Uclicious

Uclicious 是一个灵活的简化样板配置框架。

Uclicious 基于 libucl 构建。如果您曾经编写过 nginx 配置,并想“天哪,我希望所有配置文件都像这样”,那么这个库就是为您准备的。内部解析器支持:nginx 类型和 json 类型的格式。JSON 解析器比其他格式更宽松:每个 json 文件都是有效的 UCL 文件,但反之则不然。它比 json 或 TOML 复杂得多,因此我建议阅读有关它的文档。UCL 的作者做了很好的文档工作。这个库提供了:派生驱动和原始 API 驱动使用模式。

用法

原始 API

原始 API 涉及通过安全 API 与 libucl 解析器交互

use uclicious::*;
let mut parser = Parser::default();
let input = r#"
test_string = "no scope"
a_float = 3.14
an_integer = 69420
is_it_good = yes
buffer_size = 1KB
interval = 1s
"#;
parser.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let result = parser.get_object().unwrap();

let lookup_result = result.lookup("test_string").unwrap().as_string().unwrap();
assert_eq!(lookup_result.as_str(), "no scope");

let lookup_result = result.lookup("a_float").unwrap().as_f64().unwrap();
assert_eq!(lookup_result, 3.14f64);

let lookup_result = result.lookup("an_integer").unwrap().as_i64().unwrap();
assert_eq!(lookup_result, 69420i64);

let lookup_result = result.lookup("is_it_good").unwrap().as_bool().unwrap();
assert_eq!(lookup_result, true);

let lookup_result = result.lookup("buffer_size").unwrap().as_i64().unwrap();
assert_eq!(lookup_result, 1024);
let lookup_result = result.lookup("interval").unwrap().as_time().unwrap();
assert_eq!(lookup_result, 1.0f64);

为了绕过 Rust 规则,库实现了自己的 FromObject 特质以支持一些基本类型

use uclicious::*;
let mut parser = Parser::default();
let input = r#"
test_string = "no scope"
a_float = 3.14
an_integer = 69420
is_it_good = yes
buffer_size = 1KB
"#;
parser.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let result = parser.get_object().unwrap();

let lookup_result = result.lookup("is_it_good").unwrap();
let maybe: Option<bool> = FromObject::try_from(lookup_result).unwrap();
assert_eq!(Some(true), maybe);

派生驱动

在 libUCL 的“原始”接口之上,Uclicious 提供了一种简单的方式来派生结构体的构造函数

use uclicious::*;
use std::path::PathBuf;
use std::net::SocketAddr;
use std::collections::HashMap;
use std::time::Duration;

#[derive(Debug,Uclicious)]
#[ucl(var(name = "test", value = "works"))]
struct Connection {
   #[ucl(default)]
   enabled: bool,
   host: String,
   #[ucl(default = "420")]
   port: i64,
   buffer: u64,
   #[ucl(path = "type")]
   kind: String,
   locations: Vec<PathBuf>,
   addr: SocketAddr,
   extra: Extra,
   #[ucl(path = "subsection.host")]
   hosts: Vec<String>,
   #[ucl(default)]
   option: Option<String>,
   gates: HashMap<String, bool>,
   interval: Duration,
}

#[derive(Debug,Uclicious)]
#[ucl(skip_builder)]
struct Extra {
   enabled: bool
}
let mut builder = Connection::builder().unwrap();

let input = r#"
    enabled = yes
    host = "some.fake.url"
    buffer = 1mb
    type = $test
    locations = "/etc/"
    addr = "127.0.0.1:80"
    extra = {
       enabled = on
   }
    subsection {
       host = [host1, host2]
   }
   interval = 10ms
   gates {
        feature_1 = on
        feature_2 = off
        feature_3 = on
   }"#;

builder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let connection: Connection = builder.build().unwrap();

如果您选择派生构建器,则 ::builder() 方法将被添加到目标结构体中。

验证器

库支持在构建结果结构体之前在值上运行可选的验证器

use uclicious::*;
mod validators {
   use uclicious::ObjectError;
    pub fn is_positive(lookup_path: &str, value: &i64) -> Result<(), ObjectError> {
        if *value > 0 {
            Ok(())
        } else {
            Err(ObjectError::other(format!("{} is not a positive number", lookup_path)))
        }
    }
}
#[derive(Debug,Uclicious)]
struct Validated {
   #[ucl(default, validate="validators::is_positive")]
    number: i64
}
let mut builder = Validated::builder().unwrap();

let input = "number = -1";
builder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
assert!(builder.build().is_err())

类型映射

如果您的目标结构中有不实现 FromObject 的类型,您可以通过中间实现该功能的类型来使用 FromTryFrom

use uclicious::*;
use std::convert::{From,TryFrom};

#[derive(Debug, Eq, PartialEq)]
enum Mode {
    On,
    Off,
}

impl TryFrom<String> for Mode {
    type Error = ObjectError;
    fn try_from(src: String) -> Result<Mode, ObjectError> {
        match src.to_lowercase().as_str() {
            "on" => Ok(Mode::On),
            "off" => Ok(Mode::Off),
            _   => Err(ObjectError::other(format!("{} is not supported value", src)))
        }
    }
}

#[derive(Debug, Eq, PartialEq)]
struct WrappedInt(i64);

impl From<i64> for WrappedInt {
    fn from(src: i64) -> WrappedInt {
        WrappedInt(src)
    }
}

#[derive(Debug,Uclicious, Eq, PartialEq)]
struct Mapped {
   #[ucl(from="i64")]
    number: WrappedInt,
   #[ucl(try_from="String")]
    mode: Mode
}
let mut builder = Mapped::builder().unwrap();

let input = r#"
    number = -1,
    mode = "on"
"#;
builder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let actual = builder.build().unwrap();
let expected = Mapped {
number: WrappedInt(-1),
mode: Mode::On
};
assert_eq!(expected, actual);

此外,您还可以提供从 ObjectRef 到您的类型的映射

use uclicious::*;

#[derive(Debug, Eq, PartialEq)]
pub enum Mode {
    On,
    Off,
}

pub fn map_bool(src: ObjectRef) -> Result<Mode, ObjectError> {
    let bool: bool = src.try_into()?;
    if bool {
        Ok(Mode::On)
    } else {
        Ok(Mode::Off)
    }
}
#[derive(Debug,Uclicious, Eq, PartialEq)]
struct Mapped {
   #[ucl(map="map_bool")]
    mode: Mode
}
let mut builder = Mapped::builder().unwrap();

let input = r#"
    mode = on
"#;
builder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let actual = builder.build().unwrap();
let expected = Mapped {
    mode: Mode::On
};

支持的属性 (#[ucl(..)])

结构级别

  • skip_builder
    • 如果设置为 true,则不会生成构建器和构建器方法。
  • parser(..)
    • 可选属性,用于配置内部解析器。
    • 具有以下嵌套属性
      • flags
        • 返回标志的函数的路径。
      • filevars(..)
        • 在解析器上调用 set_filevars
        • 具有以下嵌套属性
          • path
            • 文件路径的字符串表示。
          • expand
            • (可选) 如果设置,则变量将被展开到绝对路径。
  • pre_source_hook(...)
    • 可选属性,在添加源之前运行函数
    • 可用于注册变量处理器
    • 必须接受 &mut Parser 作为参数并返回 Result<(), Into<UclError>>
  • var(..)
    • 可选属性,用于在解析器上注册字符串变量。
    • 具有以下嵌套属性
      • name
        • 不带 $ 部分的变量名称。
      • value
        • 变量的字符串值。
        • libUCL 仅支持字符串变量。
  • include(..)
    • 用于将文件添加到解析器中。
    • 如果文件不存在或解析失败,则构造函数将返回错误。
    • 必须指定以下源之一: pathchunkchunk_static
    • 具有以下嵌套属性
      • (半可选) path = string
        • 文件路径。可以是绝对路径或相对于当前工作目录的相对路径。
      • (半可选) chunk = string
        • 作为块添加到解析器中的字符串。
      • (半可选) chunk_static = string
        • 一个文件的路径,该文件将与 include_str!() 一起包含到二进制文件中
      • (可选) priority = u32
        • 源优先级为 0-15。有关更多信息,请参阅 libUCL 文档。
      • (可选) strategy = uclicious::DuplicateStrategy
        • 用于重复键的策略。有关更多信息,请参阅 libUCL 文档。

字段级别

所有字段级选项都是可选的。

  • default
    • 如果在对象中找不到键,则使用 Default::default。
  • default=expression
    • 如果找不到键,则使用此 表达式 作为值。
    • 可以是值或函数调用。
  • path=string
    • 默认情况下,字段名称用作路径。
    • 如果设置,则用作键。
    • 支持键的点表示法。
  • validate= path::to_method
    • Fn(key: &str,value: &T) -> Result<(), E>
    • 错误需要可转换为 ObjectError
  • from= Type
    • 尝试将 ObjectRef 转换为 Type,然后使用 std::convert::From 转换为目标类型
  • try_from=Type
    • 尝试将 ObjectRef 转换为 Type,然后使用 std::convert::TryFrom 转换为目标类型
    • 错误将被转换为 ObjectError::Other
  • from_str
    • 尝试将 ObjectRef 转换为 String,然后使用 std::str::FromStr 转换为目标类型
    • 错误将被转换为 ObjectError::Other
  • map= path::to_method
    • Fn(src:ObjectRef) -> Result<T, E>
    • 用于映射无法实现 FromTryFrom 或错误无法转换为 ObjectError 的外部对象的一种方法

其他说明

  • 如果目标类型是数组,但键是一个单一值,则创建一个隐式列表。
  • 枚举的自动推导不支持,但您可以自己实现。
  • 在发布此 crate 之前,我还有几个功能想要实现
    • 添加变量的能力。
    • 添加宏处理器的能力。
    • (可能)配置用于派生构建器的解析器。
    • (已完成)将带有属性的源添加到解析器中。

贡献

欢迎提交 PR、功能请求和错误报告。我不会添加 CoC — 请文明行事。

特别感兴趣的贡献

  • 优化派生代码。
  • 改进文档 — 我经常在深夜写作,有些可能看起来像文字汤。
  • 更好的测试
  • 在派生解析器部分支持全局匹配
  • 变量处理器

目标

  • 提供安全便捷的配置库
  • 自动派生,因此您无需考虑解析器对象

非目标

  • 提供 UCL 对象生成工具不是本项目的目标
  • 与 libUCL 的 1:1 接口
  • raw 模块中添加糖

特别感谢

  • draft6hauleth
    • libucl-rs 是一个很好的起点
    • 类型包装器基本上是从那里复制的
  • colin-kiegel
    • Rust-derive-builder 被用作 uclicious-derive 的起点
    • 文档编写得非常好的 proc_macro crate,强烈推荐

授权

BSD-2-Clause.

依赖项

~2MB
~43K SLoC