mirror of
https://mirror.suhoan.cn/https://github.com/EasyTier/EasyTier.git
synced 2025-12-19 00:07:24 +08:00
248 lines
8.6 KiB
Rust
248 lines
8.6 KiB
Rust
//! 环境变量解析模块
|
||
//!
|
||
//! 提供配置文件中环境变量占位符的解析功能
|
||
//! 支持 Shell 风格的语法:${VAR}、$VAR、${VAR:-default} 等
|
||
|
||
use std::borrow::Cow;
|
||
|
||
/// 解析字符串中的环境变量占位符
|
||
///
|
||
/// 支持的语法:
|
||
/// - `${VAR_NAME}` - 标准格式(推荐)
|
||
/// - `$VAR_NAME` - 简写格式
|
||
/// - `${VAR_NAME:-default}` - 带默认值(bash 标准语法)
|
||
///
|
||
/// # 参数
|
||
/// - `text`: 待解析的字符串
|
||
///
|
||
/// # 返回值
|
||
/// - `String`: 替换后的字符串
|
||
/// - `bool`: 是否检测到并替换了环境变量
|
||
pub fn expand_env_vars(text: &str) -> (String, bool) {
|
||
// 使用 shellexpand::env() 解析环境变量
|
||
// 该函数仅处理环境变量,不处理 tilde (~) 扩展,适合配置文件场景
|
||
match shellexpand::env(text) {
|
||
Ok(expanded) => {
|
||
// 通过比较原始字符串和扩展后的字符串判断是否发生了替换
|
||
let changed = match &expanded {
|
||
Cow::Borrowed(_) => false, // 未发生变化,仍是借用
|
||
Cow::Owned(_) => true, // 发生了变化,产生了新字符串
|
||
};
|
||
|
||
(expanded.into_owned(), changed)
|
||
}
|
||
Err(e) => {
|
||
// 如果解析失败(例如变量引用语法错误),记录警告并返回原字符串
|
||
tracing::warn!("Failed to expand environment variables in config: {}", e);
|
||
(text.to_string(), false)
|
||
}
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_expand_standard_syntax() {
|
||
std::env::set_var("TEST_VAR_STANDARD", "test_value");
|
||
let (result, changed) = expand_env_vars("secret=${TEST_VAR_STANDARD}");
|
||
assert_eq!(result, "secret=test_value");
|
||
assert!(changed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_expand_short_syntax() {
|
||
std::env::set_var("TEST_VAR_SHORT", "short_value");
|
||
let (result, changed) = expand_env_vars("key=$TEST_VAR_SHORT");
|
||
assert_eq!(result, "key=short_value");
|
||
assert!(changed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_expand_with_default() {
|
||
// 确保变量未定义
|
||
std::env::remove_var("UNDEFINED_VAR_WITH_DEFAULT");
|
||
let (result, changed) = expand_env_vars("port=${UNDEFINED_VAR_WITH_DEFAULT:-8080}");
|
||
assert_eq!(result, "port=8080");
|
||
assert!(changed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_no_env_vars() {
|
||
let (result, changed) = expand_env_vars("plain text without variables");
|
||
assert_eq!(result, "plain text without variables");
|
||
assert!(!changed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_empty_string() {
|
||
let (result, changed) = expand_env_vars("");
|
||
assert_eq!(result, "");
|
||
assert!(!changed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_multiple_vars() {
|
||
std::env::set_var("VAR1", "value1");
|
||
std::env::set_var("VAR2", "value2");
|
||
let (result, changed) = expand_env_vars("${VAR1} and ${VAR2}");
|
||
assert_eq!(result, "value1 and value2");
|
||
assert!(changed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_undefined_var_without_default() {
|
||
// 确保变量未定义
|
||
std::env::remove_var("COMPLETELY_UNDEFINED_VAR");
|
||
let (result, changed) = expand_env_vars("value=${COMPLETELY_UNDEFINED_VAR}");
|
||
// shellexpand::env 对未定义的变量会保持原样
|
||
assert_eq!(result, "value=${COMPLETELY_UNDEFINED_VAR}");
|
||
assert!(!changed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_complex_toml_config() {
|
||
std::env::set_var("ET_SECRET", "my-secret-key");
|
||
std::env::set_var("ET_PORT", "11010");
|
||
|
||
let config = r#"
|
||
[network_identity]
|
||
network_name = "test-network"
|
||
network_secret = "${ET_SECRET}"
|
||
|
||
[[peer]]
|
||
uri = "tcp://127.0.0.1:${ET_PORT}"
|
||
"#;
|
||
|
||
let (result, changed) = expand_env_vars(config);
|
||
assert!(changed);
|
||
assert!(result.contains(r#"network_secret = "my-secret-key""#));
|
||
assert!(result.contains(r#"uri = "tcp://127.0.0.1:11010""#));
|
||
}
|
||
|
||
#[test]
|
||
fn test_escape_syntax_double_dollar() {
|
||
std::env::set_var("ESCAPED_VAR", "should_not_expand");
|
||
// shellexpand 使用 $$ 作为转义序列,表示字面量的单个 $
|
||
// $$ 会被转义为单个 $,不会触发变量扩展
|
||
let (result, changed) = expand_env_vars("value=$${ESCAPED_VAR}");
|
||
assert_eq!(result, "value=${ESCAPED_VAR}");
|
||
assert!(changed); // $$ -> $ 被视为一次变换
|
||
}
|
||
|
||
#[test]
|
||
fn test_escape_syntax_backslash() {
|
||
std::env::set_var("ESCAPED_VAR", "should_not_expand");
|
||
// shellexpand 中反斜杠转义的行为:\$ 会展开为 \<变量值>
|
||
// 这不是推荐的转义方式,此测试仅为记录实际行为
|
||
let (result, changed) = expand_env_vars(r"value=\${ESCAPED_VAR}");
|
||
assert_eq!(result, r"value=\should_not_expand");
|
||
assert!(changed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_multiple_dollar_signs() {
|
||
std::env::set_var("TEST_VAR", "value");
|
||
// 测试多个连续的 $ 符号
|
||
let (result1, changed1) = expand_env_vars("$$");
|
||
assert_eq!(result1, "$");
|
||
assert!(changed1);
|
||
|
||
let (result2, changed2) = expand_env_vars("$$$$");
|
||
assert_eq!(result2, "$$");
|
||
assert!(changed2);
|
||
|
||
// $$ 后跟变量扩展
|
||
let (result3, changed3) = expand_env_vars("$$$TEST_VAR");
|
||
assert_eq!(result3, "$value");
|
||
assert!(changed3);
|
||
}
|
||
|
||
#[test]
|
||
fn test_empty_var_value() {
|
||
std::env::set_var("EMPTY_VAR", "");
|
||
let (result, changed) = expand_env_vars("value=${EMPTY_VAR}");
|
||
// 变量存在但值为空
|
||
assert_eq!(result, "value=");
|
||
assert!(changed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_default_with_special_chars() {
|
||
std::env::remove_var("UNDEFINED_SPECIAL");
|
||
// 测试默认值包含冒号、等号、空格等特殊字符
|
||
let (result, changed) = expand_env_vars("url=${UNDEFINED_SPECIAL:-http://localhost:8080}");
|
||
assert_eq!(result, "url=http://localhost:8080");
|
||
assert!(changed);
|
||
|
||
let (result2, changed2) = expand_env_vars("key=${UNDEFINED_SPECIAL:-name=value}");
|
||
assert_eq!(result2, "key=name=value");
|
||
assert!(changed2);
|
||
|
||
let (result3, changed3) = expand_env_vars("msg=${UNDEFINED_SPECIAL:-hello world}");
|
||
assert_eq!(result3, "msg=hello world");
|
||
assert!(changed3);
|
||
}
|
||
|
||
#[test]
|
||
fn test_var_name_with_numbers_underscores() {
|
||
std::env::set_var("VAR_123", "num_value");
|
||
std::env::set_var("_VAR", "underscore_prefix");
|
||
std::env::set_var("VAR_", "underscore_suffix");
|
||
|
||
let (result1, changed1) = expand_env_vars("${VAR_123}");
|
||
assert_eq!(result1, "num_value");
|
||
assert!(changed1);
|
||
|
||
let (result2, changed2) = expand_env_vars("${_VAR}");
|
||
assert_eq!(result2, "underscore_prefix");
|
||
assert!(changed2);
|
||
|
||
let (result3, changed3) = expand_env_vars("${VAR_}");
|
||
assert_eq!(result3, "underscore_suffix");
|
||
assert!(changed3);
|
||
}
|
||
|
||
#[test]
|
||
fn test_invalid_syntax() {
|
||
// 测试无效语法的处理
|
||
let (result1, changed1) = expand_env_vars("${}");
|
||
// shellexpand 会保留无效语法原样
|
||
assert_eq!(result1, "${}");
|
||
assert!(!changed1);
|
||
|
||
// 注意:未闭合的 ${VAR 实际上 shellexpand 会当作普通文本处理
|
||
// 它会尝试查找名为 "VAR" 的环境变量(到字符串末尾)
|
||
std::env::remove_var("VAR");
|
||
let (result2, _changed2) = expand_env_vars("incomplete ${VAR");
|
||
// 如果 VAR 未定义,shellexpand 会返回错误或保持原样
|
||
assert_eq!(result2, "incomplete ${VAR");
|
||
// 注意:changed2 的值取决于 shellexpand 是否认为这是有效语法
|
||
// 因此不对 changed2 做断言
|
||
}
|
||
|
||
#[test]
|
||
fn test_mixed_defined_undefined_vars() {
|
||
std::env::set_var("DEFINED_VAR", "defined");
|
||
std::env::remove_var("UNDEFINED_VAR");
|
||
|
||
// 混合已定义和未定义的变量
|
||
// shellexpand::env 在遇到未定义变量时会返回错误(默认行为)
|
||
// 因此整个字符串会保持不变
|
||
let (result, changed) = expand_env_vars("${DEFINED_VAR} and ${UNDEFINED_VAR}");
|
||
assert_eq!(result, "${DEFINED_VAR} and ${UNDEFINED_VAR}");
|
||
assert!(!changed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_nested_braces() {
|
||
std::env::set_var("OUTER", "outer_value");
|
||
// 嵌套的大括号是无效语法,shellexpand::env 会返回错误
|
||
let (result, changed) = expand_env_vars("${OUTER} and ${{INNER}}");
|
||
// 由于语法错误,整个字符串保持不变
|
||
assert_eq!(result, "${OUTER} and ${{INNER}}");
|
||
assert!(!changed);
|
||
}
|
||
}
|