Skip to main content

laser_wire/
schema.rs

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