跳过正文

config

··1978 字
Rust Rust-Crate
rust crate - 这篇文章属于一个选集。
§ 13: 本文

从各种格式的配置文件、环境变量中读取配置,然后转换为 struct 值:

struct Config 对象:

  • Config 的 merge/refresh/set 等方法已经被 Deprecated, 建议使用 ConfigBuiler 相关的方法。
pub struct Config {
    pub cache: Value,
    /* private fields */
}

// 提供的方法
pub fn builder() -> ConfigBuilder<DefaultState>

pub fn get<'de, T: Deserialize<'de>>(&self, key: &str) -> Result<T, ConfigError>
pub fn get_string(&self, key: &str) -> Result<String, ConfigError>
pub fn get_int(&self, key: &str) -> Result<i64, ConfigError>
pub fn get_float(&self, key: &str) -> Result<f64, ConfigError>
pub fn get_bool(&self, key: &str) -> Result<bool, ConfigError>
pub fn get_table(&self, key: &str) -> Result<Map<String, Value>, ConfigError>
pub fn get_array(&self, key: &str) -> Result<Vec<Value>, ConfigError>

// 将配置参数反序列化到 T 类型值上
pub fn try_deserialize<'de, T: Deserialize<'de>>(self) -> Result<T, ConfigError>

// 从可以序列化的对象值创建配置
pub fn try_from<T: Serialize>(from: &T) -> Result<Self, ConfigError>

Struct config::builder::ConfigBuilder

pub struct ConfigBuilder<St: BuilderState> { /* private fields */ }

// 实现的方法

// 设置缺省值:优先级最低
pub fn set_default<S, T>(self, key: S, value: T) -> Result<Self, ConfigError>
where
    S: AsRef<str>,
    T: Into<Value>

// 设置覆盖值:优先级最高
pub fn set_override<S, T>(self, key: S, value: T) -> Result<Self, ConfigError>
where
    S: AsRef<str>,
    T: Into<Value>

pub fn set_override_option<S, T>( self, key: S, value: Option<T>) -> Result<Self, ConfigError>
where
    S: AsRef<str>,
    T: Into<Value>

// 添加数据源,可以多次链式调用添加多个,后面的配置覆盖前面的配置(类似于配置 merge 功能)
pub fn add_source<T>(self, source: T) -> Self where T: Source + Send + Sync + 'static

pub fn add_async_source<T>(self, source: T) -> ConfigBuilder<AsyncState>
  where T: AsyncSource + Send + Sync + 'static

// 构建配置
pub fn build(self) -> Result<Config, ConfigError>

pub fn build_cloned(&self) -> Result<Config, ConfigError>

Trait config::Source:

pub trait Source: Debug {
    // Required methods
    fn clone_into_box(&self) -> Box<dyn Source + Send + Sync>;
    fn collect(&self) -> Result<Map<String, Value>, ConfigError>;

    // Provided method
    fn collect_to(&self, cache: &mut Value) -> Result<(), ConfigError> { ... }
}

impl Source for Vec<Box<dyn Source + Send + Sync>>
impl Source for [Box<dyn Source + Send + Sync>]
impl<T> Source for Vec<T> where T: Source + Sync + Send + Clone + 'static


impl Source for Config
impl Source for Environment
impl<T, F> Source for File<T, F>
where
    F: FileStoredFormat + Debug + Clone + Send + Sync + 'static,
    T: Sync + Send + FileSource<F> + 'static

Struct config::Environment

pub struct Environment { /* private fields */ }

pub fn new() -> Self

pub fn with_prefix(s: &str) -> Self

// 环境变量前缀(自动大写和加下划线),如 prefix 为 config 时,debug 对应 CONFIG_DEBUG
pub fn prefix(self, s: &str) -> Self

// 指定环境变量的大小写风格
pub fn with_convert_case(tt: Case) -> Self
pub fn convert_case(self, tt: Case) -> Self

// 指定嵌套配置参数的各级间的风格符,例如:使用 _ 作为分隔符时 redis.password 对应 REDIS_PASSWORD
pub fn prefix_separator(self, s: &str) -> Self
pub fn separator(self, s: &str) -> Self

// 尝试按照三种类型 bool、i64、f64(默认是 String)来解析参数,会影响性能。
// 一般和 list_separator() 连用。
pub fn try_parsing(self, try_parsing: bool) -> Self

