nimbus/
json.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use serde_json::{Map, Value};
6use std::collections::HashMap;
7
8#[cfg(feature = "stateful")]
9pub type JsonObject = Map<String, Value>;
10
11/// Replace any instance of [from] with [to] in any string within the [serde_json::Value].
12///
13/// This recursively descends into the object, looking at string values and keys.
14#[allow(dead_code)]
15pub(crate) fn replace_str(value: &mut Value, from: &str, to: &str) {
16    let replacer = create_str_replacer(from, to);
17    replace_str_with(value, &replacer);
18}
19
20/// Replace any instance of [from] with [to] in any string within the [serde_json::Value::Map].
21///
22/// This recursively descends into the object, looking at string values and keys.
23pub(crate) fn replace_str_in_map(map: &mut Map<String, Value>, from: &str, to: &str) {
24    let replacer = create_str_replacer(from, to);
25    replace_str_in_map_with(map, &replacer);
26}
27
28fn replace_str_with<F>(value: &mut Value, replacer: &F)
29where
30    F: Fn(&str) -> Option<String> + ?Sized,
31{
32    match value {
33        Value::String(s) => {
34            if let Some(r) = replacer(s) {
35                *s = r;
36            }
37        }
38
39        Value::Array(list) => {
40            for item in list.iter_mut() {
41                replace_str_with(item, replacer);
42            }
43        }
44
45        Value::Object(map) => {
46            replace_str_in_map_with(map, replacer);
47        }
48
49        _ => (),
50    };
51}
52
53pub(crate) fn replace_str_in_map_with<F>(map: &mut Map<String, Value>, replacer: &F)
54where
55    F: Fn(&str) -> Option<String> + ?Sized,
56{
57    // Replace values in place.
58    for v in map.values_mut() {
59        replace_str_with(v, replacer);
60    }
61
62    // Replacing keys in place is a little trickier.
63    let mut changes = HashMap::new();
64    for k in map.keys() {
65        if let Some(new) = replacer(k) {
66            changes.insert(k.to_owned(), new);
67        }
68    }
69
70    for (k, new) in changes {
71        let v = map.remove(&k).unwrap();
72        _ = map.insert(new, v);
73    }
74}
75
76fn create_str_replacer<'a>(from: &'a str, to: &'a str) -> impl Fn(&str) -> Option<String> + 'a {
77    move |s: &str| -> Option<String> {
78        if s.contains(from) {
79            Some(s.replace(from, to))
80        } else {
81            None
82        }
83    }
84}
85
86#[cfg(test)]
87mod unit_tests {
88    use super::*;
89    use serde_json::json;
90
91    #[test]
92    fn test_replace_str() {
93        let mut value = json!("{test}");
94        replace_str(&mut value, "{test}", "success");
95        assert_eq!(value, json!("success"));
96
97        let mut value = json!("{test}-postfix");
98        replace_str(&mut value, "{test}", "success");
99        assert_eq!(value, json!("success-postfix"));
100
101        let mut value = json!("prefix-{test}");
102        replace_str(&mut value, "{test}", "success");
103        assert_eq!(value, json!("prefix-success"));
104
105        let mut value = json!("prefix-{test}-postfix");
106        replace_str(&mut value, "{test}", "success");
107        assert_eq!(value, json!("prefix-success-postfix"));
108
109        let mut value = json!("prefix-{test}-multi-{test}-postfix");
110        replace_str(&mut value, "{test}", "success");
111        assert_eq!(value, json!("prefix-success-multi-success-postfix"));
112    }
113
114    #[test]
115    fn test_replace_str_in_array() {
116        let mut value = json!(["alice", "bob", "{placeholder}", "daphne"]);
117        replace_str(&mut value, "{placeholder}", "charlie");
118        assert_eq!(value, json!(["alice", "bob", "charlie", "daphne"]));
119    }
120
121    #[test]
122    fn test_replace_str_in_map() {
123        let mut value = json!({
124            "key": "{test}",
125            "not": true,
126            "or": 2,
127        });
128        replace_str(&mut value, "{test}", "success");
129        assert_eq!(
130            value,
131            json!({
132                "key": "success",
133                "not": true,
134                "or": 2,
135            })
136        );
137    }
138
139    #[test]
140    fn test_replace_str_in_map_keys() {
141        let mut value = json!({
142            "{test}-en-US": "{test}",
143            "not": true,
144            "or": 2,
145        });
146        replace_str(&mut value, "{test}", "success");
147        assert_eq!(
148            value,
149            json!({
150                "success-en-US": "success",
151                "not": true,
152                "or": 2,
153            })
154        );
155    }
156
157    #[test]
158    fn test_replace_str_mixed() {
159        let mut value = json!({
160            "messages": {
161                "{test}-en-US": {
162                    "test": "{test}"
163                },
164                "{test}{test}": {
165                    "test": "{test}{test}"
166                }
167            }
168        });
169        replace_str(&mut value, "{test}", "success");
170        assert_eq!(
171            value,
172            json!({
173                "messages": {
174                    "success-en-US": {
175                        "test": "success"
176                    },
177                    "successsuccess": {
178                        "test": "successsuccess"
179                    }
180                }
181            })
182        );
183    }
184}