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