ferron_common/util/
config_placeholders.rs1use 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 let Some(lb_offset) = input[cursor..].find('{') else {
17 output.push_str(&input[cursor..]);
19 break;
20 };
21 let lb = cursor + lb_offset;
22
23 let Some(rb_offset) = input[lb + 1..].find('}') else {
25 output.push_str(&input[cursor..]);
27 break;
28 };
29 let rb = lb + 1 + rb_offset;
30
31 output.push_str(&input[cursor..lb]);
33
34 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 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 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 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 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}