#config-parser #environment #argument #config #arguments-parser

conf

基于 derive 的 CLI 参数和环境参数配置解析器

1 个不稳定版本

0.1.0 2024年7月22日

155命令行界面

Download history 110/week @ 2024-07-20 14/week @ 2024-07-27

每月 124 次下载

MIT/Apache

115KB
1.5K SLoC

conf

conf 是一个针对注重实际的 Web 开发者、旨在构建大型 Web 项目的 derive-based 环境和参数解析器。

它底层使用 clap 来解析 CLI 参数并生成帮助文本。

conf 拥有与 clap-derive 类似的 proc-macro API,但它并不是一个分支。它是一个具有不同目标的全新库。它提供了一些强大的功能和支持,这些功能和支持是 clap-derive 所不具备的,有助于大型项目的配置。但同时也放弃了 clap 中的一些特性,这些特性在 Web 项目中通常不太有用。

您将获得以下优惠特性

  • 您可以在将结构体字段展平到另一个结构体时为其指定前缀,并且可以以类似的方式以受控方式执行 env 前缀。
  • 如果某些必需的环境变量缺失或多个值无效,您将获得所有错误,而不仅仅是其中一个。 在我的搜索中,我发现实际上很少有配置 Crates 会这样做。如果您的部署需要很长时间,这将非常有帮助。
  • 关于 env 的隔离和可测试性clap 只支持从 std::env::var_os 读取环境值。
    • 如果您想测试当不同变量被设置时会发生什么,您的测试可能会变得不稳定。
    • 如果您想测试一个将配置作为参数的组件,并使用 ::parse_from 来初始化配置,那么您的测试将取决于您的本地环境。
    • 如果您想根据您在结构体上声明的默认值实现 Default,您实际上无法做到,因为您无法将其从 env 中隔离出来。
    • conf 允许您传递一个迭代器来表示环境的快照。
  • 支持 env 别名clap 支持命令行参数的别名,但不支持 env。在不破坏兼容性的情况下进行更改。
  • 您可以声明只能从 env 读取的字段,而不能从 args 中读取。
  • 您可以声明代表秘密的字段。 这控制了在解析失败时是否应该打印整个值。
  • 支持可选的扁平语法。这可能比在 clap-derive 中使用参数组等更简单、更符合惯例。
  • 支持用户自定义验证谓词。这允许您表达 clap 中无法表达的限制。

confclap-derive 和我多年来一直使用的早期 struct-opt 的影响很大。它们都很棒,并且由于某些原因而变得流行。

在大多数情况下,conf 尝试与 clap-derive 的语法和行为保持非常接近,以便更容易地迁移大型项目。在某些情况下,与 clap-derive 的行为有一些小偏差,要么是为了帮助避免错误,要么是为了使默认值更接近好的 12-factor 应用程序 行为。对于 clap 的某些高级功能,conf 有一种方法可以实现相同的功能,但我们采取了不同的方法。这通常是为了简化用户对 derive 宏的使用,减少命名概念的数量,或者为了便于未来的维护。

这里公开的 API 限制为一种特质、一个用于生成它的 proc-macro 以及一个错误类型,而不是试图将许多底层对象和概念作为公共 API 的一部分,并支持像 clap-derive 那样手动实现可导特质。希望这将同时降低学习曲线,并使未来的开发和维护更加容易。

有关此项目和现有其他各种替代方案的更多讨论,请参阅 MOTIVATION.md

在 cargo 项目中使用 conf

首先,将 conf 添加到您的 Cargo.toml 文件中的依赖项

[dependencies]
conf = "0.1"

注意:尚未在 crates.io 上发布...在发生这种情况之前,还需要一些额外的测试覆盖率。

然后,创建一个 struct,该结构表示您的应用程序在启动时需要读取的配置数据。这个结构应该推导出 Conf 特质,并且应该使用 conf 属性来描述每个字段如何被读取。

#[derive(Conf)]
pub struct Config {
    /// This is a string parameter, which can be read from args as `--my-param` or from env as `MY_PARAM`.
    #[arg(long, env)]
    my_param: String,

    /// This flag corresponds to `-f` or `--force` in args
    #[arg(short, long)]
    force: bool,

