1use serde_json::{Map, Value};
6use std::fmt;
7use std::sync::Arc;
8
9const DANGEROUS_KEYS: [&str; 3] = ["__proto__", "constructor", "prototype"];
10
11pub fn deep(mut base: Value, overlays: impl IntoIterator<Item = Value>) -> Value {
14 for overlay in overlays {
15 base = deep_one(base, overlay);
16 }
17 base
18}
19
20fn deep_one(base: Value, overlay: Value) -> Value {
21 match (base, overlay) {
22 (Value::Object(mut base), Value::Object(overlay)) => {
23 for (key, value) in overlay {
24 if DANGEROUS_KEYS.contains(&key.as_str()) {
25 continue;
26 }
27 let merged = match base.shift_remove(&key) {
28 Some(previous) => deep_one(previous, value),
29 None => clone_safe(value),
30 };
31 base.insert(key, merged);
32 }
33 Value::Object(base)
34 }
35 (Value::Array(base), Value::Array(overlay)) => {
36 let length = base.len().max(overlay.len());
37 let mut base = base.into_iter();
38 let mut overlay = overlay.into_iter();
39 Value::Array(
40 (0..length)
41 .map(|_| match (base.next(), overlay.next()) {
42 (Some(base), Some(overlay)) => deep_one(base, overlay),
43 (Some(base), None) => clone_safe(base),
44 (None, Some(overlay)) => clone_safe(overlay),
45 (None, None) => unreachable!("length is derived from both iterators"),
46 })
47 .collect(),
48 )
49 }
50 (_, overlay) => clone_safe(overlay),
51 }
52}
53
54fn clone_safe(value: Value) -> Value {
55 match value {
56 Value::Object(map) => Value::Object(
57 map.into_iter()
58 .filter(|(key, _)| !DANGEROUS_KEYS.contains(&key.as_str()))
59 .map(|(key, value)| (key, clone_safe(value)))
60 .collect(),
61 ),
62 Value::Array(values) => Value::Array(values.into_iter().map(clone_safe).collect()),
63 scalar => scalar,
64 }
65}
66
67pub fn str_value(value: &Value, max_len: i64) -> String {
70 if max_len <= 0 {
71 return String::new();
72 }
73 let rendered = match value {
74 Value::String(value) => value.clone(),
75 _ => serde_json::to_string(value).unwrap_or_default(),
76 };
77 let chars: Vec<char> = rendered.chars().collect();
78 let max_len = max_len as usize;
79 let output = if chars.len() > max_len {
80 if max_len >= 4 {
81 chars[..max_len - 3].iter().collect::<String>() + "..."
82 } else {
83 ".".repeat(max_len)
84 }
85 } else {
86 rendered
87 };
88 output
89 .chars()
90 .take(max_len)
91 .map(|value| match value {
92 '\r' | '\n' | '\t' => '.',
93 value => value,
94 })
95 .collect()
96}
97
98pub type ListModifier<T> = Arc<dyn Fn(&mut Vec<T>) -> Option<Vec<T>> + Send + Sync>;
102
103pub struct ListMods<T = Value> {
104 pub delete: Vec<isize>,
105 pub move_items: Vec<isize>,
106 pub custom: Option<ListModifier<T>>,
107}
108
109impl<T> ListMods<T> {
110 pub fn with_custom(
111 mut self,
112 custom: impl Fn(&mut Vec<T>) -> Option<Vec<T>> + Send + Sync + 'static,
113 ) -> Self {
114 self.custom = Some(Arc::new(custom));
115 self
116 }
117}
118
119impl<T> Default for ListMods<T> {
120 fn default() -> Self {
121 Self {
122 delete: Vec::new(),
123 move_items: Vec::new(),
124 custom: None,
125 }
126 }
127}
128
129impl<T> Clone for ListMods<T> {
130 fn clone(&self) -> Self {
131 Self {
132 delete: self.delete.clone(),
133 move_items: self.move_items.clone(),
134 custom: self.custom.clone(),
135 }
136 }
137}
138
139impl<T> fmt::Debug for ListMods<T> {
140 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
141 formatter
142 .debug_struct("ListMods")
143 .field("delete", &self.delete)
144 .field("move_items", &self.move_items)
145 .field("custom", &self.custom.as_ref().map(|_| "<function>"))
146 .finish()
147 }
148}
149
150impl<T> PartialEq for ListMods<T> {
151 fn eq(&self, other: &Self) -> bool {
152 self.delete == other.delete
153 && self.move_items == other.move_items
154 && match (&self.custom, &other.custom) {
155 (None, None) => true,
156 (Some(left), Some(right)) => Arc::ptr_eq(left, right),
157 _ => false,
158 }
159 }
160}
161
162impl<T> Eq for ListMods<T> {}
163
164pub fn modlist<T>(list: Vec<T>, mods: Option<&ListMods<T>>) -> Vec<T> {
166 let Some(mods) = mods else { return list };
167 let mut list: Vec<Option<T>> = list.into_iter().map(Some).collect();
168 for raw in &mods.delete {
169 let index = if *raw < 0 {
170 list.len().checked_sub(raw.unsigned_abs())
171 } else {
172 Some(*raw as usize)
173 };
174 if let Some(item) = index.and_then(|index| list.get_mut(index)) {
175 *item = None;
176 }
177 }
178 for pair in mods.move_items.chunks_exact(2) {
179 if list.is_empty() {
180 break;
181 }
182 let length = list.len() as isize;
183 let from = pair[0].rem_euclid(length) as usize;
184 let to = pair[1].rem_euclid(length) as usize;
185 let item = list.remove(from);
186 list.insert(to, item);
187 }
188 let mut list: Vec<T> = list.into_iter().flatten().collect();
189 if let Some(custom) = &mods.custom {
190 if let Some(replacement) = custom(&mut list) {
191 list = replacement;
192 }
193 }
194 list
195}
196
197pub fn str_inject(template: &str, values: Option<&Value>) -> String {
200 let Some(values @ (Value::Object(_) | Value::Array(_))) = values else {
201 return template.to_string();
202 };
203 let mut output = String::with_capacity(template.len());
204 let mut rest = template;
205 while let Some(open) = rest.find('{') {
206 output.push_str(&rest[..open]);
207 let after_open = &rest[open + 1..];
208 let Some(close) = after_open.find('}') else {
209 output.push_str(&rest[open..]);
210 return output;
211 };
212 let path = &after_open[..close];
213 if let Some(value) = resolve_path(values, path) {
214 output.push_str(&compact(value));
215 } else {
216 output.push_str(&rest[open..open + close + 2]);
217 }
218 rest = &after_open[close + 1..];
219 }
220 output.push_str(rest);
221 output
222}
223
224fn resolve_path<'a>(mut value: &'a Value, path: &str) -> Option<&'a Value> {
225 for part in path.split('.') {
226 value = match value {
227 Value::Object(map) => map.get(part)?,
228 Value::Array(array) => array.get(part.parse::<usize>().ok()?)?,
229 _ => return None,
230 };
231 }
232 Some(value)
233}
234
235fn compact(value: &Value) -> String {
236 match value {
237 Value::Null => "null".into(),
238 Value::Bool(value) => value.to_string(),
239 Value::Number(value) => value.to_string(),
240 Value::String(value) => value.clone(),
241 Value::Array(values) => {
242 format!(
243 "[{}]",
244 values.iter().map(compact).collect::<Vec<_>>().join(",")
245 )
246 }
247 Value::Object(values) => compact_object(values),
248 }
249}
250
251fn compact_object(values: &Map<String, Value>) -> String {
252 let fields = values
253 .iter()
254 .map(|(key, value)| format!("{key}:{}", compact(value)))
255 .collect::<Vec<_>>()
256 .join(",");
257 format!("{{{fields}}}")
258}