reddb_server/
document_body.rs1use reddb_types::document_body_codec;
16
17use crate::application::entity::json_to_storage_value;
18use crate::json::{to_vec as json_to_vec, Map, Value as JsonValue};
19use crate::{RedDBError, RedDBResult};
20use reddb_types::Value;
21
22pub(crate) fn is_binary_container(bytes: &[u8]) -> bool {
27 bytes.starts_with(document_body_codec::MAGIC)
28}
29
30pub(crate) use reddb_types::document_body_codec::decode_container_to_json;
35
36pub(crate) fn read_body_field(bytes: &[u8], name: &str) -> Option<Value> {
47 if !is_binary_container(bytes) {
48 return None;
49 }
50 document_body_codec::read_field_by_name(bytes, name)
51 .ok()
52 .flatten()
53}
54
55pub(crate) fn body_fields(bytes: &[u8]) -> Option<Vec<(String, Value)>> {
60 if !is_binary_container(bytes) {
61 return None;
62 }
63 document_body_codec::decode(bytes).ok()
64}
65
66pub(crate) fn container_field_names(bytes: &[u8]) -> Option<Vec<String>> {
69 if !is_binary_container(bytes) {
70 return None;
71 }
72 document_body_codec::field_names(bytes).ok()
73}
74
75pub(crate) fn serialize_document_body(body: &JsonValue, binary: bool) -> RedDBResult<Vec<u8>> {
81 if binary {
82 if let JsonValue::Object(map) = body {
83 let typed: Vec<(String, Value)> = map
84 .iter()
85 .map(|(key, value)| Ok((key.clone(), json_to_storage_value(value)?)))
86 .collect::<RedDBResult<_>>()?;
87 let refs: Vec<(&str, &Value)> = typed
88 .iter()
89 .map(|(key, value)| (key.as_str(), value))
90 .collect();
91 let mut out = Vec::new();
92 document_body_codec::encode(&refs, &mut out).map_err(|err| {
93 RedDBError::Query(format!("failed to encode binary document body: {err}"))
94 })?;
95 return Ok(out);
96 }
97 }
98 json_to_vec(body)
99 .map_err(|err| RedDBError::Query(format!("failed to serialize document body: {err}")))
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 fn parse(text: &str) -> JsonValue {
107 crate::json::from_str(text).expect("valid JSON fixture")
108 }
109
110 fn body() -> JsonValue {
111 parse(
112 r#"{"name":"Alice","age":30,"email":"alice@example.com",
113 "tags":["admin","ops"],"profile":{"city":"SP","active":true}}"#,
114 )
115 }
116
117 #[test]
118 fn binary_off_emits_plain_json_bytes() {
119 let bytes = serialize_document_body(&body(), false).expect("serialize");
120 assert!(!is_binary_container(&bytes), "flag off must stay JSON");
121 assert_eq!(bytes.first(), Some(&b'{'));
122 assert!(decode_container_to_json(&bytes).is_none());
123 }
124
125 #[test]
126 fn binary_on_emits_container_that_decodes_to_equal_json() {
127 let original = body();
128 let bytes = serialize_document_body(&original, true).expect("serialize");
129 assert!(is_binary_container(&bytes), "flag on must produce RDOC");
130 let decoded = decode_container_to_json(&bytes).expect("decode");
131 assert_eq!(
132 decoded, original,
133 "binary body must round-trip to equal JSON"
134 );
135 }
136
137 #[test]
138 fn high_precision_decimal_body_field_round_trips_exactly() {
139 let original = parse(r#"{"amount":3.14159265358979323846,"big":18446744073709551616}"#);
140 let bytes = serialize_document_body(&original, true).expect("serialize");
141 let decoded = decode_container_to_json(&bytes).expect("decode");
142 let expected = parse(
143 r#"{"amount":{"$decimal":"3.14159265358979323846"},"big":{"$decimal":"18446744073709551616"}}"#,
144 );
145 assert_eq!(decoded, expected);
146 assert_eq!(
147 read_body_field(&bytes, "amount"),
148 Some(Value::DecimalText("3.14159265358979323846".to_string()))
149 );
150 assert_eq!(
151 decoded.to_string_compact(),
152 r#"{"amount":{"$decimal":"3.14159265358979323846"},"big":{"$decimal":"18446744073709551616"}}"#
153 );
154 }
155
156 #[test]
157 fn non_object_body_falls_back_to_json_even_with_binary_on() {
158 let scalar = JsonValue::String("just-a-string".to_string());
159 let bytes = serialize_document_body(&scalar, true).expect("serialize");
160 assert!(!is_binary_container(&bytes));
161 assert_eq!(
162 json_to_vec(&scalar).unwrap(),
163 bytes,
164 "non-object body must serialise as plain JSON"
165 );
166 }
167
168 #[test]
169 fn rich_semantic_string_types_survive_round_trip() {
170 let original = parse(
173 r##"{"email":"user@example.com","ipv4":"127.0.0.1",
174 "subnet":"10.0.0.0/8","color":"#DEADBE","url":"https://reddb.io"}"##,
175 );
176 let bytes = serialize_document_body(&original, true).expect("serialize");
177 let decoded = decode_container_to_json(&bytes).expect("decode");
178 assert_eq!(decoded, original);
179 }
180
181 #[test]
182 fn read_body_field_offset_reads_from_binary_body() {
183 let bytes = serialize_document_body(&body(), true).expect("serialize");
184 assert_eq!(read_body_field(&bytes, "name"), Some(Value::text("Alice")));
185 assert_eq!(read_body_field(&bytes, "age"), Some(Value::Integer(30)));
186 assert_eq!(read_body_field(&bytes, "missing"), None);
187 }
188
189 #[test]
190 fn numeric_field_reads_do_not_allocate_offset_tables() {
191 use crate::storage::no_malloc_hot_paths::measure_allocations;
192 let fields: Vec<_> = (0..100)
193 .map(|index| (format!("field_{index}"), Value::Integer(index)))
194 .collect();
195 let references: Vec<_> = fields
196 .iter()
197 .map(|(key, value)| (key.as_str(), value))
198 .collect();
199 let mut bytes = Vec::new();
200 document_body_codec::encode(&references, &mut bytes).expect("numeric body");
201 let (_, allocations) = measure_allocations(|| {
202 for _ in 0..1000 {
203 for index in [0, 50, 99] {
204 let name = match index {
205 0 => "field_0",
206 50 => "field_50",
207 _ => "field_99",
208 };
209 assert_eq!(read_body_field(&bytes, name), Some(Value::Integer(index)));
210 }
211 }
212 });
213 assert_eq!(allocations.allocs, 0, "{allocations:?}");
214 }
215
216 #[test]
217 fn body_field_helpers_ignore_plain_json_bodies() {
218 let bytes = serialize_document_body(&body(), false).expect("serialize");
219 assert_eq!(read_body_field(&bytes, "name"), None);
220 assert_eq!(body_fields(&bytes), None);
221 assert_eq!(container_field_names(&bytes), None);
222 }
223
224 #[test]
225 fn body_fields_and_names_cover_top_level_keys() {
226 let bytes = serialize_document_body(&body(), true).expect("serialize");
227 let names = container_field_names(&bytes).expect("names");
228 for key in ["name", "age", "email", "tags", "profile"] {
229 assert!(names.contains(&key.to_string()), "missing {key}");
230 }
231 let fields = body_fields(&bytes).expect("fields");
232 assert_eq!(fields.len(), names.len());
233 }
234
235 #[test]
236 fn empty_object_round_trips() {
237 let original = parse("{}");
238 let bytes = serialize_document_body(&original, true).expect("serialize");
239 assert!(is_binary_container(&bytes));
240 assert_eq!(decode_container_to_json(&bytes), Some(original));
241 }
242}