    /// URL to hit, which can be read from args as `--url` or from env as `URL`.
    #[arg(long, env)]
    url: Url, // This works because Url implements `FromStr`.
}

最后,您可以解析配置

    let config = Config::parse();

通常您会在 fn() 的某个地方调用它,然后使用 config 来初始化您的应用程序。

函数 parse() 将自动为用户添加一个包含自动生成的文档的 --help 选项,基于您的文档字符串。

此外,如果解析失败,它将显示有用的错误信息并退出。

Conf 特性为此函数提供了一些变体,您可以在文档中了解相关信息。)

通常,生成的 CLI 接口和帮助文本旨在符合 POSIX 和 GNU 规范。关于此,请参阅 clap 文档

导游

您的结构体中的一个字段可以来自几个来源

  • #[arg(short)] 表示它对应于一个“短”选项,例如 -u。默认情况下,使用您字段的第一个字母。这可以通过 #[conf(short='t')] 等方式覆盖。
  • #[arg(long)] 表示它对应于一个“长”选项,例如 --url。默认情况下,使用您字段的 kebab-case 名称。这可以通过 #[conf(long="target-url")] 等方式覆盖。
  • #[arg(env)] 表示它对应于一个环境变量,例如 URL。默认情况下,使用您字段的大写蛇形名称。这可以通过 #[conf(env="TARGET_URL")] 等方式覆盖。
  • #[arg(default_value)] 指定了如果没有其他三个可能的来源提供值,则为该字段指定一个默认值。

这些属性可以通过逗号分隔来组合,例如 #[arg(long, env, default_value="x")] 表示该字段具有关联的长选项、关联的环境变量,并且如果省略这两个选项,则具有默认值。

只要实现了 FromStr,您的字段就可以有任意类型,并且将用于解析它。类型 bool 是特殊的,它生成一个“标志”而不是“参数”,在解析时不需要传递字符串参数。 Option<T> 也是特殊的,它表示值是可选的而不是必需的。您还可以使用 value_parser 指定替代解析函数。

到目前为止,这几乎与 clap-derive 完全相同。更有趣的是 flatten 选项。

您可能有一个结构体派生自 Conf 并声明了一组相关的配置值

#[derive(Conf)]
pub struct DbConfig {
    /// Database connection URL.
    #[arg(long)]
    pub db_url: String,

    /// Set the maximum number of connections of the pool.
    #[arg(long)]
    pub db_max_connections: Option<u32>,

    /// Set the minimum number of connections of the pool.
    #[arg(long)]
    pub db_min_connections: Option<u32>,

    /// Set the timeout duration when acquiring a connection.
    #[arg(long)]
    pub db_connect_timeout: Option<u64>,

    /// Set the maximum amount of time to spend waiting for acquiring a connection.
    #[arg(long)]
    pub db_acquire_timeout: Option<u64>,

    /// Set the idle duration before closing a connection.
    #[arg(long)]
    pub db_idle_timeout: Option<u64>,

    /// Set the maximum lifetime of individual connections.
    #[arg(long)]
    pub db_max_lifetime: Option<u64>
}

然后您可以使用 conf(flatten) 属性将其“扁平化”为更大的 Conf 结构。

#[derive(Conf)]
pub struct Config {
    /// Database
    #[conf(flatten)]
    db: DbConfig,
}

直观地说,这应该与 serde(flatten) 属性非常相似,并且具有类似的行为。在解析过程中,解析器表现得好像 DbConfig 的每个字段都在 Config 中声明,并生成匹配的选项、环境变量和帮助信息,然后将解析后的值实际存储在 .db 字段的子字段中。

使用 flatten 可以节省很多劳动。例如,假设您的Web应用程序由十个不同的Web服务组成,并且它们都需要 DbConfig。您不必在每个 Config 中重复所有值,任何环境变量、任何默认值、任何帮助文本,只需编写一次,然后 flatten 十次。然后,当您发现 DbConfig 应该包含另一个值时,您只需将其添加到 DbConfig 一次,所有使用 DbConfig 的服务都将获得新的配置参数。此外,当您需要初始化数据库连接时,您可以只需传递整个 .db 字段,而不是逐个挑选所需的配置参数。

confclap-derive 的不同之处在于,我们预计您将在项目中大量使用 flatten

