rust实现解析yml配置
任何项目都离不开对于配置文件的读取和解析,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"
serde
、serde_json
和schemars
是用来做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配置文件就完成了。
转载自:https://juejin.cn/post/7129396788531822606