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::presentation::entity_json::storage_value_to_json;
20use crate::storage::schema::Value;
21use crate::{RedDBError, RedDBResult};
22
23pub(crate) fn is_binary_container(bytes: &[u8]) -> bool {
28 bytes.starts_with(document_body_codec::MAGIC)
29}
30
31pub(crate) fn decode_container_to_json(bytes: &[u8]) -> Option<JsonValue> {
36 if !is_binary_container(bytes) {
37 return None;
38 }
39 let fields = document_body_codec::decode(bytes).ok()?;
40 let mut map = Map::new();
41 for (key, value) in fields {
42 map.insert(key, storage_value_to_json(&value));
43 }
44 Some(JsonValue::Object(map))
45}
46
47pub(crate) fn read_body_field(bytes: &[u8], name: &str) -> Option<Value> {
58 if !is_binary_container(bytes) {
59 return None;
60 }
61 document_body_codec::read_field_by_name(bytes, name)
62 .ok()
63 .flatten()
64}
65
66pub(crate) fn body_fields(bytes: &[u8]) -> Option<Vec<(String, Value)>> {
71 if !is_binary_container(bytes) {
72 return None;
73 }
74 document_body_codec::decode(bytes).ok()
75}
76
77pub(crate) fn container_field_names(bytes: &[u8]) -> Option<Vec<String>> {
80 if !is_binary_container(bytes) {
81 return None;
82 }
83 document_body_codec::field_names(bytes).ok()
84}
85
86pub(crate) fn serialize_document_body(body: &JsonValue, binary: bool) -> RedDBResult<Vec<u8>> {
92 if binary {
93 if let JsonValue::Object(map) = body {
94 let typed: Vec<(String, Value)> = map
95 .iter()
96 .map(|(key, value)| Ok((key.clone(), json_to_storage_value(value)?)))
97 .collect::<RedDBResult<_>>()?;
98 let refs: Vec<(&str, &Value)> = typed
99 .iter()
100 .map(|(key, value)| (key.as_str(), value))
101 .collect();
102 let mut out = Vec::new();
103 document_body_codec::encode(&refs, &mut out).map_err(|err| {
104 RedDBError::Query(format!("failed to encode binary document body: {err}"))
105 })?;
106 return Ok(out);
107 }
108 }
109 json_to_vec(body)
110 .map_err(|err| RedDBError::Query(format!("failed to serialize document body: {err}")))
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 fn parse(text: &str) -> JsonValue {
118 crate::json::from_str(text).expect("valid JSON fixture")
119 }
120
121 fn body() -> JsonValue {
122 parse(
123 r#"{"name":"Alice","age":30,"email":"alice@example.com",
124 "tags":["admin","ops"],"profile":{"city":"SP","active":true}}"#,
125 )
126 }
127
128 #[test]
129 fn binary_off_emits_plain_json_bytes() {
130 let bytes = serialize_document_body(&body(), false).expect("serialize");
131 assert!(!is_binary_container(&bytes), "flag off must stay JSON");
132 assert_eq!(bytes.first(), Some(&b'{'));
133 assert!(decode_container_to_json(&bytes).is_none());
134 }
135
136 #[test]
137 fn binary_on_emits_container_that_decodes_to_equal_json() {
138 let original = body();
139 let bytes = serialize_document_body(&original, true).expect("serialize");
140 assert!(is_binary_container(&bytes), "flag on must produce RDOC");
141 let decoded = decode_container_to_json(&bytes).expect("decode");
142 assert_eq!(
143 decoded, original,
144 "binary body must round-trip to equal JSON"
145 );
146 }
147
148 #[test]
149 fn non_object_body_falls_back_to_json_even_with_binary_on() {
150 let scalar = JsonValue::String("just-a-string".to_string());
151 let bytes = serialize_document_body(&scalar, true).expect("serialize");
152 assert!(!is_binary_container(&bytes));
153 assert_eq!(
154 json_to_vec(&scalar).unwrap(),
155 bytes,
156 "non-object body must serialise as plain JSON"
157 );
158 }
159
160 #[test]
161 fn rich_semantic_string_types_survive_round_trip() {
162 let original = parse(
165 r##"{"email":"user@example.com","ipv4":"127.0.0.1",
166 "subnet":"10.0.0.0/8","color":"#DEADBE","url":"https://reddb.io"}"##,
167 );
168 let bytes = serialize_document_body(&original, true).expect("serialize");
169 let decoded = decode_container_to_json(&bytes).expect("decode");
170 assert_eq!(decoded, original);
171 }
172
173 #[test]
174 fn read_body_field_offset_reads_from_binary_body() {
175 let bytes = serialize_document_body(&body(), true).expect("serialize");
176 assert_eq!(read_body_field(&bytes, "name"), Some(Value::text("Alice")));
177 assert_eq!(read_body_field(&bytes, "age"), Some(Value::Integer(30)));
178 assert_eq!(read_body_field(&bytes, "missing"), None);
179 }
180
181 #[test]
182 fn body_field_helpers_ignore_plain_json_bodies() {
183 let bytes = serialize_document_body(&body(), false).expect("serialize");
184 assert_eq!(read_body_field(&bytes, "name"), None);
185 assert_eq!(body_fields(&bytes), None);
186 assert_eq!(container_field_names(&bytes), None);
187 }
188
189 #[test]
190 fn body_fields_and_names_cover_top_level_keys() {
191 let bytes = serialize_document_body(&body(), true).expect("serialize");
192 let names = container_field_names(&bytes).expect("names");
193 for key in ["name", "age", "email", "tags", "profile"] {
194 assert!(names.contains(&key.to_string()), "missing {key}");
195 }
196 let fields = body_fields(&bytes).expect("fields");
197 assert_eq!(fields.len(), names.len());
198 }
199
200 #[test]
201 fn empty_object_round_trips() {
202 let original = parse("{}");
203 let bytes = serialize_document_body(&original, true).expect("serialize");
204 assert!(is_binary_container(&bytes));
205 assert_eq!(decode_container_to_json(&bytes), Some(original));
206 }
207}