this-me 0.3.3

Rust ground for the modern .me semantic kernel.
Documentation
use std::collections::BTreeMap;

use serde_json::Value as JsonValue;
use this_me::kernel::{Kernel, Memory, Path, Value};

const PUBLIC_REPLAY_FIXTURE: &str = include_str!("fixtures/typescript-public-replay-v4.0.1.json");

#[test]
fn rust_replays_typescript_public_memory_fixture() {
    let fixture = parse_fixture(PUBLIC_REPLAY_FIXTURE);
    let mut kernel = Kernel::new();

    kernel
        .replay_memories(fixture.memories)
        .expect("TypeScript public memories should replay in Rust");

    assert_eq!(kernel.memories().len(), fixture.memory_count);
    assert_eq!(kernel.read("profile.name"), Some(&Value::from("Jabellae")));
    assert_eq!(kernel.read("order.price"), Some(&Value::from(10_f64)));
    assert_eq!(kernel.read("order.quantity"), Some(&Value::from(3_f64)));
    assert_eq!(kernel.read("order.total"), Some(&Value::from(30_f64)));
    assert_eq!(
        kernel.read("profile.primary.total"),
        Some(&Value::from(30_f64))
    );
    assert_eq!(kernel.read("legacy.note"), None);
}

#[test]
fn rust_preserves_typescript_expression_value_shape() {
    let fixture = parse_fixture(PUBLIC_REPLAY_FIXTURE);
    let mut kernel = Kernel::new();

    kernel.replay_memories(fixture.memories).unwrap();

    assert_eq!(
        kernel.memories()[0].expression,
        Some(Value::from("Jabellae"))
    );
    assert_eq!(kernel.memories()[1].expression, Some(Value::from(10_f64)));
    assert_eq!(kernel.memories()[3].expression, Some(Value::from(30_f64)));
    assert_eq!(
        kernel.memories()[4].expression,
        Some(Value::Object(BTreeMap::from([(
            "__ptr".to_string(),
            Value::from("order")
        )])))
    );
}

#[test]
fn rust_replay_preserves_typescript_public_hashes_for_supported_shapes() {
    let fixture = parse_fixture(PUBLIC_REPLAY_FIXTURE);
    let source_hashes = fixture
        .memories
        .iter()
        .map(|memory| memory.hash.clone())
        .collect::<Vec<_>>();
    let mut kernel = Kernel::new();

    kernel.replay_memories(fixture.memories).unwrap();

    assert_eq!(source_hashes[0], "47b6919e");
    assert_eq!(kernel.memories()[0].prev_hash, None);
    assert_eq!(
        kernel.memories()[1].prev_hash,
        Some(kernel.memories()[0].hash.clone())
    );
    assert_eq!(
        kernel
            .memories()
            .iter()
            .map(|memory| memory.hash.clone())
            .collect::<Vec<_>>(),
        source_hashes
    );
}

struct ParsedFixture {
    memories: Vec<Memory>,
    memory_count: usize,
}

fn parse_fixture(input: &str) -> ParsedFixture {
    let json = serde_json::from_str::<JsonValue>(input).expect("fixture should be valid JSON");
    let memories = json
        .get("memories")
        .and_then(JsonValue::as_array)
        .expect("fixture should contain memories")
        .iter()
        .map(parse_memory)
        .collect::<Vec<_>>();
    let memory_count = json
        .pointer("/expectations/memoryCount")
        .and_then(JsonValue::as_u64)
        .expect("fixture should contain expected memory count") as usize;

    ParsedFixture {
        memories,
        memory_count,
    }
}

fn parse_memory(raw: &JsonValue) -> Memory {
    Memory {
        path: raw
            .get("path")
            .and_then(JsonValue::as_str)
            .map(parse_path)
            .expect("memory should contain a path"),
        operator: raw
            .get("operator")
            .and_then(JsonValue::as_str)
            .map(str::to_string),
        expression: raw.get("expression").map(parse_value),
        value: raw
            .get("value")
            .map(parse_value)
            .expect("memory should contain a value"),
        prev_hash: raw
            .get("prevHash")
            .and_then(JsonValue::as_str)
            .filter(|value| !value.is_empty())
            .map(str::to_string),
        hash: raw
            .get("hash")
            .and_then(JsonValue::as_str)
            .expect("memory should contain a hash")
            .to_string(),
    }
}

fn parse_path(value: &str) -> Path {
    if value.is_empty() {
        return Vec::new();
    }
    value.split('.').map(str::to_string).collect()
}

fn parse_value(raw: &JsonValue) -> Value {
    match raw {
        JsonValue::Null => Value::Null,
        JsonValue::Bool(value) => Value::Bool(*value),
        JsonValue::Number(value) => Value::Number(value.as_f64().expect("number should fit f64")),
        JsonValue::String(value) => Value::String(value.clone()),
        JsonValue::Array(values) => Value::Array(values.iter().map(parse_value).collect()),
        JsonValue::Object(values) => Value::Object(
            values
                .iter()
                .map(|(key, value)| (key.clone(), parse_value(value)))
                .collect::<BTreeMap<_, _>>(),
        ),
    }
}