1use std::any::Any;
21use std::str::FromStr;
22
23use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
24use ordered_float::OrderedFloat;
25use serde_json::{Map as JsonMap, Number, Value as JsonValue};
26use uuid::Uuid;
27
28use super::Map;
29use super::decimal_utils::{
30 decimal_from_str_exact, decimal_mantissa, decimal_rescale, try_decimal_from_i128_with_scale,
31};
32use super::primitive::PrimitiveLiteral;
33use super::struct_value::Struct;
34use super::temporal::{date, time, timestamp, timestamptz};
35use crate::error::Result;
36use crate::spec::datatypes::{PrimitiveType, Type};
37use crate::{Error, ErrorKind};
38
39#[derive(Clone, Debug, PartialEq, Eq, Hash)]
41pub enum Literal {
42 Primitive(PrimitiveLiteral),
44 Struct(Struct),
48 List(Vec<Option<Literal>>),
52 Map(Map),
56}
57
58impl Literal {
59 pub fn bool<T: Into<bool>>(t: T) -> Self {
69 Self::Primitive(PrimitiveLiteral::Boolean(t.into()))
70 }
71
72 pub fn bool_from_str<S: AsRef<str>>(s: S) -> Result<Self> {
83 let v = s.as_ref().parse::<bool>().map_err(|e| {
84 Error::new(ErrorKind::DataInvalid, "Can't parse string to bool.").with_source(e)
85 })?;
86 Ok(Self::Primitive(PrimitiveLiteral::Boolean(v)))
87 }
88
89 pub fn int<T: Into<i32>>(t: T) -> Self {
99 Self::Primitive(PrimitiveLiteral::Int(t.into()))
100 }
101
102 pub fn long<T: Into<i64>>(t: T) -> Self {
112 Self::Primitive(PrimitiveLiteral::Long(t.into()))
113 }
114
115 pub fn float<T: Into<f32>>(t: T) -> Self {
129 Self::Primitive(PrimitiveLiteral::Float(OrderedFloat(t.into())))
130 }
131
132 pub fn double<T: Into<f64>>(t: T) -> Self {
146 Self::Primitive(PrimitiveLiteral::Double(OrderedFloat(t.into())))
147 }
148
149 pub fn date(days: i32) -> Self {
151 Self::Primitive(PrimitiveLiteral::Int(days))
152 }
153
154 pub fn date_from_str<S: AsRef<str>>(s: S) -> Result<Self> {
166 let t = s.as_ref().parse::<NaiveDate>().map_err(|e| {
167 Error::new(
168 ErrorKind::DataInvalid,
169 format!("Can't parse date from string: {}", s.as_ref()),
170 )
171 .with_source(e)
172 })?;
173
174 Ok(Self::date(date::date_from_naive_date(t)))
175 }
176
177 pub fn date_from_ymd(year: i32, month: u32, day: u32) -> Result<Self> {
190 let t = NaiveDate::from_ymd_opt(year, month, day).ok_or_else(|| {
191 Error::new(
192 ErrorKind::DataInvalid,
193 format!("Can't create date from year: {year}, month: {month}, day: {day}"),
194 )
195 })?;
196
197 Ok(Self::date(date::date_from_naive_date(t)))
198 }
199
200 pub fn time(value: i64) -> Self {
202 Self::Primitive(PrimitiveLiteral::Long(value))
203 }
204
205 fn time_from_naive_time(t: NaiveTime) -> Self {
207 let duration = t - date::unix_epoch().time();
208 let micro_secs = duration.num_microseconds().unwrap();
210
211 Literal::time(micro_secs)
212 }
213
214 pub fn time_from_str<S: AsRef<str>>(s: S) -> Result<Self> {
232 let t = s.as_ref().parse::<NaiveTime>().map_err(|e| {
233 Error::new(
234 ErrorKind::DataInvalid,
235 format!("Can't parse time from string: {}", s.as_ref()),
236 )
237 .with_source(e)
238 })?;
239
240 Ok(Self::time_from_naive_time(t))
241 }
242
243 pub fn time_from_hms_micro(hour: u32, min: u32, sec: u32, micro: u32) -> Result<Self> {
255 let t = NaiveTime::from_hms_micro_opt(hour, min, sec, micro)
256 .ok_or_else(|| Error::new(
257 ErrorKind::DataInvalid,
258 format!("Can't create time from hour: {hour}, min: {min}, second: {sec}, microsecond: {micro}"),
259 ))?;
260 Ok(Self::time_from_naive_time(t))
261 }
262
263 pub fn timestamp(value: i64) -> Self {
265 Self::Primitive(PrimitiveLiteral::Long(value))
266 }
267
268 pub fn timestamptz(value: i64) -> Self {
270 Self::Primitive(PrimitiveLiteral::Long(value))
271 }
272
273 pub(crate) fn timestamp_nano(value: i64) -> Self {
275 Self::Primitive(PrimitiveLiteral::Long(value))
276 }
277
278 pub(crate) fn timestamptz_nano(value: i64) -> Self {
280 Self::Primitive(PrimitiveLiteral::Long(value))
281 }
282
283 pub fn timestamp_from_datetime<T: TimeZone>(dt: DateTime<T>) -> Self {
285 Self::timestamp(dt.with_timezone(&Utc).timestamp_micros())
286 }
287
288 pub fn timestamptz_from_datetime<T: TimeZone>(dt: DateTime<T>) -> Self {
290 Self::timestamptz(dt.with_timezone(&Utc).timestamp_micros())
291 }
292
293 pub fn timestamp_from_str<S: AsRef<str>>(s: S) -> Result<Self> {
317 let dt = DateTime::<Utc>::from_str(s.as_ref()).map_err(|e| {
318 Error::new(ErrorKind::DataInvalid, "Can't parse datetime.").with_source(e)
319 })?;
320
321 Ok(Self::timestamp_from_datetime(dt))
322 }
323
324 pub fn timestamptz_from_str<S: AsRef<str>>(s: S) -> Result<Self> {
326 let dt = DateTime::<Utc>::from_str(s.as_ref()).map_err(|e| {
327 Error::new(ErrorKind::DataInvalid, "Can't parse datetime.").with_source(e)
328 })?;
329
330 Ok(Self::timestamptz_from_datetime(dt))
331 }
332
333 pub fn string<S: ToString>(s: S) -> Self {
335 Self::Primitive(PrimitiveLiteral::String(s.to_string()))
336 }
337
338 pub fn uuid(uuid: Uuid) -> Self {
340 Self::Primitive(PrimitiveLiteral::UInt128(uuid.as_u128()))
341 }
342
343 pub fn uuid_from_str<S: AsRef<str>>(s: S) -> Result<Self> {
356 let uuid = Uuid::parse_str(s.as_ref()).map_err(|e| {
357 Error::new(
358 ErrorKind::DataInvalid,
359 format!("Can't parse uuid from string: {}", s.as_ref()),
360 )
361 .with_source(e)
362 })?;
363 Ok(Self::uuid(uuid))
364 }
365
366 pub fn fixed<I: IntoIterator<Item = u8>>(input: I) -> Self {
378 Literal::Primitive(PrimitiveLiteral::Binary(input.into_iter().collect()))
379 }
380
381 pub fn binary<I: IntoIterator<Item = u8>>(input: I) -> Self {
393 Literal::Primitive(PrimitiveLiteral::Binary(input.into_iter().collect()))
394 }
395
396 pub fn decimal(decimal: i128) -> Self {
398 Self::Primitive(PrimitiveLiteral::Int128(decimal))
399 }
400
401 pub fn decimal_from_str<S: AsRef<str>>(s: S) -> Result<Self> {
413 let decimal = decimal_from_str_exact(s.as_ref())?;
414 Ok(Self::decimal(decimal_mantissa(&decimal)))
415 }
416
417 pub fn as_primitive_literal(&self) -> Option<PrimitiveLiteral> {
419 match self {
420 Literal::Primitive(primitive) => Some(primitive.clone()),
421 _ => None,
422 }
423 }
424
425 pub fn try_from_json(value: JsonValue, data_type: &Type) -> Result<Option<Self>> {
429 match data_type {
430 Type::Primitive(primitive) => match (primitive, value) {
431 (PrimitiveType::Boolean, JsonValue::Bool(bool)) => {
432 Ok(Some(Literal::Primitive(PrimitiveLiteral::Boolean(bool))))
433 }
434 (PrimitiveType::Int, JsonValue::Number(number)) => {
435 Ok(Some(Literal::Primitive(PrimitiveLiteral::Int(
436 number
437 .as_i64()
438 .ok_or(Error::new(
439 ErrorKind::DataInvalid,
440 "Failed to convert json number to int",
441 ))?
442 .try_into()?,
443 ))))
444 }
445 (PrimitiveType::Long, JsonValue::Number(number)) => Ok(Some(Literal::Primitive(
446 PrimitiveLiteral::Long(number.as_i64().ok_or(Error::new(
447 ErrorKind::DataInvalid,
448 "Failed to convert json number to long",
449 ))?),
450 ))),
451 (PrimitiveType::Float, JsonValue::Number(number)) => Ok(Some(Literal::Primitive(
452 PrimitiveLiteral::Float(OrderedFloat(number.as_f64().ok_or(Error::new(
453 ErrorKind::DataInvalid,
454 "Failed to convert json number to float",
455 ))? as f32)),
456 ))),
457 (PrimitiveType::Double, JsonValue::Number(number)) => Ok(Some(Literal::Primitive(
458 PrimitiveLiteral::Double(OrderedFloat(number.as_f64().ok_or(Error::new(
459 ErrorKind::DataInvalid,
460 "Failed to convert json number to double",
461 ))?)),
462 ))),
463 (PrimitiveType::Date, JsonValue::String(s)) => {
464 Ok(Some(Literal::Primitive(PrimitiveLiteral::Int(
465 date::date_to_days(&NaiveDate::parse_from_str(&s, "%Y-%m-%d")?),
466 ))))
467 }
468 (PrimitiveType::Date, JsonValue::Number(number)) => {
469 Ok(Some(Literal::Primitive(PrimitiveLiteral::Int(
470 number
471 .as_i64()
472 .ok_or(Error::new(
473 ErrorKind::DataInvalid,
474 "Failed to convert json number to date (days since epoch)",
475 ))?
476 .try_into()?,
477 ))))
478 }
479 (PrimitiveType::Time, JsonValue::String(s)) => {
480 Ok(Some(Literal::Primitive(PrimitiveLiteral::Long(
481 time::time_to_microseconds(&NaiveTime::parse_from_str(&s, "%H:%M:%S%.f")?),
482 ))))
483 }
484 (PrimitiveType::Timestamp, JsonValue::String(s)) => Ok(Some(Literal::Primitive(
485 PrimitiveLiteral::Long(timestamp::datetime_to_microseconds(
486 &NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S%.f")?,
487 )),
488 ))),
489 (PrimitiveType::Timestamptz, JsonValue::String(s)) => {
490 Ok(Some(Literal::Primitive(PrimitiveLiteral::Long(
491 timestamptz::datetimetz_to_microseconds(&Utc.from_utc_datetime(
492 &NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S%.f+00:00")?,
493 )),
494 ))))
495 }
496 (PrimitiveType::TimestampNs, JsonValue::String(s)) => {
497 let ndt = NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S%.f")?;
498 let nanos = timestamp::datetime_to_nanoseconds(&ndt).ok_or_else(|| {
499 Error::new(
500 ErrorKind::DataInvalid,
501 format!(
502 "Timestamp is outside the representable nanosecond range: {ndt}"
503 ),
504 )
505 })?;
506 Ok(Some(Literal::Primitive(PrimitiveLiteral::Long(nanos))))
507 }
508 (PrimitiveType::TimestamptzNs, JsonValue::String(s)) => {
509 let dt = Utc.from_utc_datetime(&NaiveDateTime::parse_from_str(
510 &s,
511 "%Y-%m-%dT%H:%M:%S%.f+00:00",
512 )?);
513 let nanos = timestamptz::datetimetz_to_nanoseconds(&dt).ok_or_else(|| {
514 Error::new(
515 ErrorKind::DataInvalid,
516 format!(
517 "Timestamptz is outside the representable nanosecond range: {dt}"
518 ),
519 )
520 })?;
521 Ok(Some(Literal::Primitive(PrimitiveLiteral::Long(nanos))))
522 }
523 (PrimitiveType::String, JsonValue::String(s)) => {
524 Ok(Some(Literal::Primitive(PrimitiveLiteral::String(s))))
525 }
526 (PrimitiveType::Uuid, JsonValue::String(s)) => Ok(Some(Literal::Primitive(
527 PrimitiveLiteral::UInt128(Uuid::parse_str(&s)?.as_u128()),
528 ))),
529 (PrimitiveType::Fixed(size), JsonValue::String(s)) => {
530 let bytes = decode_hex_bytes(&s)?;
531 validate_fixed_size(bytes.len(), *size)?;
532 Ok(Some(Literal::Primitive(PrimitiveLiteral::Binary(bytes))))
533 }
534 (PrimitiveType::Binary, JsonValue::String(s)) => Ok(Some(Literal::Primitive(
535 PrimitiveLiteral::Binary(decode_hex_bytes(&s)?),
536 ))),
537 (
538 PrimitiveType::Decimal {
539 precision: _,
540 scale,
541 },
542 JsonValue::String(s),
543 ) => {
544 let decimal = decimal_from_str_exact(&s)?;
545 let rescaled = decimal_rescale(decimal, *scale);
546 Ok(Some(Literal::Primitive(PrimitiveLiteral::Int128(
547 decimal_mantissa(&rescaled),
548 ))))
549 }
550 (_, JsonValue::Null) => Ok(None),
551 (i, j) => Err(Error::new(
552 ErrorKind::DataInvalid,
553 format!("The json value {j} doesn't fit to the iceberg type {i}."),
554 )),
555 },
556 Type::Struct(schema) => {
557 if let JsonValue::Object(mut object) = value {
558 Ok(Some(Literal::Struct(Struct::from_iter(
559 schema.fields().iter().map(|field| {
560 object.remove(&field.id.to_string()).and_then(|value| {
561 Literal::try_from_json(value, &field.field_type)
562 .and_then(|value| {
563 value.ok_or(Error::new(
564 ErrorKind::DataInvalid,
565 "Key of map cannot be null",
566 ))
567 })
568 .ok()
569 })
570 }),
571 ))))
572 } else {
573 Err(Error::new(
574 ErrorKind::DataInvalid,
575 "The json value for a struct type must be an object.",
576 ))
577 }
578 }
579 Type::List(list) => {
580 if let JsonValue::Array(array) = value {
581 Ok(Some(Literal::List(
582 array
583 .into_iter()
584 .map(|value| {
585 Literal::try_from_json(value, &list.element_field.field_type)
586 })
587 .collect::<Result<Vec<_>>>()?,
588 )))
589 } else {
590 Err(Error::new(
591 ErrorKind::DataInvalid,
592 "The json value for a list type must be an array.",
593 ))
594 }
595 }
596 Type::Map(map) => {
597 if let JsonValue::Object(mut object) = value {
598 if let (Some(JsonValue::Array(keys)), Some(JsonValue::Array(values))) =
599 (object.remove("keys"), object.remove("values"))
600 {
601 if keys.len() != values.len() {
602 return Err(Error::new(
603 ErrorKind::DataInvalid,
604 format!(
605 "Map keys and values must have the same length, got {} keys and {} values",
606 keys.len(),
607 values.len()
608 ),
609 ));
610 }
611
612 Ok(Some(Literal::Map(Map::from_iter(
613 keys.into_iter()
614 .zip(values)
615 .map(|(key, value)| {
616 Ok((
617 Literal::try_from_json(key, &map.key_field.field_type)
618 .and_then(|value| {
619 value.ok_or(Error::new(
620 ErrorKind::DataInvalid,
621 "Key of map cannot be null",
622 ))
623 })?,
624 Literal::try_from_json(value, &map.value_field.field_type)?,
625 ))
626 })
627 .collect::<Result<Vec<_>>>()?,
628 ))))
629 } else {
630 Err(Error::new(
631 ErrorKind::DataInvalid,
632 "The json value for a list type must be an array.",
633 ))
634 }
635 } else {
636 Err(Error::new(
637 ErrorKind::DataInvalid,
638 "The json value for a list type must be an array.",
639 ))
640 }
641 }
642 Type::Variant(_) => Err(Error::new(
643 ErrorKind::DataInvalid,
644 "Variant type is not supported for single-value JSON serialization",
645 )),
646 }
647 }
648
649 pub fn try_into_json(self, r#type: &Type) -> Result<JsonValue> {
653 match (self, r#type) {
654 (Literal::Primitive(prim), Type::Primitive(prim_type)) => match (prim_type, prim) {
655 (PrimitiveType::Boolean, PrimitiveLiteral::Boolean(val)) => {
656 Ok(JsonValue::Bool(val))
657 }
658 (PrimitiveType::Int, PrimitiveLiteral::Int(val)) => {
659 Ok(JsonValue::Number((val).into()))
660 }
661 (PrimitiveType::Long, PrimitiveLiteral::Long(val)) => {
662 Ok(JsonValue::Number((val).into()))
663 }
664 (PrimitiveType::Float, PrimitiveLiteral::Float(val)) => {
665 match Number::from_f64(val.0 as f64) {
666 Some(number) => Ok(JsonValue::Number(number)),
667 None => Ok(JsonValue::Null),
668 }
669 }
670 (PrimitiveType::Double, PrimitiveLiteral::Double(val)) => {
671 match Number::from_f64(val.0) {
672 Some(number) => Ok(JsonValue::Number(number)),
673 None => Ok(JsonValue::Null),
674 }
675 }
676 (PrimitiveType::Date, PrimitiveLiteral::Int(val)) => {
677 Ok(JsonValue::String(date::days_to_date(val).to_string()))
678 }
679 (PrimitiveType::Time, PrimitiveLiteral::Long(val)) => Ok(JsonValue::String(
680 time::microseconds_to_time(val).to_string(),
681 )),
682 (PrimitiveType::Timestamp, PrimitiveLiteral::Long(val)) => Ok(JsonValue::String(
683 timestamp::microseconds_to_datetime(val)
684 .format("%Y-%m-%dT%H:%M:%S%.f")
685 .to_string(),
686 )),
687 (PrimitiveType::Timestamptz, PrimitiveLiteral::Long(val)) => Ok(JsonValue::String(
688 timestamptz::microseconds_to_datetimetz(val)
689 .format("%Y-%m-%dT%H:%M:%S%.f+00:00")
690 .to_string(),
691 )),
692 (PrimitiveType::TimestampNs, PrimitiveLiteral::Long(val)) => Ok(JsonValue::String(
693 timestamp::nanoseconds_to_datetime(val)
694 .format("%Y-%m-%dT%H:%M:%S%.f")
695 .to_string(),
696 )),
697 (PrimitiveType::TimestamptzNs, PrimitiveLiteral::Long(val)) => {
698 Ok(JsonValue::String(
699 timestamptz::nanoseconds_to_datetimetz(val)
700 .format("%Y-%m-%dT%H:%M:%S%.f+00:00")
701 .to_string(),
702 ))
703 }
704 (PrimitiveType::String, PrimitiveLiteral::String(val)) => {
705 Ok(JsonValue::String(val.clone()))
706 }
707 (_, PrimitiveLiteral::UInt128(val)) => {
708 Ok(JsonValue::String(Uuid::from_u128(val).to_string()))
709 }
710 (PrimitiveType::Fixed(size), PrimitiveLiteral::Binary(val)) => {
711 validate_fixed_size(val.len(), *size)?;
712 Ok(JsonValue::String(encode_hex_bytes(&val)))
713 }
714 (PrimitiveType::Binary, PrimitiveLiteral::Binary(val)) => {
715 Ok(JsonValue::String(encode_hex_bytes(&val)))
716 }
717 (_, PrimitiveLiteral::Int128(val)) => match r#type {
718 Type::Primitive(PrimitiveType::Decimal {
719 precision: _precision,
720 scale,
721 }) => {
722 let decimal = try_decimal_from_i128_with_scale(val, *scale)?;
723 Ok(JsonValue::String(decimal.to_string()))
724 }
725 _ => Err(Error::new(
726 ErrorKind::DataInvalid,
727 "The iceberg type for decimal literal must be decimal.",
728 ))?,
729 },
730 _ => Err(Error::new(
731 ErrorKind::DataInvalid,
732 "The iceberg value doesn't fit to the iceberg type.",
733 )),
734 },
735 (Literal::Struct(s), Type::Struct(struct_type)) => {
736 let mut id_and_value = Vec::with_capacity(struct_type.fields().len());
737 for (value, field) in s.into_iter().zip(struct_type.fields()) {
738 let json = match value {
739 Some(val) => val.try_into_json(&field.field_type)?,
740 None => JsonValue::Null,
741 };
742 id_and_value.push((field.id.to_string(), json));
743 }
744 Ok(JsonValue::Object(JsonMap::from_iter(id_and_value)))
745 }
746 (Literal::List(list), Type::List(list_type)) => Ok(JsonValue::Array(
747 list.into_iter()
748 .map(|opt| match opt {
749 Some(literal) => literal.try_into_json(&list_type.element_field.field_type),
750 None => Ok(JsonValue::Null),
751 })
752 .collect::<Result<Vec<JsonValue>>>()?,
753 )),
754 (Literal::Map(map), Type::Map(map_type)) => {
755 let mut object = JsonMap::with_capacity(2);
756 let mut json_keys = Vec::with_capacity(map.len());
757 let mut json_values = Vec::with_capacity(map.len());
758 for (key, value) in map.into_iter() {
759 json_keys.push(key.try_into_json(&map_type.key_field.field_type)?);
760 json_values.push(match value {
761 Some(literal) => literal.try_into_json(&map_type.value_field.field_type)?,
762 None => JsonValue::Null,
763 });
764 }
765 object.insert("keys".to_string(), JsonValue::Array(json_keys));
766 object.insert("values".to_string(), JsonValue::Array(json_values));
767 Ok(JsonValue::Object(object))
768 }
769 (value, r#type) => Err(Error::new(
770 ErrorKind::DataInvalid,
771 format!("The iceberg value {value:?} doesn't fit to the iceberg type {type}."),
772 )),
773 }
774 }
775
776 pub fn into_any(self) -> Box<dyn Any> {
778 match self {
779 Literal::Primitive(prim) => match prim {
780 PrimitiveLiteral::Boolean(any) => Box::new(any),
781 PrimitiveLiteral::Int(any) => Box::new(any),
782 PrimitiveLiteral::Long(any) => Box::new(any),
783 PrimitiveLiteral::Float(any) => Box::new(any),
784 PrimitiveLiteral::Double(any) => Box::new(any),
785 PrimitiveLiteral::Binary(any) => Box::new(any),
786 PrimitiveLiteral::String(any) => Box::new(any),
787 PrimitiveLiteral::UInt128(any) => Box::new(any),
788 PrimitiveLiteral::Int128(any) => Box::new(any),
789 PrimitiveLiteral::AboveMax | PrimitiveLiteral::BelowMin => unimplemented!(),
790 },
791 _ => unimplemented!(),
792 }
793 }
794}
795
796fn decode_hex_bytes(value: &str) -> Result<Vec<u8>> {
797 if !value.len().is_multiple_of(2) {
798 return Err(Error::new(
799 ErrorKind::DataInvalid,
800 format!("Hex string must have an even number of characters: {value:?}"),
801 ));
802 }
803
804 value
805 .as_bytes()
806 .chunks_exact(2)
807 .map(|chunk| {
808 let high = decode_hex_digit(chunk[0], value)?;
809 let low = decode_hex_digit(chunk[1], value)?;
810 Ok((high << 4) | low)
811 })
812 .collect()
813}
814
815fn decode_hex_digit(digit: u8, value: &str) -> Result<u8> {
816 match digit {
817 b'0'..=b'9' => Ok(digit - b'0'),
818 b'a'..=b'f' => Ok(digit - b'a' + 10),
819 b'A'..=b'F' => Ok(digit - b'A' + 10),
820 _ => Err(Error::new(
821 ErrorKind::DataInvalid,
822 format!("Hex string contains invalid character: {value:?}"),
823 )),
824 }
825}
826
827fn encode_hex_bytes(bytes: &[u8]) -> String {
828 const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
829
830 let mut output = String::with_capacity(bytes.len() * 2);
831 for byte in bytes {
832 output.push(HEX_DIGITS[(byte >> 4) as usize] as char);
833 output.push(HEX_DIGITS[(byte & 0x0f) as usize] as char);
834 }
835 output
836}
837
838fn validate_fixed_size(actual: usize, expected: u64) -> Result<()> {
839 if actual as u64 == expected {
840 Ok(())
841 } else {
842 Err(Error::new(
843 ErrorKind::DataInvalid,
844 format!("Fixed type must be exactly {expected} bytes, got {actual}"),
845 ))
846 }
847}