cognis-graph 0.3.1

Stateful graph engine for Cognis: typed Graph<S>, Pregel-style superstep executor, per-field reducers, interrupts, time-travel via checkpointers (in-memory, SQLite, Postgres), and streaming.
Documentation
//! Per-graph typed state contract.

/// Trait implemented by structs that hold graph state. The `Update` type
/// is generated by `#[derive(GraphState)]` and represents a partial delta;
/// `apply` merges a delta into self via per-field reducers.
pub trait GraphState: Sized + Send + Sync + 'static {
    /// Per-field-reducer-aware delta type.
    type Update: Default + Send + 'static;

    /// Apply a delta to self.
    fn apply(&mut self, update: Self::Update);

    /// Reset any `#[reducer(ephemeral)]` fields to `Default::default()`.
    /// The engine calls this at the start of every superstep, before
    /// running tasks — so ephemeral fields hold only writes from the
    /// current step. Default impl is a no-op for state types with no
    /// ephemeral fields.
    fn reset_ephemeral(&mut self) {}
}

/// Deep-merge `source` into `target` for `serde_json::Value`. Used by
/// `#[derive(GraphState)]`-generated code when a field is annotated
/// `#[reducer(merge)]`.
///
/// Merge rules:
/// - Both objects: keys union; for shared keys, recurse.
/// - Both arrays: target is replaced by source. (Use `#[reducer(append)]`
///   for `Vec<T>` if you want concatenation.)
/// - Anything else: source replaces target.
#[doc(hidden)]
pub fn __merge_json(target: &mut serde_json::Value, source: serde_json::Value) {
    use serde_json::Value;
    match (target, source) {
        (Value::Object(t_map), Value::Object(s_map)) => {
            for (k, v) in s_map {
                __merge_json(t_map.entry(k).or_insert(Value::Null), v);
            }
        }
        (slot, source) => {
            *slot = source;
        }
    }
}

#[cfg(test)]
mod tests {
    use super::__merge_json;
    use serde_json::json;

    #[test]
    fn merge_objects_keys_union() {
        let mut t = json!({"a": 1, "b": 2});
        __merge_json(&mut t, json!({"b": 20, "c": 3}));
        assert_eq!(t, json!({"a": 1, "b": 20, "c": 3}));
    }

    #[test]
    fn merge_nested_objects() {
        let mut t = json!({"x": {"a": 1, "b": 2}});
        __merge_json(&mut t, json!({"x": {"b": 20, "c": 3}}));
        assert_eq!(t, json!({"x": {"a": 1, "b": 20, "c": 3}}));
    }

    #[test]
    fn merge_arrays_replace() {
        let mut t = json!([1, 2, 3]);
        __merge_json(&mut t, json!([9]));
        assert_eq!(t, json!([9]));
    }

    #[test]
    fn merge_scalar_replace() {
        let mut t = json!(1);
        __merge_json(&mut t, json!("hello"));
        assert_eq!(t, json!("hello"));
    }
}