// 调用 list_separator("x") 后,所有环境变量的值都按照指定的分隔符来进行风格,然后结果为
// Vec<String> (而非默认的 String 类型);示例:
// https://github.com/mehcode/config-rs/blob/master/examples/env-list/main.rs
pub fn list_separator(self, s: &str) -> Self
// 指定在调用 list_separator("x") 后,不解析为 Vec<String> ,而解析为默认的 String 值的 key。
pub fn with_list_parse_key(self, key: &str) -> Self

pub fn ignore_empty(self, ignore: bool) -> Self

pub fn keep_prefix(self, keep: bool) -> Self
// 指定要解析的环境变量来源 Map
pub fn source(self, source: Option<Map<String, String>>) -> Self

// 示例
#[test]
fn test_config() -> Result<(), config::ConfigError> {
  #[derive(Clone, Debug, Deserialize)]
  struct MyConfig {
    pub my_string: String,
  }

  let source = Environment::default()
    .source(Some({
      let mut env = HashMap::new();
      env.insert("MY_STRING".into(), "my-value".into());
      env
  }));

  let config: MyConfig = Config::builder()
    .add_source(source)
    .build()?
    .try_into()?;
  assert_eq!(config.my_string, "my-value");

  Ok(())
}

Struct config::File,支持自动文件格式发现(默认支持 json/yaml/toml/ini 格式):

pub struct File<T, F> { /* private fields */ }

// 实现的方法

// 将字符串 s 作为文件内容, 示例:
// https://github.com/mehcode/config-rs/blob/master/examples/custom_str_format/main.rs
impl<F> File<FileSourceString, F> where F: FileStoredFormat + 'static
  pub fn from_str(s: &str, format: F) -> Self

// 从 name 对应的文件路径读取文件内容, 示例:
// https://github.com/mehcode/config-rs/blob/master/examples/custom_file_format/main.rs
impl<F> File<FileSourceFile, F> where F: FileStoredFormat + 'static
  pub fn new(name: &str, format: F) -> Self

// name 如果不加后缀,则自动尝试默认注册支持的文件格式。name 如果带后缀,则使用后缀对应的文件格式。
// 示例:https://github.com/mehcode/config-rs/blob/master/examples/glob/main.rs
impl File<FileSourceFile, FileFormat>
  pub fn with_name(name: &str) -> Self

impl<T, F> File<T, F> where F: FileStoredFormat + 'static, T: FileSource<F>
  pub fn format(self, format: F) -> Self
  pub fn required(self, required: bool) -> Self

// 其中的 FileStoredFormat trait 定义如下
pub trait FileStoredFormat: Format {
    // Required method
    fn file_extensions(&self) -> &'static [&'static str];
}
// FileFormat 实现了 FileStoredFormat
impl FileStoredFormat for FileFormat

// Enum config::FileFormat
pub enum FileFormat {
    Toml,
    Json,
    Yaml,
    Ini,
    Ron,
    Json5,
}

示例:

// 示例:简单的 set/get
#![allow(deprecated)]
use config::Config;
use lazy_static::lazy_static;
use std::error::Error;
use std::sync::RwLock;

lazy_static! {
    static ref SETTINGS: RwLock<Config> = RwLock::new(Config::default());
}

fn try_main() -> Result<(), Box<dyn Error>> {
    // Set property
    SETTINGS.write()?.set("property", 42)?;

    // Get property
    println!("property: {}", SETTINGS.read()?.get::<i32>("property")?);

    Ok(())
}

// 示例:从配置文件和环境变量中读取配置参数
fn main() {
    let settings = Config::builder()
        // Add in `./Settings.toml`
        .add_source(config::File::with_name("examples/simple/Settings")) // 可选的指定文件名

        // Add in settings from the environment (with a prefix of APP) Eg.. `APP_DEBUG=1
        // ./target/app` would set the `debug` key
        .add_source(config::Environment::with_prefix("APP"))
        .build()
        .unwrap();

    // Print out our settings (as a HashMap)
    println!(
        "{:?}",
        settings
            .try_deserialize::<HashMap<String, String>>()
            .unwrap()
    );
}

// 示例
use config::Config;

#[derive(Debug, Default, serde_derive::Deserialize, PartialEq, Eq)]
struct AppConfig {
    list: Vec<String>, // 环境变量值被风格解析为 Vec<String>
}

fn main() {
    std::env::set_var("APP_LIST", "Hello World");

    let config = Config::builder()
        .add_source(
            config::Environment::with_prefix("APP")
                .try_parsing(true)
                .separator("_")
                .list_separator(" "), // 将所有环境变量的值,使用该分隔符风格,结果为 Vec<String>
        )
        .build()
        .unwrap();

    // 将配置反序列化到 AppConfig 类型对象
    let app: AppConfig = config.try_deserialize().unwrap();

    assert_eq!(app.list, vec![String::from("Hello"), String::from("World")]);
    std::env::remove_var("APP_LIST");
}

