1use serde_json::Value;
8
9use crate::error::NookError;
10use crate::schema::ir::CollectionIr;
11
12pub trait ValueCodec: Send + Sync {
42 fn encode(&self, value: &[u8]) -> Vec<u8>;
43 fn decode(&self, stored: &[u8]) -> Result<Vec<u8>, NookError>;
46}
47
48pub struct IdentityCodec;
50impl ValueCodec for IdentityCodec {
51 fn encode(&self, value: &[u8]) -> Vec<u8> {
52 value.to_vec()
53 }
54 fn decode(&self, stored: &[u8]) -> Result<Vec<u8>, NookError> {
55 Ok(stored.to_vec())
56 }
57}
58
59pub fn encode_document(
69 _c: &CollectionIr,
70 doc: &Value,
71 codec: &dyn ValueCodec,
72) -> Result<Vec<u8>, NookError> {
73 let json = serde_json::to_vec(doc).map_err(|e| NookError::Schema {
74 msg: format!("cannot serialize document: {e}"),
75 })?;
76 Ok(codec.encode(&json))
77}
78
79pub fn decode_document(
85 _c: &CollectionIr,
86 stored: &[u8],
87 codec: &dyn ValueCodec,
88) -> Result<Value, NookError> {
89 let json = codec.decode(stored)?;
90 serde_json::from_slice(&json).map_err(|e| NookError::Corruption {
91 msg: format!("corrupt document json: {e}"),
92 })
93}
94
95#[cfg(test)]
96mod doc_tests {
97 use super::*;
98 use crate::schema::ir::SchemaIr;
99 use serde_json::json;
100
101 fn ir() -> SchemaIr {
102 SchemaIr::compile(
103 r#"{"u":{"idField":"id","fields":[
104 {"name":"id","type":"id"},{"name":"name","type":"string"},
105 {"name":"born","type":"date"}],"indexes":[]}}"#,
106 )
107 .unwrap()
108 }
109
110 #[test]
111 fn json_round_trips_through_identity_codec() {
112 let s = ir();
113 let c = s.collection("u").unwrap();
114 let doc = json!({"id":"1","name":"Ali","born":"2026-05-19T00:00:00.000Z"});
115 let bytes = encode_document(c, &doc, &IdentityCodec).unwrap();
116 assert!(
117 serde_json::from_slice::<serde_json::Value>(&bytes).is_ok(),
118 "stored bytes must be valid JSON (debuggability goal)"
119 );
120 let back = decode_document(c, &bytes, &IdentityCodec).unwrap();
121 assert_eq!(back, doc);
122 }
123
124 #[test]
125 fn decode_fails_corruption_on_garbage() {
126 let s = ir();
127 let c = s.collection("u").unwrap();
128 let e = decode_document(c, b"\xff\xff", &IdentityCodec).unwrap_err();
129 assert_eq!(e.kind(), crate::error::NookErrorKind::Corruption);
130 }
131}
132
133#[cfg(test)]
134mod seam_tests {
135 use super::*;
136
137 #[test]
138 fn default_codec_is_identity_and_selected_via_seam() {
139 let codec: &dyn ValueCodec = &IdentityCodec;
140 let input = b"{\"a\":1}";
141 let stored = codec.encode(input);
142 assert_eq!(stored, input);
143 assert_eq!(codec.decode(&stored).unwrap(), input);
144 }
145
146 struct ReverseCodec;
149 impl ValueCodec for ReverseCodec {
150 fn encode(&self, v: &[u8]) -> Vec<u8> {
151 v.iter().rev().copied().collect()
152 }
153 fn decode(&self, v: &[u8]) -> Result<Vec<u8>, crate::error::NookError> {
154 Ok(v.iter().rev().copied().collect())
155 }
156 }
157
158 #[test]
159 fn alternate_codec_swaps_in_through_public_seam_api() {
160 let codec: &dyn ValueCodec = &ReverseCodec;
161 let input = b"abc";
162 let stored = codec.encode(input);
163 assert_eq!(stored, b"cba");
164 assert_eq!(codec.decode(&stored).unwrap(), input);
165 }
166}