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