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, RecordFieldOrder};
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_with_attributes(
428            Schema::Boolean,
429            BTreeMap::from_iter([("key1".to_string(), Value::Bool(true))]),
430        );
431        let schema_two = Schema::map_with_attributes(
432            Schema::Boolean,
433            BTreeMap::from_iter([("key2".to_string(), Value::Bool(true))]),
434        );
435        // STRUCT_FIELD_EQ does not include attributes !
436        assert!(STRUCT_FIELD_EQ.compare(&schema_one, &schema_two));
437    }
438
439    #[test]
440    fn test_avro_3939_compare_schemata_including_attributes() {
441        let struct_field_eq = StructFieldEq {
442            include_attributes: true,
443        };
444        let schema_one = Schema::map_with_attributes(
445            Schema::Boolean,
446            BTreeMap::from_iter([("key1".to_string(), Value::Bool(true))]),
447        );
448        let schema_two = Schema::map_with_attributes(
449            Schema::Boolean,
450            BTreeMap::from_iter([("key2".to_string(), Value::Bool(true))]),
451        );
452        assert!(!struct_field_eq.compare(&schema_one, &schema_two));
453    }
454
455    #[test]
456    fn test_avro_3939_compare_map_schemata() {
457        let schema_one = Schema::map(Schema::Boolean);
458        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
459        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
460
461        let schema_two = Schema::map(Schema::Boolean);
462
463        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
464        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
465        assert!(
466            specification_eq_res,
467            "SpecificationEq: Equality of two Schema::Map failed!"
468        );
469        assert!(
470            struct_field_eq_res,
471            "StructFieldEq: Equality of two Schema::Map failed!"
472        );
473        assert_eq!(specification_eq_res, struct_field_eq_res);
474    }
475
476    #[test]
477    fn test_avro_3939_compare_array_schemata() {
478        let schema_one = Schema::array(Schema::Boolean);
479        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
480        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
481
482        let schema_two = Schema::array(Schema::Boolean);
483
484        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
485        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
486        assert!(
487            specification_eq_res,
488            "SpecificationEq: Equality of two Schema::Array failed!"
489        );
490        assert!(
491            struct_field_eq_res,
492            "StructFieldEq: Equality of two Schema::Array failed!"
493        );
494        assert_eq!(specification_eq_res, struct_field_eq_res);
495    }
496
497    #[test]
498    fn test_avro_3939_compare_decimal_schemata() {
499        let schema_one = Schema::Decimal(DecimalSchema {
500            precision: 10,
501            scale: 2,
502            inner: InnerDecimalSchema::Bytes,
503        });
504        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
505        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
506
507        let schema_two = Schema::Decimal(DecimalSchema {
508            precision: 10,
509            scale: 2,
510            inner: InnerDecimalSchema::Bytes,
511        });
512
513        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
514        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
515        assert!(
516            specification_eq_res,
517            "SpecificationEq: Equality of two Schema::Decimal failed!"
518        );
519        assert!(
520            struct_field_eq_res,
521            "StructFieldEq: Equality of two Schema::Decimal failed!"
522        );
523        assert_eq!(specification_eq_res, struct_field_eq_res);
524    }
525
526    #[test]
527    fn test_avro_3939_compare_fixed_schemata() {
528        let schema_one = Schema::Fixed(FixedSchema {
529            name: Name::try_from("fixed").expect("Name is valid"),
530            doc: None,
531            size: 10,
532            aliases: None,
533            attributes: BTreeMap::new(),
534        });
535        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
536        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
537
538        let schema_two = Schema::Fixed(FixedSchema {
539            name: Name::try_from("fixed").expect("Name is valid"),
540            doc: None,
541            size: 10,
542            aliases: None,
543            attributes: BTreeMap::new(),
544        });
545
546        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
547        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
548        assert!(
549            specification_eq_res,
550            "SpecificationEq: Equality of two Schema::Fixed failed!"
551        );
552        assert!(
553            struct_field_eq_res,
554            "StructFieldEq: Equality of two Schema::Fixed failed!"
555        );
556        assert_eq!(specification_eq_res, struct_field_eq_res);
557    }
558
559    #[test]
560    fn test_avro_3939_compare_enum_schemata() {
561        let schema_one = Schema::Enum(EnumSchema {
562            name: Name::try_from("enum").expect("Name is valid"),
563            doc: None,
564            symbols: vec!["A".to_string(), "B".to_string()],
565            default: None,
566            aliases: None,
567            attributes: BTreeMap::new(),
568        });
569        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
570        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
571
572        let schema_two = Schema::Enum(EnumSchema {
573            name: Name::try_from("enum").expect("Name is valid"),
574            doc: None,
575            symbols: vec!["A".to_string(), "B".to_string()],
576            default: None,
577            aliases: None,
578            attributes: BTreeMap::new(),
579        });
580
581        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
582        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
583        assert!(
584            specification_eq_res,
585            "SpecificationEq: Equality of two Schema::Enum failed!"
586        );
587        assert!(
588            struct_field_eq_res,
589            "StructFieldEq: Equality of two Schema::Enum failed!"
590        );
591        assert_eq!(specification_eq_res, struct_field_eq_res);
592    }
593
594    #[test]
595    fn test_avro_3939_compare_ref_schemata() {
596        let schema_one = Schema::Ref {
597            name: Name::try_from("ref").expect("Name is valid"),
598        };
599        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
600        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
601
602        let schema_two = Schema::Ref {
603            name: Name::try_from("ref").expect("Name is valid"),
604        };
605
606        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
607        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
608        assert!(
609            specification_eq_res,
610            "SpecificationEq: Equality of two Schema::Ref failed!"
611        );
612        assert!(
613            struct_field_eq_res,
614            "StructFieldEq: Equality of two Schema::Ref failed!"
615        );
616        assert_eq!(specification_eq_res, struct_field_eq_res);
617    }
618
619    #[test]
620    fn test_avro_3939_compare_record_schemata() {
621        let schema_one = Schema::Record(RecordSchema {
622            name: Name::try_from("record").expect("Name is valid"),
623            doc: None,
624            fields: vec![RecordField {
625                name: "field".to_string(),
626                doc: None,
627                default: None,
628                schema: Schema::Boolean,
629                order: RecordFieldOrder::Ignore,
630                aliases: None,
631                custom_attributes: BTreeMap::new(),
632                position: 0,
633            }],
634            aliases: None,
635            attributes: BTreeMap::new(),
636            lookup: Default::default(),
637        });
638        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
639        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
640
641        let schema_two = Schema::Record(RecordSchema {
642            name: Name::try_from("record").expect("Name is valid"),
643            doc: None,
644            fields: vec![RecordField {
645                name: "field".to_string(),
646                doc: None,
647                default: None,
648                schema: Schema::Boolean,
649                order: RecordFieldOrder::Ignore,
650                aliases: None,
651                custom_attributes: BTreeMap::new(),
652                position: 0,
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}