use serde_json::Value;
use crate::error::NookError;
use crate::schema::ir::CollectionIr;
pub trait ValueCodec: Send + Sync {
fn encode(&self, value: &[u8]) -> Vec<u8>;
fn decode(&self, stored: &[u8]) -> Result<Vec<u8>, NookError>;
}
pub struct IdentityCodec;
impl ValueCodec for IdentityCodec {
fn encode(&self, value: &[u8]) -> Vec<u8> {
value.to_vec()
}
fn decode(&self, stored: &[u8]) -> Result<Vec<u8>, NookError> {
Ok(stored.to_vec())
}
}
pub fn encode_document(
_c: &CollectionIr,
doc: &Value,
codec: &dyn ValueCodec,
) -> Result<Vec<u8>, NookError> {
let json = serde_json::to_vec(doc).map_err(|e| NookError::Schema {
msg: format!("cannot serialize document: {e}"),
})?;
Ok(codec.encode(&json))
}
pub fn decode_document(
_c: &CollectionIr,
stored: &[u8],
codec: &dyn ValueCodec,
) -> Result<Value, NookError> {
let json = codec.decode(stored)?;
serde_json::from_slice(&json).map_err(|e| NookError::Corruption {
msg: format!("corrupt document json: {e}"),
})
}
#[cfg(test)]
mod doc_tests {
use super::*;
use crate::schema::ir::SchemaIr;
use serde_json::json;
fn ir() -> SchemaIr {
SchemaIr::compile(
r#"{"u":{"idField":"id","fields":[
{"name":"id","type":"id"},{"name":"name","type":"string"},
{"name":"born","type":"date"}],"indexes":[]}}"#,
)
.unwrap()
}
#[test]
fn json_round_trips_through_identity_codec() {
let s = ir();
let c = s.collection("u").unwrap();
let doc = json!({"id":"1","name":"Ali","born":"2026-05-19T00:00:00.000Z"});
let bytes = encode_document(c, &doc, &IdentityCodec).unwrap();
assert!(
serde_json::from_slice::<serde_json::Value>(&bytes).is_ok(),
"stored bytes must be valid JSON (debuggability goal)"
);
let back = decode_document(c, &bytes, &IdentityCodec).unwrap();
assert_eq!(back, doc);
}
#[test]
fn decode_fails_corruption_on_garbage() {
let s = ir();
let c = s.collection("u").unwrap();
let e = decode_document(c, b"\xff\xff", &IdentityCodec).unwrap_err();
assert_eq!(e.kind(), crate::error::NookErrorKind::Corruption);
}
}
#[cfg(test)]
mod seam_tests {
use super::*;
#[test]
fn default_codec_is_identity_and_selected_via_seam() {
let codec: &dyn ValueCodec = &IdentityCodec;
let input = b"{\"a\":1}";
let stored = codec.encode(input);
assert_eq!(stored, input);
assert_eq!(codec.decode(&stored).unwrap(), input);
}
struct ReverseCodec;
impl ValueCodec for ReverseCodec {
fn encode(&self, v: &[u8]) -> Vec<u8> {
v.iter().rev().copied().collect()
}
fn decode(&self, v: &[u8]) -> Result<Vec<u8>, crate::error::NookError> {
Ok(v.iter().rev().copied().collect())
}
}
#[test]
fn alternate_codec_swaps_in_through_public_seam_api() {
let codec: &dyn ValueCodec = &ReverseCodec;
let input = b"abc";
let stored = codec.encode(input);
assert_eq!(stored, b"cba");
assert_eq!(codec.decode(&stored).unwrap(), input);
}
}