lnmp_codec/binary/
nested_encoder.rs

1//! Nested structure encoding for LNMP v0.5 binary format.
2//!
3//! This module provides encoding support for nested records and arrays in the binary format.
4//! It implements recursive encoding with depth validation and size limits to prevent
5//! stack overflow and memory exhaustion attacks.
6
7use super::error::BinaryError;
8use super::types::TypeTag;
9use super::varint;
10use lnmp_core::{LnmpRecord, LnmpValue};
11
12/// Configuration for nested structure encoding (v0.5)
13#[derive(Debug, Clone)]
14pub struct NestedEncoderConfig {
15    /// Maximum nesting depth allowed (default: 32)
16    pub max_depth: usize,
17    /// Maximum record size in bytes (None = unlimited)
18    pub max_record_size: Option<usize>,
19    /// Whether to validate canonical ordering
20    pub validate_canonical: bool,
21}
22
23impl Default for NestedEncoderConfig {
24    fn default() -> Self {
25        Self {
26            max_depth: 32,
27            max_record_size: None,
28            validate_canonical: false,
29        }
30    }
31}
32
33impl NestedEncoderConfig {
34    /// Creates a new nested encoder configuration with default settings
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    /// Sets the maximum nesting depth
40    pub fn with_max_depth(mut self, max_depth: usize) -> Self {
41        self.max_depth = max_depth;
42        self
43    }
44
45    /// Sets the maximum record size in bytes
46    pub fn with_max_record_size(mut self, max_size: Option<usize>) -> Self {
47        self.max_record_size = max_size;
48        self
49    }
50
51    /// Sets whether to validate canonical ordering
52    pub fn with_validate_canonical(mut self, validate: bool) -> Self {
53        self.validate_canonical = validate;
54        self
55    }
56}
57
58/// Binary nested structure encoder for LNMP v0.5
59///
60/// Encodes nested records and arrays with depth validation and size limits.
61#[derive(Debug)]
62pub struct BinaryNestedEncoder {
63    config: NestedEncoderConfig,
64}
65
66impl BinaryNestedEncoder {
67    /// Creates a new nested encoder with default configuration
68    pub fn new() -> Self {
69        Self {
70            config: NestedEncoderConfig::default(),
71        }
72    }
73
74    /// Creates a nested encoder with custom configuration
75    pub fn with_config(config: NestedEncoderConfig) -> Self {
76        Self { config }
77    }
78
79    /// Encodes a nested record to binary format
80    ///
81    /// Binary layout:
82    /// ```text
83    /// ┌──────────┬──────────────┬─────────────────────────────────┐
84    /// │   TAG    │ FIELD_COUNT  │      FIELD ENTRIES...           │
85    /// │ (1 byte) │  (VarInt)    │  { FID | VALUE }*               │
86    /// └──────────┴──────────────┴─────────────────────────────────┘
87    /// ```
88    ///
89    /// # Arguments
90    ///
91    /// * `record` - The nested record to encode
92    ///
93    /// # Returns
94    ///
95    /// A vector of bytes representing the binary-encoded nested record
96    ///
97    /// # Errors
98    ///
99    /// Returns `BinaryError` if:
100    /// - Nesting depth exceeds configured maximum
101    /// - Record size exceeds configured maximum
102    /// - Field encoding fails
103    pub fn encode_nested_record(&self, record: &LnmpRecord) -> Result<Vec<u8>, BinaryError> {
104        self.encode_nested_record_with_depth(record, 0)
105    }
106
107    /// Encodes a nested record with depth tracking
108    fn encode_nested_record_with_depth(
109        &self,
110        record: &LnmpRecord,
111        current_depth: usize,
112    ) -> Result<Vec<u8>, BinaryError> {
113        // Validate depth
114        if current_depth >= self.config.max_depth {
115            return Err(BinaryError::NestingDepthExceeded {
116                depth: current_depth,
117                max: self.config.max_depth,
118            });
119        }
120
121        let mut buffer = Vec::new();
122
123        // Write TAG byte (0x06 for NestedRecord)
124        buffer.push(TypeTag::NestedRecord.to_u8());
125
126        // Get fields (sorted for canonical ordering)
127        let fields = record.sorted_fields();
128
129        // Encode FIELD_COUNT as VarInt
130        let field_count = fields.len() as i64;
131        buffer.extend_from_slice(&varint::encode(field_count));
132
133        // Recursively encode each field
134        for field in fields {
135            // Encode FID as VarInt
136            buffer.extend_from_slice(&varint::encode(field.fid as i64));
137
138            // Encode VALUE recursively
139            let value_bytes = self.encode_value_recursive(&field.value, current_depth + 1)?;
140            buffer.extend_from_slice(&value_bytes);
141
142            // Check size limit if configured
143            if let Some(max_size) = self.config.max_record_size {
144                if buffer.len() > max_size {
145                    return Err(BinaryError::RecordSizeExceeded {
146                        size: buffer.len(),
147                        max: max_size,
148                    });
149                }
150            }
151        }
152
153        Ok(buffer)
154    }
155
156    /// Encodes a nested array to binary format
157    ///
158    /// Binary layout:
159    /// ```text
160    /// ┌──────────┬──────────────┬─────────────────────────────────┐
161    /// │   TAG    │ ELEM_COUNT   │      RECORD ENTRIES...          │
162    /// │ (1 byte) │  (VarInt)    │  [ RECORD(...) ]*               │
163    /// └──────────┴──────────────┴─────────────────────────────────┘
164    /// ```
165    ///
166    /// # Arguments
167    ///
168    /// * `records` - The array of records to encode
169    ///
170    /// # Returns
171    ///
172    /// A vector of bytes representing the binary-encoded nested array
173    ///
174    /// # Errors
175    ///
176    /// Returns `BinaryError` if:
177    /// - Nesting depth exceeds configured maximum
178    /// - Record size exceeds configured maximum
179    /// - Record encoding fails
180    pub fn encode_nested_array(&self, records: &[LnmpRecord]) -> Result<Vec<u8>, BinaryError> {
181        self.encode_nested_array_with_depth(records, 0)
182    }
183
184    /// Encodes a nested array with depth tracking
185    fn encode_nested_array_with_depth(
186        &self,
187        records: &[LnmpRecord],
188        current_depth: usize,
189    ) -> Result<Vec<u8>, BinaryError> {
190        // Validate depth
191        if current_depth >= self.config.max_depth {
192            return Err(BinaryError::NestingDepthExceeded {
193                depth: current_depth,
194                max: self.config.max_depth,
195            });
196        }
197
198        let mut buffer = Vec::new();
199
200        // Write TAG byte (0x07 for NestedArray)
201        buffer.push(TypeTag::NestedArray.to_u8());
202
203        // Encode ELEMENT_COUNT as VarInt
204        let element_count = records.len() as i64;
205        buffer.extend_from_slice(&varint::encode(element_count));
206
207        // Recursively encode each record
208        for record in records {
209            // For records within arrays, we need to encode them with canonical ordering
210            let record_bytes = self.encode_nested_record_with_depth(record, current_depth + 1)?;
211            buffer.extend_from_slice(&record_bytes);
212
213            // Check size limit if configured
214            if let Some(max_size) = self.config.max_record_size {
215                if buffer.len() > max_size {
216                    return Err(BinaryError::RecordSizeExceeded {
217                        size: buffer.len(),
218                        max: max_size,
219                    });
220                }
221            }
222        }
223
224        Ok(buffer)
225    }
226
227    /// Encodes a value recursively, handling nested structures
228    fn encode_value_recursive(
229        &self,
230        value: &LnmpValue,
231        current_depth: usize,
232    ) -> Result<Vec<u8>, BinaryError> {
233        // Validate depth for nested structures
234        if current_depth >= self.config.max_depth
235            && matches!(
236                value,
237                LnmpValue::NestedRecord(_) | LnmpValue::NestedArray(_)
238            )
239        {
240            return Err(BinaryError::NestingDepthExceeded {
241                depth: current_depth,
242                max: self.config.max_depth,
243            });
244        }
245
246        match value {
247            LnmpValue::Int(i) => {
248                let mut buffer = Vec::new();
249                buffer.push(TypeTag::Int.to_u8());
250                buffer.extend_from_slice(&varint::encode(*i));
251                Ok(buffer)
252            }
253            LnmpValue::Float(f) => {
254                let mut buffer = Vec::new();
255                buffer.push(TypeTag::Float.to_u8());
256                buffer.extend_from_slice(&f.to_le_bytes());
257                Ok(buffer)
258            }
259            LnmpValue::Bool(b) => {
260                let mut buffer = Vec::new();
261                buffer.push(TypeTag::Bool.to_u8());
262                buffer.push(if *b { 0x01 } else { 0x00 });
263                Ok(buffer)
264            }
265            LnmpValue::String(s) => {
266                let mut buffer = Vec::new();
267                buffer.push(TypeTag::String.to_u8());
268                let bytes = s.as_bytes();
269                buffer.extend_from_slice(&varint::encode(bytes.len() as i64));
270                buffer.extend_from_slice(bytes);
271                Ok(buffer)
272            }
273            LnmpValue::StringArray(arr) => {
274                let mut buffer = Vec::new();
275                buffer.push(TypeTag::StringArray.to_u8());
276                buffer.extend_from_slice(&varint::encode(arr.len() as i64));
277                for s in arr {
278                    let bytes = s.as_bytes();
279                    buffer.extend_from_slice(&varint::encode(bytes.len() as i64));
280                    buffer.extend_from_slice(bytes);
281                }
282                Ok(buffer)
283            }
284            LnmpValue::EmbeddingDelta(delta) => {
285                let mut buffer = Vec::new();
286                // Note: Delta handling in nested encoder - we encode the delta directly
287                buffer.push(TypeTag::Embedding.to_u8()); // TypeTag::Embedding (deltas use same tag)
288                let delta_bytes = delta.encode().map_err(|_| BinaryError::InvalidValue {
289                    field_id: 0,
290                    type_tag: TypeTag::Embedding.to_u8(),
291                    reason: "Failed to encode delta".to_string(),
292                })?;
293                buffer.extend_from_slice(&(delta_bytes.len() as u32).to_le_bytes());
294                buffer.extend_from_slice(&delta_bytes);
295                Ok(buffer)
296            }
297            LnmpValue::Embedding(vec) => {
298                // Tag + Length + Encoded Vector
299                let encoded = lnmp_embedding::Encoder::encode(vec).map_err(|_| {
300                    BinaryError::InvalidValue {
301                        field_id: 0,
302                        type_tag: TypeTag::Embedding.to_u8(),
303                        reason: "Failed to encode embedding".to_string(),
304                    }
305                })?;
306
307                let mut buffer = Vec::new();
308                buffer.push(TypeTag::Embedding.to_u8());
309                buffer.extend_from_slice(&varint::encode(encoded.len() as i64));
310                buffer.extend_from_slice(&encoded);
311                Ok(buffer)
312            }
313            LnmpValue::NestedRecord(record) => {
314                self.encode_nested_record_with_depth(record, current_depth)
315            }
316            LnmpValue::NestedArray(records) => {
317                self.encode_nested_array_with_depth(records, current_depth)
318            }
319        }
320    }
321}
322
323impl Default for BinaryNestedEncoder {
324    fn default() -> Self {
325        Self::new()
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    #![allow(clippy::approx_constant)]
332
333    use super::*;
334    use lnmp_core::LnmpField;
335
336    #[test]
337    fn test_nested_encoder_config_default() {
338        let config = NestedEncoderConfig::default();
339        assert_eq!(config.max_depth, 32);
340        assert_eq!(config.max_record_size, None);
341        assert!(!config.validate_canonical);
342    }
343
344    #[test]
345    fn test_nested_encoder_config_builder() {
346        let config = NestedEncoderConfig::new()
347            .with_max_depth(16)
348            .with_max_record_size(Some(1024))
349            .with_validate_canonical(true);
350
351        assert_eq!(config.max_depth, 16);
352        assert_eq!(config.max_record_size, Some(1024));
353        assert!(config.validate_canonical);
354    }
355
356    #[test]
357    fn test_encode_empty_nested_record() {
358        let encoder = BinaryNestedEncoder::new();
359        let record = LnmpRecord::new();
360
361        let result = encoder.encode_nested_record(&record).unwrap();
362
363        // Should have TAG (0x06) + FIELD_COUNT (0)
364        assert_eq!(result[0], 0x06); // NestedRecord tag
365        assert_eq!(result[1], 0x00); // Field count = 0
366        assert_eq!(result.len(), 2);
367    }
368
369    #[test]
370    fn test_encode_single_level_nested_record() {
371        let encoder = BinaryNestedEncoder::new();
372        let mut record = LnmpRecord::new();
373        record.add_field(LnmpField {
374            fid: 1,
375            value: LnmpValue::Int(42),
376        });
377        record.add_field(LnmpField {
378            fid: 2,
379            value: LnmpValue::String("test".to_string()),
380        });
381
382        let result = encoder.encode_nested_record(&record).unwrap();
383
384        // Should start with TAG (0x06) + FIELD_COUNT (2)
385        assert_eq!(result[0], 0x06); // NestedRecord tag
386        assert_eq!(result[1], 0x02); // Field count = 2
387    }
388
389    #[test]
390    fn test_encode_nested_record_canonical_ordering() {
391        let encoder = BinaryNestedEncoder::new();
392        let mut record = LnmpRecord::new();
393        // Add fields in non-sorted order
394        record.add_field(LnmpField {
395            fid: 10,
396            value: LnmpValue::Int(3),
397        });
398        record.add_field(LnmpField {
399            fid: 2,
400            value: LnmpValue::Int(1),
401        });
402        record.add_field(LnmpField {
403            fid: 5,
404            value: LnmpValue::Int(2),
405        });
406
407        let result = encoder.encode_nested_record(&record).unwrap();
408
409        // Verify TAG and count
410        assert_eq!(result[0], 0x06); // NestedRecord tag
411        assert_eq!(result[1], 0x03); // Field count = 3
412
413        // Fields should be encoded in sorted order: 2, 5, 10
414        // After TAG and COUNT, we should see FID=2 first
415        assert_eq!(result[2], 0x02); // FID = 2 (VarInt)
416    }
417
418    #[test]
419    fn test_encode_empty_nested_array() {
420        let encoder = BinaryNestedEncoder::new();
421        let records: Vec<LnmpRecord> = vec![];
422
423        let result = encoder.encode_nested_array(&records).unwrap();
424
425        // Should have TAG (0x07) + ELEMENT_COUNT (0)
426        assert_eq!(result[0], 0x07); // NestedArray tag
427        assert_eq!(result[1], 0x00); // Element count = 0
428        assert_eq!(result.len(), 2);
429    }
430
431    #[test]
432    fn test_encode_nested_array_single_record() {
433        let encoder = BinaryNestedEncoder::new();
434        let mut record = LnmpRecord::new();
435        record.add_field(LnmpField {
436            fid: 1,
437            value: LnmpValue::Int(42),
438        });
439
440        let result = encoder.encode_nested_array(&[record]).unwrap();
441
442        // Should start with TAG (0x07) + ELEMENT_COUNT (1)
443        assert_eq!(result[0], 0x07); // NestedArray tag
444        assert_eq!(result[1], 0x01); // Element count = 1
445                                     // Next should be the nested record (TAG 0x06)
446        assert_eq!(result[2], 0x06); // NestedRecord tag
447    }
448
449    #[test]
450    fn test_encode_nested_array_multiple_records() {
451        let encoder = BinaryNestedEncoder::new();
452        let mut record1 = LnmpRecord::new();
453        record1.add_field(LnmpField {
454            fid: 1,
455            value: LnmpValue::Int(1),
456        });
457
458        let mut record2 = LnmpRecord::new();
459        record2.add_field(LnmpField {
460            fid: 1,
461            value: LnmpValue::Int(2),
462        });
463
464        let result = encoder.encode_nested_array(&[record1, record2]).unwrap();
465
466        // Should start with TAG (0x07) + ELEMENT_COUNT (2)
467        assert_eq!(result[0], 0x07); // NestedArray tag
468        assert_eq!(result[1], 0x02); // Element count = 2
469    }
470
471    #[test]
472    fn test_encode_depth_limit_exceeded() {
473        let config = NestedEncoderConfig::new().with_max_depth(2);
474        let encoder = BinaryNestedEncoder::with_config(config);
475
476        // Create a deeply nested structure (depth 3)
477        let mut level3 = LnmpRecord::new();
478        level3.add_field(LnmpField {
479            fid: 1,
480            value: LnmpValue::Int(42),
481        });
482
483        let mut level2 = LnmpRecord::new();
484        level2.add_field(LnmpField {
485            fid: 2,
486            value: LnmpValue::NestedRecord(Box::new(level3)),
487        });
488
489        let mut level1 = LnmpRecord::new();
490        level1.add_field(LnmpField {
491            fid: 3,
492            value: LnmpValue::NestedRecord(Box::new(level2)),
493        });
494
495        let result = encoder.encode_nested_record(&level1);
496
497        assert!(result.is_err());
498        match result {
499            Err(BinaryError::NestingDepthExceeded { depth, max }) => {
500                assert_eq!(max, 2);
501                assert!(depth >= 2);
502            }
503            _ => panic!("Expected NestingDepthExceeded error"),
504        }
505    }
506
507    #[test]
508    fn test_encode_size_limit_exceeded() {
509        let config = NestedEncoderConfig::new().with_max_record_size(Some(10));
510        let encoder = BinaryNestedEncoder::with_config(config);
511
512        let mut record = LnmpRecord::new();
513        // Add enough fields to exceed the size limit
514        for i in 0..100 {
515            record.add_field(LnmpField {
516                fid: i,
517                value: LnmpValue::String("test".to_string()),
518            });
519        }
520
521        let result = encoder.encode_nested_record(&record);
522
523        assert!(result.is_err());
524        match result {
525            Err(BinaryError::RecordSizeExceeded { size, max }) => {
526                assert_eq!(max, 10);
527                assert!(size > 10);
528            }
529            _ => panic!("Expected RecordSizeExceeded error"),
530        }
531    }
532
533    #[test]
534    fn test_encode_multi_level_nested_record() {
535        let encoder = BinaryNestedEncoder::new();
536
537        // Create a 2-level nested structure
538        let mut inner_record = LnmpRecord::new();
539        inner_record.add_field(LnmpField {
540            fid: 1,
541            value: LnmpValue::Int(42),
542        });
543
544        let mut outer_record = LnmpRecord::new();
545        outer_record.add_field(LnmpField {
546            fid: 2,
547            value: LnmpValue::NestedRecord(Box::new(inner_record)),
548        });
549
550        let result = encoder.encode_nested_record(&outer_record).unwrap();
551
552        // Should start with TAG (0x06) + FIELD_COUNT (1)
553        assert_eq!(result[0], 0x06); // NestedRecord tag
554        assert_eq!(result[1], 0x01); // Field count = 1
555                                     // Next should be FID=2
556        assert_eq!(result[2], 0x02); // FID = 2
557                                     // Next should be nested record TAG (0x06)
558        assert_eq!(result[3], 0x06); // Inner NestedRecord tag
559    }
560
561    #[test]
562    fn test_encode_nested_record_depth_2() {
563        let encoder = BinaryNestedEncoder::new();
564
565        // Create depth-2 nested structure
566        let mut level2 = LnmpRecord::new();
567        level2.add_field(LnmpField {
568            fid: 1,
569            value: LnmpValue::Int(100),
570        });
571
572        let mut level1 = LnmpRecord::new();
573        level1.add_field(LnmpField {
574            fid: 2,
575            value: LnmpValue::NestedRecord(Box::new(level2)),
576        });
577
578        let result = encoder.encode_nested_record(&level1).unwrap();
579        assert!(!result.is_empty());
580        assert_eq!(result[0], 0x06); // NestedRecord tag
581    }
582
583    #[test]
584    fn test_encode_nested_record_depth_3() {
585        let encoder = BinaryNestedEncoder::new();
586
587        // Create depth-3 nested structure
588        let mut level3 = LnmpRecord::new();
589        level3.add_field(LnmpField {
590            fid: 1,
591            value: LnmpValue::Int(100),
592        });
593
594        let mut level2 = LnmpRecord::new();
595        level2.add_field(LnmpField {
596            fid: 2,
597            value: LnmpValue::NestedRecord(Box::new(level3)),
598        });
599
600        let mut level1 = LnmpRecord::new();
601        level1.add_field(LnmpField {
602            fid: 3,
603            value: LnmpValue::NestedRecord(Box::new(level2)),
604        });
605
606        let result = encoder.encode_nested_record(&level1).unwrap();
607        assert!(!result.is_empty());
608        assert_eq!(result[0], 0x06); // NestedRecord tag
609    }
610
611    #[test]
612    fn test_encode_nested_record_depth_4() {
613        let encoder = BinaryNestedEncoder::new();
614
615        // Create depth-4 nested structure
616        let mut level4 = LnmpRecord::new();
617        level4.add_field(LnmpField {
618            fid: 1,
619            value: LnmpValue::Int(100),
620        });
621
622        let mut level3 = LnmpRecord::new();
623        level3.add_field(LnmpField {
624            fid: 2,
625            value: LnmpValue::NestedRecord(Box::new(level4)),
626        });
627
628        let mut level2 = LnmpRecord::new();
629        level2.add_field(LnmpField {
630            fid: 3,
631            value: LnmpValue::NestedRecord(Box::new(level3)),
632        });
633
634        let mut level1 = LnmpRecord::new();
635        level1.add_field(LnmpField {
636            fid: 4,
637            value: LnmpValue::NestedRecord(Box::new(level2)),
638        });
639
640        let result = encoder.encode_nested_record(&level1).unwrap();
641        assert!(!result.is_empty());
642        assert_eq!(result[0], 0x06); // NestedRecord tag
643    }
644
645    #[test]
646    fn test_encode_nested_record_depth_5() {
647        let encoder = BinaryNestedEncoder::new();
648
649        // Create depth-5 nested structure
650        let mut level5 = LnmpRecord::new();
651        level5.add_field(LnmpField {
652            fid: 1,
653            value: LnmpValue::Int(100),
654        });
655
656        let mut level4 = LnmpRecord::new();
657        level4.add_field(LnmpField {
658            fid: 2,
659            value: LnmpValue::NestedRecord(Box::new(level5)),
660        });
661
662        let mut level3 = LnmpRecord::new();
663        level3.add_field(LnmpField {
664            fid: 3,
665            value: LnmpValue::NestedRecord(Box::new(level4)),
666        });
667
668        let mut level2 = LnmpRecord::new();
669        level2.add_field(LnmpField {
670            fid: 4,
671            value: LnmpValue::NestedRecord(Box::new(level3)),
672        });
673
674        let mut level1 = LnmpRecord::new();
675        level1.add_field(LnmpField {
676            fid: 5,
677            value: LnmpValue::NestedRecord(Box::new(level2)),
678        });
679
680        let result = encoder.encode_nested_record(&level1).unwrap();
681        assert!(!result.is_empty());
682        assert_eq!(result[0], 0x06); // NestedRecord tag
683    }
684
685    #[test]
686    fn test_encode_depth_limit_enforced_at_exact_limit() {
687        let config = NestedEncoderConfig::new().with_max_depth(2);
688        let encoder = BinaryNestedEncoder::with_config(config);
689
690        // Create a structure at depth 1 (should succeed)
691        let mut level1 = LnmpRecord::new();
692        level1.add_field(LnmpField {
693            fid: 1,
694            value: LnmpValue::Int(42),
695        });
696
697        // This should succeed (depth 0 when encoding starts)
698        let result = encoder.encode_nested_record(&level1);
699        assert!(result.is_ok());
700
701        // Create a structure at depth 2 (should succeed)
702        let mut inner = LnmpRecord::new();
703        inner.add_field(LnmpField {
704            fid: 1,
705            value: LnmpValue::Int(42),
706        });
707
708        let mut outer = LnmpRecord::new();
709        outer.add_field(LnmpField {
710            fid: 2,
711            value: LnmpValue::NestedRecord(Box::new(inner)),
712        });
713
714        // This should succeed (depth 1 when encoding the nested record)
715        let result = encoder.encode_nested_record(&outer);
716        assert!(result.is_ok());
717
718        // Create a structure at depth 3 (should fail)
719        let mut level3 = LnmpRecord::new();
720        level3.add_field(LnmpField {
721            fid: 1,
722            value: LnmpValue::Int(42),
723        });
724
725        let mut level2 = LnmpRecord::new();
726        level2.add_field(LnmpField {
727            fid: 2,
728            value: LnmpValue::NestedRecord(Box::new(level3)),
729        });
730
731        let mut level1_deep = LnmpRecord::new();
732        level1_deep.add_field(LnmpField {
733            fid: 3,
734            value: LnmpValue::NestedRecord(Box::new(level2)),
735        });
736
737        // This should fail (depth 2 when encoding the deepest nested record)
738        let result = encoder.encode_nested_record(&level1_deep);
739        assert!(result.is_err());
740    }
741
742    #[test]
743    fn test_encode_size_limit_enforced_incrementally() {
744        let config = NestedEncoderConfig::new().with_max_record_size(Some(50));
745        let encoder = BinaryNestedEncoder::with_config(config);
746
747        let mut record = LnmpRecord::new();
748        // Add fields until we exceed the limit
749        for i in 0..20 {
750            record.add_field(LnmpField {
751                fid: i,
752                value: LnmpValue::String("test".to_string()),
753            });
754        }
755
756        let result = encoder.encode_nested_record(&record);
757        assert!(result.is_err());
758        match result {
759            Err(BinaryError::RecordSizeExceeded { .. }) => {}
760            _ => panic!("Expected RecordSizeExceeded error"),
761        }
762    }
763
764    #[test]
765    fn test_encode_canonical_ordering_at_all_levels() {
766        let encoder = BinaryNestedEncoder::new();
767
768        // Create nested structure with unsorted fields at each level
769        let mut inner = LnmpRecord::new();
770        inner.add_field(LnmpField {
771            fid: 10,
772            value: LnmpValue::Int(3),
773        });
774        inner.add_field(LnmpField {
775            fid: 2,
776            value: LnmpValue::Int(1),
777        });
778        inner.add_field(LnmpField {
779            fid: 5,
780            value: LnmpValue::Int(2),
781        });
782
783        let mut outer = LnmpRecord::new();
784        outer.add_field(LnmpField {
785            fid: 20,
786            value: LnmpValue::String("last".to_string()),
787        });
788        outer.add_field(LnmpField {
789            fid: 1,
790            value: LnmpValue::NestedRecord(Box::new(inner)),
791        });
792        outer.add_field(LnmpField {
793            fid: 15,
794            value: LnmpValue::String("middle".to_string()),
795        });
796
797        let result = encoder.encode_nested_record(&outer).unwrap();
798
799        // Verify outer level is sorted (FID 1 should come first)
800        assert_eq!(result[0], 0x06); // NestedRecord tag
801        assert_eq!(result[1], 0x03); // Field count = 3
802        assert_eq!(result[2], 0x01); // First FID = 1 (sorted)
803    }
804
805    #[test]
806    fn test_encode_empty_nested_records_at_multiple_levels() {
807        let encoder = BinaryNestedEncoder::new();
808
809        let inner = LnmpRecord::new(); // Empty
810        let mut outer = LnmpRecord::new();
811        outer.add_field(LnmpField {
812            fid: 1,
813            value: LnmpValue::NestedRecord(Box::new(inner)),
814        });
815
816        let result = encoder.encode_nested_record(&outer).unwrap();
817
818        assert_eq!(result[0], 0x06); // NestedRecord tag
819        assert_eq!(result[1], 0x01); // Field count = 1
820        assert_eq!(result[2], 0x01); // FID = 1
821        assert_eq!(result[3], 0x06); // Inner NestedRecord tag
822        assert_eq!(result[4], 0x00); // Inner field count = 0
823    }
824
825    #[test]
826    fn test_encode_empty_nested_arrays() {
827        let encoder = BinaryNestedEncoder::new();
828
829        let mut record = LnmpRecord::new();
830        record.add_field(LnmpField {
831            fid: 1,
832            value: LnmpValue::NestedArray(vec![]),
833        });
834
835        let result = encoder.encode_nested_record(&record).unwrap();
836
837        assert_eq!(result[0], 0x06); // NestedRecord tag
838        assert_eq!(result[1], 0x01); // Field count = 1
839        assert_eq!(result[2], 0x01); // FID = 1
840        assert_eq!(result[3], 0x07); // NestedArray tag
841        assert_eq!(result[4], 0x00); // Element count = 0
842    }
843
844    #[test]
845    fn test_encode_nested_array_with_canonical_ordering() {
846        let encoder = BinaryNestedEncoder::new();
847
848        // Create records with unsorted fields
849        let mut record1 = LnmpRecord::new();
850        record1.add_field(LnmpField {
851            fid: 10,
852            value: LnmpValue::Int(2),
853        });
854        record1.add_field(LnmpField {
855            fid: 5,
856            value: LnmpValue::Int(1),
857        });
858
859        let mut record2 = LnmpRecord::new();
860        record2.add_field(LnmpField {
861            fid: 20,
862            value: LnmpValue::Int(4),
863        });
864        record2.add_field(LnmpField {
865            fid: 15,
866            value: LnmpValue::Int(3),
867        });
868
869        let result = encoder.encode_nested_array(&[record1, record2]).unwrap();
870
871        // Verify array structure
872        assert_eq!(result[0], 0x07); // NestedArray tag
873        assert_eq!(result[1], 0x02); // Element count = 2
874
875        // First record should have sorted fields
876        assert_eq!(result[2], 0x06); // First record tag
877        assert_eq!(result[3], 0x02); // First record field count = 2
878        assert_eq!(result[4], 0x05); // First FID = 5 (sorted)
879    }
880
881    #[test]
882    fn test_encode_mixed_primitive_and_nested_fields() {
883        let encoder = BinaryNestedEncoder::new();
884
885        let mut inner = LnmpRecord::new();
886        inner.add_field(LnmpField {
887            fid: 1,
888            value: LnmpValue::String("inner".to_string()),
889        });
890
891        let mut outer = LnmpRecord::new();
892        outer.add_field(LnmpField {
893            fid: 1,
894            value: LnmpValue::Int(42),
895        });
896        outer.add_field(LnmpField {
897            fid: 2,
898            value: LnmpValue::NestedRecord(Box::new(inner)),
899        });
900        outer.add_field(LnmpField {
901            fid: 3,
902            value: LnmpValue::Bool(true),
903        });
904
905        let result = encoder.encode_nested_record(&outer).unwrap();
906
907        assert_eq!(result[0], 0x06); // NestedRecord tag
908        assert_eq!(result[1], 0x03); // Field count = 3
909    }
910
911    #[test]
912    fn test_encode_all_primitive_types_in_nested_record() {
913        let encoder = BinaryNestedEncoder::new();
914
915        let mut record = LnmpRecord::new();
916        record.add_field(LnmpField {
917            fid: 1,
918            value: LnmpValue::Int(-42),
919        });
920        record.add_field(LnmpField {
921            fid: 2,
922            value: LnmpValue::Float(3.14),
923        });
924        record.add_field(LnmpField {
925            fid: 3,
926            value: LnmpValue::Bool(false),
927        });
928        record.add_field(LnmpField {
929            fid: 4,
930            value: LnmpValue::String("test".to_string()),
931        });
932        record.add_field(LnmpField {
933            fid: 5,
934            value: LnmpValue::StringArray(vec!["a".to_string(), "b".to_string()]),
935        });
936
937        let result = encoder.encode_nested_record(&record).unwrap();
938
939        assert_eq!(result[0], 0x06); // NestedRecord tag
940        assert_eq!(result[1], 0x05); // Field count = 5
941    }
942}