例如,您可能需要这样做

#[derive(Conf)]
pub struct Config {
    #[conf(flatten)]
    pub auth_service: HttpClientConfig,

    #[conf(flatten)]
    pub friend_service: HttpClientConfig,

    #[conf(flatten)]
    pub snaps_service: HttpClientConfig,
}

因为在逻辑上,您有三个不同的HTTP客户端需要配置。

然而,使用 clap-derive,这将导致问题,因为当来自 HttpClientConfig 的字段被扁平化时,它们的名称将发生冲突,解析器将拒绝它作为模糊的。

当使用 conf 时,您可以通过声明前缀来解决它。

#[derive(Conf)]
pub struct Config {
    #[conf(flatten, prefix)]
    pub auth_service: HttpClientConfig,

    #[conf(flatten, prefix)]
    pub friend_service: HttpClientConfig,

    #[conf(flatten, prefix)]
    pub snaps_service: HttpClientConfig,
}

这将导致与auth_service结构相关的每个选项都将获得一个前缀,该前缀由字段名称auth_service派生,适用于任何长格式选项和任何环境变量。长格式选项的前缀将使用连字符大小写,而环境变量的前缀将使用大写蛇形大小写。同样,对于friend_servicesnaps_service也是如此。

您也可以覆盖此前缀

#[derive(Conf)]
pub struct Config {
    #[conf(flatten, prefix="auth")]
    pub auth_service: HttpClientConfig,

    #[conf(flatten, prefix="friend")]
    pub friend_service: HttpClientConfig,

    #[conf(flatten, prefix="snaps")]
    pub snaps_service: HttpClientConfig,
}

如果您想分别配置环境变量前缀和选项前缀,也可以这样做。设置env_prefix将使环境变量获得前缀,但选项不会。设置long_prefix将使长格式选项获得前缀,但环境变量不会。(短选项永远不会获得前缀,因此通常很难解决它们之间的冲突。在大项目中应谨慎使用短选项。)

最后,您还可以在结构的级别而不是字段的级别声明前缀。例如,如果您需要程序读取的每个环境变量都以前缀ACME_开头,您可以很容易地实现这一点。

#[derive(Conf)]
#[conf(env_prefix="ACME_")]
pub struct Config {
    #[conf(flatten, prefix="auth")]
    pub auth_service: HttpClientConfig,

    #[conf(flatten, prefix="friend")]
    pub friend_service: HttpClientConfig,

    #[conf(flatten, prefix="snaps")]
    pub snaps_service: HttpClientConfig,
}

您可以在文档中阅读有关所有属性的说明或REFERENCE.md,但希望这足以开始。

主题

本节讨论了更多高级功能和用法模式,以及替代方案。

读取文件

有时,Web服务需要在启动时读取文件。conf通过使用与clap非常相似的value_parser功能来支持此功能。

value_parser是一个函数,它接受一个&str并返回一个值或一个错误。

例如,如果您需要根据模式在启动时读取yaml文件,您可以这样做:

use conf::Conf;
use serde::Deserialize;
use std::{error::Error, fs};

#[derive(Deserialize)]
pub struct MyYamlSchema {
    pub example: String,
}

#[derive(Conf)]
pub struct Config {
    #[conf(long, env, value_parser = |file: &str| -> Result<_, Error> { Ok(serde_yaml::from_str(fs::read_to_string(&file)?)?) }]
    pub yaml_file: MyYamlSchema,
}

这将从CLI参数或环境变量中读取文件路径,然后尝试打开该文件并根据yaml模式解析它。

如果您的value_parser很复杂或需要重用,最佳实践是将它放入一个命名函数中。

#[derive(Conf)]
pub struct Config {
    #[conf(long, env, value_parser = utils::read_yaml_file)]
    pub yaml_file: MyYamlSchema,
}

这对于像从文件中读取证书或加密密钥这样的东西也是一个很好的模式,您希望在启动时检查这些内容,如果文件不存在或无效,则快速失败。

分层配置

分层配置的想法是配置值应从文件以及从参数和环境变量中合并。

