1use std::{
2 collections::HashMap,
3 path::{Path, PathBuf},
4};
5
6use anyhow::{Ok, Result};
7use rustc_hash::{FxHashMap, FxHashSet};
8
9mod providers;
10mod resolve;
11
12pub use providers::Providers;
13pub use providers::Warnings;
14pub use providers::compat;
15pub use resolve::{resolve, resolve_one};
16
17type Hydration = FxHashMap<String, String>;
18
19pub async fn hydrate(
20 env: HashMap<String, String>,
21 cwd: PathBuf,
22 extra_env: HashMap<String, String>,
23) -> Result<HashMap<String, String>> {
24 Ok(hydrate_with_maskable(env, cwd, extra_env).await?.0)
25}
26
27pub async fn hydrate_with_maskable(
28 env: HashMap<String, String>,
29 cwd: PathBuf,
30 extra_env: HashMap<String, String>,
31) -> Result<(HashMap<String, String>, FxHashSet<String>, Vec<String>)> {
32 let mut providers = Providers::new();
33 for value_or_uri in env.values() {
34 providers.add(value_or_uri.clone())?;
35 }
36 let warnings = Warnings::default();
37 let (hydration, maskable) = providers.resolve(&cwd, &extra_env, &warnings).await?;
38
39 let values = env
40 .into_iter()
41 .map(|(key, value_or_uri)| {
42 let value = hydration.get(&value_or_uri).cloned().unwrap_or_else(|| {
43 panic!(
44 "Cannot find {} in {}",
45 value_or_uri,
46 hydration
47 .keys()
48 .cloned()
49 .collect::<Vec<String>>()
50 .join(", ")
51 )
52 });
53 (key, value)
54 })
55 .collect();
56
57 Ok((values, maskable, warnings.take()))
58}
59
60pub async fn hydrate_one(
61 value: String,
62 cwd: &Path,
63 extra_env: &HashMap<String, String>,
64) -> Result<String> {
65 let mut providers = Providers::new();
66 providers.add(value.clone())?;
67 let (hydration, _) = providers
68 .resolve(cwd, extra_env, &Warnings::default())
69 .await?;
70 Ok(hydration.get(&value).unwrap().to_owned())
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use std::path::PathBuf;
77
78 #[tokio::test]
79 async fn test_hydrate_raw_values_with_empty_extra_env() {
80 let env = HashMap::from([
81 ("KEY1".to_string(), "value1".to_string()),
82 ("KEY2".to_string(), "!value2".to_string()),
83 ]);
84 let (values, maskable, _warnings) =
85 hydrate_with_maskable(env, PathBuf::from("."), HashMap::new())
86 .await
87 .unwrap();
88 assert_eq!(values.get("KEY1").unwrap(), "value1");
89 assert_eq!(values.get("KEY2").unwrap(), "value2");
90 assert!(maskable.is_empty());
91 }
92
93 #[tokio::test]
94 async fn test_hydrate_raw_values_with_extra_env_ignored_by_raw_provider() {
95 let env = HashMap::from([("KEY".to_string(), "rawval".to_string())]);
96 let extra = HashMap::from([("INJECTED".to_string(), "token123".to_string())]);
97 let (values, maskable, _warnings) = hydrate_with_maskable(env, PathBuf::from("."), extra)
98 .await
99 .unwrap();
100 assert_eq!(values.get("KEY").unwrap(), "rawval");
101 assert!(maskable.is_empty());
102 }
103
104 #[tokio::test]
105 async fn test_hydrate_one_raw_with_empty_extra_env() {
106 let result = hydrate_one("mytoken".to_string(), &PathBuf::from("."), &HashMap::new())
107 .await
108 .unwrap();
109 assert_eq!(result, "mytoken");
110 }
111
112 #[tokio::test]
113 async fn test_hydrate_duplicate_raw_values() {
114 let env = HashMap::from([
115 ("KEY1".to_string(), "a".to_string()),
116 ("KEY2".to_string(), "a".to_string()),
117 ]);
118 let (values, _, _warnings) = hydrate_with_maskable(env, PathBuf::from("."), HashMap::new())
119 .await
120 .unwrap();
121 assert_eq!(values.get("KEY1").unwrap(), "a");
122 assert_eq!(values.get("KEY2").unwrap(), "a");
123 }
124
125 #[tokio::test]
126 async fn test_hydrate_one_raw_bang_escape() {
127 let result = hydrate_one("!escaped".to_string(), &PathBuf::from("."), &HashMap::new())
128 .await
129 .unwrap();
130 assert_eq!(result, "escaped");
131 }
132}