1pub mod binary_packer;
2pub mod binary_unpacker;
3pub mod compression;
4pub mod display96;
5pub mod fiche;
6pub mod frame;
7pub mod parsers;
8pub mod serializers;
9pub mod types;
10
11#[cfg(test)]
12mod edge_cases;
13
14pub use binary_packer::pack;
16pub use binary_unpacker::unpack;
17pub use compression::SchemaCompressionAlgo;
18pub use frame::{decode_framed, encode_framed};
19pub use parsers::{InputParser, JsonParser};
20pub use serializers::{JsonSerializer, OutputSerializer};
21pub use types::{
22 FieldDef, FieldType, IntermediateRepresentation, SchemaError, SchemaHeader, SchemaValue,
23};
24
25#[allow(unused_imports)]
27pub use fiche::{parse as parse_fiche, serialize as serialize_fiche};
28
29pub fn encode_schema(
69 json: &str,
70 compress: Option<SchemaCompressionAlgo>,
71) -> Result<String, SchemaError> {
72 use parsers::{InputParser, JsonParser};
73
74 let ir = JsonParser::parse(json)?;
75 let binary = pack(&ir);
76 let compressed = compression::compress_with_prefix(&binary, compress)?;
77 Ok(frame::encode_framed(&compressed))
78}
79
80pub fn decode_schema(encoded: &str, pretty: bool) -> Result<String, SchemaError> {
128 use serializers::{JsonSerializer, OutputSerializer};
129
130 let compressed = frame::decode_framed(encoded)?;
131 let binary = compression::decompress_with_prefix(&compressed)?;
132 let ir = unpack(&binary)?;
133 JsonSerializer::serialize(&ir, pretty)
134}
135
136pub fn encode_fiche(json: &str) -> Result<String, SchemaError> {
159 encode_fiche_with_options(json, false)
160}
161
162pub fn encode_fiche_minified(json: &str) -> Result<String, SchemaError> {
163 encode_fiche_with_options(json, true)
164}
165
166fn encode_fiche_with_options(json: &str, minify: bool) -> Result<String, SchemaError> {
167 use parsers::{InputParser, JsonParser};
168
169 let ir = JsonParser::parse(json)?;
170 if minify {
171 fiche::serialize_minified(&ir)
172 } else {
173 fiche::serialize(&ir)
174 }
175}
176
177pub fn decode_fiche(fiche_input: &str, pretty: bool) -> Result<String, SchemaError> {
191 use serializers::{JsonSerializer, OutputSerializer};
192
193 let ir = fiche::parse(fiche_input)?;
194 JsonSerializer::serialize(&ir, pretty)
195}
196
197#[cfg(test)]
198mod integration_tests {
199 use super::*;
200 use crate::encoders::algorithms::schema::types::{
201 FLAG_HAS_NULLS, FLAG_HAS_ROOT_KEY, FieldDef, FieldType, IntermediateRepresentation,
202 SchemaHeader, SchemaValue,
203 };
204 use parsers::{InputParser, JsonParser};
205 use serializers::{JsonSerializer, OutputSerializer};
206
207 #[test]
208 fn test_round_trip_simple() {
209 let fields = vec![
210 FieldDef::new("id", FieldType::U64),
211 FieldDef::new("name", FieldType::String),
212 ];
213 let header = SchemaHeader::new(2, fields);
214
215 let values = vec![
216 SchemaValue::U64(1),
217 SchemaValue::String("Alice".to_string()),
218 SchemaValue::U64(2),
219 SchemaValue::String("Bob".to_string()),
220 ];
221
222 let original = IntermediateRepresentation::new(header, values).unwrap();
223
224 let packed = pack(&original);
226 let unpacked = unpack(&packed).unwrap();
227
228 assert_eq!(original, unpacked);
229 }
230
231 #[test]
232 fn test_round_trip_all_types() {
233 let fields = vec![
234 FieldDef::new("u64_field", FieldType::U64),
235 FieldDef::new("i64_field", FieldType::I64),
236 FieldDef::new("f64_field", FieldType::F64),
237 FieldDef::new("string_field", FieldType::String),
238 FieldDef::new("bool_field", FieldType::Bool),
239 ];
240 let header = SchemaHeader::new(1, fields);
241
242 let values = vec![
243 SchemaValue::U64(42),
244 SchemaValue::I64(-42),
245 SchemaValue::F64(std::f64::consts::PI),
246 SchemaValue::String("test".to_string()),
247 SchemaValue::Bool(true),
248 ];
249
250 let original = IntermediateRepresentation::new(header, values).unwrap();
251
252 let packed = pack(&original);
253 let unpacked = unpack(&packed).unwrap();
254
255 assert_eq!(original, unpacked);
256 }
257
258 #[test]
259 fn test_round_trip_with_root_key() {
260 let mut header = SchemaHeader::new(1, vec![FieldDef::new("id", FieldType::U64)]);
261 header.root_key = Some("users".to_string());
262 header.set_flag(FLAG_HAS_ROOT_KEY);
263
264 let values = vec![SchemaValue::U64(42)];
265 let original = IntermediateRepresentation::new(header, values).unwrap();
266
267 let packed = pack(&original);
268 let unpacked = unpack(&packed).unwrap();
269
270 assert_eq!(original, unpacked);
271 }
272
273 #[test]
274 fn test_round_trip_with_nulls() {
275 let mut header = SchemaHeader::new(
276 2,
277 vec![
278 FieldDef::new("id", FieldType::U64),
279 FieldDef::new("name", FieldType::String),
280 ],
281 );
282
283 let total_values: usize = 2 * 2; let bitmap_bytes = total_values.div_ceil(8); let mut null_bitmap = vec![0u8; bitmap_bytes];
287 null_bitmap[0] |= 1 << 1; header.null_bitmap = Some(null_bitmap);
290 header.set_flag(FLAG_HAS_NULLS);
291
292 let values = vec![
293 SchemaValue::U64(1),
294 SchemaValue::Null, SchemaValue::U64(2),
296 SchemaValue::String("Bob".to_string()),
297 ];
298
299 let original = IntermediateRepresentation::new(header, values).unwrap();
300
301 let packed = pack(&original);
302 let unpacked = unpack(&packed).unwrap();
303
304 assert_eq!(original, unpacked);
305 }
306
307 #[test]
308 fn test_round_trip_array() {
309 let fields = vec![FieldDef::new(
310 "tags",
311 FieldType::Array(Box::new(FieldType::U64)),
312 )];
313 let header = SchemaHeader::new(1, fields);
314
315 let values = vec![SchemaValue::Array(vec![
316 SchemaValue::U64(1),
317 SchemaValue::U64(2),
318 SchemaValue::U64(3),
319 ])];
320
321 let original = IntermediateRepresentation::new(header, values).unwrap();
322
323 let packed = pack(&original);
324 let unpacked = unpack(&packed).unwrap();
325
326 assert_eq!(original, unpacked);
327 }
328
329 #[test]
330 fn test_round_trip_large_values() {
331 let fields = vec![
332 FieldDef::new("large_u64", FieldType::U64),
333 FieldDef::new("large_i64", FieldType::I64),
334 ];
335 let header = SchemaHeader::new(1, fields);
336
337 let values = vec![SchemaValue::U64(u64::MAX), SchemaValue::I64(i64::MIN)];
338
339 let original = IntermediateRepresentation::new(header, values).unwrap();
340
341 let packed = pack(&original);
342 let unpacked = unpack(&packed).unwrap();
343
344 assert_eq!(original, unpacked);
345 }
346
347 #[test]
348 fn test_round_trip_empty_string() {
349 let fields = vec![FieldDef::new("name", FieldType::String)];
350 let header = SchemaHeader::new(1, fields);
351
352 let values = vec![SchemaValue::String("".to_string())];
353
354 let original = IntermediateRepresentation::new(header, values).unwrap();
355
356 let packed = pack(&original);
357 let unpacked = unpack(&packed).unwrap();
358
359 assert_eq!(original, unpacked);
360 }
361
362 #[test]
363 fn test_round_trip_multiple_rows() {
364 let fields = vec![
365 FieldDef::new("id", FieldType::U64),
366 FieldDef::new("score", FieldType::F64),
367 FieldDef::new("active", FieldType::Bool),
368 ];
369 let header = SchemaHeader::new(3, fields);
370
371 let values = vec![
372 SchemaValue::U64(1),
373 SchemaValue::F64(95.5),
374 SchemaValue::Bool(true),
375 SchemaValue::U64(2),
376 SchemaValue::F64(87.3),
377 SchemaValue::Bool(false),
378 SchemaValue::U64(3),
379 SchemaValue::F64(92.1),
380 SchemaValue::Bool(true),
381 ];
382
383 let original = IntermediateRepresentation::new(header, values).unwrap();
384
385 let packed = pack(&original);
386 let unpacked = unpack(&packed).unwrap();
387
388 assert_eq!(original, unpacked);
389 }
390
391 #[test]
392 fn test_invalid_data() {
393 let result = unpack(&[]);
395 assert!(matches!(
396 result,
397 Err(SchemaError::UnexpectedEndOfData { .. })
398 ));
399
400 let result = unpack(&[0, 1, 2]);
402 assert!(result.is_err());
403 }
404
405 #[test]
406 fn test_json_full_roundtrip() {
407 let input = r#"{"users":[{"id":1,"name":"alice"},{"id":2,"name":"bob"}]}"#;
408 let ir = JsonParser::parse(input).unwrap();
409 let binary = pack(&ir);
410 let compressed = compression::compress_with_prefix(&binary, None).unwrap();
411 let decompressed = compression::decompress_with_prefix(&compressed).unwrap();
412 let ir2 = unpack(&decompressed).unwrap();
413 let output = JsonSerializer::serialize(&ir2, false).unwrap();
414
415 let input_value: serde_json::Value = serde_json::from_str(input).unwrap();
417 let output_value: serde_json::Value = serde_json::from_str(&output).unwrap();
418 assert_eq!(input_value, output_value);
419 }
420
421 #[test]
422 fn test_json_simple_object() {
423 let input = r#"{"id":1,"name":"alice","score":95.5}"#;
424 let ir = JsonParser::parse(input).unwrap();
425 let binary = pack(&ir);
426 let ir2 = unpack(&binary).unwrap();
427 let output = JsonSerializer::serialize(&ir2, false).unwrap();
428
429 let input_value: serde_json::Value = serde_json::from_str(input).unwrap();
430 let output_value: serde_json::Value = serde_json::from_str(&output).unwrap();
431 assert_eq!(input_value, output_value);
432 }
433
434 #[test]
435 fn test_json_swapi_nested_arrays() {
436 let input = r#"{"people":[{"name":"Luke","height":"172","films":["film/1","film/2"],"vehicles":[]},{"name":"C-3PO","height":"167","films":["film/1","film/2","film/3"],"vehicles":[]}]}"#;
438 let ir = JsonParser::parse(input).unwrap();
439
440 let fiche_output = fiche::serialize(&ir).unwrap();
442
443 assert!(fiche_output.starts_with("@people"));
445 assert!(fiche_output.contains("films:@"));
447 assert!(fiche_output.contains("vehicles:@"));
448 assert!(fiche_output.contains("①"));
450
451 let binary = pack(&ir);
453 let ir2 = unpack(&binary).unwrap();
454 let output = JsonSerializer::serialize(&ir2, false).unwrap();
455
456 let input_value: serde_json::Value = serde_json::from_str(input).unwrap();
457 let output_value: serde_json::Value = serde_json::from_str(&output).unwrap();
458 assert_eq!(input_value, output_value);
459 }
460
461 #[test]
462 fn test_json_wrapper_keys() {
463 let test_cases = vec![
465 r#"{"results":[{"id":1,"name":"a"},{"id":2,"name":"b"}]}"#,
466 r#"{"data":[{"id":1,"name":"a"},{"id":2,"name":"b"}]}"#,
467 r#"{"items":[{"id":1,"name":"a"},{"id":2,"name":"b"}]}"#,
468 r#"{"records":[{"id":1,"name":"a"},{"id":2,"name":"b"}]}"#,
469 ];
470
471 for input in test_cases {
472 let ir = JsonParser::parse(input).unwrap();
473
474 assert!(ir.header.root_key.is_some());
476 let root = ir.header.root_key.as_ref().unwrap();
477 assert!(root == "results" || root == "data" || root == "items" || root == "records");
478
479 assert_eq!(ir.header.row_count, 2);
481
482 let binary = pack(&ir);
484 let ir2 = unpack(&binary).unwrap();
485 let output = JsonSerializer::serialize(&ir2, false).unwrap();
486
487 let input_value: serde_json::Value = serde_json::from_str(input).unwrap();
488 let output_value: serde_json::Value = serde_json::from_str(&output).unwrap();
489 assert_eq!(input_value, output_value);
490 }
491 }
492
493 #[test]
494 fn test_json_nested_objects() {
495 let input = r#"{"user":{"profile":{"name":"alice","age":30}}}"#;
496 let ir = JsonParser::parse(input).unwrap();
497 let binary = pack(&ir);
498 let ir2 = unpack(&binary).unwrap();
499 let output = JsonSerializer::serialize(&ir2, false).unwrap();
500
501 let input_value: serde_json::Value = serde_json::from_str(input).unwrap();
502 let output_value: serde_json::Value = serde_json::from_str(&output).unwrap();
503 assert_eq!(input_value, output_value);
504 }
505
506 #[test]
507 fn test_json_with_nulls() {
508 let input = r#"{"name":"alice","age":null,"active":true}"#;
509 let ir = JsonParser::parse(input).unwrap();
510 assert!(ir.header.has_flag(FLAG_HAS_NULLS));
511
512 let binary = pack(&ir);
513 let ir2 = unpack(&binary).unwrap();
514 let output = JsonSerializer::serialize(&ir2, false).unwrap();
515
516 let input_value: serde_json::Value = serde_json::from_str(input).unwrap();
517 let output_value: serde_json::Value = serde_json::from_str(&output).unwrap();
518 assert_eq!(input_value, output_value);
519 }
520
521 #[test]
522 fn test_json_with_arrays() {
523 let input = r#"{"scores":[95,87,92],"tags":["rust","json"]}"#;
524 let ir = JsonParser::parse(input).unwrap();
525 let binary = pack(&ir);
526 let ir2 = unpack(&binary).unwrap();
527 let output = JsonSerializer::serialize(&ir2, false).unwrap();
528
529 let input_value: serde_json::Value = serde_json::from_str(input).unwrap();
530 let output_value: serde_json::Value = serde_json::from_str(&output).unwrap();
531 assert_eq!(input_value, output_value);
532 }
533
534 #[test]
535 fn test_encode_schema_roundtrip() {
536 let input = r#"{"users":[{"id":1,"name":"alice"},{"id":2,"name":"bob"}]}"#;
537 let encoded = encode_schema(input, None).unwrap();
538
539 assert!(encoded.starts_with(frame::FRAME_START));
541 assert!(encoded.ends_with(frame::FRAME_END));
542
543 let decoded = decode_schema(&encoded, false).unwrap();
545
546 let input_value: serde_json::Value = serde_json::from_str(input).unwrap();
548 let output_value: serde_json::Value = serde_json::from_str(&decoded).unwrap();
549 assert_eq!(input_value, output_value);
550 }
551
552 #[test]
553 fn test_encode_schema_simple() {
554 let input = r#"{"id":1,"name":"alice","score":95.5}"#;
555 let encoded = encode_schema(input, None).unwrap();
556 let decoded = decode_schema(&encoded, false).unwrap();
557
558 let input_value: serde_json::Value = serde_json::from_str(input).unwrap();
559 let output_value: serde_json::Value = serde_json::from_str(&decoded).unwrap();
560 assert_eq!(input_value, output_value);
561 }
562
563 #[test]
564 fn test_encode_schema_with_nulls() {
565 let input = r#"{"name":"alice","age":null,"active":true}"#;
566 let encoded = encode_schema(input, None).unwrap();
567 let decoded = decode_schema(&encoded, false).unwrap();
568
569 let input_value: serde_json::Value = serde_json::from_str(input).unwrap();
570 let output_value: serde_json::Value = serde_json::from_str(&decoded).unwrap();
571 assert_eq!(input_value, output_value);
572 }
573
574 #[test]
575 fn test_encode_schema_empty_object() {
576 let input = r#"{}"#;
577 let result = encode_schema(input, None);
578 println!("Empty object result: {:?}", result);
581 }
582
583 #[test]
584 fn test_decode_schema_invalid_frame() {
585 let invalid = "not_framed_data";
586 let result = decode_schema(invalid, false);
587 assert!(matches!(result, Err(SchemaError::InvalidFrame(_))));
588 }
589
590 #[test]
591 fn test_decode_schema_invalid_chars() {
592 let invalid = format!("{}ABC{}", frame::FRAME_START, frame::FRAME_END);
593 let result = decode_schema(&invalid, false);
594 assert!(matches!(result, Err(SchemaError::InvalidCharacter(_))));
595 }
596
597 #[test]
598 fn test_visual_wire_format() {
599 let input = r#"{"users":[{"id":1,"name":"alice"},{"id":2,"name":"bob"}]}"#;
600 let encoded = encode_schema(input, None).unwrap();
601
602 println!("\n=== Visual Wire Format ===");
603 println!("Input JSON: {}", input);
604 println!("Input length: {} bytes", input.len());
605 println!("\nEncoded output: {}", encoded);
606 println!(
607 "Encoded length: {} chars ({} bytes UTF-8)",
608 encoded.chars().count(),
609 encoded.len()
610 );
611
612 let compression_ratio = input.len() as f64 / encoded.len() as f64;
614 println!("Compression ratio: {:.2}x", compression_ratio);
615
616 let decoded = decode_schema(&encoded, false).unwrap();
618 let input_value: serde_json::Value = serde_json::from_str(input).unwrap();
619 let output_value: serde_json::Value = serde_json::from_str(&decoded).unwrap();
620 assert_eq!(input_value, output_value);
621 println!("Roundtrip verified ✓\n");
622 }
623
624 #[test]
625 fn test_compression_comparison() {
626 let test_cases = [
627 r#"{"id":1}"#,
628 r#"{"id":1,"name":"alice"}"#,
629 r#"{"users":[{"id":1,"name":"alice"},{"id":2,"name":"bob"}]}"#,
630 r#"{"data":[1,2,3,4,5,6,7,8,9,10]}"#,
631 ];
632
633 println!("\n=== Compression Comparison ===");
634 for (i, input) in test_cases.iter().enumerate() {
635 let encoded = encode_schema(input, None).unwrap();
636 let ratio = input.len() as f64 / encoded.len() as f64;
637
638 println!(
639 "Test case {}: {} bytes → {} bytes ({:.2}x)",
640 i + 1,
641 input.len(),
642 encoded.len(),
643 ratio
644 );
645 }
646 println!();
647 }
648
649 #[test]
650 fn test_encode_schema_with_compression() {
651 use super::SchemaCompressionAlgo;
652
653 let input = r#"{"users":[{"id":1,"name":"alice"},{"id":2,"name":"bob"},{"id":3,"name":"charlie"}]}"#;
654
655 for algo in [
657 SchemaCompressionAlgo::Brotli,
658 SchemaCompressionAlgo::Lz4,
659 SchemaCompressionAlgo::Zstd,
660 ] {
661 let encoded = encode_schema(input, Some(algo)).unwrap();
662 let decoded = decode_schema(&encoded, false).unwrap();
663
664 let input_value: serde_json::Value = serde_json::from_str(input).unwrap();
665 let output_value: serde_json::Value = serde_json::from_str(&decoded).unwrap();
666 assert_eq!(
667 input_value, output_value,
668 "Failed for compression algorithm: {:?}",
669 algo
670 );
671 }
672 }
673
674 #[test]
675 fn test_compression_size_comparison() {
676 use super::SchemaCompressionAlgo;
677
678 let input = r#"{"users":[{"id":1,"name":"alice","active":true,"score":95.5},{"id":2,"name":"bob","active":false,"score":87.3},{"id":3,"name":"charlie","active":true,"score":92.1}]}"#;
679
680 println!("\n=== Compression Size Comparison ===");
681 println!("Input JSON: {} bytes", input.len());
682
683 let no_compress = encode_schema(input, None).unwrap();
684 println!("No compression: {} bytes", no_compress.len());
685
686 for algo in [
687 SchemaCompressionAlgo::Brotli,
688 SchemaCompressionAlgo::Lz4,
689 SchemaCompressionAlgo::Zstd,
690 ] {
691 let compressed = encode_schema(input, Some(algo)).unwrap();
692 let ratio = no_compress.len() as f64 / compressed.len() as f64;
693 println!(
694 "{:?}: {} bytes ({:.2}x vs uncompressed)",
695 algo,
696 compressed.len(),
697 ratio
698 );
699 }
700 println!();
701 }
702}