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::QuantizedEmbedding(qv) => {
314                // TAG + SCHEME + SCALE + ZERO_POINT + MIN_VAL + DIM + DATA
315                let mut buffer = Vec::new();
316                buffer.push(TypeTag::QuantizedEmbedding.to_u8()); // 0x0A
317
318                // Encode scheme as 1 byte
319                buffer.push(qv.scheme as u8);
320
321                // Encode scale (f32, 4 bytes LE)
322                buffer.extend_from_slice(&qv.scale.to_le_bytes());
323
324                // Encode zero_point (i8, 1 byte)
325                buffer.push(qv.zero_point as u8);
326
327                // Encode min_val (f32, 4 bytes LE)
328                buffer.extend_from_slice(&qv.min_val.to_le_bytes());
329
330                // Encode dim (u32, 4 bytes LE)
331                buffer.extend_from_slice(&qv.dim.to_le_bytes());
332
333                // Encode data length and data bytes
334                buffer.extend_from_slice(&varint::encode(qv.data.len() as i64));
335                buffer.extend_from_slice(&qv.data);
336
337                Ok(buffer)
338            }
339            LnmpValue::IntArray(arr) => {
340                let mut buffer = Vec::new();
341                buffer.push(TypeTag::IntArray.to_u8());
342                buffer.extend_from_slice(&varint::encode(arr.len() as i64));
343                for i in arr {
344                    buffer.extend_from_slice(&varint::encode(*i));
345                }
346                Ok(buffer)
347            }
348            LnmpValue::FloatArray(arr) => {
349                let mut buffer = Vec::new();
350                buffer.push(TypeTag::FloatArray.to_u8());
351                buffer.extend_from_slice(&varint::encode(arr.len() as i64));
352                for f in arr {
353                    buffer.extend_from_slice(&f.to_le_bytes());
354                }
355                Ok(buffer)
356            }
357            LnmpValue::BoolArray(arr) => {
358                let mut buffer = Vec::new();
359                buffer.push(TypeTag::BoolArray.to_u8());
360                buffer.extend_from_slice(&varint::encode(arr.len() as i64));
361                for b in arr {
362                    buffer.push(if *b { 0x01 } else { 0x00 });
363                }
364                Ok(buffer)
365            }
366            LnmpValue::NestedRecord(record) => {
367                self.encode_nested_record_with_depth(record, current_depth)
368            }
369            LnmpValue::NestedArray(records) => {
370                self.encode_nested_array_with_depth(records, current_depth)
371            }
372        }
373    }
374}
375
376impl Default for BinaryNestedEncoder {
377    fn default() -> Self {
378        Self::new()
379    }
380}
381
382#[cfg(test)]
383mod tests {
384    #![allow(clippy::approx_constant)]
385
386    use super::*;
387    use lnmp_core::LnmpField;
388
389    #[test]
390    fn test_nested_encoder_config_default() {
391        let config = NestedEncoderConfig::default();
392        assert_eq!(config.max_depth, 32);
393        assert_eq!(config.max_record_size, None);
394        assert!(!config.validate_canonical);
395    }
396
397    #[test]
398    fn test_nested_encoder_config_builder() {
399        let config = NestedEncoderConfig::new()
400            .with_max_depth(16)
401            .with_max_record_size(Some(1024))
402            .with_validate_canonical(true);
403
404        assert_eq!(config.max_depth, 16);
405        assert_eq!(config.max_record_size, Some(1024));
406        assert!(config.validate_canonical);
407    }
408
409    #[test]
410    fn test_encode_empty_nested_record() {
411        let encoder = BinaryNestedEncoder::new();
412        let record = LnmpRecord::new();
413
414        let result = encoder.encode_nested_record(&record).unwrap();
415
416        // Should have TAG (0x06) + FIELD_COUNT (0)
417        assert_eq!(result[0], 0x06); // NestedRecord tag
418        assert_eq!(result[1], 0x00); // Field count = 0
419        assert_eq!(result.len(), 2);
420    }
421
422    #[test]
423    fn test_encode_single_level_nested_record() {
424        let encoder = BinaryNestedEncoder::new();
425        let mut record = LnmpRecord::new();
426        record.add_field(LnmpField {
427            fid: 1,
428            value: LnmpValue::Int(42),
429        });
430        record.add_field(LnmpField {
431            fid: 2,
432            value: LnmpValue::String("test".to_string()),
433        });
434
435        let result = encoder.encode_nested_record(&record).unwrap();
436
437        // Should start with TAG (0x06) + FIELD_COUNT (2)
438        assert_eq!(result[0], 0x06); // NestedRecord tag
439        assert_eq!(result[1], 0x02); // Field count = 2
440    }
441
442    #[test]
443    fn test_encode_nested_record_canonical_ordering() {
444        let encoder = BinaryNestedEncoder::new();
445        let mut record = LnmpRecord::new();
446        // Add fields in non-sorted order
447        record.add_field(LnmpField {
448            fid: 10,
449            value: LnmpValue::Int(3),
450        });
451        record.add_field(LnmpField {
452            fid: 2,
453            value: LnmpValue::Int(1),
454        });
455        record.add_field(LnmpField {
456            fid: 5,
457            value: LnmpValue::Int(2),
458        });
459
460        let result = encoder.encode_nested_record(&record).unwrap();
461
462        // Verify TAG and count
463        assert_eq!(result[0], 0x06); // NestedRecord tag
464        assert_eq!(result[1], 0x03); // Field count = 3
465
466        // Fields should be encoded in sorted order: 2, 5, 10
467        // After TAG and COUNT, we should see FID=2 first
468        assert_eq!(result[2], 0x02); // FID = 2 (VarInt)
469    }
470
471    #[test]
472    fn test_encode_empty_nested_array() {
473        let encoder = BinaryNestedEncoder::new();
474        let records: Vec<LnmpRecord> = vec![];
475
476        let result = encoder.encode_nested_array(&records).unwrap();
477
478        // Should have TAG (0x07) + ELEMENT_COUNT (0)
479        assert_eq!(result[0], 0x07); // NestedArray tag
480        assert_eq!(result[1], 0x00); // Element count = 0
481        assert_eq!(result.len(), 2);
482    }
483
484    #[test]
485    fn test_encode_nested_array_single_record() {
486        let encoder = BinaryNestedEncoder::new();
487        let mut record = LnmpRecord::new();
488        record.add_field(LnmpField {
489            fid: 1,
490            value: LnmpValue::Int(42),
491        });
492
493        let result = encoder.encode_nested_array(&[record]).unwrap();
494
495        // Should start with TAG (0x07) + ELEMENT_COUNT (1)
496        assert_eq!(result[0], 0x07); // NestedArray tag
497        assert_eq!(result[1], 0x01); // Element count = 1
498                                     // Next should be the nested record (TAG 0x06)
499        assert_eq!(result[2], 0x06); // NestedRecord tag
500    }
501
502    #[test]
503    fn test_encode_nested_array_multiple_records() {
504        let encoder = BinaryNestedEncoder::new();
505        let mut record1 = LnmpRecord::new();
506        record1.add_field(LnmpField {
507            fid: 1,
508            value: LnmpValue::Int(1),
509        });
510
511        let mut record2 = LnmpRecord::new();
512        record2.add_field(LnmpField {
513            fid: 1,
514            value: LnmpValue::Int(2),
515        });
516
517        let result = encoder.encode_nested_array(&[record1, record2]).unwrap();
518
519        // Should start with TAG (0x07) + ELEMENT_COUNT (2)
520        assert_eq!(result[0], 0x07); // NestedArray tag
521        assert_eq!(result[1], 0x02); // Element count = 2
522    }
523
524    #[test]
525    fn test_encode_depth_limit_exceeded() {
526        let config = NestedEncoderConfig::new().with_max_depth(2);
527        let encoder = BinaryNestedEncoder::with_config(config);
528
529        // Create a deeply nested structure (depth 3)
530        let mut level3 = LnmpRecord::new();
531        level3.add_field(LnmpField {
532            fid: 1,
533            value: LnmpValue::Int(42),
534        });
535
536        let mut level2 = LnmpRecord::new();
537        level2.add_field(LnmpField {
538            fid: 2,
539            value: LnmpValue::NestedRecord(Box::new(level3)),
540        });
541
542        let mut level1 = LnmpRecord::new();
543        level1.add_field(LnmpField {
544            fid: 3,
545            value: LnmpValue::NestedRecord(Box::new(level2)),
546        });
547
548        let result = encoder.encode_nested_record(&level1);
549
550        assert!(result.is_err());
551        match result {
552            Err(BinaryError::NestingDepthExceeded { depth, max }) => {
553                assert_eq!(max, 2);
554                assert!(depth >= 2);
555            }
556            _ => panic!("Expected NestingDepthExceeded error"),
557        }
558    }
559
560    #[test]
561    fn test_encode_size_limit_exceeded() {
562        let config = NestedEncoderConfig::new().with_max_record_size(Some(10));
563        let encoder = BinaryNestedEncoder::with_config(config);
564
565        let mut record = LnmpRecord::new();
566        // Add enough fields to exceed the size limit
567        for i in 0..100 {
568            record.add_field(LnmpField {
569                fid: i,
570                value: LnmpValue::String("test".to_string()),
571            });
572        }
573
574        let result = encoder.encode_nested_record(&record);
575
576        assert!(result.is_err());
577        match result {
578            Err(BinaryError::RecordSizeExceeded { size, max }) => {
579                assert_eq!(max, 10);
580                assert!(size > 10);
581            }
582            _ => panic!("Expected RecordSizeExceeded error"),
583        }
584    }
585
586    #[test]
587    fn test_encode_multi_level_nested_record() {
588        let encoder = BinaryNestedEncoder::new();
589
590        // Create a 2-level nested structure
591        let mut inner_record = LnmpRecord::new();
592        inner_record.add_field(LnmpField {
593            fid: 1,
594            value: LnmpValue::Int(42),
595        });
596
597        let mut outer_record = LnmpRecord::new();
598        outer_record.add_field(LnmpField {
599            fid: 2,
600            value: LnmpValue::NestedRecord(Box::new(inner_record)),
601        });
602
603        let result = encoder.encode_nested_record(&outer_record).unwrap();
604
605        // Should start with TAG (0x06) + FIELD_COUNT (1)
606        assert_eq!(result[0], 0x06); // NestedRecord tag
607        assert_eq!(result[1], 0x01); // Field count = 1
608                                     // Next should be FID=2
609        assert_eq!(result[2], 0x02); // FID = 2
610                                     // Next should be nested record TAG (0x06)
611        assert_eq!(result[3], 0x06); // Inner NestedRecord tag
612    }
613
614    #[test]
615    fn test_encode_nested_record_depth_2() {
616        let encoder = BinaryNestedEncoder::new();
617
618        // Create depth-2 nested structure
619        let mut level2 = LnmpRecord::new();
620        level2.add_field(LnmpField {
621            fid: 1,
622            value: LnmpValue::Int(100),
623        });
624
625        let mut level1 = LnmpRecord::new();
626        level1.add_field(LnmpField {
627            fid: 2,
628            value: LnmpValue::NestedRecord(Box::new(level2)),
629        });
630
631        let result = encoder.encode_nested_record(&level1).unwrap();
632        assert!(!result.is_empty());
633        assert_eq!(result[0], 0x06); // NestedRecord tag
634    }
635
636    #[test]
637    fn test_encode_nested_record_depth_3() {
638        let encoder = BinaryNestedEncoder::new();
639
640        // Create depth-3 nested structure
641        let mut level3 = LnmpRecord::new();
642        level3.add_field(LnmpField {
643            fid: 1,
644            value: LnmpValue::Int(100),
645        });
646
647        let mut level2 = LnmpRecord::new();
648        level2.add_field(LnmpField {
649            fid: 2,
650            value: LnmpValue::NestedRecord(Box::new(level3)),
651        });
652
653        let mut level1 = LnmpRecord::new();
654        level1.add_field(LnmpField {
655            fid: 3,
656            value: LnmpValue::NestedRecord(Box::new(level2)),
657        });
658
659        let result = encoder.encode_nested_record(&level1).unwrap();
660        assert!(!result.is_empty());
661        assert_eq!(result[0], 0x06); // NestedRecord tag
662    }
663
664    #[test]
665    fn test_encode_nested_record_depth_4() {
666        let encoder = BinaryNestedEncoder::new();
667
668        // Create depth-4 nested structure
669        let mut level4 = LnmpRecord::new();
670        level4.add_field(LnmpField {
671            fid: 1,
672            value: LnmpValue::Int(100),
673        });
674
675        let mut level3 = LnmpRecord::new();
676        level3.add_field(LnmpField {
677            fid: 2,
678            value: LnmpValue::NestedRecord(Box::new(level4)),
679        });
680
681        let mut level2 = LnmpRecord::new();
682        level2.add_field(LnmpField {
683            fid: 3,
684            value: LnmpValue::NestedRecord(Box::new(level3)),
685        });
686
687        let mut level1 = LnmpRecord::new();
688        level1.add_field(LnmpField {
689            fid: 4,
690            value: LnmpValue::NestedRecord(Box::new(level2)),
691        });
692
693        let result = encoder.encode_nested_record(&level1).unwrap();
694        assert!(!result.is_empty());
695        assert_eq!(result[0], 0x06); // NestedRecord tag
696    }
697
698    #[test]
699    fn test_encode_nested_record_depth_5() {
700        let encoder = BinaryNestedEncoder::new();
701
702        // Create depth-5 nested structure
703        let mut level5 = LnmpRecord::new();
704        level5.add_field(LnmpField {
705            fid: 1,
706            value: LnmpValue::Int(100),
707        });
708
709        let mut level4 = LnmpRecord::new();
710        level4.add_field(LnmpField {
711            fid: 2,
712            value: LnmpValue::NestedRecord(Box::new(level5)),
713        });
714
715        let mut level3 = LnmpRecord::new();
716        level3.add_field(LnmpField {
717            fid: 3,
718            value: LnmpValue::NestedRecord(Box::new(level4)),
719        });
720
721        let mut level2 = LnmpRecord::new();
722        level2.add_field(LnmpField {
723            fid: 4,
724            value: LnmpValue::NestedRecord(Box::new(level3)),
725        });
726
727        let mut level1 = LnmpRecord::new();
728        level1.add_field(LnmpField {
729            fid: 5,
730            value: LnmpValue::NestedRecord(Box::new(level2)),
731        });
732
733        let result = encoder.encode_nested_record(&level1).unwrap();
734        assert!(!result.is_empty());
735        assert_eq!(result[0], 0x06); // NestedRecord tag
736    }
737
738    #[test]
739    fn test_encode_depth_limit_enforced_at_exact_limit() {
740        let config = NestedEncoderConfig::new().with_max_depth(2);
741        let encoder = BinaryNestedEncoder::with_config(config);
742
743        // Create a structure at depth 1 (should succeed)
744        let mut level1 = LnmpRecord::new();
745        level1.add_field(LnmpField {
746            fid: 1,
747            value: LnmpValue::Int(42),
748        });
749
750        // This should succeed (depth 0 when encoding starts)
751        let result = encoder.encode_nested_record(&level1);
752        assert!(result.is_ok());
753
754        // Create a structure at depth 2 (should succeed)
755        let mut inner = LnmpRecord::new();
756        inner.add_field(LnmpField {
757            fid: 1,
758            value: LnmpValue::Int(42),
759        });
760
761        let mut outer = LnmpRecord::new();
762        outer.add_field(LnmpField {
763            fid: 2,
764            value: LnmpValue::NestedRecord(Box::new(inner)),
765        });
766
767        // This should succeed (depth 1 when encoding the nested record)
768        let result = encoder.encode_nested_record(&outer);
769        assert!(result.is_ok());
770
771        // Create a structure at depth 3 (should fail)
772        let mut level3 = LnmpRecord::new();
773        level3.add_field(LnmpField {
774            fid: 1,
775            value: LnmpValue::Int(42),
776        });
777
778        let mut level2 = LnmpRecord::new();
779        level2.add_field(LnmpField {
780            fid: 2,
781            value: LnmpValue::NestedRecord(Box::new(level3)),
782        });
783
784        let mut level1_deep = LnmpRecord::new();
785        level1_deep.add_field(LnmpField {
786            fid: 3,
787            value: LnmpValue::NestedRecord(Box::new(level2)),
788        });
789
790        // This should fail (depth 2 when encoding the deepest nested record)
791        let result = encoder.encode_nested_record(&level1_deep);
792        assert!(result.is_err());
793    }
794
795    #[test]
796    fn test_encode_size_limit_enforced_incrementally() {
797        let config = NestedEncoderConfig::new().with_max_record_size(Some(50));
798        let encoder = BinaryNestedEncoder::with_config(config);
799
800        let mut record = LnmpRecord::new();
801        // Add fields until we exceed the limit
802        for i in 0..20 {
803            record.add_field(LnmpField {
804                fid: i,
805                value: LnmpValue::String("test".to_string()),
806            });
807        }
808
809        let result = encoder.encode_nested_record(&record);
810        assert!(result.is_err());
811        match result {
812            Err(BinaryError::RecordSizeExceeded { .. }) => {}
813            _ => panic!("Expected RecordSizeExceeded error"),
814        }
815    }
816
817    #[test]
818    fn test_encode_canonical_ordering_at_all_levels() {
819        let encoder = BinaryNestedEncoder::new();
820
821        // Create nested structure with unsorted fields at each level
822        let mut inner = LnmpRecord::new();
823        inner.add_field(LnmpField {
824            fid: 10,
825            value: LnmpValue::Int(3),
826        });
827        inner.add_field(LnmpField {
828            fid: 2,
829            value: LnmpValue::Int(1),
830        });
831        inner.add_field(LnmpField {
832            fid: 5,
833            value: LnmpValue::Int(2),
834        });
835
836        let mut outer = LnmpRecord::new();
837        outer.add_field(LnmpField {
838            fid: 20,
839            value: LnmpValue::String("last".to_string()),
840        });
841        outer.add_field(LnmpField {
842            fid: 1,
843            value: LnmpValue::NestedRecord(Box::new(inner)),
844        });
845        outer.add_field(LnmpField {
846            fid: 15,
847            value: LnmpValue::String("middle".to_string()),
848        });
849
850        let result = encoder.encode_nested_record(&outer).unwrap();
851
852        // Verify outer level is sorted (FID 1 should come first)
853        assert_eq!(result[0], 0x06); // NestedRecord tag
854        assert_eq!(result[1], 0x03); // Field count = 3
855        assert_eq!(result[2], 0x01); // First FID = 1 (sorted)
856    }
857
858    #[test]
859    fn test_encode_empty_nested_records_at_multiple_levels() {
860        let encoder = BinaryNestedEncoder::new();
861
862        let inner = LnmpRecord::new(); // Empty
863        let mut outer = LnmpRecord::new();
864        outer.add_field(LnmpField {
865            fid: 1,
866            value: LnmpValue::NestedRecord(Box::new(inner)),
867        });
868
869        let result = encoder.encode_nested_record(&outer).unwrap();
870
871        assert_eq!(result[0], 0x06); // NestedRecord tag
872        assert_eq!(result[1], 0x01); // Field count = 1
873        assert_eq!(result[2], 0x01); // FID = 1
874        assert_eq!(result[3], 0x06); // Inner NestedRecord tag
875        assert_eq!(result[4], 0x00); // Inner field count = 0
876    }
877
878    #[test]
879    fn test_encode_empty_nested_arrays() {
880        let encoder = BinaryNestedEncoder::new();
881
882        let mut record = LnmpRecord::new();
883        record.add_field(LnmpField {
884            fid: 1,
885            value: LnmpValue::NestedArray(vec![]),
886        });
887
888        let result = encoder.encode_nested_record(&record).unwrap();
889
890        assert_eq!(result[0], 0x06); // NestedRecord tag
891        assert_eq!(result[1], 0x01); // Field count = 1
892        assert_eq!(result[2], 0x01); // FID = 1
893        assert_eq!(result[3], 0x07); // NestedArray tag
894        assert_eq!(result[4], 0x00); // Element count = 0
895    }
896
897    #[test]
898    fn test_encode_nested_array_with_canonical_ordering() {
899        let encoder = BinaryNestedEncoder::new();
900
901        // Create records with unsorted fields
902        let mut record1 = LnmpRecord::new();
903        record1.add_field(LnmpField {
904            fid: 10,
905            value: LnmpValue::Int(2),
906        });
907        record1.add_field(LnmpField {
908            fid: 5,
909            value: LnmpValue::Int(1),
910        });
911
912        let mut record2 = LnmpRecord::new();
913        record2.add_field(LnmpField {
914            fid: 20,
915            value: LnmpValue::Int(4),
916        });
917        record2.add_field(LnmpField {
918            fid: 15,
919            value: LnmpValue::Int(3),
920        });
921
922        let result = encoder.encode_nested_array(&[record1, record2]).unwrap();
923
924        // Verify array structure
925        assert_eq!(result[0], 0x07); // NestedArray tag
926        assert_eq!(result[1], 0x02); // Element count = 2
927
928        // First record should have sorted fields
929        assert_eq!(result[2], 0x06); // First record tag
930        assert_eq!(result[3], 0x02); // First record field count = 2
931        assert_eq!(result[4], 0x05); // First FID = 5 (sorted)
932    }
933
934    #[test]
935    fn test_encode_mixed_primitive_and_nested_fields() {
936        let encoder = BinaryNestedEncoder::new();
937
938        let mut inner = LnmpRecord::new();
939        inner.add_field(LnmpField {
940            fid: 1,
941            value: LnmpValue::String("inner".to_string()),
942        });
943
944        let mut outer = LnmpRecord::new();
945        outer.add_field(LnmpField {
946            fid: 1,
947            value: LnmpValue::Int(42),
948        });
949        outer.add_field(LnmpField {
950            fid: 2,
951            value: LnmpValue::NestedRecord(Box::new(inner)),
952        });
953        outer.add_field(LnmpField {
954            fid: 3,
955            value: LnmpValue::Bool(true),
956        });
957
958        let result = encoder.encode_nested_record(&outer).unwrap();
959
960        assert_eq!(result[0], 0x06); // NestedRecord tag
961        assert_eq!(result[1], 0x03); // Field count = 3
962    }
963
964    #[test]
965    fn test_encode_all_primitive_types_in_nested_record() {
966        let encoder = BinaryNestedEncoder::new();
967
968        let mut record = LnmpRecord::new();
969        record.add_field(LnmpField {
970            fid: 1,
971            value: LnmpValue::Int(-42),
972        });
973        record.add_field(LnmpField {
974            fid: 2,
975            value: LnmpValue::Float(3.14),
976        });
977        record.add_field(LnmpField {
978            fid: 3,
979            value: LnmpValue::Bool(false),
980        });
981        record.add_field(LnmpField {
982            fid: 4,
983            value: LnmpValue::String("test".to_string()),
984        });
985        record.add_field(LnmpField {
986            fid: 5,
987            value: LnmpValue::StringArray(vec!["a".to_string(), "b".to_string()]),
988        });
989
990        let result = encoder.encode_nested_record(&record).unwrap();
991
992        assert_eq!(result[0], 0x06); // NestedRecord tag
993        assert_eq!(result[1], 0x05); // Field count = 5
994    }
995}