apache_avro/
schema_equality.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! # Custom schema equality comparators
19//!
20//! The library provides two implementations of schema equality comparators:
21//! 1. `StructFieldEq` (default) - compares the schemas structurally, may slightly deviate from the specification.
22//! 2. `SpecificationEq` - compares the schemas by serializing them to their canonical form and comparing
23//!    the resulting JSON.
24//!
25//! To use a custom comparator, you need to implement the `SchemataEq` trait and set it using the
26//! `set_schemata_equality_comparator` function:
27//!
28//! ```
29//! use apache_avro::{AvroResult, Schema};
30//! use apache_avro::schema::Namespace;
31//! use apache_avro::schema_equality::{SchemataEq, set_schemata_equality_comparator};
32//!
33//! #[derive(Debug)]
34//! struct MyCustomSchemataEq;
35//!
36//! impl SchemataEq for MyCustomSchemataEq {
37//!     fn compare(&self, schema_one: &Schema, schema_two: &Schema) -> bool {
38//!         todo!()
39//!     }
40//! }
41//!
42//! // don't parse any schema before registering the custom comparator!
43//!
44//! set_schemata_equality_comparator(Box::new(MyCustomSchemataEq));
45//!
46//! // ... use the library
47//! ```
48//! **Note**: the library allows to set a comparator only once per the application lifetime!
49//! If the application parses schemas before setting a comparator, the default comparator will be
50//! registered and used!
51
52use crate::schema::{InnerDecimalSchema, UuidSchema};
53use crate::{
54    Schema,
55    schema::{
56        ArraySchema, DecimalSchema, EnumSchema, FixedSchema, MapSchema, RecordField, RecordSchema,
57        UnionSchema,
58    },
59};
60use log::debug;
61use std::{fmt::Debug, sync::OnceLock};
62
63/// A trait that compares two schemata for equality.
64///
65/// To register a custom one use [`set_schemata_equality_comparator`].
66pub trait SchemataEq: Debug + Send + Sync {
67    /// Compares two schemata for equality.
68    fn compare(&self, schema_one: &Schema, schema_two: &Schema) -> bool;
69}
70
71/// Compares two schemas according to the Avro specification by using [their canonical forms].
72///
73/// [their canonical forms](https://avro.apache.org/docs/1.11.1/specification/#parsing-canonical-form-for-schemas)
74#[derive(Debug)]
75pub struct SpecificationEq;
76impl SchemataEq for SpecificationEq {
77    fn compare(&self, schema_one: &Schema, schema_two: &Schema) -> bool {
78        schema_one.canonical_form() == schema_two.canonical_form()
79    }
80}
81
82/// Compares [the canonical forms] of two schemas for equality field by field.
83///
84/// This means that attributes like `aliases`, `doc`, `default` and `logicalType` are ignored.
85///
86/// [the canonical forms](https://avro.apache.org/docs/1.11.1/specification/#parsing-canonical-form-for-schemas)
87#[derive(Debug)]
88pub struct StructFieldEq {
89    /// Whether to include custom attributes in the comparison.
90    ///
91    /// The custom attributes are not used to construct the canonical form of the schema!
92    pub include_attributes: bool,
93}
94
95impl SchemataEq for StructFieldEq {
96    #[rustfmt::skip]
97    fn compare(&self, schema_one: &Schema, schema_two: &Schema) -> bool {
98        if schema_one.name() != schema_two.name() {
99            return false;
100        }
101
102        if self.include_attributes
103            && schema_one.custom_attributes() != schema_two.custom_attributes()
104        {
105            return false;
106        }
107
108        match (schema_one, schema_two) {
109            (Schema::Null, Schema::Null) => true,
110            (Schema::Null, _) => false,
111            (Schema::Boolean, Schema::Boolean) => true,
112            (Schema::Boolean, _) => false,
113            (Schema::Int, Schema::Int) => true,
114            (Schema::Int, _) => false,
115            (Schema::Long, Schema::Long) => true,
116            (Schema::Long, _) => false,
117            (Schema::Float, Schema::Float) => true,
118            (Schema::Float, _) => false,
119            (Schema::Double, Schema::Double) => true,
120            (Schema::Double, _) => false,
121            (Schema::Bytes, Schema::Bytes) => true,
122            (Schema::Bytes, _) => false,
123            (Schema::String, Schema::String) => true,
124            (Schema::String, _) => false,
125            (Schema::BigDecimal, Schema::BigDecimal) => true,
126            (Schema::BigDecimal, _) => false,
127            (Schema::Date, Schema::Date) => true,
128            (Schema::Date, _) => false,
129            (Schema::TimeMicros, Schema::TimeMicros) => true,
130            (Schema::TimeMicros, _) => false,
131            (Schema::TimeMillis, Schema::TimeMillis) => true,
132            (Schema::TimeMillis, _) => false,
133            (Schema::TimestampMicros, Schema::TimestampMicros) => true,
134            (Schema::TimestampMicros, _) => false,
135            (Schema::TimestampMillis, Schema::TimestampMillis) => true,
136            (Schema::TimestampMillis, _) => false,
137            (Schema::TimestampNanos, Schema::TimestampNanos) => true,
138            (Schema::TimestampNanos, _) => false,
139            (Schema::LocalTimestampMicros, Schema::LocalTimestampMicros) => true,
140            (Schema::LocalTimestampMicros, _) => false,
141            (Schema::LocalTimestampMillis, Schema::LocalTimestampMillis) => true,
142            (Schema::LocalTimestampMillis, _) => false,
143            (Schema::LocalTimestampNanos, Schema::LocalTimestampNanos) => true,
144            (Schema::LocalTimestampNanos, _) => false,
145            (
146                Schema::Record(RecordSchema { fields: fields_one, ..}),
147                Schema::Record(RecordSchema { fields: fields_two, ..})
148            ) => {
149                self.compare_fields(fields_one, fields_two)
150            }
151            (Schema::Record(_), _) => false,
152            (
153                Schema::Enum(EnumSchema { symbols: symbols_one, ..}),
154                Schema::Enum(EnumSchema { symbols: symbols_two, .. })
155            ) => {
156                symbols_one == symbols_two
157            }
158            (Schema::Enum(_), _) => false,
159            (
160                Schema::Fixed(FixedSchema { size: size_one, ..}),
161                Schema::Fixed(FixedSchema { size: size_two, .. })
162            ) => {
163                size_one == size_two
164            }
165            (Schema::Fixed(_), _) => false,
166            (
167                Schema::Union(UnionSchema { schemas: schemas_one, ..}),
168                Schema::Union(UnionSchema { schemas: schemas_two, .. })
169            ) => {
170                schemas_one.len() == schemas_two.len()
171                    && schemas_one
172                    .iter()
173                    .zip(schemas_two.iter())
174                    .all(|(s1, s2)| self.compare(s1, s2))
175            }
176            (Schema::Union(_), _) => false,
177            (
178                Schema::Decimal(DecimalSchema { precision: precision_one, scale: scale_one, inner: inner_one }),
179                Schema::Decimal(DecimalSchema { precision: precision_two, scale: scale_two, inner: inner_two })
180            ) => {
181                precision_one == precision_two && scale_one == scale_two && match (inner_one, inner_two) {
182                    (InnerDecimalSchema::Bytes, InnerDecimalSchema::Bytes) => true,
183                    (InnerDecimalSchema::Fixed(FixedSchema { size: size_one, .. }), InnerDecimalSchema::Fixed(FixedSchema { size: size_two, ..})) => {
184                        size_one == size_two
185                    }
186                    _ => false,
187                }
188            }
189            (Schema::Decimal(_), _) => false,
190            (Schema::Uuid(UuidSchema::Bytes), Schema::Uuid(UuidSchema::Bytes)) => true,
191            (Schema::Uuid(UuidSchema::Bytes), _) => false,
192            (Schema::Uuid(UuidSchema::String), Schema::Uuid(UuidSchema::String)) => true,
193            (Schema::Uuid(UuidSchema::String), _) => false,
194            (Schema::Uuid(UuidSchema::Fixed(FixedSchema { size: size_one, ..})), Schema::Uuid(UuidSchema::Fixed(FixedSchema { size: size_two, ..}))) => {
195                size_one == size_two
196            },
197            (Schema::Uuid(UuidSchema::Fixed(_)), _) => false,
198            (
199                Schema::Array(ArraySchema { items: items_one, ..}),
200                Schema::Array(ArraySchema { items: items_two, ..})
201            ) => {
202                self.compare(items_one, items_two)
203            }
204            (Schema::Array(_), _) => false,
205            (Schema::Duration(FixedSchema { size: size_one, ..}), Schema::Duration(FixedSchema { size: size_two, ..})) => size_one == size_two,
206            (Schema::Duration(_), _) => false,
207            (
208                Schema::Map(MapSchema { types: types_one, ..}),
209                Schema::Map(MapSchema { types: types_two, ..})
210            ) => {
211                self.compare(types_one, types_two)
212            }
213            (Schema::Map(_), _) => false,
214            (
215                Schema::Ref { name: name_one },
216                Schema::Ref { name: name_two }
217            ) => {
218                name_one == name_two
219            }
220            (Schema::Ref { .. }, _) => false,
221        }
222    }
223}
224
225impl StructFieldEq {
226    fn compare_fields(&self, fields_one: &[RecordField], fields_two: &[RecordField]) -> bool {
227        fields_one.len() == fields_two.len()
228            && fields_one
229                .iter()
230                .zip(fields_two.iter())
231                .all(|(f1, f2)| f1.name == f2.name && self.compare(&f1.schema, &f2.schema))
232    }
233}
234
235static SCHEMATA_COMPARATOR_ONCE: OnceLock<Box<dyn SchemataEq>> = OnceLock::new();
236
237/// Sets a custom schemata equality comparator.
238///
239/// Returns a unit if the registration was successful or the already
240/// registered comparator if the registration failed.
241///
242/// **Note**: This function must be called before parsing any schema because this will
243/// register the default comparator and the registration is one time only!
244pub fn set_schemata_equality_comparator(
245    comparator: Box<dyn SchemataEq>,
246) -> Result<(), Box<dyn SchemataEq>> {
247    debug!("Setting a custom schemata equality comparator: {comparator:?}.");
248    SCHEMATA_COMPARATOR_ONCE.set(comparator)
249}
250
251pub(crate) fn compare_schemata(schema_one: &Schema, schema_two: &Schema) -> bool {
252    SCHEMATA_COMPARATOR_ONCE
253        .get_or_init(|| {
254            debug!("Going to use the default schemata equality comparator: StructFieldEq.",);
255            Box::new(StructFieldEq {
256                include_attributes: false,
257            })
258        })
259        .compare(schema_one, schema_two)
260}
261
262#[cfg(test)]
263#[allow(non_snake_case)]
264mod tests {
265    use super::*;
266    use crate::schema::{InnerDecimalSchema, Name};
267    use apache_avro_test_helper::TestResult;
268    use serde_json::Value;
269    use std::collections::BTreeMap;
270
271    const SPECIFICATION_EQ: SpecificationEq = SpecificationEq;
272    const STRUCT_FIELD_EQ: StructFieldEq = StructFieldEq {
273        include_attributes: false,
274    };
275    const STRUCT_FIELD_EQ_WITH_ATTRS: StructFieldEq = StructFieldEq {
276        include_attributes: true,
277    };
278
279    macro_rules! test_primitives {
280        ($primitive:ident) => {
281            paste::item! {
282                #[test]
283                fn [<test_avro_3939_compare_schemata_$primitive>]() {
284                    let specification_eq_res = SPECIFICATION_EQ.compare(&Schema::$primitive, &Schema::$primitive);
285                    let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&Schema::$primitive, &Schema::$primitive);
286                    assert_eq!(specification_eq_res, struct_field_eq_res)
287                }
288            }
289        };
290    }
291
292    test_primitives!(Null);
293    test_primitives!(Boolean);
294    test_primitives!(Int);
295    test_primitives!(Long);
296    test_primitives!(Float);
297    test_primitives!(Double);
298    test_primitives!(Bytes);
299    test_primitives!(String);
300    test_primitives!(BigDecimal);
301    test_primitives!(Date);
302    test_primitives!(TimeMicros);
303    test_primitives!(TimeMillis);
304    test_primitives!(TimestampMicros);
305    test_primitives!(TimestampMillis);
306    test_primitives!(TimestampNanos);
307    test_primitives!(LocalTimestampMicros);
308    test_primitives!(LocalTimestampMillis);
309    test_primitives!(LocalTimestampNanos);
310
311    #[test]
312    fn avro_rs_382_compare_schemata_duration_equal() {
313        let schema_one = Schema::Duration(FixedSchema {
314            name: Name::try_from("name1").expect("Name is valid"),
315            size: 12,
316            aliases: None,
317            doc: None,
318            attributes: BTreeMap::new(),
319        });
320        let schema_two = Schema::Duration(FixedSchema {
321            name: Name::try_from("name1").expect("Name is valid"),
322            size: 12,
323            aliases: None,
324            doc: None,
325            attributes: BTreeMap::new(),
326        });
327        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
328        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
329        assert_eq!(specification_eq_res, struct_field_eq_res)
330    }
331
332    #[test]
333    fn avro_rs_382_compare_schemata_duration_different_names() {
334        let schema_one = Schema::Duration(FixedSchema {
335            name: Name::try_from("name1").expect("Name is valid"),
336            size: 12,
337            aliases: None,
338            doc: None,
339            attributes: BTreeMap::new(),
340        });
341        let schema_two = Schema::Duration(FixedSchema {
342            name: Name::try_from("name2").expect("Name is valid"),
343            size: 12,
344            aliases: None,
345            doc: None,
346            attributes: BTreeMap::new(),
347        });
348        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
349        assert!(!specification_eq_res);
350
351        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
352        assert!(!struct_field_eq_res)
353    }
354
355    #[test]
356    fn avro_rs_382_compare_schemata_duration_different_attributes() {
357        let schema_one = Schema::Duration(FixedSchema {
358            name: Name::try_from("name1").expect("Name is valid"),
359            size: 12,
360            aliases: None,
361            doc: None,
362            attributes: vec![(String::from("attr1"), serde_json::Value::Bool(true))]
363                .into_iter()
364                .collect(),
365        });
366        let schema_two = Schema::Duration(FixedSchema {
367            name: Name::try_from("name1").expect("Name is valid"),
368            size: 12,
369            aliases: None,
370            doc: None,
371            attributes: BTreeMap::new(),
372        });
373        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
374        assert!(specification_eq_res);
375
376        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
377        assert!(struct_field_eq_res);
378
379        let struct_field_eq_with_attrs_res =
380            STRUCT_FIELD_EQ_WITH_ATTRS.compare(&schema_one, &schema_two);
381        assert!(!struct_field_eq_with_attrs_res);
382    }
383
384    #[test]
385    fn avro_rs_382_compare_schemata_duration_different_sizes() {
386        let schema_one = Schema::Duration(FixedSchema {
387            name: Name::try_from("name1").expect("Name is valid"),
388            size: 8,
389            aliases: None,
390            doc: None,
391            attributes: BTreeMap::new(),
392        });
393        let schema_two = Schema::Duration(FixedSchema {
394            name: Name::try_from("name1").expect("Name is valid"),
395            size: 12,
396            aliases: None,
397            doc: None,
398            attributes: BTreeMap::new(),
399        });
400        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
401        assert!(!specification_eq_res);
402
403        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
404        assert!(!struct_field_eq_res);
405    }
406
407    #[test]
408    fn test_avro_3939_compare_named_schemata_with_different_names() {
409        let schema_one = Schema::Ref {
410            name: Name::try_from("name1").expect("Name is valid"),
411        };
412
413        let schema_two = Schema::Ref {
414            name: Name::try_from("name2").expect("Name is valid"),
415        };
416
417        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
418        assert!(!specification_eq_res);
419        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
420        assert!(!struct_field_eq_res);
421
422        assert_eq!(specification_eq_res, struct_field_eq_res);
423    }
424
425    #[test]
426    fn test_avro_3939_compare_schemata_not_including_attributes() {
427        let schema_one = Schema::map(Schema::Boolean)
428            .attributes(BTreeMap::from_iter([(
429                "key1".to_string(),
430                Value::Bool(true),
431            )]))
432            .build();
433        let schema_two = Schema::map(Schema::Boolean)
434            .attributes(BTreeMap::from_iter([(
435                "key2".to_string(),
436                Value::Bool(true),
437            )]))
438            .build();
439        // STRUCT_FIELD_EQ does not include attributes !
440        assert!(STRUCT_FIELD_EQ.compare(&schema_one, &schema_two));
441    }
442
443    #[test]
444    fn test_avro_3939_compare_schemata_including_attributes() {
445        let struct_field_eq = StructFieldEq {
446            include_attributes: true,
447        };
448        let schema_one = Schema::map(Schema::Boolean)
449            .attributes(BTreeMap::from_iter([(
450                "key1".to_string(),
451                Value::Bool(true),
452            )]))
453            .build();
454        let schema_two = Schema::map(Schema::Boolean)
455            .attributes(BTreeMap::from_iter([(
456                "key2".to_string(),
457                Value::Bool(true),
458            )]))
459            .build();
460        assert!(!struct_field_eq.compare(&schema_one, &schema_two));
461    }
462
463    #[test]
464    fn test_avro_3939_compare_map_schemata() {
465        let schema_one = Schema::map(Schema::Boolean).build();
466        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
467        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
468
469        let schema_two = Schema::map(Schema::Boolean).build();
470
471        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
472        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
473        assert!(
474            specification_eq_res,
475            "SpecificationEq: Equality of two Schema::Map failed!"
476        );
477        assert!(
478            struct_field_eq_res,
479            "StructFieldEq: Equality of two Schema::Map failed!"
480        );
481        assert_eq!(specification_eq_res, struct_field_eq_res);
482    }
483
484    #[test]
485    fn test_avro_3939_compare_array_schemata() {
486        let schema_one = Schema::array(Schema::Boolean).build();
487        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
488        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
489
490        let schema_two = Schema::array(Schema::Boolean).build();
491
492        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
493        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
494        assert!(
495            specification_eq_res,
496            "SpecificationEq: Equality of two Schema::Array failed!"
497        );
498        assert!(
499            struct_field_eq_res,
500            "StructFieldEq: Equality of two Schema::Array failed!"
501        );
502        assert_eq!(specification_eq_res, struct_field_eq_res);
503    }
504
505    #[test]
506    fn test_avro_3939_compare_decimal_schemata() {
507        let schema_one = Schema::Decimal(DecimalSchema {
508            precision: 10,
509            scale: 2,
510            inner: InnerDecimalSchema::Bytes,
511        });
512        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
513        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
514
515        let schema_two = Schema::Decimal(DecimalSchema {
516            precision: 10,
517            scale: 2,
518            inner: InnerDecimalSchema::Bytes,
519        });
520
521        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
522        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
523        assert!(
524            specification_eq_res,
525            "SpecificationEq: Equality of two Schema::Decimal failed!"
526        );
527        assert!(
528            struct_field_eq_res,
529            "StructFieldEq: Equality of two Schema::Decimal failed!"
530        );
531        assert_eq!(specification_eq_res, struct_field_eq_res);
532    }
533
534    #[test]
535    fn test_avro_3939_compare_fixed_schemata() {
536        let schema_one = Schema::Fixed(FixedSchema {
537            name: Name::try_from("fixed").expect("Name is valid"),
538            doc: None,
539            size: 10,
540            aliases: None,
541            attributes: BTreeMap::new(),
542        });
543        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
544        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
545
546        let schema_two = Schema::Fixed(FixedSchema {
547            name: Name::try_from("fixed").expect("Name is valid"),
548            doc: None,
549            size: 10,
550            aliases: None,
551            attributes: BTreeMap::new(),
552        });
553
554        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
555        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
556        assert!(
557            specification_eq_res,
558            "SpecificationEq: Equality of two Schema::Fixed failed!"
559        );
560        assert!(
561            struct_field_eq_res,
562            "StructFieldEq: Equality of two Schema::Fixed failed!"
563        );
564        assert_eq!(specification_eq_res, struct_field_eq_res);
565    }
566
567    #[test]
568    fn test_avro_3939_compare_enum_schemata() {
569        let schema_one = Schema::Enum(EnumSchema {
570            name: Name::try_from("enum").expect("Name is valid"),
571            doc: None,
572            symbols: vec!["A".to_string(), "B".to_string()],
573            default: None,
574            aliases: None,
575            attributes: BTreeMap::new(),
576        });
577        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
578        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
579
580        let schema_two = Schema::Enum(EnumSchema {
581            name: Name::try_from("enum").expect("Name is valid"),
582            doc: None,
583            symbols: vec!["A".to_string(), "B".to_string()],
584            default: None,
585            aliases: None,
586            attributes: BTreeMap::new(),
587        });
588
589        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
590        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
591        assert!(
592            specification_eq_res,
593            "SpecificationEq: Equality of two Schema::Enum failed!"
594        );
595        assert!(
596            struct_field_eq_res,
597            "StructFieldEq: Equality of two Schema::Enum failed!"
598        );
599        assert_eq!(specification_eq_res, struct_field_eq_res);
600    }
601
602    #[test]
603    fn test_avro_3939_compare_ref_schemata() {
604        let schema_one = Schema::Ref {
605            name: Name::try_from("ref").expect("Name is valid"),
606        };
607        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
608        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
609
610        let schema_two = Schema::Ref {
611            name: Name::try_from("ref").expect("Name is valid"),
612        };
613
614        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
615        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
616        assert!(
617            specification_eq_res,
618            "SpecificationEq: Equality of two Schema::Ref failed!"
619        );
620        assert!(
621            struct_field_eq_res,
622            "StructFieldEq: Equality of two Schema::Ref failed!"
623        );
624        assert_eq!(specification_eq_res, struct_field_eq_res);
625    }
626
627    #[test]
628    fn test_avro_3939_compare_record_schemata() {
629        let schema_one = Schema::Record(RecordSchema {
630            name: Name::try_from("record").expect("Name is valid"),
631            doc: None,
632            fields: vec![
633                RecordField::builder()
634                    .name("field".to_string())
635                    .schema(Schema::Boolean)
636                    .build(),
637            ],
638            aliases: None,
639            attributes: BTreeMap::new(),
640            lookup: Default::default(),
641        });
642        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
643        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
644
645        let schema_two = Schema::Record(RecordSchema {
646            name: Name::try_from("record").expect("Name is valid"),
647            doc: None,
648            fields: vec![
649                RecordField::builder()
650                    .name("field".to_string())
651                    .schema(Schema::Boolean)
652                    .build(),
653            ],
654            aliases: None,
655            attributes: BTreeMap::new(),
656            lookup: Default::default(),
657        });
658
659        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
660        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
661        assert!(
662            specification_eq_res,
663            "SpecificationEq: Equality of two Schema::Record failed!"
664        );
665        assert!(
666            struct_field_eq_res,
667            "StructFieldEq: Equality of two Schema::Record failed!"
668        );
669        assert_eq!(specification_eq_res, struct_field_eq_res);
670    }
671
672    #[test]
673    fn test_avro_3939_compare_union_schemata() -> TestResult {
674        let schema_one = Schema::Union(UnionSchema::new(vec![Schema::Boolean, Schema::Int])?);
675        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
676        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
677
678        let schema_two = Schema::Union(UnionSchema::new(vec![Schema::Boolean, Schema::Int])?);
679
680        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
681        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
682        assert!(
683            specification_eq_res,
684            "SpecificationEq: Equality of two Schema::Union failed!"
685        );
686        assert!(
687            struct_field_eq_res,
688            "StructFieldEq: Equality of two Schema::Union failed!"
689        );
690        assert_eq!(specification_eq_res, struct_field_eq_res);
691        Ok(())
692    }
693
694    #[test]
695    fn test_uuid_compare_uuid() -> TestResult {
696        let string = Schema::Uuid(UuidSchema::String);
697        let bytes = Schema::Uuid(UuidSchema::Bytes);
698        let mut fixed_schema = FixedSchema {
699            name: Name {
700                name: "some_name".to_string(),
701                namespace: None,
702            },
703            aliases: None,
704            doc: None,
705            size: 16,
706            attributes: Default::default(),
707        };
708        let fixed = Schema::Uuid(UuidSchema::Fixed(fixed_schema.clone()));
709        fixed_schema
710            .attributes
711            .insert("Something".to_string(), Value::Null);
712        let fixed_different = Schema::Uuid(UuidSchema::Fixed(fixed_schema));
713
714        assert!(SPECIFICATION_EQ.compare(&string, &string));
715        assert!(STRUCT_FIELD_EQ.compare(&string, &string));
716        assert!(SPECIFICATION_EQ.compare(&bytes, &bytes));
717        assert!(STRUCT_FIELD_EQ.compare(&bytes, &bytes));
718        assert!(SPECIFICATION_EQ.compare(&fixed, &fixed));
719        assert!(STRUCT_FIELD_EQ.compare(&fixed, &fixed));
720
721        assert!(!SPECIFICATION_EQ.compare(&string, &bytes));
722        assert!(!STRUCT_FIELD_EQ.compare(&string, &bytes));
723        assert!(!SPECIFICATION_EQ.compare(&bytes, &string));
724        assert!(!STRUCT_FIELD_EQ.compare(&bytes, &string));
725        assert!(!SPECIFICATION_EQ.compare(&string, &fixed));
726        assert!(!STRUCT_FIELD_EQ.compare(&string, &fixed));
727        assert!(!SPECIFICATION_EQ.compare(&fixed, &string));
728        assert!(!STRUCT_FIELD_EQ.compare(&fixed, &string));
729        assert!(!SPECIFICATION_EQ.compare(&bytes, &fixed));
730        assert!(!STRUCT_FIELD_EQ.compare(&bytes, &fixed));
731        assert!(!SPECIFICATION_EQ.compare(&fixed, &bytes));
732        assert!(!STRUCT_FIELD_EQ.compare(&fixed, &bytes));
733
734        assert!(SPECIFICATION_EQ.compare(&fixed, &fixed_different));
735        assert!(STRUCT_FIELD_EQ.compare(&fixed, &fixed_different));
736        assert!(SPECIFICATION_EQ.compare(&fixed_different, &fixed));
737        assert!(STRUCT_FIELD_EQ.compare(&fixed_different, &fixed));
738
739        let strict = StructFieldEq {
740            include_attributes: true,
741        };
742
743        assert!(!strict.compare(&fixed, &fixed_different));
744        assert!(!strict.compare(&fixed_different, &fixed));
745
746        Ok(())
747    }
748}