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
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}