应用程序应该遵循分层配置结构。以下是从最高优先级到最低优先级的顺序。

  1. 命令行参数
  2. 环境变量
  3. 目录或仓库范围的配置
  4. 用户范围的配置
  5. 系统范围的配置
  6. 与程序一起提供的默认配置。

conf具有对(1)、(2)和(6)的内置支持。

要使用类似conf的东西获得其他配置,常见做法是使用类似dotenvy的crate。这个crate可以搜索.env文件,然后在程序中未设置环境变量时设置它们。您可以在调用Config::parse()之前这样做,并通过这种方式实现分层配置。如果您需要,您可以通过这种方式加载多个.env文件。

在Web应用程序中,我经常在开发时使用这种方法,而不是在生产时使用。

如果您的应用程序有很多必需的值,工程师可能需要一段时间才能弄清楚如何在本地运行它。但出于安全考虑,您可能不希望在程序中提供在生产环境中不适用的默认值。相反,您可以为本地测试/CI提供包含适当值的.env文件,并将其检入到仓库中。然后工程师可以使用cargo run来运行它,它就会正常运行。当您构建Docker容器时,可以省略这些.env文件,并确保在部署环境中,Kubernetes或类似系统完全控制,任何 Helm 图表中的缺失或拼写错误的值都会被快速、大声地报告出来。

如果您使用的是diesel,这些.env文件会工作得很好,因为diesel命令行工具也使用dotenvy来搜索一个.env文件,并在本地管理数据库迁移时找到DATABASE_URL

这种层次化配置的方法比configfigment等crate提供的方法要少,但它也更简单,易于更改和调试。在MOTIVATION.md中还有其他一些原因,我个人更喜欢这种方法。当然,这是非常主观的——随着时间的推移,conf可能会添加更多功能以支持其他使用方式。最初,我只构建了我认为需要的功能。您的体验可能会有所不同。

秘密

conf试图提供最有用、最详细的错误信息,并在解析失败时报告尽可能多的问题。

通常,如果用户提供的值无法解析,我们希望将值和错误信息一起显示在错误消息中,以帮助调试。但如果该值代表一个秘密,则记录其值是错误的。

要防止conf记录值,可以将该字段标记为secret

    #[arg(env, secret)]
    pub api_key: ApiKey

conf知道某物是秘密时,它将避免在生成任何类型的错误消息或帮助文本时泄露值。conf还会在帮助文本中使用[secret]标签描述它。

处理秘密是一个复杂的话题,其中大部分讨论超出了本文的范围。我们将提供关于此工具的三点指导。

  1. 秘密越有价值,威胁模型越复杂,投入更多时间来制定防御措施就越有意义。反之亦然。
  2. 如果您认为系统地标记 secret 是一个好主意,那么您也应该使用特殊类型来管理秘密。例如,使用来自 secrecy crate 的 SecretString 而不是 String,可以在密码被加载后防止它在调试日志中显示。如果 secrecy crate 不适合您的用例,还有其他替代方案。这通常是一个相当低成本的改进,并且与 secret 标记所做的相符。
    • 如果您不这样做,很容易意外地暴露您的秘密。例如,只需在一个将来会接受 config 结构体的函数上放置一个 #[tracing::instrument] 注解,您就可能会意外地记录您的密码。
  3. 如果您认为您需要在不再需要时,系统性地将您在进程内存中存储的秘密的所有副本 归零化,那么您已经超过了可以使用环境变量将秘密值传递给应用程序的点。您的应用程序很可能是需要 从文件中读取秘密值
    • Rust 标准库内部和其 API 中将环境值作为 std::ffi::OsString 处理,但此类型不能安全地归零化。没有公开的 API 可以以可变方式访问底层字节。
    • 在更低的层面上,glibc 将环境作为 char **environ 公开,每次使用 set_var 或类似方法更改环境时,都会复制整个环境,并泄露旧值。如果它们包含敏感数据,很难系统地确保清理所有这些副本。通常在进程开始早期,其他一些东西就会复制 environ。Rust 标准库也通过这些 glibc API 与环境交互,这意味着像 dotenvy 这样的典型 Rust 库也是如此。

参数组和约束

clap 支持概念上的“参数组” (ArgGroup) 和 Arg 之间的“依赖关系”。这用于创建必须满足的额外条件,以使配置有效,并在配置无效时提供错误信息。clapArgArgGroup 提供了许多函数,可用于定义各种类型的约束,例如在 ArgArgGroup 之间定义条件依赖或互斥。

