zombienet_support/
replacer.rs1use std::collections::{HashMap, HashSet};
2
3use lazy_static::lazy_static;
4use regex::{Captures, Regex};
5use tracing::{trace, warn};
6
7use crate::constants::{SHOULD_COMPILE, THIS_IS_A_BUG};
8
9lazy_static! {
10 static ref RE: Regex = Regex::new(r#"\{\{([a-zA-Z0-9_]*)\}\}"#)
11 .unwrap_or_else(|_| panic!("{SHOULD_COMPILE}, {THIS_IS_A_BUG}"));
12 static ref TOKEN_PLACEHOLDER: Regex = Regex::new(r#"\{\{ZOMBIE:(.*?):(.*?)\}\}"#)
13 .unwrap_or_else(|_| panic!("{SHOULD_COMPILE}, {THIS_IS_A_BUG}"));
14 static ref PLACEHOLDER_COMPAT: HashMap<&'static str, &'static str> = {
15 let mut m = HashMap::new();
16 m.insert("multiAddress", "multiaddr");
17 m.insert("wsUri", "ws_uri");
18 m.insert("prometheusUri", "prometheus_uri");
19
20 m
21 };
22}
23
24pub fn has_tokens(text: &str) -> bool {
26 TOKEN_PLACEHOLDER.is_match(text)
27}
28
29pub fn apply_replacements(text: &str, replacements: &HashMap<&str, &str>) -> String {
30 let augmented_text = RE.replace_all(text, |caps: &Captures| {
31 if let Some(replacements_value) = replacements.get(&caps[1]) {
32 replacements_value.to_string()
33 } else {
34 caps[0].to_string()
35 }
36 });
37
38 augmented_text.to_string()
39}
40
41pub fn apply_env_replacements(text: &str) -> String {
42 let augmented_text = RE.replace_all(text, |caps: &Captures| {
43 if let Ok(replacements_value) = std::env::var(&caps[1]) {
44 replacements_value
45 } else {
46 caps[0].to_string()
47 }
48 });
49
50 augmented_text.to_string()
51}
52
53pub fn apply_running_network_replacements(text: &str, network: &serde_json::Value) -> String {
54 let augmented_text = TOKEN_PLACEHOLDER.replace_all(text, |caps: &Captures| {
55 trace!("appling replacements for caps: {caps:#?}");
56 if let Some(node) = network.get(&caps[1]) {
57 trace!("caps1 {} - node: {node}", &caps[1]);
58 let field = *PLACEHOLDER_COMPAT.get(&caps[2]).unwrap_or(&&caps[2]);
59 if let Some(val) = node.get(field) {
60 trace!("caps2 {} - node: {node}", field);
61 val.as_str().unwrap_or("Invalid string").to_string()
62 } else {
63 warn!(
64 "⚠️ The node with name {} doesn't have the value {} in context",
65 &caps[1], &caps[2]
66 );
67 caps[0].to_string()
68 }
69 } else {
70 warn!("⚠️ No node with name {} in context", &caps[1]);
71 caps[0].to_string()
72 }
73 });
74
75 augmented_text.to_string()
76}
77
78pub fn get_tokens_to_replace(text: &str) -> HashSet<String> {
79 let mut tokens = HashSet::new();
80
81 TOKEN_PLACEHOLDER
82 .captures_iter(text)
83 .for_each(|caps: Captures| {
84 tokens.insert(caps[1].to_string());
85 });
86
87 tokens
88}
89
90#[cfg(test)]
91mod tests {
92 use serde_json::json;
93
94 use super::*;
95
96 #[test]
97 fn replace_should_works() {
98 let text = "some {{namespace}}";
99 let mut replacements = HashMap::new();
100 replacements.insert("namespace", "demo-123");
101 let res = apply_replacements(text, &replacements);
102 assert_eq!("some demo-123".to_string(), res);
103 }
104
105 #[test]
106 fn replace_env_should_works() {
107 let text = "some {{namespace}}";
108 std::env::set_var("namespace", "demo-123");
109 let res = apply_env_replacements(text);
112 assert_eq!("some demo-123".to_string(), res);
113 }
114
115 #[test]
116 fn replace_multiple_should_works() {
117 let text = r#"some {{namespace}}
118 other is {{other}}"#;
119 let augmented_text = r#"some demo-123
120 other is other-123"#;
121
122 let mut replacements = HashMap::new();
123 replacements.insert("namespace", "demo-123");
124 replacements.insert("other", "other-123");
125 let res = apply_replacements(text, &replacements);
126 assert_eq!(augmented_text, res);
127 }
128
129 #[test]
130 fn replace_multiple_with_missing_should_works() {
131 let text = r#"some {{namespace}}
132 other is {{other}}"#;
133 let augmented_text = r#"some demo-123
134 other is {{other}}"#;
135
136 let mut replacements = HashMap::new();
137 replacements.insert("namespace", "demo-123");
138
139 let res = apply_replacements(text, &replacements);
140 assert_eq!(augmented_text, res);
141 }
142
143 #[test]
144 fn replace_without_replacement_should_leave_text_unchanged() {
145 let text = "some {{namespace}}";
146 let mut replacements = HashMap::new();
147 replacements.insert("other", "demo-123");
148 let res = apply_replacements(text, &replacements);
149 assert_eq!(text.to_string(), res);
150 }
151
152 #[test]
153 fn replace_running_network_should_work() {
154 let network = json!({
155 "alice" : {
156 "multiaddr": "some/demo/127.0.0.1"
157 }
158 });
159
160 let res = apply_running_network_replacements("{{ZOMBIE:alice:multiaddr}}", &network);
161 assert_eq!(res.as_str(), "some/demo/127.0.0.1");
162 }
163
164 #[test]
165 fn replace_running_network_with_compat_should_work() {
166 let network = json!({
167 "alice" : {
168 "multiaddr": "some/demo/127.0.0.1"
169 }
170 });
171
172 let res = apply_running_network_replacements("{{ZOMBIE:alice:multiAddress}}", &network);
173 assert_eq!(res.as_str(), "some/demo/127.0.0.1");
174 }
175
176 #[test]
177 fn replace_running_network_with_missing_field_should_not_replace_nothing() {
178 let network = json!({
179 "alice" : {
180 "multiaddr": "some/demo/127.0.0.1"
181 }
182 });
183
184 let res = apply_running_network_replacements("{{ZOMBIE:alice:someField}}", &network);
185 assert_eq!(res.as_str(), "{{ZOMBIE:alice:someField}}");
186 }
187
188 #[test]
189 fn get_tokens_to_replace_should_work() {
190 let res = get_tokens_to_replace("{{ZOMBIE:alice:multiaddr}} {{ZOMBIE:bob:multiaddr}}");
191 let mut expected = HashSet::new();
192 expected.insert("alice".to_string());
193 expected.insert("bob".to_string());
194
195 assert_eq!(res, expected);
196 }
197}