Skip to main content

ferron_common/util/
config_placeholders.rs

1use std::env;
2
3fn resolve_placeholder(kind: &str, value: &str) -> Option<String> {
4  match kind {
5    "env" => env::var(value).ok(),
6    _ => None,
7  }
8}
9
10pub fn replace_placeholders(input: &str) -> String {
11  let mut output = String::new();
12  let mut cursor = 0;
13
14  while cursor < input.len() {
15    // Find the next opening brace
16    let Some(lb_offset) = input[cursor..].find('{') else {
17      // No more placeholders, push remaining text and break
18      output.push_str(&input[cursor..]);
19      break;
20    };
21    let lb = cursor + lb_offset;
22
23    // Look for closing brace after the opening brace
24    let Some(rb_offset) = input[lb + 1..].find('}') else {
25      // No closing brace found, push the rest of the string as-is
26      output.push_str(&input[cursor..]);
27      break;
28    };
29    let rb = lb + 1 + rb_offset;
30
31    // Push text before this placeholder
32    output.push_str(&input[cursor..lb]);
33
34    // Try to resolve the placeholder (no colon or unknown kind => None)
35    let placeholder = &input[lb + 1..rb];
36    let resolved = placeholder
37      .split_once(':')
38      .and_then(|(kind, value)| resolve_placeholder(kind, value));
39
40    match resolved {
41      Some(value) => output.push_str(&value),
42      // Keep original placeholder
43      None => output.push_str(&input[lb..=rb]),
44    }
45
46    cursor = rb + 1;
47  }
48
49  output
50}
51
52#[cfg(test)]
53mod tests {
54  use super::*;
55  use std::env;
56
57  #[test]
58  fn env_var_missing() {
59    let result = resolve_placeholder("env", "LALA_SHOULD_NOT_EXIST");
60    assert_eq!(result, None);
61  }
62
63  #[test]
64  fn env_var_exists() {
65    env::set_var("TEST_ENV_EXISTS", "value");
66
67    let result = resolve_placeholder("env", "TEST_ENV_EXISTS");
68    assert_eq!(result, Some("value".to_string()));
69  }
70
71  #[test]
72  fn passthrough_no_placeholders() {
73    let input = "LALA";
74    let result = replace_placeholders(input);
75    assert_eq!(result, input);
76  }
77
78  #[test]
79  fn single_env_placeholder() {
80    env::set_var("TEST_HOME", "/home/test");
81
82    let result = replace_placeholders("{env:TEST_HOME}");
83    assert_eq!(result, "/home/test");
84  }
85
86  #[test]
87  fn unknown_kind_passthrough() {
88    let result = replace_placeholders("{envA:HOME}");
89    assert_eq!(result, "{envA:HOME}");
90  }
91
92  #[test]
93  fn interpolate_env_with_suffix() {
94    env::set_var("TEST_HOME", "/home/test");
95
96    let result = replace_placeholders("{env:TEST_HOME}/src/modules");
97    assert_eq!(result, "/home/test/src/modules");
98  }
99
100  #[test]
101  fn interpolate_multiple_env_values() {
102    env::set_var("TEST_HOME", "/home/test");
103    env::set_var("TEST_USER", "user");
104
105    let input = "prefix_{env:TEST_HOME}_middle_{env:TEST_USER}_suffix";
106    let result = replace_placeholders(input);
107
108    let expected = "prefix_/home/test_middle_user_suffix";
109    assert_eq!(result, expected);
110  }
111
112  #[test]
113  fn plain_string_passthrough() {
114    let input = "plain_string_without_env";
115    let result = replace_placeholders(input);
116    assert_eq!(result, input);
117  }
118
119  #[test]
120  fn missing_closing_brace() {
121    let result = replace_placeholders("{env:TEST_HOME");
122    // Reference behavior: push all remaining text including the unmatched '{'
123    assert_eq!(result, "{env:TEST_HOME");
124  }
125
126  #[test]
127  fn missing_closing_brace_with_text_after() {
128    let result = replace_placeholders("{env:TEST_HOME and then more text");
129    // Should keep everything including the opening brace
130    assert_eq!(result, "{env:TEST_HOME and then more text");
131  }
132
133  #[test]
134  fn nested_or_multiple_braces() {
135    let result = replace_placeholders("text {first} middle {second");
136    // Second has no closing brace, so everything from first { to end stays
137    assert_eq!(result, "text {first} middle {second");
138  }
139
140  #[test]
141  fn nonexistent_env_var() {
142    let result = replace_placeholders("{env:THIS_SHOULD_NOT_EXIST_123}");
143    assert_eq!(result, "{env:THIS_SHOULD_NOT_EXIST_123}");
144  }
145
146  #[test]
147  fn nonexistent_env_var_in_interpolation() {
148    let result = replace_placeholders("prefix_{env:THIS_SHOULD_NOT_EXIST_456}_suffix");
149    assert_eq!(result, "prefix_{env:THIS_SHOULD_NOT_EXIST_456}_suffix");
150  }
151
152  #[test]
153  fn placeholder_without_colon_passthrough() {
154    let result = replace_placeholders("{justtext}");
155    assert_eq!(result, "{justtext}");
156  }
157
158  #[test]
159  fn multiple_placeholders_mixed_with_missing() {
160    env::set_var("EXISTS", "found");
161    let result = replace_placeholders("{env:EXISTS} then {missing");
162    assert_eq!(result, "found then {missing");
163  }
164}