rust实现解析yml配置

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

任何项目都离不开对于配置文件的读取和解析,rust项目也一样。同样的,我们还是需要依赖第三方crate来帮助我们完成针对yml文件的读取和解析工作。

依赖

首先要做的就是引入第三方依赖:

[dependencies]
# 序列化工具
serde = { version = "1.0.140", features = ["derive"] }
serde_json = "1.0.75"
schemars = "0.8.8"
serde_yaml = "0.8.23"
# 初始化工具
lazy_static = "1.4.0"
复制代码

serdeserde_jsonschemars是用来做json序列化操作的;serde_yaml是用来解析yaml字符串的;

lazy_static是用来一次性初始化读取的配置,保持全局都是单例。

创建配置文件

我们在src目录同级创建application.yml和application-dev.yml:

application.yml

profiles:
  active: dev
复制代码

application-dev.yml

# mysql 配置
mysql:
  host: 127.0.0.1
  port: 3306
  username: root
  password: "123456"
  db_name: awesome_db
复制代码

创建配置对应的model

配置文件创建好后,借用面向对象的思想,我们应该要构造与之对应的数据模型,在rust中也就是struct:

use serde::{Serialize, Deserialize};
​
#[derive(Serialize, Deserialize, Debug)]
pub struct Profiles {
    pub active: String,
}
// 用来接收application.yml解析结果
#[derive(Serialize, Deserialize, Debug)]
pub struct EnvConfig {
    pub profiles: Profiles,
}
​
#[derive(Serialize, Deserialize, Debug)]
pub struct Mysql {
    pub host: String,
    pub port: u32,
    pub username: String,
    pub password: String,
    pub db_name: String,
}
// 用来接收application-dev.yml解析结果
#[derive(Serialize, Deserialize, Debug)]
pub struct GlobalConfig {
    // 解析对应的mysql配置
    pub mysql: Mysql,
    // 还可以添加其他需要解析的配置
  
}
复制代码

解析

我们前面已经把配置文件和对应的model都准备好了,下面最关键的就是如何把配置解析成指定的model:

// 加载指定配置文件
fn load_config<T>(path: &str) -> Option<T> where T: DeserializeOwned {
    // 1.通过std::fs读取配置文件内容
    // 2.通过serde_yaml解析读取到的yaml配置转换成json对象
    match serde_yaml::from_str::<RootSchema>(&std::fs::read_to_string(path).expect(&format!("failure read file {}", path))) {
        Ok(root_schema) => {
            // 通过serde_json把json对象转换指定的model
            let data = serde_json::to_string_pretty(&root_schema).expect("failure to parse RootSchema");
            let config = serde_json::from_str::<T>(&*data).expect(&format!("failure to format json str {}",&data));
            // 返回格式化结果
            Some(config)
        }
        Err(err) => {
            // 记录日志
            info!("{}",err);
            // 返回None
            None
        }
    }
}
复制代码

上面是一个抽象化的方法,也是最核心的方法。需要注意的是泛型对象得是DeserializeOwned,而不是Deserialize<'a>,原因在于serde_json::from_str::<T>()所需要的参数是一个有生命周期的参数,但是data在方法执行完毕后会被销毁掉,但是返回值并不是马上销毁,它还需要给外面的调用中使用,所以导致Deserialize<'a>解决不了问题,一直无法通过编译。DeserializeOwned就可以解决这种问题。

// 加载目标文件application.yml
fn load_env_config() -> Option<EnvConfig> {
    load_config::<EnvConfig>("application.yml")
}
// 根据环境加载application-{}.yml文件
fn load_global_config_from_env(active: String) -> Option<GlobalConfig> {
    let path = format!("application-{}.yml", active);
    load_config::<GlobalConfig>(&path)
}
// 真正对外暴露的方法,根据application.yml指定的环境变量动态加载对应的配置文件
pub fn load_global_config() -> Option<GlobalConfig> {
    if let Some(env_config) = load_env_config() {
        return load_global_config_from_env(env_config.profiles.active);
    }
    None
}
复制代码

测试

为了测试我们的代码是否能正常运行,我们编写一些简单的测试用例:

#[cfg(test)]
mod test {
    use crate::load_config::init_load_config::load_global_config;
    use crate::load_config::models::GlobalConfig;
​
    #[test]
    pub fn load_config_test() {
        match load_global_config() {
            None => {
                println!("None");
            }
            Some(config) => {
                println!("{:#?}", config);
            }
        }
    }
}
复制代码

输出结果如下:

Finished test [unoptimized + debuginfo] target(s) in 1.08s
Running unittests src/main.rs (target/debug/deps/hello_salvo-b9ecf8dc1aa86991)
GlobalConfig {
    mysql: Mysql {
        host: "127.0.0.1",
        port: 3306,
        username: "root",
        password: "123456",
        db_name: "awesome_db",
    },
}
复制代码

单例化

为了能在项目中使用咱们上面已经实现的全局配置参数,我们需要对它做一次初始化操作。也就是在整个项目中,只加载一次该配置:

use lazy_static::lazy_static;
use crate::load_config::init_load_config;
use crate::load_config::models::GlobalConfig;
​
lazy_static! {
    pub static ref GLOBAL_CONFIG:GlobalConfig=init_load_config::load_global_config().unwrap();
}
复制代码

通过lazy_static的宏来控制配置文件一次性载入后,在项目中只需要直接使用即可。

main函数使用

fn main() {
    let config = &GLOBAL_CONFIG;
    println!("{:?}", config.mysql);
}
复制代码

在main函数中,可以直接使用已经初始化成功后的GLOBAL_CONFIG了。

至此,我们使用rust解析yml配置文件就完成了。

猜你喜欢

转载自juejin.im/post/7129396788531822606