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