使用 clap 中的这些功能的主要原因是,如果违反了这些约束,它将生成格式良好的错误,然后您就不必担心在应用程序代码中处理这种情况。

conf 同样希望以这种方式支持在解析期间检查的约束,但其设计目标是所有这些错误都应该与其他类型的错误一起报告。

由于几个原因,conf 选择为这些目的提供与 clap 不同的 API。

  • clap 中,这个API最初是为clap构建器API设计的,然后通过 clap-derive API公开。
  • 总共有大约一打函数被公开,以及多个命名概念(现在 ArgArgGroup 结合使用,与 Args 不同)。
  • API依赖于显式的 id 值为 ArgArgGroup,但在 derive API 中这并不符合习惯。如果这些 id 并未真正公开,而更像是一些实现细节,那么 derive API 从用户的角度来看会更为简单。
  • API通常提供了多种执行相同任务的方式,这使得使用它的代码更难以预测。
  • API有许多默认设置,我很难记住。例如,在 ArgGroup 中,required 默认为 true 还是 falsemultiple 默认为 true 还是 false?这些默认设置对于 Args 是不同的。
  • 有时API感觉不太符合习惯。例如,如果我有这样一组选项,其中一个出现则所有选项都必须出现,最符合习惯的做法是API可以给我一个包含所有选项的单个 Option。否则,我必须在应用代码中展开很多选项,假设我的约束按预期工作。

conf 提供了一种机制来习惯性地表示某些参数集合是可选但相互依赖的情况。然后它提供了一些一次性设置来表达参数之间的排他性。最后,它提供了一个非常通用的机制来表示任意约束。

flatten-optional

conf 支持以下语法

#[derive(Conf)]
pub struct Config {
    #[conf(flatten, prefix="auth")]
    pub auth_service: HttpClientConfig,

    #[conf(flatten, prefix="friend")]
    pub friend_service: HttpClientConfig,

    #[conf(flatten, prefix="snaps")]
    pub snaps_service: Option<HttpClientConfig>,
}

直观上,这意味着 snaps_service 配置是可选的,如果没有这些字段出现,则不会出错,并且解析的配置对象中的 snaps_service 将是 None。然而,如果 snaps_service 的任何字段出现,则其所有必需字段都必须出现,并且解析整个扁平化对象必须成功。

这使得消费条件配置的代码更简单——你可以根据 snaps_service 是否出现或不存在进行匹配,并且类型系统在那些字段出现时编码了这一点。你还可以通过在 HttpClientConfig 中标记它们是否可选(或提供默认值)来表达组中哪些参数必须存在或不存在。

这个功能实际上涵盖了我在所有Web项目中使用 clap 的参数组和约束的每一个实际用例,我喜欢它,因为它感觉引入了更少的命名概念,并促进了代码重用。同一个结构可以在一个设置中以必需方式扁平化,在另一个设置中以可选方式扁平化。

只需看看数据的类型,想想它成功需要发生什么,就很容易记住它的含义。如果我们看不到任何(前缀)子结构的字段出现,那么我们就返回 None。如果我们看到其中一些出现,这表明我们应该产生一个 Some。一旦我们决定要产生 Some,如果我们在正常(非可选)方式下无法在 flatten 结构中做到这一点,则是一个错误。

one_of_fields

conf 提供了一种简单的方法来指定结构中的一些字段是互斥的。

#[derive(Conf)]
#[conf(at_most_one_of_fields(a, b, c))]
pub struct FooConfig {
    #[conf(short, long)]
    pub a: bool,
    #[conf(short, long)]
    pub b: Option<String>,
    #[conf(long, env)]
    pub c: Vec<String>,
}

当与两个字段一起使用时,它提供了在 clap-derive API 中使用 conflicts_with 的许多用法的翻译方式。

当与结构中的所有字段一起使用时,它类似于在 clap-derive API 中具有 multiple=falserequired=falseArgGroup

这也与 flatten-optional 功能一起工作,因此可以在这个结构中将一个或多个可选扁平化组彼此或与简单参数互斥。

