prolly-map 0.3.0

Content-addressed versioned map storage primitives.
Documentation
use prolly::{
    decode_cbor, decode_json, encode_cbor, encode_json, CborCodec, Config, Encoding, JsonCodec,
    MemStore, Prolly, ValueCodec, VersionedCborCodec, VersionedJsonCodec, VersionedValue,
};
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
struct MemoryRecord {
    source: String,
    content: String,
    confidence: u8,
}

#[test]
fn json_and_cbor_helpers_are_public_crate_root_api() {
    let record = MemoryRecord {
        source: "conversation/c1".to_string(),
        content: "User prefers concise answers.".to_string(),
        confidence: 92,
    };

    let json = encode_json(&record).unwrap();
    assert_eq!(decode_json::<MemoryRecord>(&json).unwrap(), record);

    let cbor = encode_cbor(&record).unwrap();
    assert_eq!(decode_cbor::<MemoryRecord>(&cbor).unwrap(), record);
}

#[test]
fn value_codec_trait_round_trips_json_and_cbor_values() {
    let record = MemoryRecord {
        source: "conversation/c2".to_string(),
        content: "Codec objects can be passed around with schema owners.".to_string(),
        confidence: 91,
    };

    let json = JsonCodec;
    let json_bytes = json.encode(&record).unwrap();
    assert_eq!(json.decode::<MemoryRecord>(&json_bytes).unwrap(), record);

    let cbor = CborCodec;
    let cbor_bytes = cbor.encode(&record).unwrap();
    assert_eq!(cbor.decode::<MemoryRecord>(&cbor_bytes).unwrap(), record);
}

#[test]
fn versioned_value_envelope_can_be_stored_in_a_tree() {
    let prolly = Prolly::new(MemStore::new(), Config::default());
    let tree = prolly.create();
    let record = MemoryRecord {
        source: "doc/readme".to_string(),
        content: "Prolly roots make snapshots reproducible.".to_string(),
        confidence: 100,
    };

    let value = VersionedValue::json("ai.memory.record", 1, &record)
        .unwrap()
        .to_bytes()
        .unwrap();
    let tree = prolly
        .put(&tree, b"memory/record/1".to_vec(), value)
        .unwrap();

    let stored = prolly
        .get(&tree, b"memory/record/1")
        .unwrap()
        .expect("value exists");
    let envelope = VersionedValue::from_bytes(&stored).unwrap();

    envelope.require_schema("ai.memory.record", 1).unwrap();
    assert_eq!(envelope.encoding, Encoding::Json);
    assert_eq!(envelope.decode_json::<MemoryRecord>().unwrap(), record);
    assert!(!envelope.matches_schema("ai.memory.record", 2));
}

#[test]
fn versioned_codecs_validate_schema_and_version_when_decoding() {
    let prolly = Prolly::new(MemStore::new(), Config::default());
    let tree = prolly.create();
    let codec = VersionedJsonCodec::new("ai.memory.record", 2);
    let record = MemoryRecord {
        source: "agent/run/1".to_string(),
        content: "Schema-checked codecs guard old roots.".to_string(),
        confidence: 97,
    };

    assert_eq!(codec.schema(), "ai.memory.record");
    assert_eq!(codec.version(), 2);

    let bytes = codec.encode(&record).unwrap();
    let tree = prolly
        .put(&tree, b"memory/record/2".to_vec(), bytes)
        .unwrap();
    let stored = prolly
        .get(&tree, b"memory/record/2")
        .unwrap()
        .expect("value exists");

    assert_eq!(codec.decode::<MemoryRecord>(&stored).unwrap(), record);

    let older_codec = VersionedJsonCodec::new("ai.memory.record", 1);
    let decoded: Result<MemoryRecord, _> = older_codec.decode(&stored);
    assert!(decoded.is_err());
}

#[test]
fn versioned_cbor_codec_round_trips_and_reports_expected_schema() {
    let codec = VersionedCborCodec::new("rag.chunk", 5);
    let record = MemoryRecord {
        source: "rag/index/current".to_string(),
        content: "Chunk metadata encoded with CBOR.".to_string(),
        confidence: 86,
    };

    assert_eq!(codec.schema(), "rag.chunk");
    assert_eq!(codec.version(), 5);

    let bytes = codec.encode(&record).unwrap();
    assert_eq!(codec.decode::<MemoryRecord>(&bytes).unwrap(), record);
}

#[test]
fn versioned_value_supports_cbor_and_custom_payloads() {
    let record = MemoryRecord {
        source: "rag/index/root".to_string(),
        content: "Chunk metadata".to_string(),
        confidence: 88,
    };

    let cbor = VersionedValue::cbor("rag.chunk", 3, &record).unwrap();
    assert_eq!(cbor.decode_cbor::<MemoryRecord>().unwrap(), record);

    let custom = VersionedValue::with_encoding(
        "vector.embedding",
        7,
        Encoding::Custom("f32-le-1536".to_string()),
        vec![1, 2, 3, 4],
    );
    let decoded = VersionedValue::from_bytes(&custom.to_bytes().unwrap()).unwrap();
    assert_eq!(decoded, custom);
}