1use crate::error::InvalidError;
2use crate::limits::MAX_VALUE_BYTES;
3use crate::validate::Validate;
4use serde::de::{self, SeqAccess, Visitor};
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use sha2::{Digest, Sha256};
7use std::collections::BTreeSet;
8use std::fmt;
9
10pub const MAX_LOGICAL_SCHEMA_DEPTH: usize = 16;
11pub const MAX_LOGICAL_SCHEMA_FIELDS: usize = 4_096;
12pub const MAX_LOGICAL_SCHEMA_BYTES: usize = 1_048_576;
13pub const MAX_FIELD_NAME_BYTES: usize = 255;
14pub const MAX_FIELD_DOC_BYTES: usize = 4_096;
15pub const MAX_FIXED_BYTES: u32 = 1_048_576;
16pub const MAX_DECIMAL_PRECISION: u8 = 38;
17pub const MICROS_PER_DAY: i64 = 86_400_000_000;
18pub const PROVENANCE_FIELD_ID_START: u32 = 2_000_000_000;
19
20pub const PROVENANCE_FIELDS: &[(u32, &str)] = &[
21 (2_000_000_001, "__laser_cluster_incarnation"),
22 (2_000_000_002, "__laser_source_incarnation"),
23 (2_000_000_003, "__laser_stream_id"),
24 (2_000_000_004, "__laser_topic_id"),
25 (2_000_000_005, "__laser_partition_id"),
26 (2_000_000_006, "__laser_offset"),
27 (2_000_000_007, "__laser_row_ordinal"),
28 (2_000_000_008, "__laser_projection_id"),
29 (2_000_000_009, "__laser_projection_version"),
30 (2_000_000_010, "__laser_destination_id"),
31 (2_000_000_011, "__laser_destination_generation"),
32 (2_000_000_012, "__laser_original_payload"),
33 (2_000_000_013, "__laser_original_content_type"),
34 (2_000_000_014, "__laser_original_schema_id"),
35 (2_000_000_015, "__laser_original_headers"),
36];
37
38pub const ORIGINAL_PAYLOAD_FIELD_ID: u32 = 2_000_000_012;
39pub const ORIGINAL_PAYLOAD_FIELD_NAME: &str = "__laser_original_payload";
40pub const ORIGINAL_CONTENT_TYPE_FIELD_ID: u32 = 2_000_000_013;
41pub const ORIGINAL_CONTENT_TYPE_FIELD_NAME: &str = "__laser_original_content_type";
42pub const ORIGINAL_SCHEMA_ID_FIELD_ID: u32 = 2_000_000_014;
43pub const ORIGINAL_SCHEMA_ID_FIELD_NAME: &str = "__laser_original_schema_id";
44pub const ORIGINAL_HEADERS_FIELD_ID: u32 = 2_000_000_015;
45pub const ORIGINAL_HEADERS_FIELD_NAME: &str = "__laser_original_headers";
46pub const PARTITION_ID_FIELD_NAME: &str = "__laser_partition_id";
47pub const OFFSET_FIELD_NAME: &str = "__laser_offset";
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
51pub struct LogicalSchemaId(u128);
52
53impl LogicalSchemaId {
54 pub const fn from_u128(value: u128) -> Self {
55 Self(value)
56 }
57
58 pub const fn as_u128(self) -> u128 {
59 self.0
60 }
61
62 pub const fn to_bytes(self) -> [u8; 16] {
63 self.0.to_be_bytes()
64 }
65
66 pub const fn from_bytes(bytes: [u8; 16]) -> Self {
67 Self(u128::from_be_bytes(bytes))
68 }
69}
70
71impl Serialize for LogicalSchemaId {
72 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
73 serializer.serialize_bytes(&self.to_bytes())
74 }
75}
76
77impl<'de> Deserialize<'de> for LogicalSchemaId {
78 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
79 struct LogicalSchemaIdVisitor;
80
81 impl<'de> Visitor<'de> for LogicalSchemaIdVisitor {
82 type Value = LogicalSchemaId;
83
84 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85 formatter.write_str("16 big-endian logical schema id bytes")
86 }
87
88 fn visit_bytes<E: de::Error>(self, bytes: &[u8]) -> Result<Self::Value, E> {
89 let bytes = bytes
90 .try_into()
91 .map_err(|_| E::invalid_length(bytes.len(), &self))?;
92 Ok(LogicalSchemaId::from_bytes(bytes))
93 }
94
95 fn visit_byte_buf<E: de::Error>(self, bytes: Vec<u8>) -> Result<Self::Value, E> {
96 self.visit_bytes(&bytes)
97 }
98
99 fn visit_seq<A: SeqAccess<'de>>(
100 self,
101 mut sequence: A,
102 ) -> Result<Self::Value, A::Error> {
103 let mut bytes = [0_u8; 16];
104 for (index, byte) in bytes.iter_mut().enumerate() {
105 *byte = sequence
106 .next_element()?
107 .ok_or_else(|| de::Error::invalid_length(index, &self))?;
108 }
109 if sequence.next_element::<u8>()?.is_some() {
110 return Err(de::Error::invalid_length(17, &self));
111 }
112 Ok(LogicalSchemaId::from_bytes(bytes))
113 }
114 }
115
116 deserializer.deserialize_bytes(LogicalSchemaIdVisitor)
117 }
118}
119
120#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
121pub struct SchemaFingerprint(#[serde(with = "crate::encoding::bin_bytes")] pub Vec<u8>);
122
123impl SchemaFingerprint {
124 pub const BYTES: usize = 32;
125
126 pub fn new(bytes: [u8; Self::BYTES]) -> Self {
127 Self(bytes.to_vec())
128 }
129
130 pub fn as_bytes(&self) -> &[u8] {
131 &self.0
132 }
133}
134
135#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
136pub struct Digest32(#[serde(with = "crate::encoding::bin_bytes")] pub Vec<u8>);
137
138impl Digest32 {
139 pub const BYTES: usize = 32;
140
141 pub fn new(bytes: [u8; Self::BYTES]) -> Self {
142 Self(bytes.to_vec())
143 }
144
145 pub fn as_bytes(&self) -> &[u8] {
146 &self.0
147 }
148}
149
150impl Validate for Digest32 {
151 fn validate(&self) -> Result<(), InvalidError> {
152 if self.0.len() != Self::BYTES {
153 return Err(InvalidError::new(format!(
154 "digest must be {} bytes, got {}",
155 Self::BYTES,
156 self.0.len()
157 )));
158 }
159 Ok(())
160 }
161}
162
163impl Validate for SchemaFingerprint {
164 fn validate(&self) -> Result<(), InvalidError> {
165 if self.0.len() != Self::BYTES {
166 return Err(InvalidError::new(format!(
167 "schema fingerprint must be {} bytes, got {}",
168 Self::BYTES,
169 self.0.len()
170 )));
171 }
172 Ok(())
173 }
174}
175
176#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
177pub struct LogicalSchemaRef {
178 pub id: LogicalSchemaId,
179 pub version: u32,
180 pub fingerprint: SchemaFingerprint,
181}
182
183impl Validate for LogicalSchemaRef {
184 fn validate(&self) -> Result<(), InvalidError> {
185 if self.id.as_u128() == 0 {
186 return Err(InvalidError::new("logical schema id must be nonzero"));
187 }
188 if self.version == 0 {
189 return Err(InvalidError::new("logical schema version must be nonzero"));
190 }
191 self.fingerprint.validate()
192 }
193}
194
195#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
196pub struct LogicalSchema {
197 pub schema: LogicalSchemaRef,
198 pub fields: Vec<LogicalField>,
199}
200
201impl Validate for LogicalSchema {
202 fn validate(&self) -> Result<(), InvalidError> {
203 self.validate_shape()?;
204 let computed = self.compute_fingerprint()?;
205 if self.schema.fingerprint != computed {
206 return Err(InvalidError::new(
207 "logical schema fingerprint does not match its canonical bytes",
208 ));
209 }
210 Ok(())
211 }
212}
213
214impl LogicalSchema {
215 pub fn new(
216 id: LogicalSchemaId,
217 version: u32,
218 fields: Vec<LogicalField>,
219 ) -> Result<Self, InvalidError> {
220 let mut schema = Self {
221 schema: LogicalSchemaRef {
222 id,
223 version,
224 fingerprint: SchemaFingerprint(Vec::new()),
225 },
226 fields,
227 };
228 schema.validate_shape()?;
229 schema.schema.fingerprint = schema.compute_fingerprint()?;
230 Ok(schema)
231 }
232
233 pub fn canonical_fingerprint_bytes(&self) -> Result<Vec<u8>, InvalidError> {
234 self.validate_shape()?;
235 let mut bytes = b"AGDX-SCHEMA-V1\0".to_vec();
236 bytes.extend_from_slice(&self.schema.id.to_bytes());
237 bytes.extend_from_slice(&self.schema.version.to_be_bytes());
238 encode_fields(&mut bytes, &self.fields)?;
239 Ok(bytes)
240 }
241
242 pub fn compute_fingerprint(&self) -> Result<SchemaFingerprint, InvalidError> {
243 let digest: [u8; SchemaFingerprint::BYTES] =
244 Sha256::digest(self.canonical_fingerprint_bytes()?).into();
245 Ok(SchemaFingerprint::new(digest))
246 }
247
248 fn validate_shape(&self) -> Result<(), InvalidError> {
249 if self.schema.id.as_u128() == 0 {
250 return Err(InvalidError::new("logical schema id must be nonzero"));
251 }
252 if self.schema.version == 0 {
253 return Err(InvalidError::new("logical schema version must be nonzero"));
254 }
255 if self.fields.is_empty() {
256 return Err(InvalidError::new("logical schema must contain a field"));
257 }
258
259 let mut ids = BTreeSet::new();
260 let mut field_count = 0usize;
261 let mut encoded_size = 0usize;
262 validate_struct_fields(
263 &self.fields,
264 1,
265 &mut ids,
266 &mut field_count,
267 &mut encoded_size,
268 false,
269 )?;
270 Ok(())
271 }
272}
273
274pub(crate) fn validate_result_fields(fields: &[LogicalField]) -> Result<(), InvalidError> {
275 if fields.is_empty() {
276 return Err(InvalidError::new("query result schema must not be empty"));
277 }
278 let mut ids = BTreeSet::new();
279 let mut field_count = 0usize;
280 let mut encoded_size = 0usize;
281 validate_struct_fields(
282 fields,
283 1,
284 &mut ids,
285 &mut field_count,
286 &mut encoded_size,
287 true,
288 )
289}
290
291fn encode_fields(bytes: &mut Vec<u8>, fields: &[LogicalField]) -> Result<(), InvalidError> {
292 let count = u32::try_from(fields.len())
293 .map_err(|_| InvalidError::new("logical schema field count does not fit u32"))?;
294 bytes.extend_from_slice(&count.to_be_bytes());
295 for field in fields {
296 bytes.extend_from_slice(&field.id.to_be_bytes());
297 bytes.push(u8::from(field.required));
298 encode_text(bytes, &field.name)?;
299 match &field.doc {
300 Some(doc) => {
301 bytes.push(1);
302 encode_text(bytes, doc)?;
303 }
304 None => bytes.push(0),
305 }
306 encode_logical_type(bytes, &field.field_type)?;
307 }
308 Ok(())
309}
310
311fn encode_text(bytes: &mut Vec<u8>, value: &str) -> Result<(), InvalidError> {
312 let length = u32::try_from(value.len())
313 .map_err(|_| InvalidError::new("logical schema text length does not fit u32"))?;
314 bytes.extend_from_slice(&length.to_be_bytes());
315 bytes.extend_from_slice(value.as_bytes());
316 Ok(())
317}
318
319fn encode_logical_type(
320 bytes: &mut Vec<u8>,
321 logical_type: &LogicalType,
322) -> Result<(), InvalidError> {
323 match logical_type {
324 LogicalType::Boolean => bytes.push(0),
325 LogicalType::Int => bytes.push(1),
326 LogicalType::Long => bytes.push(2),
327 LogicalType::Float => bytes.push(3),
328 LogicalType::Double => bytes.push(4),
329 LogicalType::Decimal { precision, scale } => {
330 bytes.extend_from_slice(&[5, *precision, *scale]);
331 }
332 LogicalType::Date => bytes.push(6),
333 LogicalType::TimeMicros => bytes.push(7),
334 LogicalType::TimestampMicros => bytes.push(8),
335 LogicalType::TimestampTzMicros => bytes.push(9),
336 LogicalType::String => bytes.push(10),
337 LogicalType::Uuid => bytes.push(11),
338 LogicalType::Fixed { length } => {
339 bytes.push(12);
340 bytes.extend_from_slice(&length.to_be_bytes());
341 }
342 LogicalType::Binary => bytes.push(13),
343 LogicalType::Struct { fields } => {
344 bytes.push(14);
345 encode_fields(bytes, fields)?;
346 }
347 LogicalType::List {
348 element_id,
349 element_required,
350 element,
351 } => {
352 bytes.push(15);
353 bytes.extend_from_slice(&element_id.to_be_bytes());
354 bytes.push(u8::from(*element_required));
355 encode_logical_type(bytes, element)?;
356 }
357 LogicalType::Map {
358 key_id,
359 key,
360 value_id,
361 value_required,
362 value,
363 } => {
364 bytes.push(16);
365 bytes.extend_from_slice(&key_id.to_be_bytes());
366 encode_logical_type(bytes, key)?;
367 bytes.extend_from_slice(&value_id.to_be_bytes());
368 bytes.push(u8::from(*value_required));
369 encode_logical_type(bytes, value)?;
370 }
371 LogicalType::Variant => bytes.push(17),
372 }
373 Ok(())
374}
375
376#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
377pub struct LogicalField {
378 pub id: u32,
379 pub name: String,
380 pub required: bool,
381 pub field_type: LogicalType,
382 #[serde(default, skip_serializing_if = "Option::is_none")]
383 pub doc: Option<String>,
384}
385
386#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
387#[serde(tag = "kind", rename_all = "snake_case")]
388#[non_exhaustive]
389pub enum LogicalType {
390 Boolean,
391 Int,
392 Long,
393 Float,
394 Double,
395 Decimal {
396 precision: u8,
397 scale: u8,
398 },
399 Date,
400 TimeMicros,
401 TimestampMicros,
402 TimestampTzMicros,
403 String,
404 Uuid,
405 Fixed {
406 length: u32,
407 },
408 Binary,
409 Struct {
410 fields: Vec<LogicalField>,
411 },
412 List {
413 element_id: u32,
414 element_required: bool,
415 element: Box<LogicalType>,
416 },
417 Map {
418 key_id: u32,
419 key: Box<LogicalType>,
420 value_id: u32,
421 value_required: bool,
422 value: Box<LogicalType>,
423 },
424 Variant,
426}
427
428#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
430#[serde(rename_all = "snake_case")]
431#[non_exhaustive]
432pub enum LogicalTypeKind {
433 Boolean,
434 Int,
435 Long,
436 Float,
437 Double,
438 Decimal,
439 Date,
440 TimeMicros,
441 TimestampMicros,
442 TimestampTzMicros,
443 String,
444 Uuid,
445 Fixed,
446 Binary,
447 Struct,
448 List,
449 Map,
450 Variant,
451}
452
453impl LogicalType {
454 pub const fn kind(&self) -> LogicalTypeKind {
455 match self {
456 Self::Boolean => LogicalTypeKind::Boolean,
457 Self::Int => LogicalTypeKind::Int,
458 Self::Long => LogicalTypeKind::Long,
459 Self::Float => LogicalTypeKind::Float,
460 Self::Double => LogicalTypeKind::Double,
461 Self::Decimal { .. } => LogicalTypeKind::Decimal,
462 Self::Date => LogicalTypeKind::Date,
463 Self::TimeMicros => LogicalTypeKind::TimeMicros,
464 Self::TimestampMicros => LogicalTypeKind::TimestampMicros,
465 Self::TimestampTzMicros => LogicalTypeKind::TimestampTzMicros,
466 Self::String => LogicalTypeKind::String,
467 Self::Uuid => LogicalTypeKind::Uuid,
468 Self::Fixed { .. } => LogicalTypeKind::Fixed,
469 Self::Binary => LogicalTypeKind::Binary,
470 Self::Struct { .. } => LogicalTypeKind::Struct,
471 Self::List { .. } => LogicalTypeKind::List,
472 Self::Map { .. } => LogicalTypeKind::Map,
473 Self::Variant => LogicalTypeKind::Variant,
474 }
475 }
476}
477
478impl LogicalType {
479 pub fn accepts_map_key(&self) -> bool {
480 matches!(
481 self,
482 Self::Boolean
483 | Self::Int
484 | Self::Long
485 | Self::Decimal { .. }
486 | Self::Date
487 | Self::TimeMicros
488 | Self::TimestampMicros
489 | Self::TimestampTzMicros
490 | Self::String
491 | Self::Uuid
492 | Self::Fixed { .. }
493 | Self::Binary
494 )
495 }
496}
497
498#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
499pub struct DecimalValue {
500 #[serde(with = "crate::encoding::bin_bytes")]
501 pub unscaled: Vec<u8>,
502 pub precision: u8,
503 pub scale: u8,
504}
505
506impl DecimalValue {
507 pub fn validate_canonical(&self) -> Result<(), InvalidError> {
508 validate_decimal(self)
509 }
510}
511
512#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
513pub struct UuidValue(#[serde(with = "crate::encoding::bin_bytes")] pub Vec<u8>);
514
515impl UuidValue {
516 pub const BYTES: usize = 16;
517
518 pub fn new(bytes: [u8; Self::BYTES]) -> Self {
519 Self(bytes.to_vec())
520 }
521
522 pub fn as_bytes(&self) -> &[u8] {
523 &self.0
524 }
525}
526
527impl fmt::Display for UuidValue {
528 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
529 if self.0.len() != Self::BYTES {
530 return Err(fmt::Error);
531 }
532 for (index, byte) in self.0.iter().enumerate() {
533 if matches!(index, 4 | 6 | 8 | 10) {
534 formatter.write_str("-")?;
535 }
536 write!(formatter, "{byte:02x}")?;
537 }
538 Ok(())
539 }
540}
541
542impl std::str::FromStr for UuidValue {
543 type Err = InvalidError;
544
545 fn from_str(value: &str) -> Result<Self, Self::Err> {
546 if value.len() != 36
547 || value
548 .bytes()
549 .enumerate()
550 .any(|(index, byte)| matches!(index, 8 | 13 | 18 | 23) != (byte == b'-'))
551 {
552 return Err(InvalidError::new(
553 "UUID must use the lowercase canonical 8-4-4-4-12 form",
554 ));
555 }
556 if value.bytes().any(|byte| byte.is_ascii_uppercase()) {
557 return Err(InvalidError::new(
558 "UUID hexadecimal digits must be lowercase",
559 ));
560 }
561
562 let mut bytes = [0u8; Self::BYTES];
563 let mut output = 0usize;
564 let mut nibbles = value.bytes().filter(|byte| *byte != b'-');
565 while output < bytes.len() {
566 let high = decode_hex(nibbles.next().expect("UUID shape checked"))?;
567 let low = decode_hex(nibbles.next().expect("UUID shape checked"))?;
568 bytes[output] = (high << 4) | low;
569 output += 1;
570 }
571 Ok(Self::new(bytes))
572 }
573}
574
575fn decode_hex(byte: u8) -> Result<u8, InvalidError> {
576 match byte {
577 b'0'..=b'9' => Ok(byte - b'0'),
578 b'a'..=b'f' => Ok(byte - b'a' + 10),
579 _ => Err(InvalidError::new("UUID contains a non-hexadecimal byte")),
580 }
581}
582
583#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
584pub struct BinaryValue(#[serde(with = "crate::encoding::bin_bytes")] pub Vec<u8>);
585
586#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
587#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
588#[non_exhaustive]
589pub enum TypedValue {
590 Null,
591 Boolean(bool),
592 Int(i32),
593 Long(i64),
594 Float(f32),
595 Double(f64),
596 Decimal(DecimalValue),
597 Date(i32),
598 TimeMicros(i64),
599 TimestampMicros(i64),
600 TimestampTzMicros(i64),
601 String(String),
602 Uuid(UuidValue),
603 Fixed(BinaryValue),
604 Binary(BinaryValue),
605 Struct(Vec<FieldValue>),
606 List(Vec<TypedValue>),
607 Map(Vec<MapEntry>),
608}
609
610impl From<&str> for TypedValue {
611 fn from(value: &str) -> Self {
612 Self::String(value.to_owned())
613 }
614}
615
616impl From<String> for TypedValue {
617 fn from(value: String) -> Self {
618 Self::String(value)
619 }
620}
621
622impl From<&String> for TypedValue {
623 fn from(value: &String) -> Self {
624 Self::String(value.clone())
625 }
626}
627
628impl From<i32> for TypedValue {
629 fn from(value: i32) -> Self {
630 Self::Int(value)
631 }
632}
633
634impl From<u32> for TypedValue {
635 fn from(value: u32) -> Self {
636 Self::Long(i64::from(value))
637 }
638}
639
640impl From<i64> for TypedValue {
641 fn from(value: i64) -> Self {
642 Self::Long(value)
643 }
644}
645
646impl From<f32> for TypedValue {
647 fn from(value: f32) -> Self {
648 Self::Float(value)
649 }
650}
651
652impl From<f64> for TypedValue {
653 fn from(value: f64) -> Self {
654 Self::Double(value)
655 }
656}
657
658impl From<bool> for TypedValue {
659 fn from(value: bool) -> Self {
660 Self::Boolean(value)
661 }
662}
663
664impl<T: Into<TypedValue>> From<Vec<T>> for TypedValue {
665 fn from(values: Vec<T>) -> Self {
666 Self::List(values.into_iter().map(Into::into).collect())
667 }
668}
669
670impl TypedValue {
671 pub fn as_str(&self) -> Option<&str> {
672 match self {
673 Self::String(value) => Some(value),
674 _ => None,
675 }
676 }
677
678 pub fn as_u64(&self) -> Option<u64> {
679 match self {
680 Self::Int(value) => u64::try_from(*value).ok(),
681 Self::Long(value) => u64::try_from(*value).ok(),
682 _ => None,
683 }
684 }
685
686 pub fn as_i64(&self) -> Option<i64> {
687 match self {
688 Self::Int(value) => Some(i64::from(*value)),
689 Self::Long(value) => Some(*value),
690 _ => None,
691 }
692 }
693
694 pub fn diagnostic_text(&self) -> String {
695 match self {
696 Self::Null => "null".to_owned(),
697 Self::Boolean(value) => value.to_string(),
698 Self::Int(value) => value.to_string(),
699 Self::Long(value) => value.to_string(),
700 Self::Float(value) => value.to_string(),
701 Self::Double(value) => value.to_string(),
702 Self::Date(value) => value.to_string(),
703 Self::TimeMicros(value) => value.to_string(),
704 Self::TimestampMicros(value) => value.to_string(),
705 Self::TimestampTzMicros(value) => value.to_string(),
706 Self::String(value) => value.clone(),
707 Self::Uuid(value) => value.to_string(),
708 Self::Decimal(value) => {
709 format!("0x{} scale {}", encode_hex(&value.unscaled), value.scale)
710 }
711 Self::Fixed(value) | Self::Binary(value) => format!("0x{}", encode_hex(&value.0)),
712 Self::Struct(_) | Self::List(_) | Self::Map(_) => {
713 serde_json::to_string(self).unwrap_or_else(|_| "<invalid typed value>".to_owned())
714 }
715 }
716 }
717
718 pub fn validate_canonical(&self) -> Result<(), InvalidError> {
719 validate_typed_value(self, 1)
720 }
721
722 pub fn validate_against(
723 &self,
724 logical_type: &LogicalType,
725 required: bool,
726 ) -> Result<(), InvalidError> {
727 if matches!(self, Self::Null) {
728 return if required {
729 Err(InvalidError::new("required value is null"))
730 } else {
731 Ok(())
732 };
733 }
734
735 self.validate_canonical()?;
736
737 match (self, logical_type) {
738 (Self::Boolean(_), LogicalType::Boolean)
739 | (Self::Int(_), LogicalType::Int)
740 | (Self::Long(_), LogicalType::Long)
741 | (Self::Date(_), LogicalType::Date)
742 | (Self::TimestampMicros(_), LogicalType::TimestampMicros)
743 | (Self::TimestampTzMicros(_), LogicalType::TimestampTzMicros)
744 | (Self::String(_), LogicalType::String)
745 | (Self::Binary(_), LogicalType::Binary) => Ok(()),
746 (Self::Float(value), LogicalType::Float) => validate_float(*value as f64),
747 (Self::Double(value), LogicalType::Double) => validate_float(*value),
748 (Self::Decimal(value), LogicalType::Decimal { precision, scale }) => {
749 value.validate_canonical()?;
750 if value.precision != *precision || value.scale != *scale {
751 return Err(InvalidError::new(format!(
752 "decimal type mismatch: value is ({}, {}), schema is ({precision}, {scale})",
753 value.precision, value.scale
754 )));
755 }
756 Ok(())
757 }
758 (Self::TimeMicros(value), LogicalType::TimeMicros) => {
759 if !(0..MICROS_PER_DAY).contains(value) {
760 return Err(InvalidError::new(format!(
761 "time value {value} is outside one day in microseconds"
762 )));
763 }
764 Ok(())
765 }
766 (Self::Uuid(value), LogicalType::Uuid) => {
767 if value.0.len() != UuidValue::BYTES {
768 return Err(InvalidError::new(format!(
769 "UUID must be {} bytes, got {}",
770 UuidValue::BYTES,
771 value.0.len()
772 )));
773 }
774 Ok(())
775 }
776 (Self::Fixed(value), LogicalType::Fixed { length }) => {
777 if value.0.len() != *length as usize {
778 return Err(InvalidError::new(format!(
779 "fixed value is {} bytes, expected {length}",
780 value.0.len()
781 )));
782 }
783 Ok(())
784 }
785 (Self::Struct(values), LogicalType::Struct { fields }) => {
786 validate_struct_value(values, fields)
787 }
788 (
789 Self::List(values),
790 LogicalType::List {
791 element_required,
792 element,
793 ..
794 },
795 ) => {
796 for value in values {
797 value.validate_against(element, *element_required)?;
798 }
799 Ok(())
800 }
801 (
802 Self::Map(entries),
803 LogicalType::Map {
804 key,
805 value,
806 value_required,
807 ..
808 },
809 ) => validate_map(entries, key, value, *value_required),
810 (_, LogicalType::Variant) => Ok(()),
811 _ => Err(InvalidError::new("typed value does not match logical type")),
812 }
813 }
814}
815
816fn encode_hex(bytes: &[u8]) -> String {
817 let mut encoded = String::with_capacity(bytes.len() * 2);
818 for byte in bytes {
819 use std::fmt::Write as _;
820 let _ = write!(encoded, "{byte:02x}");
821 }
822 encoded
823}
824
825#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
826pub struct FieldValue {
827 pub field_id: u32,
828 pub value: TypedValue,
829}
830
831#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
832pub struct MapEntry {
833 pub key: TypedValue,
834 pub value: TypedValue,
835}
836
837impl Validate for TypedValue {
838 fn validate(&self) -> Result<(), InvalidError> {
839 self.validate_canonical()
840 }
841}
842
843fn validate_typed_value(value: &TypedValue, depth: usize) -> Result<(), InvalidError> {
844 validate_depth(depth)?;
845 match value {
846 TypedValue::Null
847 | TypedValue::Boolean(_)
848 | TypedValue::Int(_)
849 | TypedValue::Long(_)
850 | TypedValue::Date(_)
851 | TypedValue::TimestampMicros(_)
852 | TypedValue::TimestampTzMicros(_) => Ok(()),
853 TypedValue::String(value) => {
854 if value.len() > MAX_VALUE_BYTES {
855 return Err(InvalidError::new(format!(
856 "string value is {} bytes, exceeds cap {MAX_VALUE_BYTES}",
857 value.len()
858 )));
859 }
860 Ok(())
861 }
862 TypedValue::Float(value) => validate_float(f64::from(*value)),
863 TypedValue::Double(value) => validate_float(*value),
864 TypedValue::Decimal(value) => value.validate_canonical(),
865 TypedValue::TimeMicros(value) => {
866 if !(0..MICROS_PER_DAY).contains(value) {
867 return Err(InvalidError::new(format!(
868 "time value {value} is outside one day in microseconds"
869 )));
870 }
871 Ok(())
872 }
873 TypedValue::Uuid(value) => {
874 if value.0.len() != UuidValue::BYTES {
875 return Err(InvalidError::new(format!(
876 "UUID must be {} bytes, got {}",
877 UuidValue::BYTES,
878 value.0.len()
879 )));
880 }
881 Ok(())
882 }
883 TypedValue::Fixed(value) => {
884 if value.0.len() > MAX_FIXED_BYTES as usize {
885 return Err(InvalidError::new(format!(
886 "binary value is {} bytes, exceeds cap {MAX_FIXED_BYTES}",
887 value.0.len()
888 )));
889 }
890 Ok(())
891 }
892 TypedValue::Binary(value) => {
893 if value.0.len() > MAX_VALUE_BYTES {
894 return Err(InvalidError::new(format!(
895 "binary value is {} bytes, exceeds cap {MAX_VALUE_BYTES}",
896 value.0.len()
897 )));
898 }
899 Ok(())
900 }
901 TypedValue::Struct(values) => {
902 if values.len() > MAX_LOGICAL_SCHEMA_FIELDS {
903 return Err(InvalidError::new(format!(
904 "struct carries {} fields, exceeds cap {MAX_LOGICAL_SCHEMA_FIELDS}",
905 values.len()
906 )));
907 }
908 let mut previous_id = 0;
909 for value in values {
910 if value.field_id == 0 || value.field_id <= previous_id {
911 return Err(InvalidError::new(
912 "struct field values must have strictly increasing positive field IDs",
913 ));
914 }
915 previous_id = value.field_id;
916 validate_typed_value(&value.value, depth + 1)?;
917 }
918 Ok(())
919 }
920 TypedValue::List(values) => {
921 if values.len() > MAX_LOGICAL_SCHEMA_FIELDS {
922 return Err(InvalidError::new(format!(
923 "list carries {} values, exceeds cap {MAX_LOGICAL_SCHEMA_FIELDS}",
924 values.len()
925 )));
926 }
927 for value in values {
928 validate_typed_value(value, depth + 1)?;
929 }
930 Ok(())
931 }
932 TypedValue::Map(entries) => {
933 if entries.len() > MAX_LOGICAL_SCHEMA_FIELDS {
934 return Err(InvalidError::new(format!(
935 "map carries {} entries, exceeds cap {MAX_LOGICAL_SCHEMA_FIELDS}",
936 entries.len()
937 )));
938 }
939 let mut previous = None;
940 for entry in entries {
941 validate_typed_value(&entry.key, depth + 1)?;
942 validate_typed_value(&entry.value, depth + 1)?;
943 let key = canonical_map_key(&entry.key)?;
944 if previous.as_ref().is_some_and(|before| before >= &key) {
945 return Err(InvalidError::new(
946 "map entries must be strictly ordered by canonical key",
947 ));
948 }
949 previous = Some(key);
950 }
951 Ok(())
952 }
953 }
954}
955
956fn validate_struct_fields(
957 fields: &[LogicalField],
958 depth: usize,
959 ids: &mut BTreeSet<u32>,
960 field_count: &mut usize,
961 encoded_size: &mut usize,
962 allow_provenance: bool,
963) -> Result<(), InvalidError> {
964 if depth > MAX_LOGICAL_SCHEMA_DEPTH {
965 return Err(InvalidError::new(format!(
966 "logical schema depth {depth} exceeds cap {MAX_LOGICAL_SCHEMA_DEPTH}"
967 )));
968 }
969
970 let mut names = BTreeSet::new();
971 for field in fields {
972 validate_field_identity(field.id, &field.name, ids, &mut names, allow_provenance)?;
973 *field_count += 1;
974 if *field_count > MAX_LOGICAL_SCHEMA_FIELDS {
975 return Err(InvalidError::new(format!(
976 "logical schema field count exceeds cap {MAX_LOGICAL_SCHEMA_FIELDS}"
977 )));
978 }
979
980 let doc_bytes = field.doc.as_ref().map_or(0, String::len);
981 if doc_bytes > MAX_FIELD_DOC_BYTES {
982 return Err(InvalidError::new(format!(
983 "field `{}` documentation is {doc_bytes} bytes, exceeds cap {MAX_FIELD_DOC_BYTES}",
984 field.name
985 )));
986 }
987 *encoded_size = encoded_size
988 .checked_add(field.name.len() + doc_bytes + 16)
989 .ok_or_else(|| InvalidError::new("logical schema size overflow"))?;
990 if *encoded_size > MAX_LOGICAL_SCHEMA_BYTES {
991 return Err(InvalidError::new(format!(
992 "logical schema exceeds cap {MAX_LOGICAL_SCHEMA_BYTES} bytes"
993 )));
994 }
995 validate_logical_type(&field.field_type, depth, ids, field_count, encoded_size)?;
996 }
997 Ok(())
998}
999
1000fn validate_field_identity(
1001 id: u32,
1002 name: &str,
1003 ids: &mut BTreeSet<u32>,
1004 names: &mut BTreeSet<String>,
1005 allow_provenance: bool,
1006) -> Result<(), InvalidError> {
1007 let provenance_pair = PROVENANCE_FIELDS
1008 .iter()
1009 .any(|(reserved_id, reserved_name)| *reserved_id == id && *reserved_name == name);
1010 if id == 0 || (id >= PROVENANCE_FIELD_ID_START && !(allow_provenance && provenance_pair)) {
1011 return Err(InvalidError::new(format!(
1012 "field id {id} is not in the user field range"
1013 )));
1014 }
1015 if !ids.insert(id) {
1016 return Err(InvalidError::new(format!("duplicate field id {id}")));
1017 }
1018 validate_field_name(name)?;
1019 if PROVENANCE_FIELDS
1020 .iter()
1021 .any(|(_, reserved)| *reserved == name)
1022 && !(allow_provenance && provenance_pair)
1023 {
1024 return Err(InvalidError::new(format!(
1025 "field name `{name}` is reserved for provenance"
1026 )));
1027 }
1028 if !names.insert(name.to_owned()) {
1029 return Err(InvalidError::new(format!(
1030 "duplicate field name `{name}` in one struct"
1031 )));
1032 }
1033 Ok(())
1034}
1035
1036fn validate_field_name(name: &str) -> Result<(), InvalidError> {
1037 if name.is_empty() {
1038 return Err(InvalidError::new("field name must not be empty"));
1039 }
1040 if name.len() > MAX_FIELD_NAME_BYTES {
1041 return Err(InvalidError::new(format!(
1042 "field name is {} bytes, exceeds cap {MAX_FIELD_NAME_BYTES}",
1043 name.len()
1044 )));
1045 }
1046 if name.trim() != name || name.chars().any(char::is_control) {
1047 return Err(InvalidError::new(format!(
1048 "field name `{name}` contains surrounding whitespace or control characters"
1049 )));
1050 }
1051 Ok(())
1052}
1053
1054fn validate_logical_type(
1055 logical_type: &LogicalType,
1056 depth: usize,
1057 ids: &mut BTreeSet<u32>,
1058 field_count: &mut usize,
1059 encoded_size: &mut usize,
1060) -> Result<(), InvalidError> {
1061 match logical_type {
1062 LogicalType::Decimal { precision, scale } => validate_precision_scale(*precision, *scale),
1063 LogicalType::Fixed { length } => {
1064 if *length == 0 || *length > MAX_FIXED_BYTES {
1065 return Err(InvalidError::new(format!(
1066 "fixed length {length} is outside 1..={MAX_FIXED_BYTES}"
1067 )));
1068 }
1069 Ok(())
1070 }
1071 LogicalType::Struct { fields } => {
1072 validate_struct_fields(fields, depth + 1, ids, field_count, encoded_size, false)
1073 }
1074 LogicalType::List {
1075 element_id,
1076 element,
1077 ..
1078 } => {
1079 validate_nested_id(*element_id, "list element", ids, field_count)?;
1080 validate_depth(depth + 1)?;
1081 validate_logical_type(element, depth + 1, ids, field_count, encoded_size)
1082 }
1083 LogicalType::Map {
1084 key_id,
1085 key,
1086 value_id,
1087 value,
1088 ..
1089 } => {
1090 validate_nested_id(*key_id, "map key", ids, field_count)?;
1091 validate_nested_id(*value_id, "map value", ids, field_count)?;
1092 if !key.accepts_map_key() {
1093 return Err(InvalidError::new(
1094 "map key type must be a deterministic non-floating primitive",
1095 ));
1096 }
1097 validate_depth(depth + 1)?;
1098 validate_logical_type(key, depth + 1, ids, field_count, encoded_size)?;
1099 validate_logical_type(value, depth + 1, ids, field_count, encoded_size)
1100 }
1101 _ => Ok(()),
1102 }
1103}
1104
1105fn validate_nested_id(
1106 id: u32,
1107 label: &str,
1108 ids: &mut BTreeSet<u32>,
1109 field_count: &mut usize,
1110) -> Result<(), InvalidError> {
1111 if id == 0 || id >= PROVENANCE_FIELD_ID_START {
1112 return Err(InvalidError::new(format!(
1113 "{label} id {id} is not in the user field range"
1114 )));
1115 }
1116 if !ids.insert(id) {
1117 return Err(InvalidError::new(format!("duplicate {label} id {id}")));
1118 }
1119 *field_count += 1;
1120 if *field_count > MAX_LOGICAL_SCHEMA_FIELDS {
1121 return Err(InvalidError::new(format!(
1122 "logical schema field count exceeds cap {MAX_LOGICAL_SCHEMA_FIELDS}"
1123 )));
1124 }
1125 Ok(())
1126}
1127
1128fn validate_depth(depth: usize) -> Result<(), InvalidError> {
1129 if depth > MAX_LOGICAL_SCHEMA_DEPTH {
1130 return Err(InvalidError::new(format!(
1131 "logical schema depth {depth} exceeds cap {MAX_LOGICAL_SCHEMA_DEPTH}"
1132 )));
1133 }
1134 Ok(())
1135}
1136
1137fn validate_precision_scale(precision: u8, scale: u8) -> Result<(), InvalidError> {
1138 if precision == 0 || precision > MAX_DECIMAL_PRECISION || scale > precision {
1139 return Err(InvalidError::new(format!(
1140 "decimal precision and scale ({precision}, {scale}) are invalid"
1141 )));
1142 }
1143 Ok(())
1144}
1145
1146fn validate_decimal(value: &DecimalValue) -> Result<(), InvalidError> {
1147 validate_precision_scale(value.precision, value.scale)?;
1148 if value.unscaled.is_empty() || value.unscaled.len() > 16 {
1149 return Err(InvalidError::new(format!(
1150 "decimal unscaled value must contain 1..=16 bytes, got {}",
1151 value.unscaled.len()
1152 )));
1153 }
1154 if value.unscaled.len() > 1 {
1155 let first = value.unscaled[0];
1156 let second = value.unscaled[1];
1157 if (first == 0 && second & 0x80 == 0) || (first == 0xff && second & 0x80 != 0) {
1158 return Err(InvalidError::new(
1159 "decimal unscaled bytes are not minimal two's complement",
1160 ));
1161 }
1162 }
1163
1164 let fill = if value.unscaled[0] & 0x80 == 0 {
1165 0
1166 } else {
1167 0xff
1168 };
1169 let mut bytes = [fill; 16];
1170 let start = bytes.len() - value.unscaled.len();
1171 bytes[start..].copy_from_slice(&value.unscaled);
1172 let unscaled = i128::from_be_bytes(bytes);
1173 let digits = if unscaled == 0 {
1174 1
1175 } else {
1176 unscaled.unsigned_abs().ilog10() + 1
1177 };
1178 if digits > u32::from(value.precision) {
1179 return Err(InvalidError::new(format!(
1180 "decimal value has {digits} digits, exceeds precision {}",
1181 value.precision
1182 )));
1183 }
1184 Ok(())
1185}
1186
1187fn validate_float(value: f64) -> Result<(), InvalidError> {
1188 if !value.is_finite() {
1189 return Err(InvalidError::new("floating-point value must be finite"));
1190 }
1191 if value == 0.0 && value.is_sign_negative() {
1192 return Err(InvalidError::new(
1193 "negative zero is not a canonical floating-point value",
1194 ));
1195 }
1196 Ok(())
1197}
1198
1199fn validate_struct_value(
1200 values: &[FieldValue],
1201 fields: &[LogicalField],
1202) -> Result<(), InvalidError> {
1203 if values.len() != fields.len() {
1204 return Err(InvalidError::new(format!(
1205 "struct carries {} values, schema has {} fields",
1206 values.len(),
1207 fields.len()
1208 )));
1209 }
1210 for (value, field) in values.iter().zip(fields) {
1211 if value.field_id != field.id {
1212 return Err(InvalidError::new(format!(
1213 "struct field id {} appears where {} is required",
1214 value.field_id, field.id
1215 )));
1216 }
1217 value
1218 .value
1219 .validate_against(&field.field_type, field.required)?;
1220 }
1221 Ok(())
1222}
1223
1224fn validate_map(
1225 entries: &[MapEntry],
1226 key_type: &LogicalType,
1227 value_type: &LogicalType,
1228 value_required: bool,
1229) -> Result<(), InvalidError> {
1230 let mut previous = None;
1231 for entry in entries {
1232 entry.key.validate_against(key_type, true)?;
1233 entry.value.validate_against(value_type, value_required)?;
1234 let key = canonical_map_key(&entry.key)?;
1235 if previous.as_ref().is_some_and(|before| before >= &key) {
1236 return Err(InvalidError::new(
1237 "map entries must be strictly ordered by canonical key",
1238 ));
1239 }
1240 previous = Some(key);
1241 }
1242 Ok(())
1243}
1244
1245fn canonical_map_key(value: &TypedValue) -> Result<Vec<u8>, InvalidError> {
1246 let mut key = Vec::new();
1247 match value {
1248 TypedValue::Boolean(value) => {
1249 key.push(0);
1250 key.push(u8::from(*value));
1251 }
1252 TypedValue::Int(value) => {
1253 key.push(1);
1254 key.extend_from_slice(&value.to_be_bytes());
1255 }
1256 TypedValue::Long(value) => {
1257 key.push(2);
1258 key.extend_from_slice(&value.to_be_bytes());
1259 }
1260 TypedValue::Decimal(value) => {
1261 key.push(3);
1262 key.push(value.precision);
1263 key.push(value.scale);
1264 key.extend_from_slice(&value.unscaled);
1265 }
1266 TypedValue::Date(value) => {
1267 key.push(4);
1268 key.extend_from_slice(&value.to_be_bytes());
1269 }
1270 TypedValue::TimeMicros(value) => {
1271 key.push(5);
1272 key.extend_from_slice(&value.to_be_bytes());
1273 }
1274 TypedValue::TimestampMicros(value) => {
1275 key.push(6);
1276 key.extend_from_slice(&value.to_be_bytes());
1277 }
1278 TypedValue::TimestampTzMicros(value) => {
1279 key.push(7);
1280 key.extend_from_slice(&value.to_be_bytes());
1281 }
1282 TypedValue::String(value) => {
1283 key.push(8);
1284 key.extend_from_slice(value.as_bytes());
1285 }
1286 TypedValue::Uuid(value) => {
1287 key.push(9);
1288 key.extend_from_slice(&value.0);
1289 }
1290 TypedValue::Fixed(value) => {
1291 key.push(10);
1292 key.extend_from_slice(&value.0);
1293 }
1294 TypedValue::Binary(value) => {
1295 key.push(11);
1296 key.extend_from_slice(&value.0);
1297 }
1298 _ => {
1299 return Err(InvalidError::new(
1300 "map key must be a deterministic non-floating primitive",
1301 ));
1302 }
1303 }
1304 Ok(key)
1305}
1306
1307#[cfg(test)]
1308mod tests {
1309 use super::*;
1310
1311 fn fields() -> Vec<LogicalField> {
1312 vec![
1313 LogicalField {
1314 id: 1,
1315 name: "id".to_owned(),
1316 required: true,
1317 field_type: LogicalType::Uuid,
1318 doc: Some("Stable row identity".to_owned()),
1319 },
1320 LogicalField {
1321 id: 2,
1322 name: "attributes".to_owned(),
1323 required: false,
1324 field_type: LogicalType::Map {
1325 key_id: 3,
1326 key: Box::new(LogicalType::String),
1327 value_id: 4,
1328 value_required: true,
1329 value: Box::new(LogicalType::Long),
1330 },
1331 doc: None,
1332 },
1333 ]
1334 }
1335
1336 #[test]
1337 fn given_a_logical_schema_when_fingerprinted_then_should_be_deterministic_and_cover_metadata() {
1338 let schema = LogicalSchema::new(LogicalSchemaId::from_u128(1), 1, fields())
1339 .expect("schema is valid");
1340 assert_eq!(
1341 schema.schema.fingerprint,
1342 schema.compute_fingerprint().expect("fingerprint")
1343 );
1344 schema.validate().expect("fingerprint validates");
1345
1346 let mut changed_fields = fields();
1347 changed_fields[0].doc = Some("Different documentation".to_owned());
1348 let changed = LogicalSchema::new(LogicalSchemaId::from_u128(1), 1, changed_fields)
1349 .expect("changed schema is valid");
1350 assert_ne!(schema.schema.fingerprint, changed.schema.fingerprint);
1351 }
1352
1353 #[test]
1354 fn given_reserved_or_duplicate_nested_ids_when_a_schema_is_validated_then_should_reject() {
1355 let reserved = vec![LogicalField {
1356 id: PROVENANCE_FIELDS[0].0,
1357 name: PROVENANCE_FIELDS[0].1.to_owned(),
1358 required: true,
1359 field_type: LogicalType::String,
1360 doc: None,
1361 }];
1362 assert!(LogicalSchema::new(LogicalSchemaId::from_u128(1), 1, reserved).is_err());
1363
1364 let duplicate = vec![LogicalField {
1365 id: 1,
1366 name: "items".to_owned(),
1367 required: true,
1368 field_type: LogicalType::List {
1369 element_id: 1,
1370 element_required: true,
1371 element: Box::new(LogicalType::String),
1372 },
1373 doc: None,
1374 }];
1375 assert!(LogicalSchema::new(LogicalSchemaId::from_u128(1), 1, duplicate).is_err());
1376 }
1377
1378 #[test]
1379 fn given_typed_values_when_validated_then_should_enforce_canonical_decimal_float_time_and_map_forms()
1380 {
1381 assert!(TypedValue::Double(f64::NAN).validate().is_err());
1382 assert!(TypedValue::Float(-0.0).validate().is_err());
1383 assert!(TypedValue::TimeMicros(MICROS_PER_DAY).validate().is_err());
1384 assert!(
1385 TypedValue::Decimal(DecimalValue {
1386 unscaled: vec![0, 1],
1387 precision: 2,
1388 scale: 0,
1389 })
1390 .validate()
1391 .is_err()
1392 );
1393 assert!(
1394 TypedValue::Map(vec![
1395 MapEntry {
1396 key: TypedValue::String("b".to_owned()),
1397 value: TypedValue::Long(1),
1398 },
1399 MapEntry {
1400 key: TypedValue::String("a".to_owned()),
1401 value: TypedValue::Long(2),
1402 },
1403 ])
1404 .validate()
1405 .is_err()
1406 );
1407 }
1408
1409 #[test]
1410 fn given_a_variant_field_when_validated_then_should_accept_every_canonical_value_and_refuse_a_map_key()
1411 {
1412 for value in [
1413 TypedValue::Null,
1414 TypedValue::Boolean(true),
1415 TypedValue::Long(i64::MAX),
1416 TypedValue::Double(2.5),
1417 TypedValue::String("text".to_owned()),
1418 ] {
1419 value
1420 .validate_against(&LogicalType::Variant, false)
1421 .expect("a variant takes any tagged value");
1422 }
1423 assert!(
1424 TypedValue::Double(f64::NAN)
1425 .validate_against(&LogicalType::Variant, false)
1426 .is_err()
1427 );
1428 assert!(
1429 TypedValue::Null
1430 .validate_against(&LogicalType::Variant, true)
1431 .is_err()
1432 );
1433 assert!(!LogicalType::Variant.accepts_map_key());
1434 assert_eq!(LogicalType::Variant.kind(), LogicalTypeKind::Variant);
1435 let json = serde_json::to_string(&LogicalType::Variant).expect("type serializes");
1436 assert_eq!(json, r#"{"kind":"variant"}"#);
1437 let mut variant_fields = fields();
1438 variant_fields[1].field_type = LogicalType::Variant;
1439 let variant = LogicalSchema::new(LogicalSchemaId::from_u128(1), 1, variant_fields)
1440 .expect("a variant schema is valid");
1441 let map = LogicalSchema::new(LogicalSchemaId::from_u128(1), 1, fields())
1442 .expect("the map schema is valid");
1443 assert_ne!(variant.schema.fingerprint, map.schema.fingerprint);
1444 }
1445
1446 #[test]
1447 fn given_ids_and_binary_values_when_round_tripped_through_json_then_should_be_lossless() {
1448 let id = LogicalSchemaId::from_u128(u128::MAX - 7);
1449 let json = serde_json::to_string(&id).expect("id serializes");
1450 let decoded: LogicalSchemaId = serde_json::from_str(&json).expect("id deserializes");
1451 assert_eq!(decoded, id);
1452
1453 let value = TypedValue::Binary(BinaryValue(vec![0, 127, 255]));
1454 let json = serde_json::to_string(&value).expect("value serializes");
1455 let decoded: TypedValue = serde_json::from_str(&json).expect("value deserializes");
1456 assert_eq!(decoded, value);
1457 }
1458}