zombienet_support/
replacer.rs

1use 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
24/// Return true if the text contains any TOKEN_PLACEHOLDER
25pub 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 mut replacements = HashMap::new();
110        // replacements.insert("namespace", "demo-123");
111        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}