use reddb_types::document_body_codec;
use crate::application::entity::json_to_storage_value;
use crate::json::{to_vec as json_to_vec, Map, Value as JsonValue};
use crate::presentation::entity_json::storage_value_to_json;
use crate::storage::schema::Value;
use crate::{RedDBError, RedDBResult};
pub(crate) fn is_binary_container(bytes: &[u8]) -> bool {
bytes.starts_with(document_body_codec::MAGIC)
}
pub(crate) fn decode_container_to_json(bytes: &[u8]) -> Option<JsonValue> {
if !is_binary_container(bytes) {
return None;
}
let fields = document_body_codec::decode(bytes).ok()?;
let mut map = Map::new();
for (key, value) in fields {
map.insert(key, storage_value_to_json(&value));
}
Some(JsonValue::Object(map))
}
pub(crate) fn read_body_field(bytes: &[u8], name: &str) -> Option<Value> {
if !is_binary_container(bytes) {
return None;
}
document_body_codec::read_field_by_name(bytes, name)
.ok()
.flatten()
}
pub(crate) fn body_fields(bytes: &[u8]) -> Option<Vec<(String, Value)>> {
if !is_binary_container(bytes) {
return None;
}
document_body_codec::decode(bytes).ok()
}
pub(crate) fn container_field_names(bytes: &[u8]) -> Option<Vec<String>> {
if !is_binary_container(bytes) {
return None;
}
document_body_codec::field_names(bytes).ok()
}
pub(crate) fn serialize_document_body(body: &JsonValue, binary: bool) -> RedDBResult<Vec<u8>> {
if binary {
if let JsonValue::Object(map) = body {
let typed: Vec<(String, Value)> = map
.iter()
.map(|(key, value)| Ok((key.clone(), json_to_storage_value(value)?)))
.collect::<RedDBResult<_>>()?;
let refs: Vec<(&str, &Value)> = typed
.iter()
.map(|(key, value)| (key.as_str(), value))
.collect();
let mut out = Vec::new();
document_body_codec::encode(&refs, &mut out).map_err(|err| {
RedDBError::Query(format!("failed to encode binary document body: {err}"))
})?;
return Ok(out);
}
}
json_to_vec(body)
.map_err(|err| RedDBError::Query(format!("failed to serialize document body: {err}")))
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(text: &str) -> JsonValue {
crate::json::from_str(text).expect("valid JSON fixture")
}
fn body() -> JsonValue {
parse(
r#"{"name":"Alice","age":30,"email":"alice@example.com",
"tags":["admin","ops"],"profile":{"city":"SP","active":true}}"#,
)
}
#[test]
fn binary_off_emits_plain_json_bytes() {
let bytes = serialize_document_body(&body(), false).expect("serialize");
assert!(!is_binary_container(&bytes), "flag off must stay JSON");
assert_eq!(bytes.first(), Some(&b'{'));
assert!(decode_container_to_json(&bytes).is_none());
}
#[test]
fn binary_on_emits_container_that_decodes_to_equal_json() {
let original = body();
let bytes = serialize_document_body(&original, true).expect("serialize");
assert!(is_binary_container(&bytes), "flag on must produce RDOC");
let decoded = decode_container_to_json(&bytes).expect("decode");
assert_eq!(
decoded, original,
"binary body must round-trip to equal JSON"
);
}
#[test]
fn non_object_body_falls_back_to_json_even_with_binary_on() {
let scalar = JsonValue::String("just-a-string".to_string());
let bytes = serialize_document_body(&scalar, true).expect("serialize");
assert!(!is_binary_container(&bytes));
assert_eq!(
json_to_vec(&scalar).unwrap(),
bytes,
"non-object body must serialise as plain JSON"
);
}
#[test]
fn rich_semantic_string_types_survive_round_trip() {
let original = parse(
r##"{"email":"user@example.com","ipv4":"127.0.0.1",
"subnet":"10.0.0.0/8","color":"#DEADBE","url":"https://reddb.io"}"##,
);
let bytes = serialize_document_body(&original, true).expect("serialize");
let decoded = decode_container_to_json(&bytes).expect("decode");
assert_eq!(decoded, original);
}
#[test]
fn read_body_field_offset_reads_from_binary_body() {
let bytes = serialize_document_body(&body(), true).expect("serialize");
assert_eq!(read_body_field(&bytes, "name"), Some(Value::text("Alice")));
assert_eq!(read_body_field(&bytes, "age"), Some(Value::Integer(30)));
assert_eq!(read_body_field(&bytes, "missing"), None);
}
#[test]
fn body_field_helpers_ignore_plain_json_bodies() {
let bytes = serialize_document_body(&body(), false).expect("serialize");
assert_eq!(read_body_field(&bytes, "name"), None);
assert_eq!(body_fields(&bytes), None);
assert_eq!(container_field_names(&bytes), None);
}
#[test]
fn body_fields_and_names_cover_top_level_keys() {
let bytes = serialize_document_body(&body(), true).expect("serialize");
let names = container_field_names(&bytes).expect("names");
for key in ["name", "age", "email", "tags", "profile"] {
assert!(names.contains(&key.to_string()), "missing {key}");
}
let fields = body_fields(&bytes).expect("fields");
assert_eq!(fields.len(), names.len());
}
#[test]
fn empty_object_round_trips() {
let original = parse("{}");
let bytes = serialize_document_body(&original, true).expect("serialize");
assert!(is_binary_container(&bytes));
assert_eq!(decode_container_to_json(&bytes), Some(original));
}
}