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