#[cfg(feature = "json")]
mod roundtrip_tests {
use tson::{TsonData, TsonHeader};
fn verify_roundtrip(json_input: &str) {
let doc = tson::compile_json(json_input).unwrap();
let bytes = tson::to_bytes(&doc).expect("encode should succeed");
let recovered = tson::from_bytes(&bytes).expect("decode should succeed");
let output = tson::decompile_to_value(&recovered).expect("decompile should succeed");
let expected: serde_json::Value =
serde_json::from_str(json_input).expect("input should be valid JSON");
assert_eq!(output, expected, "round-trip structural mismatch");
let _output_str = serde_json::to_string(&output).expect("should serialize to string");
}
#[test]
fn null_value() {
verify_roundtrip("null");
}
#[test]
fn bool_true() {
verify_roundtrip("true");
}
#[test]
fn bool_false() {
verify_roundtrip("false");
}
#[test]
fn integer() {
verify_roundtrip("42");
}
#[test]
fn negative_int() {
verify_roundtrip("-17");
}
#[test]
fn unsigned_int() {
verify_roundtrip("65535");
}
#[test]
fn float_exact() {
verify_roundtrip("0.5");
}
#[test]
fn float_exact_neg() {
verify_roundtrip("-1.25");
}
#[test]
fn string() {
verify_roundtrip(r#""hello world""#);
}
#[test]
fn empty_string() {
verify_roundtrip(r#""""#);
}
#[test]
fn empty_array() {
verify_roundtrip("[]");
}
#[test]
fn int_array() {
verify_roundtrip("[1, 2, 3]");
}
#[test]
fn homogeneous_string_array() {
verify_roundtrip(r#"["a", "b", "c"]"#);
}
#[test]
fn nested_array() {
verify_roundtrip("[[1, 2], [3, 4]]");
}
#[test]
fn empty_object() {
verify_roundtrip("{}");
}
#[test]
fn simple_object() {
verify_roundtrip(r#"{"name":"Alice","age":30}"#);
}
#[test]
fn object_with_nested_object() {
verify_roundtrip(r#"{"user":{"name":"Bob","score":100}}"#);
}
#[test]
fn array_of_objects() {
verify_roundtrip(
r#"[
{"id": 1, "name": "Alice", "active": true},
{"id": 2, "name": "Bob", "active": false}
]"#,
);
}
#[test]
fn deeply_nested() {
verify_roundtrip(
r#"{
"level1": {
"level2": {
"level3": {
"value": 42
}
}
}
}"#,
);
}
#[test]
fn object_with_array_field() {
verify_roundtrip(r#"{"tags": ["a", "b", "c"], "count": 3}"#);
}
#[test]
fn array_of_arrays() {
verify_roundtrip("[[1], [1, 2], [1, 2, 3]]");
}
#[test]
fn large_integer() {
verify_roundtrip("2147483647"); }
#[test]
fn zero() {
verify_roundtrip("0");
}
#[test]
fn scientific_float() {
verify_roundtrip("4.0");
}
#[test]
fn users_example() {
let json = r#"[
{
"id": 1,
"name": "Alice",
"age": 30,
"address": {
"street": "123 Main St",
"city": "Anytown",
"state": "CA",
"zip": "12345"
},
"hobbies": ["reading", "hiking", "cooking"]
},
{
"id": 2,
"name": "Bob",
"age": 25,
"address": {
"street": "456 Elm St",
"city": "Othertown",
"state": "NY",
"zip": "67890"
},
"hobbies": ["gaming", "traveling", "photography"]
},
{
"id": 3,
"name": "Charlie",
"age": 35,
"address": {
"street": "789 Oak St",
"city": "Sometown",
"state": "TX",
"zip": "54321"
},
"hobbies": ["music", "sports", "gardening"]
}
]"#;
verify_roundtrip(json);
}
#[test]
fn header_size_is_13_bytes() {
let doc = tson::compile_json("\"test\"").unwrap();
let bytes = tson::to_bytes(&doc).unwrap();
assert!(bytes.len() >= TsonHeader::SIZE, "must have header");
assert_eq!(bytes[0], 1, "version must be 1");
let def_off = u32::from_le_bytes(bytes[1..5].try_into().unwrap()) as usize;
assert_eq!(
def_off,
TsonHeader::SIZE,
"definition block must start after header"
);
}
#[test]
fn definitions_are_included() {
let doc = tson::compile_json("{\"a\": 1, \"b\": 2}").unwrap();
assert!(!doc.definitions.is_empty(), "must have definitions");
assert!(doc.definitions.len() >= 7, "need primitives + object");
}
#[test]
#[cfg(feature = "dict")]
fn dict_empty_when_no_duplicate_strings() {
let json = r#"{
"users": [
{"name": "Alice", "role": "admin"},
{"name": "Bob", "role": "editor"},
{"name": "Charlie", "role": "viewer"}
]
}"#;
let doc = tson::compile_json(json).unwrap();
assert_eq!(
doc.dict.len(),
0,
"Expected empty dict (no repeated strings), got {} entries: {:?}",
doc.dict.len(),
&doc.dict
);
}
#[test]
#[cfg(feature = "dict")]
fn dict_only_contains_repeated_strings() {
let json = r#"{
"users": [
{"name": "Alice", "role": "admin"},
{"name": "Charlie", "role": "admin"},
{"name": "Charlie", "role": "user"}
]
}"#;
let doc = tson::compile_json(json).unwrap();
let dict_strs: Vec<&str> = doc.dict.iter().map(|s| s.as_str()).collect();
assert!(
dict_strs.contains(&"Charlie"),
"Charlie should be in dict (appears ≥2 times)"
);
assert!(
dict_strs.contains(&"admin"),
"admin should be in dict (appears ≥2 times)"
);
assert!(
!dict_strs.contains(&"Alice"),
"Alice should NOT be in dict (appears once)"
);
assert!(
!dict_strs.contains(&"user"),
"user should NOT be in dict (appears once)"
);
}
#[test]
#[cfg(feature = "dict")]
fn strref_roundtrip_preserves_all_values() {
let json = r#"{"names": ["Alice", "Bob", "Alice", "Charlie", "Bob"]}"#;
let doc = tson::compile_json(json).unwrap();
let bytes = tson::to_bytes(&doc).unwrap();
let restored = tson::from_bytes(&bytes).unwrap();
let value = tson::decompile_to_value(&restored).unwrap();
let expected: serde_json::Value = serde_json::from_str(json).unwrap();
assert_eq!(
value, expected,
"Round-trip with dict should preserve all values"
);
}
#[test]
#[cfg(feature = "dict")]
fn strref_doc_contains_both_inline_and_strref() {
let json = r#"["Alice", "Bob", "Alice"]"#;
let doc = tson::compile_json(json).unwrap();
assert!(
!doc.dict.is_empty(),
"Dict should have entries (Alice repeats)"
);
let bytes = tson::to_bytes(&doc).unwrap();
let back = tson::from_bytes(&bytes).unwrap();
let val = tson::decompile_to_value(&back).unwrap();
assert_eq!(val.as_array().unwrap().len(), 3);
}
#[test]
fn emit_roundtrip_primitive_values() {
let data = TsonData::Object(
0,
vec![
TsonData::Float(22.5),
TsonData::Int(61),
TsonData::String("nominal".to_string()),
],
);
let bytes = tson::emit(&data).unwrap();
assert!(!bytes.is_empty(), "Emitted bytes should not be empty");
let restored = tson::from_bytes(&bytes).unwrap();
let value = tson::decompile_to_value(&restored).unwrap();
assert!(
value.is_object(),
"Emitted data should round-trip to an object"
);
let obj = value.as_object().unwrap();
assert_eq!(obj["f0"].as_f64().unwrap(), 22.5);
assert_eq!(obj["f1"].as_i64().unwrap(), 61);
assert_eq!(obj["f2"].as_str().unwrap(), "nominal");
}
#[test]
fn emit_value_produces_payload_only() {
let data = TsonData::Int(42);
let payload = tson::emit_value(&data).unwrap();
assert_eq!(payload, 42i32.to_le_bytes().to_vec());
}
#[test]
fn document_get_returns_field_by_name() {
let json = r#"{"name":"Alice","age":30,"address":{"city":"Anytown"}}"#;
let doc = tson::compile_json(json).unwrap();
let name = doc.get("name").unwrap();
assert!(matches!(name, TsonData::String(s) if s == "Alice"));
let age = doc.get("age").unwrap();
assert!(matches!(age, TsonData::Int(30)));
assert!(doc.get("nonexistent").is_none());
let address = doc.get("address").unwrap();
let city = address.field("city", &doc.definitions).unwrap();
assert!(matches!(city, TsonData::String(s) if s == "Anytown"));
}
#[test]
fn tson_data_values_len_and_is_empty() {
let json = r#"["a", "b", "c"]"#;
let doc = tson::compile_json(json).unwrap();
let entry = doc.first_entry().unwrap();
let arr = &entry.data;
assert_eq!(arr.len(), 3);
assert!(!arr.is_empty());
assert_eq!(arr.values().len(), 3);
}
#[test]
fn document_get_handles_all_primitive_types() {
let json = r#"{"b":true,"n":null,"f":3.5,"u":42,"s":"hello"}"#;
let doc = tson::compile_json(json).unwrap();
assert!(matches!(doc.get("b"), Some(TsonData::Bool(true))));
assert!(matches!(doc.get("n"), Some(TsonData::Null)));
assert!(matches!(doc.get("f"), Some(TsonData::Float(f)) if (*f - 3.5).abs() < 0.01));
assert!(matches!(doc.get("u"), Some(TsonData::Int(42))));
assert!(matches!(doc.get("s"), Some(TsonData::String(s)) if s == "hello"));
}
#[test]
fn document_get_returns_none_on_primitive_entry() {
let json = r#""just a string""#;
let doc = tson::compile_json(json).unwrap();
assert!(doc.get("any_field").is_none());
}
#[test]
fn document_get_returns_none_on_empty_document() {
let json = r#"null"#;
let doc = tson::compile_json(json).unwrap();
assert!(doc.get("anything").is_none());
}
#[test]
fn tson_data_field_nested_lookup() {
let json = r#"{"user":{"name":"Alice","meta":{"role":"admin"}}}"#;
let doc = tson::compile_json(json).unwrap();
let defs = &doc.definitions;
let user = doc.get("user").unwrap();
let name = user.field("name", defs).unwrap();
assert!(matches!(name, TsonData::String(s) if s == "Alice"));
let meta = user.field("meta", defs).unwrap();
let role = meta.field("role", defs).unwrap();
assert!(matches!(role, TsonData::String(s) if s == "admin"));
}
#[test]
fn tson_data_values_on_primitives_returns_empty() {
assert_eq!(TsonData::Null.values().len(), 0);
assert_eq!(TsonData::Bool(true).values().len(), 0);
assert_eq!(TsonData::Int(1).values().len(), 0);
assert_eq!(TsonData::String("x".to_string()).values().len(), 0);
}
#[test]
fn tson_data_len_on_empty_array() {
let arr = TsonData::Array(0, 0, vec![]);
assert_eq!(arr.len(), 0);
assert!(arr.is_empty());
}
#[test]
fn emit_then_field_access_preserves_value_tree() {
use tson::{emit, TsonData};
let inner = TsonData::Object(
1,
vec![TsonData::String("x".to_string()), TsonData::Int(99)],
);
let outer = TsonData::Object(0, vec![TsonData::Float(1.5), inner]);
let bytes = emit(&outer).unwrap();
let doc = tson::from_bytes(&bytes).unwrap();
let defs = &doc.definitions;
let f0 = doc.get("f0").unwrap();
assert!(matches!(f0, TsonData::Float(v) if (*v - 1.5).abs() < 0.01));
let f1 = doc.get("f1").unwrap();
let inner_val = f1.field("f0", defs).unwrap();
assert!(matches!(inner_val, TsonData::String(s) if s == "x"));
let inner_num = f1.field("f1", defs).unwrap();
assert!(matches!(inner_num, TsonData::Int(99)));
}
#[test]
fn index_and_get_by_index() {
let json = r#"{"name":"Alice","age":30,"city":"NYC"}"#;
let doc = tson::compile_json(json).unwrap();
let name_idx = doc.index("name").unwrap();
let age_idx = doc.index("age").unwrap();
let city_idx = doc.index("city").unwrap();
assert!(matches!(doc.get_by_index(name_idx), Some(TsonData::String(s)) if s == "Alice"));
assert!(matches!(doc.get_by_index(age_idx), Some(TsonData::Int(30))));
assert!(matches!(doc.get_by_index(city_idx), Some(TsonData::String(s)) if s == "NYC"));
assert!(
doc.get_by_index(999).is_none(),
"out of bounds returns None"
);
assert!(doc.index("missing").is_none());
}
#[test]
fn index_and_get_by_index_primitive_root() {
let json = r#""just a string""#;
let doc = tson::compile_json(json).unwrap();
assert!(doc.index("anything").is_none());
assert!(doc.get_by_index(0).is_none());
}
#[test]
fn emit_with_context_roundtrip() {
let template = r#"{"f0":"x","f1":0}"#;
let tpl_doc = tson::compile_json(template).unwrap();
let defs = tpl_doc.definitions.clone();
let dict = tpl_doc.dict.clone();
let obj_def = defs
.iter()
.find(|d| d.def_type == tson::TsonType::Object)
.unwrap();
let def_idx = obj_def.index;
let response = tson::TsonData::Object(
def_idx,
vec![
tson::TsonData::String("processed".to_string()),
tson::TsonData::Int(42),
],
);
let bytes = tson::emit_with_context(&response, &defs, &dict).unwrap();
assert!(!bytes.is_empty(), "emit_with_context produced bytes");
let restored = tson::from_bytes(&bytes).unwrap();
let restored_json = tson::decompile_to_value(&restored).unwrap();
assert_eq!(restored_json["f0"].as_str().unwrap(), "processed");
assert_eq!(restored_json["f1"].as_i64().unwrap(), 42);
}
#[test]
fn emit_with_context_reuses_dict() {
let template = r#"{"status":"ok","code":0}"#;
let tpl_doc = tson::compile_json(template).unwrap();
let defs = tpl_doc.definitions.clone();
let dict = tpl_doc.dict.clone();
let obj_def = defs
.iter()
.find(|d| d.def_type == tson::TsonType::Object)
.unwrap();
let def_idx = obj_def.index;
let response = tson::TsonData::Object(
def_idx,
vec![
tson::TsonData::Int(200), tson::TsonData::String("ok".to_string()), ],
);
let bytes = tson::emit_with_context(&response, &defs, &dict).unwrap();
let restored = tson::from_bytes(&bytes).unwrap();
assert_eq!(
restored.dict.len(),
dict.len(),
"emit_with_context should preserve dict content"
);
}
#[test]
fn tson_doc_reader_multi_document() {
use std::io::Cursor;
use tson::stream::TsonDocReader;
let json1 = r#"{"a":1}"#;
let json2 = r#"{"b":2}"#;
let doc1 = tson::compile_json(json1).unwrap();
let doc2 = tson::compile_json(json2).unwrap();
let bin1 = tson::to_bytes(&doc1).unwrap();
let bin2 = tson::to_bytes(&doc2).unwrap();
let mut stream = Vec::new();
stream.extend_from_slice(&(bin1.len() as u32).to_le_bytes());
stream.extend_from_slice(&bin1);
stream.extend_from_slice(&(bin2.len() as u32).to_le_bytes());
stream.extend_from_slice(&bin2);
let cursor = Cursor::new(stream);
let mut reader = TsonDocReader::new(cursor);
let mut count = 0;
for result in &mut reader {
let doc = result.unwrap();
count += 1;
assert!(doc.data.len() >= 1, "each doc has at least one entry");
}
assert_eq!(count, 2, "read 2 documents from the stream");
let cursor = Cursor::new(Vec::new());
let mut reader = TsonDocReader::new(cursor);
assert!(reader.next().is_none(), "empty stream yields None");
}
#[test]
fn stream_reader_yields_all_entries() {
let json = r#"[
{"x": 1},
{"x": 2},
{"x": 3}
]"#;
let doc = tson::compile_json(json).unwrap();
let bytes = tson::to_bytes(&doc).unwrap();
let mut reader = tson::TsonStreamReader::new(&bytes).unwrap();
let mut count = 0;
for result in &mut reader {
let _chunk = result.unwrap();
count += 1;
}
assert_eq!(count, 1, "array of 3 objects is 1 root entry");
}
}