use serde_json::{Map, Value};
use std::fmt;
use std::sync::Arc;
const DANGEROUS_KEYS: [&str; 3] = ["__proto__", "constructor", "prototype"];
pub fn deep(mut base: Value, overlays: impl IntoIterator<Item = Value>) -> Value {
for overlay in overlays {
base = deep_one(base, overlay);
}
base
}
fn deep_one(base: Value, overlay: Value) -> Value {
match (base, overlay) {
(Value::Object(mut base), Value::Object(overlay)) => {
for (key, value) in overlay {
if DANGEROUS_KEYS.contains(&key.as_str()) {
continue;
}
let merged = match base.shift_remove(&key) {
Some(previous) => deep_one(previous, value),
None => clone_safe(value),
};
base.insert(key, merged);
}
Value::Object(base)
}
(Value::Array(base), Value::Array(overlay)) => {
let length = base.len().max(overlay.len());
let mut base = base.into_iter();
let mut overlay = overlay.into_iter();
Value::Array(
(0..length)
.map(|_| match (base.next(), overlay.next()) {
(Some(base), Some(overlay)) => deep_one(base, overlay),
(Some(base), None) => clone_safe(base),
(None, Some(overlay)) => clone_safe(overlay),
(None, None) => unreachable!("length is derived from both iterators"),
})
.collect(),
)
}
(_, overlay) => clone_safe(overlay),
}
}
fn clone_safe(value: Value) -> Value {
match value {
Value::Object(map) => Value::Object(
map.into_iter()
.filter(|(key, _)| !DANGEROUS_KEYS.contains(&key.as_str()))
.map(|(key, value)| (key, clone_safe(value)))
.collect(),
),
Value::Array(values) => Value::Array(values.into_iter().map(clone_safe).collect()),
scalar => scalar,
}
}
pub fn str_value(value: &Value, max_len: i64) -> String {
if max_len <= 0 {
return String::new();
}
let rendered = match value {
Value::String(value) => value.clone(),
_ => serde_json::to_string(value).unwrap_or_default(),
};
let chars: Vec<char> = rendered.chars().collect();
let max_len = max_len as usize;
let output = if chars.len() > max_len {
if max_len >= 4 {
chars[..max_len - 3].iter().collect::<String>() + "..."
} else {
".".repeat(max_len)
}
} else {
rendered
};
output
.chars()
.take(max_len)
.map(|value| match value {
'\r' | '\n' | '\t' => '.',
value => value,
})
.collect()
}
pub type ListModifier<T> = Arc<dyn Fn(&mut Vec<T>) -> Option<Vec<T>> + Send + Sync>;
pub struct ListMods<T = Value> {
pub delete: Vec<isize>,
pub move_items: Vec<isize>,
pub custom: Option<ListModifier<T>>,
}
impl<T> ListMods<T> {
pub fn with_custom(
mut self,
custom: impl Fn(&mut Vec<T>) -> Option<Vec<T>> + Send + Sync + 'static,
) -> Self {
self.custom = Some(Arc::new(custom));
self
}
}
impl<T> Default for ListMods<T> {
fn default() -> Self {
Self {
delete: Vec::new(),
move_items: Vec::new(),
custom: None,
}
}
}
impl<T> Clone for ListMods<T> {
fn clone(&self) -> Self {
Self {
delete: self.delete.clone(),
move_items: self.move_items.clone(),
custom: self.custom.clone(),
}
}
}
impl<T> fmt::Debug for ListMods<T> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ListMods")
.field("delete", &self.delete)
.field("move_items", &self.move_items)
.field("custom", &self.custom.as_ref().map(|_| "<function>"))
.finish()
}
}
impl<T> PartialEq for ListMods<T> {
fn eq(&self, other: &Self) -> bool {
self.delete == other.delete
&& self.move_items == other.move_items
&& match (&self.custom, &other.custom) {
(None, None) => true,
(Some(left), Some(right)) => Arc::ptr_eq(left, right),
_ => false,
}
}
}
impl<T> Eq for ListMods<T> {}
pub fn modlist<T>(list: Vec<T>, mods: Option<&ListMods<T>>) -> Vec<T> {
let Some(mods) = mods else { return list };
let mut list: Vec<Option<T>> = list.into_iter().map(Some).collect();
for raw in &mods.delete {
let index = if *raw < 0 {
list.len().checked_sub(raw.unsigned_abs())
} else {
Some(*raw as usize)
};
if let Some(item) = index.and_then(|index| list.get_mut(index)) {
*item = None;
}
}
for pair in mods.move_items.chunks_exact(2) {
if list.is_empty() {
break;
}
let length = list.len() as isize;
let from = pair[0].rem_euclid(length) as usize;
let to = pair[1].rem_euclid(length) as usize;
let item = list.remove(from);
list.insert(to, item);
}
let mut list: Vec<T> = list.into_iter().flatten().collect();
if let Some(custom) = &mods.custom {
if let Some(replacement) = custom(&mut list) {
list = replacement;
}
}
list
}
pub fn str_inject(template: &str, values: Option<&Value>) -> String {
let Some(values @ (Value::Object(_) | Value::Array(_))) = values else {
return template.to_string();
};
let mut output = String::with_capacity(template.len());
let mut rest = template;
while let Some(open) = rest.find('{') {
output.push_str(&rest[..open]);
let after_open = &rest[open + 1..];
let Some(close) = after_open.find('}') else {
output.push_str(&rest[open..]);
return output;
};
let path = &after_open[..close];
if let Some(value) = resolve_path(values, path) {
output.push_str(&compact(value));
} else {
output.push_str(&rest[open..open + close + 2]);
}
rest = &after_open[close + 1..];
}
output.push_str(rest);
output
}
fn resolve_path<'a>(mut value: &'a Value, path: &str) -> Option<&'a Value> {
for part in path.split('.') {
value = match value {
Value::Object(map) => map.get(part)?,
Value::Array(array) => array.get(part.parse::<usize>().ok()?)?,
_ => return None,
};
}
Some(value)
}
fn compact(value: &Value) -> String {
match value {
Value::Null => "null".into(),
Value::Bool(value) => value.to_string(),
Value::Number(value) => value.to_string(),
Value::String(value) => value.clone(),
Value::Array(values) => {
format!(
"[{}]",
values.iter().map(compact).collect::<Vec<_>>().join(",")
)
}
Value::Object(values) => compact_object(values),
}
}
fn compact_object(values: &Map<String, Value>) -> String {
let fields = values
.iter()
.map(|(key, value)| format!("{key}:{}", compact(value)))
.collect::<Vec<_>>()
.join(",");
format!("{{{fields}}}")
}