// buidler 示例
let mut builder = Config::builder()
    .set_default("default", "1")?
    .add_source(File::new("config/settings", FileFormat::Json))
    //  .add_async_source(...)
    .set_override("override", "1")?;

match builder.build() {
    Ok(config) => {
        // use your config
    },
    Err(e) => {
        // something went wrong
    }
}

let mut builder = Config::builder();
builder = builder.set_default("default", "1")?; // 优先级最低
builder = builder.add_source(File::new("config/settings", FileFormat::Json));
// 后续的 source 覆盖前面 source 的配置参数
builder = builder.add_source(File::new("config/settings.prod", FileFormat::Json));
builder = builder.set_override("override", "1")?; // 优先级最高

自定义文件格式:

  1. 创建 File 类型的 Source:<:new>(name: &str, format: FileStoredFormat + ‘static): 指定文件名路径和它的格式;
    • format 需要实现 Format 和 FileStoredFormat trait;
    • Format trait 返回一个 Map<String, config::Value> , key 为配置参数 key 名称,value 为该 key 的配置参数值(来源于文件);
    • FileStoredFormat trait 返回支持的文件格式名称后缀;
  2. 创建 Config Builder,然后添加上面创建的 File Source;
use config::{Config, File, FileStoredFormat, Format, Map, Value, ValueKind};
use std::io::{Error, ErrorKind};

/// The private and public key sources will be read into their associated variable:
#[derive(serde::Deserialize, Clone, Debug)]
pub struct Settings {
    // 各 field 对应 PemFile 实现的 Format trait 返回的 map 的 key。
    pub private_key: Option<String>,
    pub public_key: Option<String>,
}

fn main() {
    // Sourcing from two separate files for the `Settings` struct,:
    let file_public_key = File::new("examples/custom_file_format/files/public.pem", PemFile);
    let file_private_key = File::new("examples/custom_file_format/files/private.pem", PemFile);

    // Provide the sources and build the config object:
    // Both are marked as optional to avoid failure if the file doesn't exist.
    let settings = Config::builder()
        .add_source(file_public_key.required(false))
        .add_source(file_private_key.required(false))
        .build()
        .unwrap();

    // Deserialize the config object into your Settings struct:
    let settings: Settings = settings.try_deserialize().unwrap();
    println!("{:#?}", settings);
}

#[derive(Debug, Clone)]
pub struct PemFile;

impl Format for PemFile {
    fn parse(&self, uri: Option<&String>, text: &str, ) -> Result<Map<String, config::Value>, Box<dyn std::error::Error + Send + Sync>> {
        // Store any valid keys into this map, they'll be merged with other sources into the final
        // config map:
        let mut result = Map::new();

        // Identify the PEM encoded data type by the first occurrence found: NOTE: This example is
        // kept simple, multiple or other encoded types are not handled.
        let key_type = vec!["PUBLIC", "PRIVATE"]
            .into_iter()
            .find(|s| text.contains(s));

        let key = match key_type {
            Some("PRIVATE") => "private_key",
            Some("PUBLIC") => "public_key",
            // Otherwise fail with an error message (the filename is implicitly appended):
            _ => {
                return Err(Box::new(Error::new(
                    ErrorKind::InvalidData,
                    "PEM file did not contain a Private or Public key",
                )))
            }
        };

        result.insert(
            key.to_owned(),
            Value::new(uri, ValueKind::String(text.into())), // String 类型值
        );

        Ok(result)
    }
}

// A slice of extensions associated to this format, when an extension is omitted from a file
// source, these will be tried implicitly:
impl FileStoredFormat for PemFile {
    fn file_extensions(&self) -> &'static [&'static str] {
        &["pem"]
    }
}
rust crate - 这篇文章属于一个选集。
§ 13: 本文

相关文章

axum
··14835 字
Rust Rust-Crate
axum 是基于 hyper 实现的高性能异步 HTTP 1/2 Server 库。
clap
··5454 字
Rust Rust-Crate
clap 用于快速构建命令行程序,提供命令&参数定义、解析等功能。
diesel
··34358 字
Rust Rust-Crate
diesel 是高性能的 ORM 和 Query Builder,crates.io 使用它来操作数据库。
http/http_body crate
··5416 字
Rust Rust-Crate
http/http_body crate 是公共的 http 和 body 定义,在 tokio 系列的 HTTP 库,如 hyper/axum/reqwest 中得到广泛应用。