foundry_config/
resolve.rs
1use regex::Regex;
4use std::{env, env::VarError, fmt, sync::LazyLock};
5
6pub static RE_PLACEHOLDER: LazyLock<Regex> =
8 LazyLock::new(|| Regex::new(r"(?m)(?P<outer>\$\{\s*(?P<inner>.*?)\s*})").unwrap());
9
10#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct UnresolvedEnvVarError {
13 pub unresolved: String,
15 pub var: String,
17 pub source: VarError,
19}
20
21impl UnresolvedEnvVarError {
22 pub fn try_resolve(&self) -> Result<String, Self> {
24 interpolate(&self.unresolved)
25 }
26
27 fn is_simple(&self) -> bool {
28 RE_PLACEHOLDER.captures_iter(&self.unresolved).count() <= 1
29 }
30}
31
32impl fmt::Display for UnresolvedEnvVarError {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 write!(f, "environment variable `{}` ", self.var)?;
35 f.write_str(match self.source {
36 VarError::NotPresent => "not found",
37 VarError::NotUnicode(_) => "is not valid unicode",
38 })?;
39 if !self.is_simple() {
40 write!(f, " in `{}`", self.unresolved)?;
41 }
42 Ok(())
43 }
44}
45
46impl std::error::Error for UnresolvedEnvVarError {
47 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
48 Some(&self.source)
49 }
50}
51
52pub fn interpolate(input: &str) -> Result<String, UnresolvedEnvVarError> {
54 let mut res = input.to_string();
55
56 for caps in RE_PLACEHOLDER.captures_iter(input) {
58 let var = &caps["inner"];
59 let value = env::var(var).map_err(|source| UnresolvedEnvVarError {
60 unresolved: input.to_string(),
61 var: var.to_string(),
62 source,
63 })?;
64
65 res = res.replacen(&caps["outer"], &value, 1);
66 }
67 Ok(res)
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn can_find_placeholder() {
76 let val = "https://eth-mainnet.alchemyapi.io/v2/346273846238426342";
77 assert!(!RE_PLACEHOLDER.is_match(val));
78
79 let val = "${RPC_ENV}";
80 assert!(RE_PLACEHOLDER.is_match(val));
81
82 let val = "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}";
83 assert!(RE_PLACEHOLDER.is_match(val));
84
85 let cap = RE_PLACEHOLDER.captures(val).unwrap();
86 assert_eq!(cap.name("outer").unwrap().as_str(), "${API_KEY}");
87 assert_eq!(cap.name("inner").unwrap().as_str(), "API_KEY");
88 }
89}