lnmp_codec/binary/
types.rs

1//! Binary type system for LNMP v0.4 protocol.
2
3use super::error::BinaryError;
4use lnmp_core::LnmpValue;
5
6/// Type tag for binary values (LNMP v0.4/v0.5)
7#[repr(u8)]
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum TypeTag {
10    /// Integer type (VarInt encoded)
11    Int = 0x01,
12    /// Float type (8-byte IEEE754 LE)
13    Float = 0x02,
14    /// Boolean type (1 byte: 0x00 or 0x01)
15    Bool = 0x03,
16    /// String type (length VarInt + UTF-8 bytes)
17    String = 0x04,
18    /// String array type (count VarInt + repeated length+UTF-8)
19    StringArray = 0x05,
20    /// Nested record type (v0.5) - TAG + FIELD_COUNT + FID/VALUE pairs
21    NestedRecord = 0x06,
22    /// Nested array type (v0.5) - TAG + ELEMENT_COUNT + RECORD entries
23    NestedArray = 0x07,
24    /// Embedding type (v0.5) - TAG + ENCODED_VECTOR
25    Embedding = 0x08,
26    /// Reserved for future use (v0.5+)
27    Reserved09 = 0x09,
28    /// Quantized embedding type (v0.5.2) - TAG + QUANTIZED_VECTOR
29    QuantizedEmbedding = 0x0A,
30    /// Integer array type (v0.6) - TAG + COUNT + INT entries
31    IntArray = 0x0B,
32    /// Float array type (v0.6) - TAG + COUNT + FLOAT entries
33    FloatArray = 0x0C,
34    /// Boolean array type (v0.6) - TAG + COUNT + BOOL entries
35    BoolArray = 0x0D,
36    /// Reserved for future use (v0.5+)
37    Reserved0E = 0x0E,
38    /// Reserved for future use (v0.5+)
39    Reserved0F = 0x0F,
40}
41
42impl TypeTag {
43    /// Converts a byte to a TypeTag
44    pub fn from_u8(byte: u8) -> Result<Self, BinaryError> {
45        match byte {
46            0x01 => Ok(TypeTag::Int),
47            0x02 => Ok(TypeTag::Float),
48            0x03 => Ok(TypeTag::Bool),
49            0x04 => Ok(TypeTag::String),
50            0x05 => Ok(TypeTag::StringArray),
51            0x06 => Ok(TypeTag::NestedRecord),
52            0x07 => Ok(TypeTag::NestedArray),
53            0x08 => Ok(TypeTag::Embedding),
54            0x09 => Ok(TypeTag::Reserved09),
55            0x0A => Ok(TypeTag::QuantizedEmbedding),
56            0x0B => Ok(TypeTag::IntArray),
57            0x0C => Ok(TypeTag::FloatArray),
58            0x0D => Ok(TypeTag::BoolArray),
59            0x0E => Ok(TypeTag::Reserved0E),
60            0x0F => Ok(TypeTag::Reserved0F),
61            _ => Err(BinaryError::InvalidTypeTag { tag: byte }),
62        }
63    }
64
65    /// Converts the TypeTag to a byte
66    pub fn to_u8(self) -> u8 {
67        self as u8
68    }
69
70    /// Returns true if this is a v0.5+ type tag (nested or reserved)
71    pub fn is_v0_5_type(&self) -> bool {
72        matches!(
73            self,
74            TypeTag::NestedRecord
75                | TypeTag::NestedArray
76                | TypeTag::Embedding
77                | TypeTag::QuantizedEmbedding
78                | TypeTag::Reserved09
79                | TypeTag::IntArray
80                | TypeTag::FloatArray
81                | TypeTag::BoolArray
82                | TypeTag::Reserved0E
83                | TypeTag::Reserved0F
84        )
85    }
86
87    /// Returns true if this is a reserved type tag
88    pub fn is_reserved(&self) -> bool {
89        matches!(
90            self,
91            TypeTag::Reserved09 | TypeTag::Reserved0E | TypeTag::Reserved0F
92        )
93    }
94}
95
96/// Binary value representation for LNMP v0.4/v0.5
97#[derive(Debug, Clone, PartialEq)]
98pub enum BinaryValue {
99    /// Integer value (i64)
100    Int(i64),
101    /// Floating-point value (f64)
102    Float(f64),
103    /// Boolean value
104    Bool(bool),
105    /// String value
106    String(String),
107    /// Array of strings
108    StringArray(Vec<String>),
109    /// Array of integers (v0.6)
110    IntArray(Vec<i64>),
111    /// Array of floats (v0.6)
112    FloatArray(Vec<f64>),
113    /// Array of booleans (v0.6)
114    BoolArray(Vec<bool>),
115    /// Nested record (v0.5)
116    NestedRecord(Box<lnmp_core::LnmpRecord>),
117    /// Array of nested records (v0.5)
118    NestedArray(Vec<lnmp_core::LnmpRecord>),
119    /// Embedding (v0.5)
120    Embedding(lnmp_embedding::Vector),
121    /// Quantized embedding (v0.5.2)
122    QuantizedEmbedding(lnmp_quant::QuantizedVector),
123}
124
125impl BinaryValue {
126    /// Converts from LnmpValue to BinaryValue
127    ///
128    /// In v0.5, nested structures are supported. Use `from_lnmp_value_v0_4` for v0.4 compatibility.
129    pub fn from_lnmp_value(value: &LnmpValue) -> Result<Self, BinaryError> {
130        match value {
131            LnmpValue::Int(i) => Ok(BinaryValue::Int(*i)),
132            LnmpValue::Float(f) => Ok(BinaryValue::Float(*f)),
133            LnmpValue::Bool(b) => Ok(BinaryValue::Bool(*b)),
134            LnmpValue::String(s) => Ok(BinaryValue::String(s.clone())),
135            LnmpValue::StringArray(arr) => Ok(BinaryValue::StringArray(arr.clone())),
136            LnmpValue::IntArray(arr) => Ok(BinaryValue::IntArray(arr.clone())),
137            LnmpValue::FloatArray(arr) => Ok(BinaryValue::FloatArray(arr.clone())),
138            LnmpValue::BoolArray(arr) => Ok(BinaryValue::BoolArray(arr.clone())),
139            LnmpValue::NestedRecord(rec) => Ok(BinaryValue::NestedRecord(rec.clone())),
140            LnmpValue::NestedArray(arr) => Ok(BinaryValue::NestedArray(arr.clone())),
141            LnmpValue::Embedding(vec) => Ok(BinaryValue::Embedding(vec.clone())),
142            LnmpValue::EmbeddingDelta(_) => Err(BinaryError::InvalidValue {
143                reason: "EmbeddingDelta cannot be encoded as BinaryValue, use full embedding"
144                    .into(),
145                field_id: 0,
146                type_tag: 0x08,
147            }),
148            LnmpValue::QuantizedEmbedding(qv) => Ok(BinaryValue::QuantizedEmbedding(qv.clone())),
149        }
150    }
151
152    /// Converts from LnmpValue to BinaryValue (v0.4 compatibility mode)
153    ///
154    /// Returns an error if the value contains nested structures (not supported in v0.4)
155    pub fn from_lnmp_value_v0_4(value: &LnmpValue) -> Result<Self, BinaryError> {
156        match value {
157            LnmpValue::Int(i) => Ok(BinaryValue::Int(*i)),
158            LnmpValue::Float(f) => Ok(BinaryValue::Float(*f)),
159            LnmpValue::Bool(b) => Ok(BinaryValue::Bool(*b)),
160            LnmpValue::String(s) => Ok(BinaryValue::String(s.clone())),
161            LnmpValue::StringArray(arr) => Ok(BinaryValue::StringArray(arr.clone())),
162            LnmpValue::IntArray(_) => Err(BinaryError::InvalidValue {
163                field_id: 0,
164                type_tag: 0x0B,
165                reason: "IntArray not supported in v0.4 binary format".to_string(),
166            }),
167            LnmpValue::FloatArray(_) => Err(BinaryError::InvalidValue {
168                field_id: 0,
169                type_tag: 0x0C,
170                reason: "FloatArray not supported in v0.4 binary format".to_string(),
171            }),
172            LnmpValue::BoolArray(_) => Err(BinaryError::InvalidValue {
173                field_id: 0,
174                type_tag: 0x0D,
175                reason: "BoolArray not supported in v0.4 binary format".to_string(),
176            }),
177            LnmpValue::NestedRecord(_) => Err(BinaryError::InvalidValue {
178                field_id: 0,
179                type_tag: 0x06,
180                reason: "Nested records not supported in v0.4 binary format".to_string(),
181            }),
182            LnmpValue::NestedArray(_) => Err(BinaryError::InvalidValue {
183                field_id: 0,
184                type_tag: 0x07,
185                reason: "Nested arrays not supported in v0.4 binary format".to_string(),
186            }),
187            LnmpValue::Embedding(_) => Err(BinaryError::InvalidValue {
188                field_id: 0,
189                type_tag: 0x08,
190                reason: "Embeddings not supported in v0.4 binary format".to_string(),
191            }),
192            LnmpValue::EmbeddingDelta(_) => Err(BinaryError::InvalidValue {
193                reason: "EmbeddingDelta not supported in v0.4".to_string(),
194                field_id: 0,
195                type_tag: 0x08,
196            }),
197            LnmpValue::QuantizedEmbedding(_) => Err(BinaryError::InvalidValue {
198                reason: "QuantizedEmbedding not supported in v0.4".to_string(),
199                field_id: 0,
200                type_tag: 0x0A,
201            }),
202        }
203    }
204
205    /// Converts to LnmpValue
206    pub fn to_lnmp_value(&self) -> LnmpValue {
207        match self {
208            BinaryValue::Int(i) => LnmpValue::Int(*i),
209            BinaryValue::Float(f) => LnmpValue::Float(*f),
210            BinaryValue::Bool(b) => LnmpValue::Bool(*b),
211            BinaryValue::String(s) => LnmpValue::String(s.clone()),
212            BinaryValue::StringArray(arr) => LnmpValue::StringArray(arr.clone()),
213            BinaryValue::IntArray(arr) => LnmpValue::IntArray(arr.clone()),
214            BinaryValue::FloatArray(arr) => LnmpValue::FloatArray(arr.clone()),
215            BinaryValue::BoolArray(arr) => LnmpValue::BoolArray(arr.clone()),
216            BinaryValue::NestedRecord(rec) => LnmpValue::NestedRecord(rec.clone()),
217            BinaryValue::NestedArray(arr) => LnmpValue::NestedArray(arr.clone()),
218            BinaryValue::Embedding(vec) => LnmpValue::Embedding(vec.clone()),
219            BinaryValue::QuantizedEmbedding(qv) => LnmpValue::QuantizedEmbedding(qv.clone()),
220        }
221    }
222
223    /// Returns the type tag for this value
224    pub fn type_tag(&self) -> TypeTag {
225        match self {
226            BinaryValue::Int(_) => TypeTag::Int,
227            BinaryValue::Float(_) => TypeTag::Float,
228            BinaryValue::Bool(_) => TypeTag::Bool,
229            BinaryValue::String(_) => TypeTag::String,
230            BinaryValue::StringArray(_) => TypeTag::StringArray,
231            BinaryValue::IntArray(_) => TypeTag::IntArray,
232            BinaryValue::FloatArray(_) => TypeTag::FloatArray,
233            BinaryValue::BoolArray(_) => TypeTag::BoolArray,
234            BinaryValue::NestedRecord(_) => TypeTag::NestedRecord,
235            BinaryValue::NestedArray(_) => TypeTag::NestedArray,
236            BinaryValue::Embedding(_) => TypeTag::Embedding,
237            BinaryValue::QuantizedEmbedding(_) => TypeTag::QuantizedEmbedding,
238        }
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    #![allow(clippy::approx_constant)]
245
246    use super::*;
247    use lnmp_core::LnmpRecord;
248
249    #[test]
250    fn test_type_tag_from_u8() {
251        assert_eq!(TypeTag::from_u8(0x01).unwrap(), TypeTag::Int);
252        assert_eq!(TypeTag::from_u8(0x02).unwrap(), TypeTag::Float);
253        assert_eq!(TypeTag::from_u8(0x03).unwrap(), TypeTag::Bool);
254        assert_eq!(TypeTag::from_u8(0x04).unwrap(), TypeTag::String);
255        assert_eq!(TypeTag::from_u8(0x05).unwrap(), TypeTag::StringArray);
256    }
257
258    #[test]
259    fn test_type_tag_from_u8_invalid() {
260        assert!(TypeTag::from_u8(0x00).is_err());
261        assert!(TypeTag::from_u8(0xFF).is_err());
262    }
263
264    #[test]
265    fn test_type_tag_from_u8_v0_5_types() {
266        // v0.5 types should now be valid
267        assert_eq!(TypeTag::from_u8(0x06).unwrap(), TypeTag::NestedRecord);
268        assert_eq!(TypeTag::from_u8(0x07).unwrap(), TypeTag::NestedArray);
269    }
270
271    #[test]
272    fn test_type_tag_from_u8_reserved() {
273        // Reserved types should be valid but marked as reserved
274        assert_eq!(TypeTag::from_u8(0x08).unwrap(), TypeTag::Embedding);
275        assert_eq!(TypeTag::from_u8(0x09).unwrap(), TypeTag::Reserved09);
276        assert_eq!(TypeTag::from_u8(0x0A).unwrap(), TypeTag::QuantizedEmbedding);
277        assert_eq!(TypeTag::from_u8(0x0B).unwrap(), TypeTag::IntArray);
278        assert_eq!(TypeTag::from_u8(0x0C).unwrap(), TypeTag::FloatArray);
279        assert_eq!(TypeTag::from_u8(0x0D).unwrap(), TypeTag::BoolArray);
280        assert_eq!(TypeTag::from_u8(0x0E).unwrap(), TypeTag::Reserved0E);
281        assert_eq!(TypeTag::from_u8(0x0F).unwrap(), TypeTag::Reserved0F);
282    }
283
284    #[test]
285    fn test_type_tag_to_u8() {
286        assert_eq!(TypeTag::Int.to_u8(), 0x01);
287        assert_eq!(TypeTag::Float.to_u8(), 0x02);
288        assert_eq!(TypeTag::Bool.to_u8(), 0x03);
289        assert_eq!(TypeTag::String.to_u8(), 0x04);
290        assert_eq!(TypeTag::StringArray.to_u8(), 0x05);
291    }
292
293    #[test]
294    fn test_type_tag_round_trip() {
295        let tags = vec![
296            TypeTag::Int,
297            TypeTag::Float,
298            TypeTag::Bool,
299            TypeTag::String,
300            TypeTag::StringArray,
301            TypeTag::NestedRecord,
302            TypeTag::NestedArray,
303            TypeTag::Embedding,
304            TypeTag::Reserved09,
305            TypeTag::QuantizedEmbedding,
306            TypeTag::IntArray,
307            TypeTag::FloatArray,
308            TypeTag::BoolArray,
309            TypeTag::Reserved0E,
310            TypeTag::Reserved0F,
311        ];
312
313        for tag in tags {
314            let byte = tag.to_u8();
315            let parsed = TypeTag::from_u8(byte).unwrap();
316            assert_eq!(parsed, tag);
317        }
318    }
319
320    #[test]
321    fn test_type_tag_is_v0_5_type() {
322        // v0.4 types should return false
323        assert!(!TypeTag::Int.is_v0_5_type());
324        assert!(!TypeTag::Float.is_v0_5_type());
325        assert!(!TypeTag::Bool.is_v0_5_type());
326        assert!(!TypeTag::String.is_v0_5_type());
327        assert!(!TypeTag::StringArray.is_v0_5_type());
328
329        // v0.5 types should return true
330        assert!(TypeTag::NestedRecord.is_v0_5_type());
331        assert!(TypeTag::NestedArray.is_v0_5_type());
332        assert!(TypeTag::Embedding.is_v0_5_type());
333        assert!(TypeTag::Reserved09.is_v0_5_type());
334        assert!(TypeTag::QuantizedEmbedding.is_v0_5_type());
335        assert!(TypeTag::IntArray.is_v0_5_type());
336        assert!(TypeTag::FloatArray.is_v0_5_type());
337        assert!(TypeTag::BoolArray.is_v0_5_type());
338        assert!(TypeTag::Reserved0E.is_v0_5_type());
339        assert!(TypeTag::Reserved0F.is_v0_5_type());
340    }
341
342    #[test]
343    fn test_type_tag_is_reserved() {
344        // Non-reserved types should return false
345        assert!(!TypeTag::Int.is_reserved());
346        assert!(!TypeTag::Float.is_reserved());
347        assert!(!TypeTag::Bool.is_reserved());
348        assert!(!TypeTag::String.is_reserved());
349        assert!(!TypeTag::StringArray.is_reserved());
350        assert!(!TypeTag::NestedRecord.is_reserved());
351        assert!(!TypeTag::NestedArray.is_reserved());
352
353        // Reserved types should return true
354        assert!(!TypeTag::Embedding.is_reserved());
355        assert!(TypeTag::Reserved09.is_reserved());
356        assert!(!TypeTag::QuantizedEmbedding.is_reserved());
357        assert!(!TypeTag::IntArray.is_reserved());
358        assert!(!TypeTag::FloatArray.is_reserved());
359        assert!(!TypeTag::BoolArray.is_reserved());
360        assert!(TypeTag::Reserved0E.is_reserved());
361        assert!(TypeTag::Reserved0F.is_reserved());
362    }
363
364    #[test]
365    fn test_binary_value_from_lnmp_int() {
366        let lnmp_val = LnmpValue::Int(42);
367        let binary_val = BinaryValue::from_lnmp_value(&lnmp_val).unwrap();
368        assert_eq!(binary_val, BinaryValue::Int(42));
369    }
370
371    #[test]
372    fn test_binary_value_from_lnmp_float() {
373        let lnmp_val = LnmpValue::Float(3.14);
374        let binary_val = BinaryValue::from_lnmp_value(&lnmp_val).unwrap();
375        assert_eq!(binary_val, BinaryValue::Float(3.14));
376    }
377
378    #[test]
379    fn test_binary_value_from_lnmp_bool() {
380        let lnmp_val = LnmpValue::Bool(true);
381        let binary_val = BinaryValue::from_lnmp_value(&lnmp_val).unwrap();
382        assert_eq!(binary_val, BinaryValue::Bool(true));
383    }
384
385    #[test]
386    fn test_binary_value_from_lnmp_string() {
387        let lnmp_val = LnmpValue::String("hello".to_string());
388        let binary_val = BinaryValue::from_lnmp_value(&lnmp_val).unwrap();
389        assert_eq!(binary_val, BinaryValue::String("hello".to_string()));
390    }
391
392    #[test]
393    fn test_binary_value_from_lnmp_string_array() {
394        let lnmp_val = LnmpValue::StringArray(vec!["a".to_string(), "b".to_string()]);
395        let binary_val = BinaryValue::from_lnmp_value(&lnmp_val).unwrap();
396        assert_eq!(
397            binary_val,
398            BinaryValue::StringArray(vec!["a".to_string(), "b".to_string()])
399        );
400    }
401
402    #[test]
403    fn test_binary_value_from_lnmp_nested_record() {
404        // v0.5 now supports nested records
405        let nested = LnmpValue::NestedRecord(Box::new(LnmpRecord::new()));
406        let result = BinaryValue::from_lnmp_value(&nested);
407        assert!(result.is_ok());
408        match result.unwrap() {
409            BinaryValue::NestedRecord(_) => {}
410            _ => panic!("Expected NestedRecord variant"),
411        }
412    }
413
414    #[test]
415    fn test_binary_value_from_lnmp_nested_array() {
416        // v0.5 now supports nested arrays
417        let nested = LnmpValue::NestedArray(vec![]);
418        let result = BinaryValue::from_lnmp_value(&nested);
419        assert!(result.is_ok());
420        match result.unwrap() {
421            BinaryValue::NestedArray(_) => {}
422            _ => panic!("Expected NestedArray variant"),
423        }
424    }
425
426    #[test]
427    fn test_binary_value_from_lnmp_nested_record_error_v0_4() {
428        // v0.4 compatibility mode should still reject nested records
429        let nested = LnmpValue::NestedRecord(Box::new(LnmpRecord::new()));
430        let result = BinaryValue::from_lnmp_value_v0_4(&nested);
431        assert!(result.is_err());
432        match result {
433            Err(BinaryError::InvalidValue { reason, .. }) => {
434                assert!(reason.contains("not supported in v0.4"));
435            }
436            _ => panic!("Expected InvalidValue error"),
437        }
438    }
439
440    #[test]
441    fn test_binary_value_from_lnmp_nested_array_error_v0_4() {
442        // v0.4 compatibility mode should still reject nested arrays
443        let nested = LnmpValue::NestedArray(vec![]);
444        let result = BinaryValue::from_lnmp_value_v0_4(&nested);
445        assert!(result.is_err());
446        match result {
447            Err(BinaryError::InvalidValue { reason, .. }) => {
448                assert!(reason.contains("not supported in v0.4"));
449            }
450            _ => panic!("Expected InvalidValue error"),
451        }
452    }
453
454    #[test]
455    fn test_binary_value_to_lnmp_int() {
456        let binary_val = BinaryValue::Int(-42);
457        let lnmp_val = binary_val.to_lnmp_value();
458        assert_eq!(lnmp_val, LnmpValue::Int(-42));
459    }
460
461    #[test]
462    fn test_binary_value_to_lnmp_float() {
463        let binary_val = BinaryValue::Float(2.718);
464        let lnmp_val = binary_val.to_lnmp_value();
465        assert_eq!(lnmp_val, LnmpValue::Float(2.718));
466    }
467
468    #[test]
469    fn test_binary_value_to_lnmp_bool() {
470        let binary_val = BinaryValue::Bool(false);
471        let lnmp_val = binary_val.to_lnmp_value();
472        assert_eq!(lnmp_val, LnmpValue::Bool(false));
473    }
474
475    #[test]
476    fn test_binary_value_to_lnmp_string() {
477        let binary_val = BinaryValue::String("world".to_string());
478        let lnmp_val = binary_val.to_lnmp_value();
479        assert_eq!(lnmp_val, LnmpValue::String("world".to_string()));
480    }
481
482    #[test]
483    fn test_binary_value_to_lnmp_string_array() {
484        let binary_val = BinaryValue::StringArray(vec!["x".to_string(), "y".to_string()]);
485        let lnmp_val = binary_val.to_lnmp_value();
486        assert_eq!(
487            lnmp_val,
488            LnmpValue::StringArray(vec!["x".to_string(), "y".to_string()])
489        );
490    }
491
492    #[test]
493    fn test_binary_value_round_trip_int() {
494        let original = LnmpValue::Int(12345);
495        let binary = BinaryValue::from_lnmp_value(&original).unwrap();
496        let back = binary.to_lnmp_value();
497        assert_eq!(original, back);
498    }
499
500    #[test]
501    fn test_binary_value_round_trip_float() {
502        let original = LnmpValue::Float(1.414);
503        let binary = BinaryValue::from_lnmp_value(&original).unwrap();
504        let back = binary.to_lnmp_value();
505        assert_eq!(original, back);
506    }
507
508    #[test]
509    fn test_binary_value_round_trip_bool() {
510        let original = LnmpValue::Bool(true);
511        let binary = BinaryValue::from_lnmp_value(&original).unwrap();
512        let back = binary.to_lnmp_value();
513        assert_eq!(original, back);
514    }
515
516    #[test]
517    fn test_binary_value_round_trip_string() {
518        let original = LnmpValue::String("test".to_string());
519        let binary = BinaryValue::from_lnmp_value(&original).unwrap();
520        let back = binary.to_lnmp_value();
521        assert_eq!(original, back);
522    }
523
524    #[test]
525    fn test_binary_value_round_trip_string_array() {
526        let original = LnmpValue::StringArray(vec!["admin".to_string(), "dev".to_string()]);
527        let binary = BinaryValue::from_lnmp_value(&original).unwrap();
528        let back = binary.to_lnmp_value();
529        assert_eq!(original, back);
530    }
531
532    #[test]
533    fn test_binary_value_type_tag_int() {
534        let val = BinaryValue::Int(100);
535        assert_eq!(val.type_tag(), TypeTag::Int);
536    }
537
538    #[test]
539    fn test_binary_value_type_tag_float() {
540        let val = BinaryValue::Float(3.14);
541        assert_eq!(val.type_tag(), TypeTag::Float);
542    }
543
544    #[test]
545    fn test_binary_value_type_tag_bool() {
546        let val = BinaryValue::Bool(true);
547        assert_eq!(val.type_tag(), TypeTag::Bool);
548    }
549
550    #[test]
551    fn test_binary_value_type_tag_string() {
552        let val = BinaryValue::String("test".to_string());
553        assert_eq!(val.type_tag(), TypeTag::String);
554    }
555
556    #[test]
557    fn test_binary_value_type_tag_string_array() {
558        let val = BinaryValue::StringArray(vec!["a".to_string()]);
559        assert_eq!(val.type_tag(), TypeTag::StringArray);
560    }
561
562    #[test]
563    fn test_binary_value_type_tag_nested_record() {
564        let val = BinaryValue::NestedRecord(Box::new(LnmpRecord::new()));
565        assert_eq!(val.type_tag(), TypeTag::NestedRecord);
566    }
567
568    #[test]
569    fn test_binary_value_type_tag_nested_array() {
570        let val = BinaryValue::NestedArray(vec![]);
571        assert_eq!(val.type_tag(), TypeTag::NestedArray);
572    }
573
574    #[test]
575    fn test_binary_value_round_trip_nested_record() {
576        let mut record = LnmpRecord::new();
577        record.add_field(lnmp_core::LnmpField {
578            fid: 1,
579            value: LnmpValue::Int(42),
580        });
581        let original = LnmpValue::NestedRecord(Box::new(record));
582        let binary = BinaryValue::from_lnmp_value(&original).unwrap();
583        let back = binary.to_lnmp_value();
584        assert_eq!(original, back);
585    }
586
587    #[test]
588    fn test_binary_value_round_trip_nested_array() {
589        let mut record = LnmpRecord::new();
590        record.add_field(lnmp_core::LnmpField {
591            fid: 1,
592            value: LnmpValue::String("test".to_string()),
593        });
594        let original = LnmpValue::NestedArray(vec![record]);
595        let binary = BinaryValue::from_lnmp_value(&original).unwrap();
596        let back = binary.to_lnmp_value();
597        assert_eq!(original, back);
598    }
599
600    #[test]
601    fn test_binary_value_empty_string() {
602        let lnmp_val = LnmpValue::String("".to_string());
603        let binary_val = BinaryValue::from_lnmp_value(&lnmp_val).unwrap();
604        assert_eq!(binary_val, BinaryValue::String("".to_string()));
605        let back = binary_val.to_lnmp_value();
606        assert_eq!(back, lnmp_val);
607    }
608
609    #[test]
610    fn test_binary_value_empty_string_array() {
611        let lnmp_val = LnmpValue::StringArray(vec![]);
612        let binary_val = BinaryValue::from_lnmp_value(&lnmp_val).unwrap();
613        assert_eq!(binary_val, BinaryValue::StringArray(vec![]));
614        let back = binary_val.to_lnmp_value();
615        assert_eq!(back, lnmp_val);
616    }
617
618    #[test]
619    fn test_binary_value_negative_int() {
620        let lnmp_val = LnmpValue::Int(-9999);
621        let binary_val = BinaryValue::from_lnmp_value(&lnmp_val).unwrap();
622        assert_eq!(binary_val, BinaryValue::Int(-9999));
623        let back = binary_val.to_lnmp_value();
624        assert_eq!(back, lnmp_val);
625    }
626
627    #[test]
628    fn test_binary_value_special_floats() {
629        // Test NaN
630        let nan_val = LnmpValue::Float(f64::NAN);
631        let binary_nan = BinaryValue::from_lnmp_value(&nan_val).unwrap();
632        match binary_nan {
633            BinaryValue::Float(f) => assert!(f.is_nan()),
634            _ => panic!("Expected Float variant"),
635        }
636
637        // Test Infinity
638        let inf_val = LnmpValue::Float(f64::INFINITY);
639        let binary_inf = BinaryValue::from_lnmp_value(&inf_val).unwrap();
640        assert_eq!(binary_inf, BinaryValue::Float(f64::INFINITY));
641
642        // Test Negative Infinity
643        let neg_inf_val = LnmpValue::Float(f64::NEG_INFINITY);
644        let binary_neg_inf = BinaryValue::from_lnmp_value(&neg_inf_val).unwrap();
645        assert_eq!(binary_neg_inf, BinaryValue::Float(f64::NEG_INFINITY));
646    }
647}