base_d/encoders/algorithms/schema/
mod.rs

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
14// Re-export key types for convenience
15pub 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// Re-export fiche functions for library users
26#[allow(unused_imports)]
27pub use fiche::{parse as parse_fiche, serialize as serialize_fiche};
28
29/// Encode JSON to schema format: JSON → IR → binary → \[compress\] → display96 → framed
30///
31/// Transforms JSON into a compact, display-safe wire format suitable for LLM-to-LLM communication.
32/// The output is wrapped in Egyptian hieroglyph delimiters (`𓍹...𓍺`) and uses a 96-character
33/// alphabet of box-drawing and geometric shapes.
34///
35/// # Arguments
36///
37/// * `json` - JSON string to encode (must be object or array of objects)
38/// * `compress` - Optional compression algorithm (brotli, lz4, or zstd)
39///
40/// # Returns
41///
42/// Returns a framed, display-safe string like `𓍹{encoded_payload}𓍺`
43///
44/// # Errors
45///
46/// * `SchemaError::InvalidInput` - Invalid JSON or unsupported structure (e.g., root primitives)
47/// * `SchemaError::Compression` - Compression failure
48///
49/// # Example
50///
51/// ```ignore
52/// use base_d::{encode_schema, SchemaCompressionAlgo};
53///
54/// let json = r#"{"users":[{"id":1,"name":"alice"}]}"#;
55///
56/// // Without compression
57/// let encoded = encode_schema(json, None)?;
58/// println!("{}", encoded); // 𓍹╣◟╥◕◝▰◣◥▟╺▖◘▰◝▤◀╧𓍺
59///
60/// // With brotli compression
61/// let compressed = encode_schema(json, Some(SchemaCompressionAlgo::Brotli))?;
62/// ```
63///
64/// # See Also
65///
66/// * [`decode_schema`] - Decode schema format back to JSON
67/// * [SCHEMA.md](../../../SCHEMA.md) - Full format specification
68pub 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
80/// Decode schema format to JSON: framed → display96 → \[decompress\] → binary → IR → JSON
81///
82/// Reverses the schema encoding pipeline to reconstruct the original JSON from the framed,
83/// display-safe wire format. Automatically detects and handles compression.
84///
85/// # Arguments
86///
87/// * `encoded` - Schema-encoded string with delimiters (`𓍹...𓍺`)
88/// * `pretty` - Pretty-print JSON output with indentation
89///
90/// # Returns
91///
92/// Returns the decoded JSON string (minified or pretty-printed)
93///
94/// # Errors
95///
96/// * `SchemaError::InvalidFrame` - Missing or invalid frame delimiters
97/// * `SchemaError::InvalidCharacter` - Invalid character in display96 payload
98/// * `SchemaError::Decompression` - Decompression failure
99/// * `SchemaError::UnexpectedEndOfData` - Truncated or corrupted binary data
100/// * `SchemaError::InvalidTypeTag` - Invalid type tag in header
101///
102/// # Example
103///
104/// ```ignore
105/// use base_d::decode_schema;
106///
107/// let encoded = "𓍹╣◟╥◕◝▰◣◥▟╺▖◘▰◝▤◀╧𓍺";
108///
109/// // Minified output
110/// let json = decode_schema(encoded, false)?;
111/// println!("{}", json); // {"users":[{"id":1,"name":"alice"}]}
112///
113/// // Pretty-printed output
114/// let pretty = decode_schema(encoded, true)?;
115/// println!("{}", pretty);
116/// // {
117/// //   "users": [
118/// //     {"id": 1, "name": "alice"}
119/// //   ]
120/// // }
121/// ```
122///
123/// # See Also
124///
125/// * [`encode_schema`] - Encode JSON to schema format
126/// * [SCHEMA.md](../../../SCHEMA.md) - Full format specification
127pub 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
136/// Encode JSON to fiche format: JSON → IR → fiche
137///
138/// Transforms JSON into a model-readable structured format using Unicode delimiters.
139/// Unlike carrier98 (opaque binary), fiche is designed for models to parse directly.
140///
141/// # Format
142///
143/// ```text
144/// @{root}┃{field}:{type}┃{field}:{type}...
145/// ◉{value}┃{value}┃{value}...
146/// ```
147///
148/// # Example
149///
150/// ```ignore
151/// use base_d::encode_fiche;
152///
153/// let json = r#"{"users":[{"id":1,"name":"alice"}]}"#;
154/// let fiche = encode_fiche(json)?;
155/// // @users┃id:int┃name:str
156/// // ◉1┃alice
157/// ```
158pub 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
177/// Decode fiche format to JSON: fiche → IR → JSON
178///
179/// Reverses the fiche encoding to reconstruct JSON from the model-readable format.
180///
181/// # Example
182///
183/// ```ignore
184/// use base_d::decode_fiche;
185///
186/// let fiche = "@users┃id:int┃name:str\n◉1┃alice";
187/// let json = decode_fiche(fiche, false)?;
188/// // {"users":[{"id":1,"name":"alice"}]}
189/// ```
190pub 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        // Pack and unpack
225        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        // Mark second value as null (row 0, field 1)
284        let total_values: usize = 2 * 2; // 2 rows * 2 fields = 4 values
285        let bitmap_bytes = total_values.div_ceil(8); // 1 byte
286        let mut null_bitmap = vec![0u8; bitmap_bytes];
287        null_bitmap[0] |= 1 << 1; // Set bit 1 (second value)
288
289        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, // This is marked as null in bitmap
295            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        // Empty data
394        let result = unpack(&[]);
395        assert!(matches!(
396            result,
397            Err(SchemaError::UnexpectedEndOfData { .. })
398        ));
399
400        // Truncated data
401        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        // Parse both as serde_json::Value and compare (order-independent)
416        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        // SWAPI-like data with nested arrays of primitives
437        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        // Verify fiche representation
441        let fiche_output = fiche::serialize(&ir).unwrap();
442
443        // Should have @people root key
444        assert!(fiche_output.starts_with("@people"));
445        // Should have @ markers for array fields
446        assert!(fiche_output.contains("films:@"));
447        assert!(fiche_output.contains("vehicles:@"));
448        // Should have circled numbers for nested elements
449        assert!(fiche_output.contains("①"));
450
451        // Verify round trip
452        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        // Test common pagination wrapper keys get unwrapped
464        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            // Should have root key from wrapper
475            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            // Should have 2 rows (unwrapped the array)
480            assert_eq!(ir.header.row_count, 2);
481
482            // Round trip should preserve data
483            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        // Validate frame delimiters
540        assert!(encoded.starts_with(frame::FRAME_START));
541        assert!(encoded.ends_with(frame::FRAME_END));
542
543        // Decode back to JSON
544        let decoded = decode_schema(&encoded, false).unwrap();
545
546        // Compare as JSON values (order-independent)
547        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        // Empty objects should fail or handle gracefully
579        // This depends on JsonParser behavior
580        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        // Calculate compression ratio
613        let compression_ratio = input.len() as f64 / encoded.len() as f64;
614        println!("Compression ratio: {:.2}x", compression_ratio);
615
616        // Decode and verify
617        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        // Test each compression algorithm
656        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}