Skip to main content

lade_sdk/
lib.rs

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}