任何项目都离不开对于配置文件的读取和解析,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配置文件就完成了。