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