但是,它只能用于标记有此属性的结构的字段,不能用于扁平化结构内部的字段或结构中的其他地方。

conf 提供了一种需要 恰好 出现一个字段的变化。

#[derive(Conf)]
#[conf(one_of_fields(a, b, c))]
pub struct FooConfig {
    #[conf(short, long)]
    pub a: bool,
    #[conf(short, long)]
    pub b: Option<String>,
    #[conf(long, env)]
    pub c: Vec<String>,
}

当与结构中的所有字段一起使用时,这类似于在 clap-derive API 中具有 multiple=falserequired=trueArgGroup

最后,conf 提供了另一种变化

#[derive(Conf)]
#[conf(at_least_one_of_fields(a, b, c))]
pub struct FooConfig {
    #[conf(short, long)]
    pub a: bool,
    #[conf(short, long)]
    pub b: Option<String>,
    #[conf(long, env)]
    pub c: Vec<String>,
}

当与结构中的所有字段一起使用时,这类似于在 clap-derive API 中具有 multiple=truerequired=trueArgGroup

这些属性中的任何一个都可以在同一个结构上多次使用,以创建应用于该结构的多重约束。

验证谓词

flatten-optionalone_of_fields 提供了一些易于理解的方法来在 conf 结构中创建不同可选字段之间的依赖性和排他性约束。它们可以直接翻译许多简单的 ArgGroup 使用和 clap-derive API 中的某些约束。但是,clap 支持的许多其他约束类型不能直接转换为这种形式,并且我们不支持在字段上直接声明 arg group 成员关系,这是 clap 支持的。

同时,还有其他类型的约束,您可能有合法的使用需求,但在 clap 的 API 中无法表达。例如,您的某个参数可能是一个 Url 对象,您可能希望如果 Urlhttps 开头,则需要其他一些选项。据我所知,在 clap 中没有做这件事的方法。

与为 clap 的约束 API 中的每个函数提供直接类似物不同,conf 支持在每结构的基础上定义用户定义的验证谓词。

验证谓词是一个函数,它接受类型为 &T 的参数,其中 T 是当前的结构体,并返回 Result<(), impl Display>

其行为与 value_parser 类似,任何函数表达式都可以接受。

这里的想法是,与其向 conf 中添加越来越多的单次约束类型,或者使用 proc-macro 属性来启用你编写非局部约束,不如你用 Rust 代码表达你想要的内容,一旦你的约束变得足够复杂。这样对你和 conf 来说都更容易维护。对于你来说,需要学习和记住的 API 更少,对于 conf 来说,需要测试和维护的 API 表面积也更小。当复杂的约束失败时,你还能生成非常精确的错误信息。

结合使用这些特性,你可以表达任何想要施加在配置结构体上的约束,并希望让它看起来更加自然。


鉴于对 Tvalidation_predicate 在实际解析 T 之后运行,为什么还要有这个特性呢?用户可以在 Config::parse 成功后自行运行这些函数。

使用 validation_predicate 的好处是,如果谓词失败,conf 仍然能够报告这些错误以及树中其他地方发生的任何错误。

例如,在这个配置结构体中

#[derive(Conf)]
pub struct Config {
    #[conf(flatten, prefix="auth")]
    pub auth_service: HttpClientConfig,

    #[conf(flatten, prefix="friend")]
    pub friend_service: HttpClientConfig,

    #[conf(flatten, prefix="snaps")]
    pub snaps_service: Option<HttpClientConfig>,
}

在解析 Config 时,可能会出现 auth_service 解析失败,因为缺少必要的参数,friend_service 解析失败,因为缺少参数和无效值,以及 snaps_service 解析成功但验证谓词失败。在这种情况下,conf 将报告所有这些错误,这与其他同类 crate 相区分。

谁应该使用这个包?

使用这个 crate 的最佳理由是,如果你有一个中型到大型项目,例如一个由多个服务组成的 Web 应用程序,它有大量的配置需求。你有多个服务,这些服务有几个共同的子系统或组件,这些组件有子组件,其中一些是共享的等,所有这些都应该按照 12 因素风格从环境中读取配置,并且可能需要紧急读取更多此类配置。你可能已经使用过 clap-derive,但随着项目的增长,你已经遇到了限制。

