use std::marker::PhantomData;
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json::Value;
pub trait TypedReducer: Send + Sync {
type Value: Serialize + DeserializeOwned + Send + Sync;
fn reduce(&self, current: Self::Value, incoming: Self::Value) -> Self::Value;
}
pub struct ReplaceReducer<T> {
_marker: PhantomData<T>,
}
impl<T> ReplaceReducer<T> {
pub fn new() -> Self {
Self { _marker: PhantomData }
}
}
impl<T> Default for ReplaceReducer<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> TypedReducer for ReplaceReducer<T>
where
T: Serialize + DeserializeOwned + Send + Sync,
{
type Value = T;
fn reduce(&self, _current: T, incoming: T) -> T {
incoming
}
}
unsafe impl<T> Send for ReplaceReducer<T> {}
unsafe impl<T> Sync for ReplaceReducer<T> {}
pub struct AppendReducer<T> {
_marker: PhantomData<T>,
}
impl<T> AppendReducer<T> {
pub fn new() -> Self {
Self { _marker: PhantomData }
}
}
impl<T> Default for AppendReducer<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> TypedReducer for AppendReducer<T>
where
T: Serialize + DeserializeOwned + Send + Sync,
{
type Value = Vec<T>;
fn reduce(&self, mut current: Vec<T>, incoming: Vec<T>) -> Vec<T> {
current.extend(incoming);
current
}
}
unsafe impl<T> Send for AppendReducer<T> {}
unsafe impl<T> Sync for AppendReducer<T> {}
pub struct MergeReducer;
impl TypedReducer for MergeReducer {
type Value = Value;
fn reduce(&self, current: Value, incoming: Value) -> Value {
deep_merge(current, incoming)
}
}
fn deep_merge(current: Value, incoming: Value) -> Value {
match (current, incoming) {
(Value::Object(mut current_map), Value::Object(incoming_map)) => {
for (key, incoming_val) in incoming_map {
let merged = if let Some(current_val) = current_map.remove(&key) {
deep_merge(current_val, incoming_val)
} else {
incoming_val
};
current_map.insert(key, merged);
}
Value::Object(current_map)
}
(_, incoming) => incoming,
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn test_replace_reducer_returns_incoming() {
let reducer = ReplaceReducer::<i32>::new();
assert_eq!(reducer.reduce(10, 20), 20);
assert_eq!(reducer.reduce(0, 42), 42);
}
#[test]
fn test_replace_reducer_with_strings() {
let reducer = ReplaceReducer::<String>::new();
let result = reducer.reduce("old".to_string(), "new".to_string());
assert_eq!(result, "new");
}
#[test]
fn test_append_reducer_concatenates_vecs() {
let reducer = AppendReducer::<i32>::new();
let result = reducer.reduce(vec![1, 2, 3], vec![4, 5]);
assert_eq!(result, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_append_reducer_empty_current() {
let reducer = AppendReducer::<i32>::new();
let result = reducer.reduce(vec![], vec![1, 2, 3]);
assert_eq!(result, vec![1, 2, 3]);
}
#[test]
fn test_append_reducer_empty_incoming() {
let reducer = AppendReducer::<i32>::new();
let result = reducer.reduce(vec![1, 2, 3], vec![]);
assert_eq!(result, vec![1, 2, 3]);
}
#[test]
fn test_append_reducer_both_empty() {
let reducer = AppendReducer::<i32>::new();
let result = reducer.reduce(vec![], vec![]);
assert_eq!(result, Vec::<i32>::new());
}
#[test]
fn test_merge_reducer_objects() {
let reducer = MergeReducer;
let current = json!({"a": 1, "b": 2});
let incoming = json!({"b": 3, "c": 4});
let result = reducer.reduce(current, incoming);
assert_eq!(result, json!({"a": 1, "b": 3, "c": 4}));
}
#[test]
fn test_merge_reducer_nested_objects() {
let reducer = MergeReducer;
let current = json!({"nested": {"x": 10, "y": 20}});
let incoming = json!({"nested": {"y": 30, "z": 40}});
let result = reducer.reduce(current, incoming);
assert_eq!(result, json!({"nested": {"x": 10, "y": 30, "z": 40}}));
}
#[test]
fn test_merge_reducer_deeply_nested() {
let reducer = MergeReducer;
let current = json!({"a": {"b": {"c": 1, "d": 2}}});
let incoming = json!({"a": {"b": {"d": 3, "e": 4}}});
let result = reducer.reduce(current, incoming);
assert_eq!(result, json!({"a": {"b": {"c": 1, "d": 3, "e": 4}}}));
}
#[test]
fn test_merge_reducer_non_object_incoming_replaces() {
let reducer = MergeReducer;
let current = json!({"a": 1});
let incoming = json!(42);
let result = reducer.reduce(current, incoming);
assert_eq!(result, json!(42));
}
#[test]
fn test_merge_reducer_non_object_current_replaced() {
let reducer = MergeReducer;
let current = json!(42);
let incoming = json!({"a": 1});
let result = reducer.reduce(current, incoming);
assert_eq!(result, json!({"a": 1}));
}
#[test]
fn test_merge_reducer_null_values() {
let reducer = MergeReducer;
let current = json!({"a": 1});
let incoming = json!({"a": null});
let result = reducer.reduce(current, incoming);
assert_eq!(result, json!({"a": null}));
}
#[test]
fn test_merge_reducer_array_replaced() {
let reducer = MergeReducer;
let current = json!({"items": [1, 2, 3]});
let incoming = json!({"items": [4, 5]});
let result = reducer.reduce(current, incoming);
assert_eq!(result, json!({"items": [4, 5]}));
}
#[test]
fn test_merge_reducer_empty_objects() {
let reducer = MergeReducer;
let current = json!({});
let incoming = json!({"a": 1});
let result = reducer.reduce(current, incoming);
assert_eq!(result, json!({"a": 1}));
}
#[test]
fn test_merge_reducer_both_empty_objects() {
let reducer = MergeReducer;
let current = json!({});
let incoming = json!({});
let result = reducer.reduce(current, incoming);
assert_eq!(result, json!({}));
}
}