writerslogic_warm/
compatibility.rs1use std::env::VarError;
5use std::ffi::OsString;
6
7pub fn var_os(name: &str) -> Option<OsString> {
9 std::env::var_os(name).or_else(|| {
10 name.strip_prefix("WARM_")
11 .and_then(|suffix| std::env::var_os(format!("HMS_{suffix}")))
12 })
13}
14
15pub fn var(name: &str) -> Result<String, VarError> {
16 var_os(name)
17 .ok_or(VarError::NotPresent)?
18 .into_string()
19 .map_err(VarError::NotUnicode)
20}
21
22pub fn env_name(current: &'static str, legacy: &'static str) -> &'static str {
24 if std::env::var_os(current).is_none() && std::env::var_os(legacy).is_some() {
25 legacy
26 } else {
27 current
28 }
29}
30
31#[cfg(test)]
32mod tests {
33 use super::*;
34
35 const CURRENT: &str = "WARM_COMPATIBILITY_TEST_VALUE";
36 const LEGACY: &str = "HMS_COMPATIBILITY_TEST_VALUE";
37
38 #[test]
39 fn environment_precedence() {
40 for case in ["absent", "legacy", "current", "both", "empty", "unicode"] {
41 let mut child = std::process::Command::new(std::env::current_exe().unwrap());
42 child
43 .args(["--exact", "compatibility::tests::environment_probe"])
44 .env("WARM_COMPATIBILITY_TEST_CASE", case)
45 .env_remove(CURRENT)
46 .env_remove(LEGACY);
47 match case {
48 "legacy" => {
49 child.env(LEGACY, "17");
50 }
51 "current" => {
52 child.env(CURRENT, "23");
53 }
54 "both" => {
55 child.env(LEGACY, "17").env(CURRENT, "23");
56 }
57 "empty" => {
58 child.env(LEGACY, "17").env(CURRENT, "");
59 }
60 "unicode" => {
61 child.env(LEGACY, "17").env(CURRENT, "記憶");
62 }
63 _ => {}
64 }
65 assert!(child.status().unwrap().success(), "case {case}");
66 }
67 #[cfg(unix)]
68 {
69 use std::os::unix::ffi::OsStringExt;
70 let status = std::process::Command::new(std::env::current_exe().unwrap())
71 .args(["--exact", "compatibility::tests::environment_probe"])
72 .env("WARM_COMPATIBILITY_TEST_CASE", "nonunicode")
73 .env(LEGACY, "17")
74 .env(CURRENT, OsString::from_vec(vec![0xff]))
75 .status()
76 .unwrap();
77 assert!(status.success());
78 }
79 }
80
81 #[test]
82 fn environment_probe() {
83 let Ok(case) = std::env::var("WARM_COMPATIBILITY_TEST_CASE") else {
84 return;
85 };
86 if case == "nonunicode" {
87 assert!(matches!(var(CURRENT), Err(VarError::NotUnicode(_))));
88 assert_eq!(env_name(CURRENT, LEGACY), CURRENT);
89 return;
90 }
91 let expected = match case.as_str() {
92 "absent" => None,
93 "legacy" => Some("17"),
94 "current" | "both" => Some("23"),
95 "empty" => Some(""),
96 "unicode" => Some("記憶"),
97 _ => panic!("unknown test case"),
98 };
99 assert_eq!(var(CURRENT).ok().as_deref(), expected);
100 assert_eq!(
101 env_name(CURRENT, LEGACY),
102 if case == "legacy" { LEGACY } else { CURRENT }
103 );
104 #[cfg(feature = "cli")]
105 {
106 use clap::Parser;
107 #[derive(Parser)]
108 struct Settings {
109 #[arg(long, env = env_name(CURRENT, LEGACY))]
110 value: Option<String>,
111 }
112 assert_eq!(
113 Settings::try_parse_from(["test"]).unwrap().value.as_deref(),
114 expected
115 );
116 assert_eq!(
117 Settings::try_parse_from(["test", "--value", "31"])
118 .unwrap()
119 .value
120 .as_deref(),
121 Some("31")
122 );
123 }
124 }
125}