该 crate 的目的是帮助您以最简单、最易于维护的方式安排所有配置,同时确保在程序启动时检查所有需要的值(快速失败),在您的部署出现问题时,以最有帮助的方式报告尽可能多的配置错误,并提供正在读取的所有配置的自动化 --help 文档。

如果您认为这个 crate 很适合您,建议的使用方法是

  • 每当您有一个您认为应该使用启动时读取的值的组件时,您应该为该组件创建一个配置结构体。您应该在结构体上 derive(Conf),并在初始化时将配置结构体传递给组件。该配置结构体应与其配置的组件位于同一模块中。
  • 如果您的组件由更大的组件初始化,那么该组件应具有自己的配置结构体,并且您应使用 flatten 来组装它。在展平时,通常应使用 prefixhelp_prefix 选项。
  • 每个二进制目标都应该有一个配置结构体,并且应该在 fn main()::parse() 它。

这样,无论将来您发现需要为您的某个小组件添加更多配置值,您只需将其添加到相关的配置结构体中,它就会自动出现在需要它的每个服务中,需要多少次就出现多少次,并带有适当的命名前缀,而无需您在每一步都进行连接。此外,这还使得创建任何未来服务或工具的正确配置变得更加容易。它还使所有服务和工具都具有相似、可预测的风格,并将它们的所有配置文档化在 --help 中,即使是相当隐蔽的环境变量等,通常如果您直接从 std::env 读取它们,通常不会进行文档化。

何时应优先使用 clap-derive 而不是此软件包?

此软件包相对于 clap-derive 定义得有所不同,具有不同的功能和目标。

  • clap-derive 旨在作为 clap 构建器 API 的替代方案,并暴露了构建器的基本所有功能。
  • clap 本身主要是 CLI 参数解析器 (根据维护者所说),并且许多关于 env 支持的简单功能,如只能从 env 读取的参数,都被认为是范围之外的。

conf 对特性的强调有所不同。

  • env 实际上是 12 因素 Web 应用程序最重要的东西。
  • conf 拥有不同的架构,这使得在运行时在 struct 和将其展平的 struct 之间传递信息更加容易。这使它能够带来许多新的特性。许多细节尚未记录,也不属于公共 API 的一部分,这样它们就可以在不进行破坏性更改的情况下进行扩展和改进。
  • conf 围绕错误报告有非常具体的目标。我们希望一次返回尽可能多的配置错误,因为部署可能需要相对较长的时间。

为了实现其目标,conf 实际上根本不使用 clap 来处理 envclap 仅用于将 CLI 参数作为字符串解析,并渲染帮助文本,这是它最擅长做的两件事。

此软件包可以公开底层 clap 构建器的更多功能,并更接近 clap-derive 提供的功能集,但它可能永远不会公开所有这些功能 - 我们只能公开那些我们确信将很好地与我们所创建的附加功能(如 flatten-with-prefix)一起工作的功能,并且将很好地与底层 clap 构建器的使用方式一起工作。最有趣的功能是可以由常见的 Web 开发需求推动的功能。

如果您有非常特定的命令行界面参数解析需求,或者需要像素级精确的帮助文本,那么直接使用 clap 而不是这个crate会更好,因为这样您将拥有更多的控制权。 clap 是最成熟且功能最全面的命令行界面参数解析器,远远领先于其他。

在许多网络项目中,您并不真正有这样的需求。您并没有对 clap 进行非常复杂的使用,您的项目很小,您也不特别需要 conf 的任何功能,因此您可以使用 clap-deriveconf 一样好,并且几乎感觉不到任何差异。

如果您愿意,您可以坚持使用 clap-derive,然后只有在您发现需要 flatten-with-prefix 或其他功能时,再尝试切换到 conf

conf 是为了使此类项目的迁移相对容易而设计的。(实际上,我开始开发 conf 是因为我有几个基于 clap-derive 的大型项目,我遇到了限制并被迫寻找我不满意的解决方案,而且我没有找到完全令人满意的替代方案。)如果您在尝试迁移时遇到困难,您可以发起讨论,我们可以尝试帮助您。

许可证

代码可供您选择在 MIT 或 Apache 2 许可下使用。

依赖项

~1.2–1.7MB
~33K SLoC