Skip to main content

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    use std::num::NonZero;
271
272    const SPECIFICATION_EQ: SpecificationEq = SpecificationEq;
273    const STRUCT_FIELD_EQ: StructFieldEq = StructFieldEq {
274        include_attributes: false,
275    };
276    const STRUCT_FIELD_EQ_WITH_ATTRS: StructFieldEq = StructFieldEq {
277        include_attributes: true,
278    };
279
280    macro_rules! test_primitives {
281        ($primitive:ident) => {
282            paste::item! {
283                #[test]
284                fn [<test_avro_3939_compare_schemata_$primitive>]() {
285                    let specification_eq_res = SPECIFICATION_EQ.compare(&Schema::$primitive, &Schema::$primitive);
286                    let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&Schema::$primitive, &Schema::$primitive);
287                    assert_eq!(specification_eq_res, struct_field_eq_res)
288                }
289            }
290        };
291    }
292
293    test_primitives!(Null);
294    test_primitives!(Boolean);
295    test_primitives!(Int);
296    test_primitives!(Long);
297    test_primitives!(Float);
298    test_primitives!(Double);
299    test_primitives!(Bytes);
300    test_primitives!(String);
301    test_primitives!(BigDecimal);
302    test_primitives!(Date);
303    test_primitives!(TimeMicros);
304    test_primitives!(TimeMillis);
305    test_primitives!(TimestampMicros);
306    test_primitives!(TimestampMillis);
307    test_primitives!(TimestampNanos);
308    test_primitives!(LocalTimestampMicros);
309    test_primitives!(LocalTimestampMillis);
310    test_primitives!(LocalTimestampNanos);
311
312    #[test]
313    fn avro_rs_382_compare_schemata_duration_equal() {
314        let schema_one = Schema::Duration(FixedSchema {
315            name: Name::try_from("name1").expect("Name is valid"),
316            size: 12,
317            aliases: None,
318            doc: None,
319            attributes: BTreeMap::new(),
320        });
321        let schema_two = Schema::Duration(FixedSchema {
322            name: Name::try_from("name1").expect("Name is valid"),
323            size: 12,
324            aliases: None,
325            doc: None,
326            attributes: BTreeMap::new(),
327        });
328        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
329        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
330        assert_eq!(specification_eq_res, struct_field_eq_res);
331    }
332
333    #[test]
334    fn avro_rs_382_compare_schemata_duration_different_names() {
335        let schema_one = Schema::Duration(FixedSchema {
336            name: Name::try_from("name1").expect("Name is valid"),
337            size: 12,
338            aliases: None,
339            doc: None,
340            attributes: BTreeMap::new(),
341        });
342        let schema_two = Schema::Duration(FixedSchema {
343            name: Name::try_from("name2").expect("Name is valid"),
344            size: 12,
345            aliases: None,
346            doc: None,
347            attributes: BTreeMap::new(),
348        });
349        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
350        assert!(!specification_eq_res);
351
352        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
353        assert!(!struct_field_eq_res);
354    }
355
356    #[test]
357    fn avro_rs_382_compare_schemata_duration_different_attributes() {
358        let schema_one = Schema::Duration(FixedSchema {
359            name: Name::try_from("name1").expect("Name is valid"),
360            size: 12,
361            aliases: None,
362            doc: None,
363            attributes: vec![(String::from("attr1"), serde_json::Value::Bool(true))]
364                .into_iter()
365                .collect(),
366        });
367        let schema_two = Schema::Duration(FixedSchema {
368            name: Name::try_from("name1").expect("Name is valid"),
369            size: 12,
370            aliases: None,
371            doc: None,
372            attributes: BTreeMap::new(),
373        });
374        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
375        assert!(specification_eq_res);
376
377        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
378        assert!(struct_field_eq_res);
379
380        let struct_field_eq_with_attrs_res =
381            STRUCT_FIELD_EQ_WITH_ATTRS.compare(&schema_one, &schema_two);
382        assert!(!struct_field_eq_with_attrs_res);
383    }
384
385    #[test]
386    fn avro_rs_382_compare_schemata_duration_different_sizes() {
387        let schema_one = Schema::Duration(FixedSchema {
388            name: Name::try_from("name1").expect("Name is valid"),
389            size: 8,
390            aliases: None,
391            doc: None,
392            attributes: BTreeMap::new(),
393        });
394        let schema_two = Schema::Duration(FixedSchema {
395            name: Name::try_from("name1").expect("Name is valid"),
396            size: 12,
397            aliases: None,
398            doc: None,
399            attributes: BTreeMap::new(),
400        });
401        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
402        assert!(!specification_eq_res);
403
404        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
405        assert!(!struct_field_eq_res);
406    }
407
408    #[test]
409    fn test_avro_3939_compare_named_schemata_with_different_names() {
410        let schema_one = Schema::Ref {
411            name: Name::try_from("name1").expect("Name is valid"),
412        };
413
414        let schema_two = Schema::Ref {
415            name: Name::try_from("name2").expect("Name is valid"),
416        };
417
418        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
419        assert!(!specification_eq_res);
420        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
421        assert!(!struct_field_eq_res);
422
423        assert_eq!(specification_eq_res, struct_field_eq_res);
424    }
425
426    #[test]
427    fn test_avro_3939_compare_schemata_not_including_attributes() {
428        let schema_one = Schema::map(Schema::Boolean)
429            .attributes(BTreeMap::from_iter([(
430                "key1".to_string(),
431                Value::Bool(true),
432            )]))
433            .build();
434        let schema_two = Schema::map(Schema::Boolean)
435            .attributes(BTreeMap::from_iter([(
436                "key2".to_string(),
437                Value::Bool(true),
438            )]))
439            .build();
440        // STRUCT_FIELD_EQ does not include attributes !
441        assert!(STRUCT_FIELD_EQ.compare(&schema_one, &schema_two));
442    }
443
444    #[test]
445    fn test_avro_3939_compare_schemata_including_attributes() {
446        let struct_field_eq = StructFieldEq {
447            include_attributes: true,
448        };
449        let schema_one = Schema::map(Schema::Boolean)
450            .attributes(BTreeMap::from_iter([(
451                "key1".to_string(),
452                Value::Bool(true),
453            )]))
454            .build();
455        let schema_two = Schema::map(Schema::Boolean)
456            .attributes(BTreeMap::from_iter([(
457                "key2".to_string(),
458                Value::Bool(true),
459            )]))
460            .build();
461        assert!(!struct_field_eq.compare(&schema_one, &schema_two));
462    }
463
464    #[test]
465    fn test_avro_3939_compare_map_schemata() {
466        let schema_one = Schema::map(Schema::Boolean).build();
467        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
468        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
469
470        let schema_two = Schema::map(Schema::Boolean).build();
471
472        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
473        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
474        assert!(
475            specification_eq_res,
476            "SpecificationEq: Equality of two Schema::Map failed!"
477        );
478        assert!(
479            struct_field_eq_res,
480            "StructFieldEq: Equality of two Schema::Map failed!"
481        );
482        assert_eq!(specification_eq_res, struct_field_eq_res);
483    }
484
485    #[test]
486    fn test_avro_3939_compare_array_schemata() {
487        let schema_one = Schema::array(Schema::Boolean).build();
488        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
489        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
490
491        let schema_two = Schema::array(Schema::Boolean).build();
492
493        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
494        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
495        assert!(
496            specification_eq_res,
497            "SpecificationEq: Equality of two Schema::Array failed!"
498        );
499        assert!(
500            struct_field_eq_res,
501            "StructFieldEq: Equality of two Schema::Array failed!"
502        );
503        assert_eq!(specification_eq_res, struct_field_eq_res);
504    }
505
506    #[test]
507    fn test_avro_3939_compare_decimal_schemata() {
508        let schema_one = Schema::Decimal(DecimalSchema {
509            precision: NonZero::new(10).unwrap(),
510            scale: 2,
511            inner: InnerDecimalSchema::Bytes,
512        });
513        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
514        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
515
516        let schema_two = Schema::Decimal(DecimalSchema {
517            precision: NonZero::new(10).unwrap(),
518            scale: 2,
519            inner: InnerDecimalSchema::Bytes,
520        });
521
522        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
523        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
524        assert!(
525            specification_eq_res,
526            "SpecificationEq: Equality of two Schema::Decimal failed!"
527        );
528        assert!(
529            struct_field_eq_res,
530            "StructFieldEq: Equality of two Schema::Decimal failed!"
531        );
532        assert_eq!(specification_eq_res, struct_field_eq_res);
533    }
534
535    #[test]
536    fn test_avro_3939_compare_fixed_schemata() {
537        let schema_one = Schema::Fixed(FixedSchema {
538            name: Name::try_from("fixed").expect("Name is valid"),
539            doc: None,
540            size: 10,
541            aliases: None,
542            attributes: BTreeMap::new(),
543        });
544        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
545        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
546
547        let schema_two = Schema::Fixed(FixedSchema {
548            name: Name::try_from("fixed").expect("Name is valid"),
549            doc: None,
550            size: 10,
551            aliases: None,
552            attributes: BTreeMap::new(),
553        });
554
555        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
556        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
557        assert!(
558            specification_eq_res,
559            "SpecificationEq: Equality of two Schema::Fixed failed!"
560        );
561        assert!(
562            struct_field_eq_res,
563            "StructFieldEq: Equality of two Schema::Fixed failed!"
564        );
565        assert_eq!(specification_eq_res, struct_field_eq_res);
566    }
567
568    #[test]
569    fn test_avro_3939_compare_enum_schemata() {
570        let schema_one = Schema::Enum(EnumSchema {
571            name: Name::try_from("enum").expect("Name is valid"),
572            doc: None,
573            symbols: vec!["A".to_string(), "B".to_string()],
574            default: None,
575            aliases: None,
576            attributes: BTreeMap::new(),
577        });
578        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
579        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
580
581        let schema_two = Schema::Enum(EnumSchema {
582            name: Name::try_from("enum").expect("Name is valid"),
583            doc: None,
584            symbols: vec!["A".to_string(), "B".to_string()],
585            default: None,
586            aliases: None,
587            attributes: BTreeMap::new(),
588        });
589
590        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
591        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
592        assert!(
593            specification_eq_res,
594            "SpecificationEq: Equality of two Schema::Enum failed!"
595        );
596        assert!(
597            struct_field_eq_res,
598            "StructFieldEq: Equality of two Schema::Enum failed!"
599        );
600        assert_eq!(specification_eq_res, struct_field_eq_res);
601    }
602
603    #[test]
604    fn test_avro_3939_compare_ref_schemata() {
605        let schema_one = Schema::Ref {
606            name: Name::try_from("ref").expect("Name is valid"),
607        };
608        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
609        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
610
611        let schema_two = Schema::Ref {
612            name: Name::try_from("ref").expect("Name is valid"),
613        };
614
615        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
616        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
617        assert!(
618            specification_eq_res,
619            "SpecificationEq: Equality of two Schema::Ref failed!"
620        );
621        assert!(
622            struct_field_eq_res,
623            "StructFieldEq: Equality of two Schema::Ref failed!"
624        );
625        assert_eq!(specification_eq_res, struct_field_eq_res);
626    }
627
628    #[test]
629    fn test_avro_3939_compare_record_schemata() {
630        let schema_one = Schema::Record(RecordSchema {
631            name: Name::try_from("record").expect("Name is valid"),
632            doc: None,
633            fields: vec![
634                RecordField::builder()
635                    .name("field".to_string())
636                    .schema(Schema::Boolean)
637                    .build(),
638            ],
639            aliases: None,
640            attributes: BTreeMap::new(),
641            lookup: Default::default(),
642        });
643        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
644        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
645
646        let schema_two = Schema::Record(RecordSchema {
647            name: Name::try_from("record").expect("Name is valid"),
648            doc: None,
649            fields: vec![
650                RecordField::builder()
651                    .name("field".to_string())
652                    .schema(Schema::Boolean)
653                    .build(),
654            ],
655            aliases: None,
656            attributes: BTreeMap::new(),
657            lookup: Default::default(),
658        });
659
660        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
661        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
662        assert!(
663            specification_eq_res,
664            "SpecificationEq: Equality of two Schema::Record failed!"
665        );
666        assert!(
667            struct_field_eq_res,
668            "StructFieldEq: Equality of two Schema::Record failed!"
669        );
670        assert_eq!(specification_eq_res, struct_field_eq_res);
671    }
672
673    #[test]
674    fn test_avro_3939_compare_union_schemata() -> TestResult {
675        let schema_one = Schema::Union(UnionSchema::new(vec![Schema::Boolean, Schema::Int])?);
676        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
677        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
678
679        let schema_two = Schema::Union(UnionSchema::new(vec![Schema::Boolean, Schema::Int])?);
680
681        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
682        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
683        assert!(
684            specification_eq_res,
685            "SpecificationEq: Equality of two Schema::Union failed!"
686        );
687        assert!(
688            struct_field_eq_res,
689            "StructFieldEq: Equality of two Schema::Union failed!"
690        );
691        assert_eq!(specification_eq_res, struct_field_eq_res);
692        Ok(())
693    }
694
695    #[test]
696    fn test_uuid_compare_uuid() -> TestResult {
697        let string = Schema::Uuid(UuidSchema::String);
698        let bytes = Schema::Uuid(UuidSchema::Bytes);
699        let mut fixed_schema = FixedSchema {
700            name: Name::new("some_name")?,
701            aliases: None,
702            doc: None,
703            size: 16,
704            attributes: Default::default(),
705        };
706        let fixed = Schema::Uuid(UuidSchema::Fixed(fixed_schema.clone()));
707        fixed_schema
708            .attributes
709            .insert("Something".to_string(), Value::Null);
710        let fixed_different = Schema::Uuid(UuidSchema::Fixed(fixed_schema));
711
712        assert!(SPECIFICATION_EQ.compare(&string, &string));
713        assert!(STRUCT_FIELD_EQ.compare(&string, &string));
714        assert!(SPECIFICATION_EQ.compare(&bytes, &bytes));
715        assert!(STRUCT_FIELD_EQ.compare(&bytes, &bytes));
716        assert!(SPECIFICATION_EQ.compare(&fixed, &fixed));
717        assert!(STRUCT_FIELD_EQ.compare(&fixed, &fixed));
718
719        assert!(!SPECIFICATION_EQ.compare(&string, &bytes));
720        assert!(!STRUCT_FIELD_EQ.compare(&string, &bytes));
721        assert!(!SPECIFICATION_EQ.compare(&bytes, &string));
722        assert!(!STRUCT_FIELD_EQ.compare(&bytes, &string));
723        assert!(!SPECIFICATION_EQ.compare(&string, &fixed));
724        assert!(!STRUCT_FIELD_EQ.compare(&string, &fixed));
725        assert!(!SPECIFICATION_EQ.compare(&fixed, &string));
726        assert!(!STRUCT_FIELD_EQ.compare(&fixed, &string));
727        assert!(!SPECIFICATION_EQ.compare(&bytes, &fixed));
728        assert!(!STRUCT_FIELD_EQ.compare(&bytes, &fixed));
729        assert!(!SPECIFICATION_EQ.compare(&fixed, &bytes));
730        assert!(!STRUCT_FIELD_EQ.compare(&fixed, &bytes));
731
732        assert!(SPECIFICATION_EQ.compare(&fixed, &fixed_different));
733        assert!(STRUCT_FIELD_EQ.compare(&fixed, &fixed_different));
734        assert!(SPECIFICATION_EQ.compare(&fixed_different, &fixed));
735        assert!(STRUCT_FIELD_EQ.compare(&fixed_different, &fixed));
736
737        let strict = StructFieldEq {
738            include_attributes: true,
739        };
740
741        assert!(!strict.compare(&fixed, &fixed_different));
742        assert!(!strict.compare(&fixed_different, &fixed));
743
744        Ok(())
745    }
746}