Skip to main content

apache_avro/schema/
mod.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//! Logic for parsing and interacting with schemas in Avro format.
19
20mod builders;
21mod name;
22mod parser;
23mod record;
24mod resolve;
25mod union;
26
27pub(crate) use crate::schema::resolve::{
28    ResolvedOwnedSchema, resolve_names, resolve_names_with_schemata,
29};
30pub use crate::schema::{
31    name::{Alias, Aliases, Name, Names, NamesRef, Namespace, NamespaceRef},
32    record::{RecordField, RecordFieldBuilder, RecordSchema, RecordSchemaBuilder},
33    resolve::ResolvedSchema,
34    union::{UnionSchema, UnionSchemaBuilder},
35};
36use crate::{
37    AvroResult,
38    error::{Details, Error},
39    schema::parser::Parser,
40    schema_equality, types,
41};
42use digest::Digest;
43use serde::{
44    Serialize, Serializer,
45    ser::{SerializeMap, SerializeSeq},
46};
47use serde_json::{Map, Value as JsonValue};
48use std::borrow::Cow;
49use std::fmt::Formatter;
50use std::{
51    collections::{BTreeMap, HashMap, HashSet},
52    fmt,
53    fmt::Debug,
54    hash::Hash,
55    io::Read,
56};
57use strum::{Display, EnumDiscriminants};
58
59/// Represents documentation for complex Avro schemas.
60pub type Documentation = Option<String>;
61
62/// Represents an Avro schema fingerprint.
63///
64/// More information about Avro schema fingerprints can be found in the
65/// [Avro Schema Fingerprint documentation](https://avro.apache.org/docs/++version++/specification/#schema-fingerprints)
66pub struct SchemaFingerprint {
67    pub bytes: Vec<u8>,
68}
69
70impl fmt::Display for SchemaFingerprint {
71    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72        write!(
73            f,
74            "{}",
75            self.bytes
76                .iter()
77                .map(|byte| format!("{byte:02x}"))
78                .collect::<Vec<String>>()
79                .join("")
80        )
81    }
82}
83
84/// Represents any valid Avro schema
85/// More information about Avro schemas can be found in the
86/// [Avro Specification](https://avro.apache.org/docs/++version++/specification/#schema-declaration)
87#[derive(Clone, Debug, EnumDiscriminants, Display)]
88#[strum_discriminants(name(SchemaKind), derive(Hash, Ord, PartialOrd))]
89pub enum Schema {
90    /// A `null` Avro schema.
91    Null,
92    /// A `boolean` Avro schema.
93    Boolean,
94    /// An `int` Avro schema.
95    Int,
96    /// A `long` Avro schema.
97    Long,
98    /// A `float` Avro schema.
99    Float,
100    /// A `double` Avro schema.
101    Double,
102    /// A `bytes` Avro schema.
103    ///
104    /// `Bytes` represents a sequence of 8-bit unsigned bytes.
105    Bytes,
106    /// A `string` Avro schema.
107    ///
108    /// `String` represents a unicode character sequence.
109    String,
110    /// An `array` Avro schema.
111    ///
112    /// All items will have the same schema.
113    Array(ArraySchema),
114    /// A `map` Avro schema.
115    ///
116    /// Keys are always a `Schema::String` and all values will have the same schema.
117    Map(MapSchema),
118    /// A `union` Avro schema.
119    Union(UnionSchema),
120    /// A `record` Avro schema.
121    Record(RecordSchema),
122    /// An `enum` Avro schema.
123    Enum(EnumSchema),
124    /// A `fixed` Avro schema.
125    Fixed(FixedSchema),
126    /// Logical type which represents `Decimal` values.
127    ///
128    /// The underlying type is serialized and deserialized as `Schema::Bytes` or `Schema::Fixed`.
129    Decimal(DecimalSchema),
130    /// Logical type which represents `Decimal` values without predefined scale.
131    ///
132    /// The underlying type is serialized and deserialized as `Schema::Bytes`
133    BigDecimal,
134    /// A universally unique identifier, annotating a string, bytes or fixed.
135    Uuid(UuidSchema),
136    /// Logical type which represents the number of days since the unix epoch.
137    ///
138    /// Serialization format is `Schema::Int`.
139    Date,
140    /// The time of day in number of milliseconds after midnight.
141    ///
142    /// This type has no reference to any calendar, time zone or date in particular.
143    TimeMillis,
144    /// The time of day in number of microseconds after midnight.
145    ///
146    /// This type has no reference to any calendar, time zone or date in particular.
147    TimeMicros,
148    /// An instant in time represented as the number of milliseconds after the UNIX epoch.
149    TimestampMillis,
150    /// An instant in time represented as the number of microseconds after the UNIX epoch.
151    TimestampMicros,
152    /// An instant in time represented as the number of nanoseconds after the UNIX epoch.
153    TimestampNanos,
154    /// An instant in localtime represented as the number of milliseconds after the UNIX epoch.
155    LocalTimestampMillis,
156    /// An instant in local time represented as the number of microseconds after the UNIX epoch.
157    LocalTimestampMicros,
158    /// An instant in local time represented as the number of nanoseconds after the UNIX epoch.
159    LocalTimestampNanos,
160    /// An amount of time defined by a number of months, days and milliseconds.
161    Duration(FixedSchema),
162    /// A reference to another schema.
163    Ref { name: Name },
164}
165
166#[derive(Clone, PartialEq)]
167pub struct MapSchema {
168    pub types: Box<Schema>,
169    pub attributes: BTreeMap<String, JsonValue>,
170}
171
172impl Debug for MapSchema {
173    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
174        let mut debug = f.debug_struct("MapSchema");
175        debug.field("types", &self.types);
176        if !self.attributes.is_empty() {
177            debug.field("attributes", &self.attributes);
178        }
179        if self.attributes.is_empty() {
180            debug.finish_non_exhaustive()
181        } else {
182            debug.finish()
183        }
184    }
185}
186
187#[derive(Clone, PartialEq)]
188pub struct ArraySchema {
189    pub items: Box<Schema>,
190    pub attributes: BTreeMap<String, JsonValue>,
191}
192
193impl Debug for ArraySchema {
194    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
195        let mut debug = f.debug_struct("ArraySchema");
196        debug.field("items", &self.items);
197        if !self.attributes.is_empty() {
198            debug.field("attributes", &self.attributes);
199        }
200        if self.attributes.is_empty() {
201            debug.finish_non_exhaustive()
202        } else {
203            debug.finish()
204        }
205    }
206}
207
208impl PartialEq for Schema {
209    /// Assess equality of two `Schema` based on [Parsing Canonical Form].
210    ///
211    /// [Parsing Canonical Form]:
212    /// https://avro.apache.org/docs/1.11.1/specification/#parsing-canonical-form-for-schemas
213    fn eq(&self, other: &Self) -> bool {
214        schema_equality::compare_schemata(self, other)
215    }
216}
217
218impl SchemaKind {
219    pub fn is_primitive(self) -> bool {
220        matches!(
221            self,
222            SchemaKind::Null
223                | SchemaKind::Boolean
224                | SchemaKind::Int
225                | SchemaKind::Long
226                | SchemaKind::Double
227                | SchemaKind::Float
228                | SchemaKind::Bytes
229                | SchemaKind::String,
230        )
231    }
232
233    #[deprecated(since = "0.22.0", note = "Use Schema::is_named instead")]
234    pub fn is_named(self) -> bool {
235        matches!(
236            self,
237            SchemaKind::Record
238                | SchemaKind::Enum
239                | SchemaKind::Fixed
240                | SchemaKind::Ref
241                | SchemaKind::Duration
242        )
243    }
244}
245
246impl From<&types::Value> for SchemaKind {
247    fn from(value: &types::Value) -> Self {
248        use crate::types::Value;
249        match value {
250            Value::Null => Self::Null,
251            Value::Boolean(_) => Self::Boolean,
252            Value::Int(_) => Self::Int,
253            Value::Long(_) => Self::Long,
254            Value::Float(_) => Self::Float,
255            Value::Double(_) => Self::Double,
256            Value::Bytes(_) => Self::Bytes,
257            Value::String(_) => Self::String,
258            Value::Array(_) => Self::Array,
259            Value::Map(_) => Self::Map,
260            Value::Union(_, _) => Self::Union,
261            Value::Record(_) => Self::Record,
262            Value::Enum(_, _) => Self::Enum,
263            Value::Fixed(_, _) => Self::Fixed,
264            Value::Decimal { .. } => Self::Decimal,
265            Value::BigDecimal(_) => Self::BigDecimal,
266            Value::Uuid(_) => Self::Uuid,
267            Value::Date(_) => Self::Date,
268            Value::TimeMillis(_) => Self::TimeMillis,
269            Value::TimeMicros(_) => Self::TimeMicros,
270            Value::TimestampMillis(_) => Self::TimestampMillis,
271            Value::TimestampMicros(_) => Self::TimestampMicros,
272            Value::TimestampNanos(_) => Self::TimestampNanos,
273            Value::LocalTimestampMillis(_) => Self::LocalTimestampMillis,
274            Value::LocalTimestampMicros(_) => Self::LocalTimestampMicros,
275            Value::LocalTimestampNanos(_) => Self::LocalTimestampNanos,
276            Value::Duration { .. } => Self::Duration,
277        }
278    }
279}
280
281/// A description of an Enum schema.
282#[derive(bon::Builder, Clone)]
283pub struct EnumSchema {
284    /// The name of the schema
285    pub name: Name,
286    /// The aliases of the schema
287    #[builder(default)]
288    pub aliases: Aliases,
289    /// The documentation of the schema
290    #[builder(default)]
291    pub doc: Documentation,
292    /// The set of symbols of the schema
293    pub symbols: Vec<String>,
294    /// An optional default symbol used for compatibility
295    pub default: Option<String>,
296    /// The custom attributes of the schema
297    #[builder(default = BTreeMap::new())]
298    pub attributes: BTreeMap<String, JsonValue>,
299}
300
301impl Debug for EnumSchema {
302    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
303        let mut debug = f.debug_struct("EnumSchema");
304        debug.field("name", &self.name);
305        if let Some(aliases) = &self.aliases {
306            debug.field("aliases", aliases);
307        }
308        if let Some(doc) = &self.doc {
309            debug.field("doc", doc);
310        }
311        debug.field("symbols", &self.symbols);
312        if let Some(default) = &self.default {
313            debug.field("default", default);
314        }
315        if !self.attributes.is_empty() {
316            debug.field("attributes", &self.attributes);
317        }
318        if self.aliases.is_none()
319            || self.doc.is_none()
320            || self.default.is_none()
321            || self.attributes.is_empty()
322        {
323            debug.finish_non_exhaustive()
324        } else {
325            debug.finish()
326        }
327    }
328}
329
330/// A description of a Fixed schema.
331#[derive(bon::Builder, Clone)]
332pub struct FixedSchema {
333    /// The name of the schema
334    pub name: Name,
335    /// The aliases of the schema
336    #[builder(default)]
337    pub aliases: Aliases,
338    /// The documentation of the schema
339    #[builder(default)]
340    pub doc: Documentation,
341    /// The size of the fixed schema
342    pub size: usize,
343    /// The custom attributes of the schema
344    #[builder(default = BTreeMap::new())]
345    pub attributes: BTreeMap<String, JsonValue>,
346}
347
348impl Debug for FixedSchema {
349    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
350        let mut debug = f.debug_struct("FixedSchema");
351        debug.field("name", &self.name);
352        if let Some(aliases) = &self.aliases {
353            debug.field("aliases", aliases);
354        }
355        if let Some(doc) = &self.doc {
356            debug.field("doc", doc);
357        }
358        debug.field("size", &self.size);
359        if !self.attributes.is_empty() {
360            debug.field("attributes", &self.attributes);
361        }
362        if self.aliases.is_none() || self.doc.is_none() || !self.attributes.is_empty() {
363            debug.finish_non_exhaustive()
364        } else {
365            debug.finish()
366        }
367    }
368}
369
370impl FixedSchema {
371    fn serialize_to_map<S>(&self, mut map: S::SerializeMap) -> Result<S::SerializeMap, S::Error>
372    where
373        S: Serializer,
374    {
375        map.serialize_entry("type", "fixed")?;
376        if let Some(n) = self.name.namespace() {
377            map.serialize_entry("namespace", n)?;
378        }
379        map.serialize_entry("name", &self.name.name())?;
380        if let Some(docstr) = self.doc.as_ref() {
381            map.serialize_entry("doc", docstr)?;
382        }
383        map.serialize_entry("size", &self.size)?;
384
385        if let Some(aliases) = self.aliases.as_ref() {
386            map.serialize_entry("aliases", aliases)?;
387        }
388
389        for attr in &self.attributes {
390            map.serialize_entry(attr.0, attr.1)?;
391        }
392
393        Ok(map)
394    }
395
396    /// Create a new `FixedSchema` copying only the size.
397    ///
398    /// All other fields are `None` or empty.
399    pub(crate) fn copy_only_size(&self) -> Self {
400        Self {
401            name: Name::invalid_empty_name(),
402            aliases: None,
403            doc: None,
404            size: self.size,
405            attributes: Default::default(),
406        }
407    }
408}
409
410/// A description of a Decimal schema.
411///
412/// `scale` defaults to 0 and is an integer greater than or equal to 0 and `precision` is an
413/// integer greater than 0.
414#[derive(Debug, Clone)]
415pub struct DecimalSchema {
416    /// The number of digits in the unscaled value
417    pub precision: Precision,
418    /// The number of digits to the right of the decimal point
419    pub scale: Scale,
420    /// The inner schema of the decimal (fixed or bytes)
421    pub inner: InnerDecimalSchema,
422}
423
424/// The inner schema of the Decimal type.
425#[derive(Debug, Clone)]
426pub enum InnerDecimalSchema {
427    Bytes,
428    Fixed(FixedSchema),
429}
430
431impl TryFrom<Schema> for InnerDecimalSchema {
432    type Error = Error;
433
434    fn try_from(value: Schema) -> Result<Self, Self::Error> {
435        match value {
436            Schema::Bytes => Ok(InnerDecimalSchema::Bytes),
437            Schema::Fixed(fixed) => Ok(InnerDecimalSchema::Fixed(fixed)),
438            _ => Err(Details::ResolveDecimalSchema(value.into()).into()),
439        }
440    }
441}
442
443/// The inner schema of the Uuid type.
444#[derive(Debug, Clone)]
445pub enum UuidSchema {
446    /// [`Schema::Bytes`] with size of 16.
447    ///
448    /// This is not according to specification, but was what happened in `0.21.0` and earlier when
449    /// a schema with logical type `uuid` and inner type `fixed` was used.
450    Bytes,
451    /// [`Schema::String`].
452    String,
453    /// [`Schema::Fixed`] with size of 16.
454    Fixed(FixedSchema),
455}
456
457type DecimalMetadata = usize;
458pub(crate) type Precision = DecimalMetadata;
459pub(crate) type Scale = DecimalMetadata;
460
461impl Schema {
462    /// Converts `self` into its [Parsing Canonical Form].
463    ///
464    /// [Parsing Canonical Form]:
465    /// https://avro.apache.org/docs/++version++/specification/#parsing-canonical-form-for-schemas
466    pub fn canonical_form(&self) -> String {
467        let json = serde_json::to_value(self).unwrap_or_else(|e| {
468            unreachable!("Cannot parse Schema from JSON that was just generated: {e}")
469        });
470        let mut defined_names = HashSet::new();
471        parsing_canonical_form(&json, &mut defined_names)
472    }
473
474    /// Returns the [Parsing Canonical Form] of `self` that is self contained (not dependent on
475    /// any definitions in `schemata`)
476    ///
477    /// If you require a self contained schema including `default` and `doc` attributes, see [`denormalize`][Schema::denormalize].
478    ///
479    /// [Parsing Canonical Form]:
480    /// https://avro.apache.org/docs/++version++/specification/#parsing-canonical-form-for-schemas
481    pub fn independent_canonical_form(&self, schemata: &[Schema]) -> Result<String, Error> {
482        let mut this = self.clone();
483        this.denormalize(schemata)?;
484        Ok(this.canonical_form())
485    }
486
487    /// Generate the [fingerprint] of the schema's [Parsing Canonical Form].
488    ///
489    /// # Example
490    /// ```
491    /// use apache_avro::rabin::Rabin;
492    /// use apache_avro::{Schema, Error};
493    /// use md5::Md5;
494    /// use sha2::Sha256;
495    ///
496    /// fn main() -> Result<(), Error> {
497    ///     let raw_schema = r#"
498    ///         {
499    ///             "type": "record",
500    ///             "name": "test",
501    ///             "fields": [
502    ///                 {"name": "a", "type": "long", "default": 42},
503    ///                 {"name": "b", "type": "string"}
504    ///             ]
505    ///         }
506    ///     "#;
507    ///     let schema = Schema::parse_str(raw_schema)?;
508    ///     println!("{}", schema.fingerprint::<Sha256>());
509    ///     println!("{}", schema.fingerprint::<Md5>());
510    ///     println!("{}", schema.fingerprint::<Rabin>());
511    ///     Ok(())
512    /// }
513    /// ```
514    ///
515    /// [Parsing Canonical Form]:
516    /// https://avro.apache.org/docs/++version++/specification/#parsing-canonical-form-for-schemas
517    /// [fingerprint]:
518    /// https://avro.apache.org/docs/++version++/specification/#schema-fingerprints
519    pub fn fingerprint<D: Digest>(&self) -> SchemaFingerprint {
520        let mut d = D::new();
521        d.update(self.canonical_form());
522        SchemaFingerprint {
523            bytes: d.finalize().to_vec(),
524        }
525    }
526
527    /// Create a `Schema` from a string representing a JSON Avro schema.
528    pub fn parse_str(input: &str) -> Result<Schema, Error> {
529        let mut parser = Parser::default();
530        parser.parse_str(input)
531    }
532
533    /// Create an array of `Schema`'s from a list of named JSON Avro schemas (Record, Enum, and
534    /// Fixed).
535    ///
536    /// It is allowed that the schemas have cross-dependencies; these will be resolved
537    /// during parsing.
538    ///
539    /// If two of the input schemas have the same fullname, an Error will be returned.
540    pub fn parse_list(input: impl IntoIterator<Item = impl AsRef<str>>) -> AvroResult<Vec<Schema>> {
541        let input = input.into_iter();
542        let input_len = input.size_hint().0;
543        let mut input_schemas: HashMap<Name, JsonValue> = HashMap::with_capacity(input_len);
544        let mut input_order: Vec<Name> = Vec::with_capacity(input_len);
545        for json in input {
546            let json = json.as_ref();
547            let schema: JsonValue = serde_json::from_str(json).map_err(Details::ParseSchemaJson)?;
548            if let JsonValue::Object(inner) = &schema {
549                let name = Name::parse(inner, None)?;
550                let previous_value = input_schemas.insert(name.clone(), schema);
551                if previous_value.is_some() {
552                    return Err(Details::NameCollision(name.fullname(None)).into());
553                }
554                input_order.push(name);
555            } else {
556                return Err(Details::GetNameField.into());
557            }
558        }
559        let mut parser = Parser::new(
560            input_schemas,
561            input_order,
562            HashMap::with_capacity(input_len),
563        );
564        parser.parse_list()
565    }
566
567    /// Create a `Schema` from a string representing a JSON Avro schema,
568    /// along with an array of `Schema`'s from a list of named JSON Avro schemas (Record, Enum, and
569    /// Fixed).
570    ///
571    /// It is allowed that the schemas have cross-dependencies; these will be resolved
572    /// during parsing.
573    ///
574    /// If two of the named input schemas have the same fullname, an Error will be returned.
575    ///
576    /// # Arguments
577    /// * `schema` - the JSON string of the schema to parse
578    /// * `schemata` - a slice of additional schemas that is used to resolve cross-references
579    pub fn parse_str_with_list(
580        schema: &str,
581        schemata: impl IntoIterator<Item = impl AsRef<str>>,
582    ) -> AvroResult<(Schema, Vec<Schema>)> {
583        let schemata = schemata.into_iter();
584        let schemata_len = schemata.size_hint().0;
585        let mut input_schemas: HashMap<Name, JsonValue> = HashMap::with_capacity(schemata_len);
586        let mut input_order: Vec<Name> = Vec::with_capacity(schemata_len);
587        for json in schemata {
588            let json = json.as_ref();
589            let schema: JsonValue = serde_json::from_str(json).map_err(Details::ParseSchemaJson)?;
590            if let JsonValue::Object(inner) = &schema {
591                let name = Name::parse(inner, None)?;
592                if let Some(_previous) = input_schemas.insert(name.clone(), schema) {
593                    return Err(Details::NameCollision(name.fullname(None)).into());
594                }
595                input_order.push(name);
596            } else {
597                return Err(Details::GetNameField.into());
598            }
599        }
600        let mut parser = Parser::new(
601            input_schemas,
602            input_order,
603            HashMap::with_capacity(schemata_len),
604        );
605        parser.parse_input_schemas()?;
606
607        let value = serde_json::from_str(schema).map_err(Details::ParseSchemaJson)?;
608        let schema = parser.parse(&value, None)?;
609        let schemata = parser.parse_list()?;
610        Ok((schema, schemata))
611    }
612
613    /// Create a `Schema` from a reader which implements [`Read`].
614    pub fn parse_reader(reader: &mut (impl Read + ?Sized)) -> AvroResult<Schema> {
615        let mut buf = String::new();
616        match reader.read_to_string(&mut buf) {
617            Ok(_) => Self::parse_str(&buf),
618            Err(e) => Err(Details::ReadSchemaFromReader(e).into()),
619        }
620    }
621
622    /// Parses an Avro schema from JSON.
623    pub fn parse(value: &JsonValue) -> AvroResult<Schema> {
624        let mut parser = Parser::default();
625        parser.parse(value, None)
626    }
627
628    /// Parses an Avro schema from JSON.
629    /// Any `Schema::Ref`s must be known in the `names` map.
630    pub(crate) fn parse_with_names(value: &JsonValue, names: Names) -> AvroResult<Schema> {
631        let mut parser = Parser::new(HashMap::with_capacity(1), Vec::with_capacity(1), names);
632        parser.parse(value, None)
633    }
634
635    /// Returns the custom attributes (metadata) if the schema supports them.
636    pub fn custom_attributes(&self) -> Option<&BTreeMap<String, JsonValue>> {
637        match self {
638            Schema::Record(RecordSchema { attributes, .. })
639            | Schema::Enum(EnumSchema { attributes, .. })
640            | Schema::Fixed(FixedSchema { attributes, .. })
641            | Schema::Array(ArraySchema { attributes, .. })
642            | Schema::Map(MapSchema { attributes, .. })
643            | Schema::Decimal(DecimalSchema {
644                inner: InnerDecimalSchema::Fixed(FixedSchema { attributes, .. }),
645                ..
646            })
647            | Schema::Uuid(UuidSchema::Fixed(FixedSchema { attributes, .. })) => Some(attributes),
648            Schema::Duration(FixedSchema { attributes, .. }) => Some(attributes),
649            _ => None,
650        }
651    }
652
653    /// Returns whether the schema represents a named type according to the avro specification
654    pub fn is_named(&self) -> bool {
655        matches!(
656            self,
657            Schema::Ref { .. }
658                | Schema::Record(_)
659                | Schema::Enum(_)
660                | Schema::Fixed(_)
661                | Schema::Decimal(DecimalSchema {
662                    inner: InnerDecimalSchema::Fixed(_),
663                    ..
664                })
665                | Schema::Uuid(UuidSchema::Fixed(_))
666                | Schema::Duration(_)
667        )
668    }
669
670    /// Returns the name of the schema if it has one.
671    pub fn name(&self) -> Option<&Name> {
672        match self {
673            Schema::Ref { name, .. }
674            | Schema::Record(RecordSchema { name, .. })
675            | Schema::Enum(EnumSchema { name, .. })
676            | Schema::Fixed(FixedSchema { name, .. })
677            | Schema::Decimal(DecimalSchema {
678                inner: InnerDecimalSchema::Fixed(FixedSchema { name, .. }),
679                ..
680            })
681            | Schema::Uuid(UuidSchema::Fixed(FixedSchema { name, .. }))
682            | Schema::Duration(FixedSchema { name, .. }) => Some(name),
683            _ => None,
684        }
685    }
686
687    /// Returns the namespace of the schema if it has one.
688    pub fn namespace(&self) -> NamespaceRef<'_> {
689        self.name().and_then(|n| n.namespace())
690    }
691
692    /// Returns the aliases of the schema if it has ones.
693    pub fn aliases(&self) -> Option<&Vec<Alias>> {
694        match self {
695            Schema::Record(RecordSchema { aliases, .. })
696            | Schema::Enum(EnumSchema { aliases, .. })
697            | Schema::Fixed(FixedSchema { aliases, .. })
698            | Schema::Decimal(DecimalSchema {
699                inner: InnerDecimalSchema::Fixed(FixedSchema { aliases, .. }),
700                ..
701            })
702            | Schema::Uuid(UuidSchema::Fixed(FixedSchema { aliases, .. })) => aliases.as_ref(),
703            Schema::Duration(FixedSchema { aliases, .. }) => aliases.as_ref(),
704            _ => None,
705        }
706    }
707
708    /// Returns the doc of the schema if it has one.
709    pub fn doc(&self) -> Option<&String> {
710        match self {
711            Schema::Record(RecordSchema { doc, .. })
712            | Schema::Enum(EnumSchema { doc, .. })
713            | Schema::Fixed(FixedSchema { doc, .. })
714            | Schema::Decimal(DecimalSchema {
715                inner: InnerDecimalSchema::Fixed(FixedSchema { doc, .. }),
716                ..
717            })
718            | Schema::Uuid(UuidSchema::Fixed(FixedSchema { doc, .. })) => doc.as_ref(),
719            Schema::Duration(FixedSchema { doc, .. }) => doc.as_ref(),
720            _ => None,
721        }
722    }
723
724    /// Remove all external references from the schema.
725    ///
726    /// `schemata` must contain all externally referenced schemas.
727    ///
728    /// # Errors
729    /// Will return a [`Details::SchemaResolutionError`] if it fails to find
730    /// a referenced schema. This will put the schema in a partly denormalized state.
731    pub fn denormalize(&mut self, schemata: &[Schema]) -> AvroResult<()> {
732        self.denormalize_inner(schemata, &mut HashSet::new())
733    }
734
735    fn denormalize_inner(
736        &mut self,
737        schemata: &[Schema],
738        defined_names: &mut HashSet<Name>,
739    ) -> AvroResult<()> {
740        // If this name already exists in this schema we can reference it.
741        // This makes the denormalized form as small as possible and prevent infinite loops for recursive types.
742        if let Some(name) = self.name()
743            && defined_names.contains(name)
744        {
745            *self = Schema::Ref { name: name.clone() };
746            return Ok(());
747        }
748        match self {
749            Schema::Ref { name } => {
750                let replacement_schema = schemata
751                    .iter()
752                    .find(|s| s.name().map(|n| *n == *name).unwrap_or(false));
753                if let Some(schema) = replacement_schema {
754                    let mut denorm = schema.clone();
755                    denorm.denormalize_inner(schemata, defined_names)?;
756                    *self = denorm;
757                } else {
758                    return Err(Details::SchemaResolutionError(name.clone()).into());
759                }
760            }
761            Schema::Record(record_schema) => {
762                defined_names.insert(record_schema.name.clone());
763                for field in &mut record_schema.fields {
764                    field.schema.denormalize_inner(schemata, defined_names)?;
765                }
766            }
767            Schema::Array(array_schema) => {
768                array_schema
769                    .items
770                    .denormalize_inner(schemata, defined_names)?;
771            }
772            Schema::Map(map_schema) => {
773                map_schema
774                    .types
775                    .denormalize_inner(schemata, defined_names)?;
776            }
777            Schema::Union(union_schema) => {
778                for schema in &mut union_schema.schemas {
779                    schema.denormalize_inner(schemata, defined_names)?;
780                }
781            }
782            schema if schema.is_named() => {
783                defined_names.insert(schema.name().expect("Schema is named").clone());
784            }
785            _ => (),
786        }
787        Ok(())
788    }
789
790    /// Derive a name for this schema.
791    ///
792    /// The name is a valid schema name and will be unique if the named
793    /// schemas in this schema have unique names.
794    pub(crate) fn unique_normalized_name(&self) -> Cow<'static, str> {
795        match self {
796            Schema::Null => Cow::Borrowed("n"),
797            Schema::Boolean => Cow::Borrowed("B"),
798            Schema::Int => Cow::Borrowed("i"),
799            Schema::Long => Cow::Borrowed("l"),
800            Schema::Float => Cow::Borrowed("f"),
801            Schema::Double => Cow::Borrowed("d"),
802            Schema::Bytes => Cow::Borrowed("b"),
803            Schema::String => Cow::Borrowed("s"),
804            Schema::Array(array) => {
805                Cow::Owned(format!("a_{}", array.items.unique_normalized_name()))
806            }
807            Schema::Map(map) => Cow::Owned(format!("m_{}", map.types.unique_normalized_name())),
808            Schema::Union(union) => {
809                let mut name = format!("u{}", union.schemas.len());
810                for schema in &union.schemas {
811                    name.push('_');
812                    name.push_str(&schema.unique_normalized_name());
813                }
814                Cow::Owned(name)
815            }
816            Schema::BigDecimal => Cow::Borrowed("bd"),
817            Schema::Date => Cow::Borrowed("D"),
818            Schema::TimeMillis => Cow::Borrowed("t"),
819            Schema::TimeMicros => Cow::Borrowed("tm"),
820            Schema::TimestampMillis => Cow::Borrowed("T"),
821            Schema::TimestampMicros => Cow::Borrowed("TM"),
822            Schema::TimestampNanos => Cow::Borrowed("TN"),
823            Schema::LocalTimestampMillis => Cow::Borrowed("L"),
824            Schema::LocalTimestampMicros => Cow::Borrowed("LM"),
825            Schema::LocalTimestampNanos => Cow::Borrowed("LN"),
826            Schema::Decimal(DecimalSchema {
827                inner: InnerDecimalSchema::Bytes,
828                precision,
829                scale,
830            }) => Cow::Owned(format!("db_{precision}_{scale}")),
831            Schema::Uuid(UuidSchema::Bytes) => Cow::Borrowed("ub"),
832            Schema::Uuid(UuidSchema::String) => Cow::Borrowed("us"),
833            Schema::Record(RecordSchema { name, .. })
834            | Schema::Enum(EnumSchema { name, .. })
835            | Schema::Fixed(FixedSchema { name, .. })
836            | Schema::Decimal(DecimalSchema {
837                inner: InnerDecimalSchema::Fixed(FixedSchema { name, .. }),
838                ..
839            })
840            | Schema::Uuid(UuidSchema::Fixed(FixedSchema { name, .. }))
841            | Schema::Duration(FixedSchema { name, .. })
842            | Schema::Ref { name } => {
843                let name: String = name
844                    .to_string()
845                    .replace('_', "__")
846                    .chars()
847                    .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
848                    .collect();
849                Cow::Owned(format!("r{}_{}", name.len(), name))
850            }
851        }
852    }
853}
854
855impl Serialize for Schema {
856    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
857    where
858        S: Serializer,
859    {
860        match &self {
861            Schema::Ref { name } => serializer.serialize_str(&name.fullname(None)),
862            Schema::Null => serializer.serialize_str("null"),
863            Schema::Boolean => serializer.serialize_str("boolean"),
864            Schema::Int => serializer.serialize_str("int"),
865            Schema::Long => serializer.serialize_str("long"),
866            Schema::Float => serializer.serialize_str("float"),
867            Schema::Double => serializer.serialize_str("double"),
868            Schema::Bytes => serializer.serialize_str("bytes"),
869            Schema::String => serializer.serialize_str("string"),
870            Schema::Array(ArraySchema { items, attributes }) => {
871                let mut map = serializer.serialize_map(Some(2 + attributes.len()))?;
872                map.serialize_entry("type", "array")?;
873                map.serialize_entry("items", items)?;
874                for (key, value) in attributes {
875                    map.serialize_entry(key, value)?;
876                }
877                map.end()
878            }
879            Schema::Map(MapSchema { types, attributes }) => {
880                let mut map = serializer.serialize_map(Some(2 + attributes.len()))?;
881                map.serialize_entry("type", "map")?;
882                map.serialize_entry("values", types)?;
883                for (key, value) in attributes {
884                    map.serialize_entry(key, value)?;
885                }
886                map.end()
887            }
888            Schema::Union(inner) => {
889                let variants = inner.variants();
890                let mut seq = serializer.serialize_seq(Some(variants.len()))?;
891                for v in variants {
892                    seq.serialize_element(v)?;
893                }
894                seq.end()
895            }
896            Schema::Record(RecordSchema {
897                name,
898                aliases,
899                doc,
900                fields,
901                attributes,
902                lookup: _lookup,
903            }) => {
904                let mut map = serializer.serialize_map(None)?;
905                map.serialize_entry("type", "record")?;
906                if let Some(ref n) = name.namespace() {
907                    map.serialize_entry("namespace", n)?;
908                }
909                map.serialize_entry("name", &name.name())?;
910                if let Some(docstr) = doc {
911                    map.serialize_entry("doc", docstr)?;
912                }
913                if let Some(aliases) = aliases {
914                    map.serialize_entry("aliases", aliases)?;
915                }
916                map.serialize_entry("fields", fields)?;
917                for attr in attributes {
918                    map.serialize_entry(attr.0, attr.1)?;
919                }
920                map.end()
921            }
922            Schema::Enum(EnumSchema {
923                name,
924                symbols,
925                aliases,
926                attributes,
927                default,
928                doc,
929            }) => {
930                let mut map = serializer.serialize_map(None)?;
931                map.serialize_entry("type", "enum")?;
932                if let Some(ref n) = name.namespace() {
933                    map.serialize_entry("namespace", n)?;
934                }
935                map.serialize_entry("name", &name.name())?;
936                map.serialize_entry("symbols", symbols)?;
937
938                if let Some(aliases) = aliases {
939                    map.serialize_entry("aliases", aliases)?;
940                }
941                if let Some(default) = default {
942                    map.serialize_entry("default", default)?;
943                }
944                if let Some(doc) = doc {
945                    map.serialize_entry("doc", doc)?;
946                }
947                for attr in attributes {
948                    map.serialize_entry(attr.0, attr.1)?;
949                }
950                map.end()
951            }
952            Schema::Fixed(fixed_schema) => {
953                let mut map = serializer.serialize_map(None)?;
954                map = fixed_schema.serialize_to_map::<S>(map)?;
955                map.end()
956            }
957            Schema::Decimal(DecimalSchema {
958                scale,
959                precision,
960                inner,
961            }) => {
962                let mut map = serializer.serialize_map(None)?;
963                match inner {
964                    InnerDecimalSchema::Fixed(fixed_schema) => {
965                        map = fixed_schema.serialize_to_map::<S>(map)?;
966                    }
967                    InnerDecimalSchema::Bytes => {
968                        map.serialize_entry("type", "bytes")?;
969                    }
970                }
971                map.serialize_entry("logicalType", "decimal")?;
972                map.serialize_entry("scale", scale)?;
973                map.serialize_entry("precision", precision)?;
974                map.end()
975            }
976
977            Schema::BigDecimal => {
978                let mut map = serializer.serialize_map(None)?;
979                map.serialize_entry("type", "bytes")?;
980                map.serialize_entry("logicalType", "big-decimal")?;
981                map.end()
982            }
983            Schema::Uuid(inner) => {
984                let mut map = serializer.serialize_map(None)?;
985                match inner {
986                    UuidSchema::Bytes => {
987                        map.serialize_entry("type", "bytes")?;
988                    }
989                    UuidSchema::String => {
990                        map.serialize_entry("type", "string")?;
991                    }
992                    UuidSchema::Fixed(fixed_schema) => {
993                        map = fixed_schema.serialize_to_map::<S>(map)?;
994                    }
995                }
996                map.serialize_entry("logicalType", "uuid")?;
997                map.end()
998            }
999            Schema::Date => {
1000                let mut map = serializer.serialize_map(None)?;
1001                map.serialize_entry("type", "int")?;
1002                map.serialize_entry("logicalType", "date")?;
1003                map.end()
1004            }
1005            Schema::TimeMillis => {
1006                let mut map = serializer.serialize_map(None)?;
1007                map.serialize_entry("type", "int")?;
1008                map.serialize_entry("logicalType", "time-millis")?;
1009                map.end()
1010            }
1011            Schema::TimeMicros => {
1012                let mut map = serializer.serialize_map(None)?;
1013                map.serialize_entry("type", "long")?;
1014                map.serialize_entry("logicalType", "time-micros")?;
1015                map.end()
1016            }
1017            Schema::TimestampMillis => {
1018                let mut map = serializer.serialize_map(None)?;
1019                map.serialize_entry("type", "long")?;
1020                map.serialize_entry("logicalType", "timestamp-millis")?;
1021                map.end()
1022            }
1023            Schema::TimestampMicros => {
1024                let mut map = serializer.serialize_map(None)?;
1025                map.serialize_entry("type", "long")?;
1026                map.serialize_entry("logicalType", "timestamp-micros")?;
1027                map.end()
1028            }
1029            Schema::TimestampNanos => {
1030                let mut map = serializer.serialize_map(None)?;
1031                map.serialize_entry("type", "long")?;
1032                map.serialize_entry("logicalType", "timestamp-nanos")?;
1033                map.end()
1034            }
1035            Schema::LocalTimestampMillis => {
1036                let mut map = serializer.serialize_map(None)?;
1037                map.serialize_entry("type", "long")?;
1038                map.serialize_entry("logicalType", "local-timestamp-millis")?;
1039                map.end()
1040            }
1041            Schema::LocalTimestampMicros => {
1042                let mut map = serializer.serialize_map(None)?;
1043                map.serialize_entry("type", "long")?;
1044                map.serialize_entry("logicalType", "local-timestamp-micros")?;
1045                map.end()
1046            }
1047            Schema::LocalTimestampNanos => {
1048                let mut map = serializer.serialize_map(None)?;
1049                map.serialize_entry("type", "long")?;
1050                map.serialize_entry("logicalType", "local-timestamp-nanos")?;
1051                map.end()
1052            }
1053            Schema::Duration(fixed) => {
1054                let map = serializer.serialize_map(None)?;
1055
1056                let mut map = fixed.serialize_to_map::<S>(map)?;
1057                map.serialize_entry("logicalType", "duration")?;
1058                map.end()
1059            }
1060        }
1061    }
1062}
1063
1064/// Parses a valid Avro schema into [the Parsing Canonical Form].
1065///
1066/// [the Parsing Canonical Form](https://avro.apache.org/docs/++version++/specification/#parsing-canonical-form-for-schemas)
1067fn parsing_canonical_form(schema: &JsonValue, defined_names: &mut HashSet<String>) -> String {
1068    match schema {
1069        JsonValue::Object(map) => pcf_map(map, defined_names),
1070        JsonValue::String(s) => pcf_string(s),
1071        JsonValue::Array(v) => pcf_array(v, defined_names),
1072        json => panic!("got invalid JSON value for canonical form of schema: {json}"),
1073    }
1074}
1075
1076fn pcf_map(schema: &Map<String, JsonValue>, defined_names: &mut HashSet<String>) -> String {
1077    let typ = schema.get("type").and_then(|v| v.as_str());
1078    let name = if is_named_type(typ) {
1079        let ns = schema.get("namespace").and_then(|v| v.as_str());
1080        let raw_name = schema.get("name").and_then(|v| v.as_str());
1081        Some(format!(
1082            "{}{}",
1083            ns.map_or("".to_string(), |n| { format!("{n}.") }),
1084            raw_name.unwrap_or_default()
1085        ))
1086    } else {
1087        None
1088    };
1089
1090    //if this is already a defined type, early return
1091    if let Some(ref n) = name {
1092        if defined_names.contains(n) {
1093            return pcf_string(n);
1094        } else {
1095            defined_names.insert(n.clone());
1096        }
1097    }
1098
1099    let mut fields = Vec::new();
1100    for (k, v) in schema {
1101        // Reduce primitive types to their simple form. ([PRIMITIVE] rule)
1102        if schema.len() == 1 && k == "type" {
1103            // Invariant: function is only callable from a valid schema, so this is acceptable.
1104            if let JsonValue::String(s) = v {
1105                return pcf_string(s);
1106            }
1107        }
1108
1109        // Strip out unused fields ([STRIP] rule)
1110        if field_ordering_position(k).is_none()
1111            || k == "default"
1112            || k == "doc"
1113            || k == "aliases"
1114            || k == "logicalType"
1115        {
1116            continue;
1117        }
1118
1119        // Fully qualify the name, if it isn't already ([FULLNAMES] rule).
1120        if k == "name"
1121            && let Some(ref n) = name
1122        {
1123            fields.push(("name", format!("{}:{}", pcf_string(k), pcf_string(n))));
1124            continue;
1125        }
1126
1127        // Strip off quotes surrounding "size" type, if they exist ([INTEGERS] rule).
1128        if k == "size" || k == "precision" || k == "scale" {
1129            let i = match v.as_str() {
1130                Some(s) => s.parse::<i64>().expect("Only valid schemas are accepted!"),
1131                None => v.as_i64().unwrap(),
1132            };
1133            fields.push((k, format!("{}:{}", pcf_string(k), i)));
1134            continue;
1135        }
1136
1137        // For anything else, recursively process the result.
1138        fields.push((
1139            k,
1140            format!(
1141                "{}:{}",
1142                pcf_string(k),
1143                parsing_canonical_form(v, defined_names)
1144            ),
1145        ));
1146    }
1147
1148    // Sort the fields by their canonical ordering ([ORDER] rule).
1149    fields.sort_unstable_by_key(|(k, _)| field_ordering_position(k).unwrap());
1150    let inter = fields
1151        .into_iter()
1152        .map(|(_, v)| v)
1153        .collect::<Vec<_>>()
1154        .join(",");
1155    format!("{{{inter}}}")
1156}
1157
1158fn is_named_type(typ: Option<&str>) -> bool {
1159    matches!(
1160        typ,
1161        Some("record") | Some("enum") | Some("fixed") | Some("ref")
1162    )
1163}
1164
1165fn pcf_array(arr: &[JsonValue], defined_names: &mut HashSet<String>) -> String {
1166    let inter = arr
1167        .iter()
1168        .map(|a| parsing_canonical_form(a, defined_names))
1169        .collect::<Vec<String>>()
1170        .join(",");
1171    format!("[{inter}]")
1172}
1173
1174fn pcf_string(s: &str) -> String {
1175    format!(r#""{s}""#)
1176}
1177
1178const RESERVED_FIELDS: &[&str] = &[
1179    "name",
1180    "type",
1181    "fields",
1182    "symbols",
1183    "items",
1184    "values",
1185    "size",
1186    "logicalType",
1187    "order",
1188    "doc",
1189    "aliases",
1190    "default",
1191    "precision",
1192    "scale",
1193];
1194
1195// Used to define the ordering and inclusion of fields.
1196fn field_ordering_position(field: &str) -> Option<usize> {
1197    RESERVED_FIELDS
1198        .iter()
1199        .position(|&f| f == field)
1200        .map(|pos| pos + 1)
1201}
1202
1203#[cfg(test)]
1204mod tests {
1205    use super::*;
1206    use crate::writer::datum::GenericDatumWriter;
1207    use crate::{error::Details, rabin::Rabin, reader::datum::GenericDatumReader};
1208    use apache_avro_test_helper::{
1209        TestResult,
1210        logger::{assert_logged, assert_not_logged},
1211    };
1212    use serde::{Deserialize, Serialize};
1213    use serde_json::json;
1214
1215    #[test]
1216    fn test_invalid_schema() {
1217        assert!(Schema::parse_str("invalid").is_err());
1218    }
1219
1220    #[test]
1221    fn test_primitive_schema() -> TestResult {
1222        assert_eq!(Schema::Null, Schema::parse_str(r#""null""#)?);
1223        assert_eq!(Schema::Int, Schema::parse_str(r#""int""#)?);
1224        assert_eq!(Schema::Double, Schema::parse_str(r#""double""#)?);
1225        Ok(())
1226    }
1227
1228    #[test]
1229    fn test_array_schema() -> TestResult {
1230        let schema = Schema::parse_str(r#"{"type": "array", "items": "string"}"#)?;
1231        assert_eq!(Schema::array(Schema::String).build(), schema);
1232        Ok(())
1233    }
1234
1235    #[test]
1236    fn test_map_schema() -> TestResult {
1237        let schema = Schema::parse_str(r#"{"type": "map", "values": "double"}"#)?;
1238        assert_eq!(Schema::map(Schema::Double).build(), schema);
1239        Ok(())
1240    }
1241
1242    #[test]
1243    fn test_union_schema() -> TestResult {
1244        let schema = Schema::parse_str(r#"["null", "int"]"#)?;
1245        assert_eq!(
1246            Schema::Union(UnionSchema::new(vec![Schema::Null, Schema::Int])?),
1247            schema
1248        );
1249        Ok(())
1250    }
1251
1252    #[test]
1253    fn test_union_unsupported_schema() {
1254        let schema = Schema::parse_str(r#"["null", ["null", "int"], "string"]"#);
1255        assert!(schema.is_err());
1256    }
1257
1258    #[test]
1259    fn test_multi_union_schema() -> TestResult {
1260        let schema = Schema::parse_str(r#"["null", "int", "float", "string", "bytes"]"#);
1261        assert!(schema.is_ok());
1262        let schema = schema?;
1263        assert_eq!(SchemaKind::from(&schema), SchemaKind::Union);
1264        let union_schema = match schema {
1265            Schema::Union(u) => u,
1266            _ => unreachable!(),
1267        };
1268        assert_eq!(union_schema.variants().len(), 5);
1269        let mut variants = union_schema.variants().iter();
1270        assert_eq!(SchemaKind::from(variants.next().unwrap()), SchemaKind::Null);
1271        assert_eq!(SchemaKind::from(variants.next().unwrap()), SchemaKind::Int);
1272        assert_eq!(
1273            SchemaKind::from(variants.next().unwrap()),
1274            SchemaKind::Float
1275        );
1276        assert_eq!(
1277            SchemaKind::from(variants.next().unwrap()),
1278            SchemaKind::String
1279        );
1280        assert_eq!(
1281            SchemaKind::from(variants.next().unwrap()),
1282            SchemaKind::Bytes
1283        );
1284        assert_eq!(variants.next(), None);
1285
1286        Ok(())
1287    }
1288
1289    // AVRO-3248
1290    #[test]
1291    fn test_union_of_records() -> TestResult {
1292        // A and B are the same except the name.
1293        let schema_str_a = r#"{
1294            "name": "A",
1295            "type": "record",
1296            "fields": [
1297                {"name": "field_one", "type": "float"}
1298            ]
1299        }"#;
1300
1301        let schema_str_b = r#"{
1302            "name": "B",
1303            "type": "record",
1304            "fields": [
1305                {"name": "field_one", "type": "float"}
1306            ]
1307        }"#;
1308
1309        // we get Details::GetNameField if we put ["A", "B"] directly here.
1310        let schema_str_c = r#"{
1311            "name": "C",
1312            "type": "record",
1313            "fields": [
1314                {"name": "field_one",  "type": ["A", "B"]}
1315            ]
1316        }"#;
1317
1318        let schema_c = Schema::parse_list([schema_str_a, schema_str_b, schema_str_c])?
1319            .last()
1320            .unwrap()
1321            .clone();
1322
1323        let schema_c_expected = Schema::Record(
1324            RecordSchema::builder()
1325                .try_name("C")?
1326                .fields(vec![
1327                    RecordField::builder()
1328                        .name("field_one".to_string())
1329                        .schema(Schema::Union(UnionSchema::new(vec![
1330                            Schema::Ref {
1331                                name: Name::new("A")?,
1332                            },
1333                            Schema::Ref {
1334                                name: Name::new("B")?,
1335                            },
1336                        ])?))
1337                        .build(),
1338                ])
1339                .build(),
1340        );
1341
1342        assert_eq!(schema_c, schema_c_expected);
1343        Ok(())
1344    }
1345
1346    #[test]
1347    fn avro_rs_104_test_root_union_of_records() -> TestResult {
1348        // A and B are the same except the name.
1349        let schema_str_a = r#"{
1350            "name": "A",
1351            "type": "record",
1352            "fields": [
1353                {"name": "field_one", "type": "float"}
1354            ]
1355        }"#;
1356
1357        let schema_str_b = r#"{
1358            "name": "B",
1359            "type": "record",
1360            "fields": [
1361                {"name": "field_one", "type": "float"}
1362            ]
1363        }"#;
1364
1365        let schema_str_c = r#"["A", "B"]"#;
1366
1367        let (schema_c, schemata) =
1368            Schema::parse_str_with_list(schema_str_c, [schema_str_a, schema_str_b])?;
1369
1370        let schema_a_expected = Schema::Record(RecordSchema {
1371            name: Name::new("A")?,
1372            aliases: None,
1373            doc: None,
1374            fields: vec![
1375                RecordField::builder()
1376                    .name("field_one".to_string())
1377                    .schema(Schema::Float)
1378                    .build(),
1379            ],
1380            lookup: BTreeMap::from_iter(vec![("field_one".to_string(), 0)]),
1381            attributes: Default::default(),
1382        });
1383
1384        let schema_b_expected = Schema::Record(RecordSchema {
1385            name: Name::new("B")?,
1386            aliases: None,
1387            doc: None,
1388            fields: vec![
1389                RecordField::builder()
1390                    .name("field_one".to_string())
1391                    .schema(Schema::Float)
1392                    .build(),
1393            ],
1394            lookup: BTreeMap::from_iter(vec![("field_one".to_string(), 0)]),
1395            attributes: Default::default(),
1396        });
1397
1398        let schema_c_expected = Schema::Union(UnionSchema::new(vec![
1399            Schema::Ref {
1400                name: Name::new("A")?,
1401            },
1402            Schema::Ref {
1403                name: Name::new("B")?,
1404            },
1405        ])?);
1406
1407        assert_eq!(schema_c, schema_c_expected);
1408        assert_eq!(schemata[0], schema_a_expected);
1409        assert_eq!(schemata[1], schema_b_expected);
1410
1411        Ok(())
1412    }
1413
1414    #[test]
1415    fn avro_rs_104_test_root_union_of_records_name_collision() -> TestResult {
1416        // A and B are exactly the same.
1417        let schema_str_a1 = r#"{
1418            "name": "A",
1419            "type": "record",
1420            "fields": [
1421                {"name": "field_one", "type": "float"}
1422            ]
1423        }"#;
1424
1425        let schema_str_a2 = r#"{
1426            "name": "A",
1427            "type": "record",
1428            "fields": [
1429                {"name": "field_one", "type": "float"}
1430            ]
1431        }"#;
1432
1433        let schema_str_c = r#"["A", "A"]"#;
1434
1435        match Schema::parse_str_with_list(schema_str_c, [schema_str_a1, schema_str_a2]) {
1436            Ok(_) => unreachable!("Expected an error that the name is already defined"),
1437            Err(e) => assert_eq!(
1438                e.to_string(),
1439                r#"Two schemas with the same fullname were given: "A""#
1440            ),
1441        }
1442
1443        Ok(())
1444    }
1445
1446    #[test]
1447    fn avro_rs_104_test_root_union_of_records_no_name() -> TestResult {
1448        let schema_str_a = r#"{
1449            "name": "A",
1450            "type": "record",
1451            "fields": [
1452                {"name": "field_one", "type": "float"}
1453            ]
1454        }"#;
1455
1456        // B has no name field.
1457        let schema_str_b = r#"{
1458            "type": "record",
1459            "fields": [
1460                {"name": "field_one", "type": "float"}
1461            ]
1462        }"#;
1463
1464        let schema_str_c = r#"["A", "A"]"#;
1465
1466        match Schema::parse_str_with_list(schema_str_c, [schema_str_a, schema_str_b]) {
1467            Ok(_) => unreachable!("Expected an error that schema_str_b is missing a name field"),
1468            Err(e) => assert_eq!(e.to_string(), "No `name` field"),
1469        }
1470
1471        Ok(())
1472    }
1473
1474    #[test]
1475    fn avro_3584_test_recursion_records() -> TestResult {
1476        // A and B are the same except the name.
1477        let schema_str_a = r#"{
1478            "name": "A",
1479            "type": "record",
1480            "fields": [ {"name": "field_one", "type": "B"} ]
1481        }"#;
1482
1483        let schema_str_b = r#"{
1484            "name": "B",
1485            "type": "record",
1486            "fields": [ {"name": "field_one", "type": "A"} ]
1487        }"#;
1488
1489        let list = Schema::parse_list([schema_str_a, schema_str_b])?;
1490
1491        let schema_a = list.first().unwrap().clone();
1492
1493        match schema_a {
1494            Schema::Record(RecordSchema { fields, .. }) => {
1495                let f1 = fields.first();
1496
1497                let ref_schema = Schema::Ref {
1498                    name: Name::new("B")?,
1499                };
1500                assert_eq!(ref_schema, f1.unwrap().schema);
1501            }
1502            _ => panic!("Expected a record schema!"),
1503        }
1504
1505        Ok(())
1506    }
1507
1508    #[test]
1509    fn test_avro_3248_nullable_record() -> TestResult {
1510        use std::iter::FromIterator;
1511
1512        let schema_str_a = r#"{
1513            "name": "A",
1514            "type": "record",
1515            "fields": [
1516                {"name": "field_one", "type": "float"}
1517            ]
1518        }"#;
1519
1520        // we get Details::GetNameField if we put ["null", "B"] directly here.
1521        let schema_str_option_a = r#"{
1522            "name": "OptionA",
1523            "type": "record",
1524            "fields": [
1525                {"name": "field_one",  "type": ["null", "A"], "default": null}
1526            ]
1527        }"#;
1528
1529        let schema_option_a = Schema::parse_list([schema_str_a, schema_str_option_a])?
1530            .last()
1531            .unwrap()
1532            .clone();
1533
1534        let schema_option_a_expected = Schema::Record(RecordSchema {
1535            name: Name::new("OptionA")?,
1536            aliases: None,
1537            doc: None,
1538            fields: vec![
1539                RecordField::builder()
1540                    .name("field_one".to_string())
1541                    .default(JsonValue::Null)
1542                    .schema(Schema::Union(UnionSchema::new(vec![
1543                        Schema::Null,
1544                        Schema::Ref {
1545                            name: Name::new("A")?,
1546                        },
1547                    ])?))
1548                    .build(),
1549            ],
1550            lookup: BTreeMap::from_iter(vec![("field_one".to_string(), 0)]),
1551            attributes: Default::default(),
1552        });
1553
1554        assert_eq!(schema_option_a, schema_option_a_expected);
1555
1556        Ok(())
1557    }
1558
1559    #[test]
1560    fn test_record_schema() -> TestResult {
1561        let parsed = Schema::parse_str(
1562            r#"
1563            {
1564                "type": "record",
1565                "name": "test",
1566                "fields": [
1567                    {"name": "a", "type": "long", "default": 42},
1568                    {"name": "b", "type": "string"}
1569                ]
1570            }
1571        "#,
1572        )?;
1573
1574        let mut lookup = BTreeMap::new();
1575        lookup.insert("a".to_owned(), 0);
1576        lookup.insert("b".to_owned(), 1);
1577
1578        let expected = Schema::Record(RecordSchema {
1579            name: Name::new("test")?,
1580            aliases: None,
1581            doc: None,
1582            fields: vec![
1583                RecordField::builder()
1584                    .name("a".to_string())
1585                    .default(JsonValue::Number(42i64.into()))
1586                    .schema(Schema::Long)
1587                    .build(),
1588                RecordField::builder()
1589                    .name("b".to_string())
1590                    .schema(Schema::String)
1591                    .build(),
1592            ],
1593            lookup,
1594            attributes: Default::default(),
1595        });
1596
1597        assert_eq!(parsed, expected);
1598
1599        Ok(())
1600    }
1601
1602    #[test]
1603    fn test_avro_3302_record_schema_with_currently_parsing_schema() -> TestResult {
1604        let schema = Schema::parse_str(
1605            r#"
1606            {
1607                "type": "record",
1608                "name": "test",
1609                "fields": [{
1610                    "name": "recordField",
1611                    "type": {
1612                        "type": "record",
1613                        "name": "Node",
1614                        "fields": [
1615                            {"name": "label", "type": "string"},
1616                            {"name": "children", "type": {"type": "array", "items": "Node"}}
1617                        ]
1618                    }
1619                }]
1620            }
1621        "#,
1622        )?;
1623
1624        let mut lookup = BTreeMap::new();
1625        lookup.insert("recordField".to_owned(), 0);
1626
1627        let mut node_lookup = BTreeMap::new();
1628        node_lookup.insert("children".to_owned(), 1);
1629        node_lookup.insert("label".to_owned(), 0);
1630
1631        let expected = Schema::Record(RecordSchema {
1632            name: Name::new("test")?,
1633            aliases: None,
1634            doc: None,
1635            fields: vec![
1636                RecordField::builder()
1637                    .name("recordField".to_string())
1638                    .schema(Schema::Record(RecordSchema {
1639                        name: Name::new("Node")?,
1640                        aliases: None,
1641                        doc: None,
1642                        fields: vec![
1643                            RecordField::builder()
1644                                .name("label".to_string())
1645                                .schema(Schema::String)
1646                                .build(),
1647                            RecordField::builder()
1648                                .name("children".to_string())
1649                                .schema(
1650                                    Schema::array(Schema::Ref {
1651                                        name: Name::new("Node")?,
1652                                    })
1653                                    .build(),
1654                                )
1655                                .build(),
1656                        ],
1657                        lookup: node_lookup,
1658                        attributes: Default::default(),
1659                    }))
1660                    .build(),
1661            ],
1662            lookup,
1663            attributes: Default::default(),
1664        });
1665        assert_eq!(schema, expected);
1666
1667        let canonical_form = &schema.canonical_form();
1668        let expected = r#"{"name":"test","type":"record","fields":[{"name":"recordField","type":{"name":"Node","type":"record","fields":[{"name":"label","type":"string"},{"name":"children","type":{"type":"array","items":"Node"}}]}}]}"#;
1669        assert_eq!(canonical_form, &expected);
1670
1671        Ok(())
1672    }
1673
1674    // https://github.com/flavray/avro-rs/pull/99#issuecomment-1016948451
1675    #[test]
1676    fn test_parsing_of_recursive_type_enum() -> TestResult {
1677        let schema = r#"
1678    {
1679        "type": "record",
1680        "name": "User",
1681        "namespace": "office",
1682        "fields": [
1683            {
1684              "name": "details",
1685              "type": [
1686                {
1687                  "type": "record",
1688                  "name": "Employee",
1689                  "fields": [
1690                    {
1691                      "name": "gender",
1692                      "type": {
1693                        "type": "enum",
1694                        "name": "Gender",
1695                        "symbols": [
1696                          "male",
1697                          "female"
1698                        ]
1699                      },
1700                      "default": "female"
1701                    }
1702                  ]
1703                },
1704                {
1705                  "type": "record",
1706                  "name": "Manager",
1707                  "fields": [
1708                    {
1709                      "name": "gender",
1710                      "type": "Gender"
1711                    }
1712                  ]
1713                }
1714              ]
1715            }
1716          ]
1717        }
1718        "#;
1719
1720        let schema = Schema::parse_str(schema)?;
1721        let schema_str = schema.canonical_form();
1722        let expected = r#"{"name":"office.User","type":"record","fields":[{"name":"details","type":[{"name":"office.Employee","type":"record","fields":[{"name":"gender","type":{"name":"office.Gender","type":"enum","symbols":["male","female"]}}]},{"name":"office.Manager","type":"record","fields":[{"name":"gender","type":"office.Gender"}]}]}]}"#;
1723        assert_eq!(schema_str, expected);
1724
1725        Ok(())
1726    }
1727
1728    #[test]
1729    fn test_parsing_of_recursive_type_fixed() -> TestResult {
1730        let schema = r#"
1731    {
1732        "type": "record",
1733        "name": "User",
1734        "namespace": "office",
1735        "fields": [
1736            {
1737              "name": "details",
1738              "type": [
1739                {
1740                  "type": "record",
1741                  "name": "Employee",
1742                  "fields": [
1743                    {
1744                      "name": "id",
1745                      "type": {
1746                        "type": "fixed",
1747                        "name": "EmployeeId",
1748                        "size": 16
1749                      },
1750                      "default": "female"
1751                    }
1752                  ]
1753                },
1754                {
1755                  "type": "record",
1756                  "name": "Manager",
1757                  "fields": [
1758                    {
1759                      "name": "id",
1760                      "type": "EmployeeId"
1761                    }
1762                  ]
1763                }
1764              ]
1765            }
1766          ]
1767        }
1768        "#;
1769
1770        let schema = Schema::parse_str(schema)?;
1771        let schema_str = schema.canonical_form();
1772        let expected = r#"{"name":"office.User","type":"record","fields":[{"name":"details","type":[{"name":"office.Employee","type":"record","fields":[{"name":"id","type":{"name":"office.EmployeeId","type":"fixed","size":16}}]},{"name":"office.Manager","type":"record","fields":[{"name":"id","type":"office.EmployeeId"}]}]}]}"#;
1773        assert_eq!(schema_str, expected);
1774
1775        Ok(())
1776    }
1777
1778    #[test]
1779    fn test_avro_3302_record_schema_with_currently_parsing_schema_aliases() -> TestResult {
1780        let schema = Schema::parse_str(
1781            r#"
1782            {
1783              "type": "record",
1784              "name": "LongList",
1785              "aliases": ["LinkedLongs"],
1786              "fields" : [
1787                {"name": "value", "type": "long"},
1788                {"name": "next", "type": ["null", "LinkedLongs"]}
1789              ]
1790            }
1791        "#,
1792        )?;
1793
1794        let mut lookup = BTreeMap::new();
1795        lookup.insert("value".to_owned(), 0);
1796        lookup.insert("next".to_owned(), 1);
1797
1798        let expected = Schema::Record(RecordSchema {
1799            name: Name::new("LongList")?,
1800            aliases: Some(vec![Alias::new("LinkedLongs").unwrap()]),
1801            doc: None,
1802            fields: vec![
1803                RecordField::builder()
1804                    .name("value".to_string())
1805                    .schema(Schema::Long)
1806                    .build(),
1807                RecordField::builder()
1808                    .name("next".to_string())
1809                    .schema(Schema::Union(UnionSchema::new(vec![
1810                        Schema::Null,
1811                        Schema::Ref {
1812                            name: Name::new("LongList")?,
1813                        },
1814                    ])?))
1815                    .build(),
1816            ],
1817            lookup,
1818            attributes: Default::default(),
1819        });
1820        assert_eq!(schema, expected);
1821
1822        let canonical_form = &schema.canonical_form();
1823        let expected = r#"{"name":"LongList","type":"record","fields":[{"name":"value","type":"long"},{"name":"next","type":["null","LongList"]}]}"#;
1824        assert_eq!(canonical_form, &expected);
1825
1826        Ok(())
1827    }
1828
1829    #[test]
1830    fn test_avro_3370_record_schema_with_currently_parsing_schema_named_record() -> TestResult {
1831        let schema = Schema::parse_str(
1832            r#"
1833            {
1834              "type" : "record",
1835              "name" : "record",
1836              "fields" : [
1837                 { "name" : "value", "type" : "long" },
1838                 { "name" : "next", "type" : "record" }
1839             ]
1840            }
1841        "#,
1842        )?;
1843
1844        let mut lookup = BTreeMap::new();
1845        lookup.insert("value".to_owned(), 0);
1846        lookup.insert("next".to_owned(), 1);
1847
1848        let expected = Schema::Record(RecordSchema {
1849            name: Name::new("record")?,
1850            aliases: None,
1851            doc: None,
1852            fields: vec![
1853                RecordField::builder()
1854                    .name("value".to_string())
1855                    .schema(Schema::Long)
1856                    .build(),
1857                RecordField::builder()
1858                    .name("next".to_string())
1859                    .schema(Schema::Ref {
1860                        name: Name::new("record")?,
1861                    })
1862                    .build(),
1863            ],
1864            lookup,
1865            attributes: Default::default(),
1866        });
1867        assert_eq!(schema, expected);
1868
1869        let canonical_form = &schema.canonical_form();
1870        let expected = r#"{"name":"record","type":"record","fields":[{"name":"value","type":"long"},{"name":"next","type":"record"}]}"#;
1871        assert_eq!(canonical_form, &expected);
1872
1873        Ok(())
1874    }
1875
1876    #[test]
1877    fn test_avro_3370_record_schema_with_currently_parsing_schema_named_enum() -> TestResult {
1878        let schema = Schema::parse_str(
1879            r#"
1880            {
1881              "type" : "record",
1882              "name" : "record",
1883              "fields" : [
1884                 {
1885                    "name" : "enum",
1886                    "type": {
1887                        "name" : "enum",
1888                        "type" : "enum",
1889                        "symbols": ["one", "two", "three"]
1890                    }
1891                 },
1892                 { "name" : "next", "type" : "enum" }
1893             ]
1894            }
1895        "#,
1896        )?;
1897
1898        let mut lookup = BTreeMap::new();
1899        lookup.insert("enum".to_owned(), 0);
1900        lookup.insert("next".to_owned(), 1);
1901
1902        let expected = Schema::Record(RecordSchema {
1903            name: Name::new("record")?,
1904            aliases: None,
1905            doc: None,
1906            fields: vec![
1907                RecordField::builder()
1908                    .name("enum".to_string())
1909                    .schema(Schema::Enum(
1910                        EnumSchema::builder()
1911                            .name(Name::new("enum")?)
1912                            .symbols(vec![
1913                                "one".to_string(),
1914                                "two".to_string(),
1915                                "three".to_string(),
1916                            ])
1917                            .build(),
1918                    ))
1919                    .build(),
1920                RecordField::builder()
1921                    .name("next".to_string())
1922                    .schema(Schema::Ref {
1923                        name: Name::new("enum")?,
1924                    })
1925                    .build(),
1926            ],
1927            lookup,
1928            attributes: Default::default(),
1929        });
1930        assert_eq!(schema, expected);
1931
1932        let canonical_form = &schema.canonical_form();
1933        let expected = r#"{"name":"record","type":"record","fields":[{"name":"enum","type":{"name":"enum","type":"enum","symbols":["one","two","three"]}},{"name":"next","type":"enum"}]}"#;
1934        assert_eq!(canonical_form, &expected);
1935
1936        Ok(())
1937    }
1938
1939    #[test]
1940    fn test_avro_3370_record_schema_with_currently_parsing_schema_named_fixed() -> TestResult {
1941        let schema = Schema::parse_str(
1942            r#"
1943            {
1944              "type" : "record",
1945              "name" : "record",
1946              "fields" : [
1947                 {
1948                    "name": "fixed",
1949                    "type": {
1950                        "type" : "fixed",
1951                        "name" : "fixed",
1952                        "size": 456
1953                    }
1954                 },
1955                 { "name" : "next", "type" : "fixed" }
1956             ]
1957            }
1958        "#,
1959        )?;
1960
1961        let mut lookup = BTreeMap::new();
1962        lookup.insert("fixed".to_owned(), 0);
1963        lookup.insert("next".to_owned(), 1);
1964
1965        let expected = Schema::Record(RecordSchema {
1966            name: Name::new("record")?,
1967            aliases: None,
1968            doc: None,
1969            fields: vec![
1970                RecordField::builder()
1971                    .name("fixed".to_string())
1972                    .schema(Schema::Fixed(FixedSchema {
1973                        name: Name::new("fixed")?,
1974                        aliases: None,
1975                        doc: None,
1976                        size: 456,
1977                        attributes: Default::default(),
1978                    }))
1979                    .build(),
1980                RecordField::builder()
1981                    .name("next".to_string())
1982                    .schema(Schema::Ref {
1983                        name: Name::new("fixed")?,
1984                    })
1985                    .build(),
1986            ],
1987            lookup,
1988            attributes: Default::default(),
1989        });
1990        assert_eq!(schema, expected);
1991
1992        let canonical_form = &schema.canonical_form();
1993        let expected = r#"{"name":"record","type":"record","fields":[{"name":"fixed","type":{"name":"fixed","type":"fixed","size":456}},{"name":"next","type":"fixed"}]}"#;
1994        assert_eq!(canonical_form, &expected);
1995
1996        Ok(())
1997    }
1998
1999    #[test]
2000    fn test_enum_schema() -> TestResult {
2001        let schema = Schema::parse_str(
2002            r#"{"type": "enum", "name": "Suit", "symbols": ["diamonds", "spades", "clubs", "hearts"]}"#,
2003        )?;
2004
2005        let expected = Schema::Enum(EnumSchema {
2006            name: Name::new("Suit")?,
2007            aliases: None,
2008            doc: None,
2009            symbols: vec![
2010                "diamonds".to_owned(),
2011                "spades".to_owned(),
2012                "clubs".to_owned(),
2013                "hearts".to_owned(),
2014            ],
2015            default: None,
2016            attributes: Default::default(),
2017        });
2018
2019        assert_eq!(expected, schema);
2020
2021        Ok(())
2022    }
2023
2024    #[test]
2025    fn test_enum_schema_duplicate() -> TestResult {
2026        // Duplicate "diamonds"
2027        let schema = Schema::parse_str(
2028            r#"{"type": "enum", "name": "Suit", "symbols": ["diamonds", "spades", "clubs", "diamonds"]}"#,
2029        );
2030        assert!(schema.is_err());
2031
2032        Ok(())
2033    }
2034
2035    #[test]
2036    fn test_enum_schema_name() -> TestResult {
2037        // Invalid name "0000" does not match [A-Za-z_][A-Za-z0-9_]*
2038        let schema = Schema::parse_str(
2039            r#"{"type": "enum", "name": "Enum", "symbols": ["0000", "variant"]}"#,
2040        );
2041        assert!(schema.is_err());
2042
2043        Ok(())
2044    }
2045
2046    #[test]
2047    fn test_fixed_schema() -> TestResult {
2048        let schema = Schema::parse_str(r#"{"type": "fixed", "name": "test", "size": 16}"#)?;
2049
2050        let expected = Schema::Fixed(FixedSchema {
2051            name: Name::new("test")?,
2052            aliases: None,
2053            doc: None,
2054            size: 16_usize,
2055            attributes: Default::default(),
2056        });
2057
2058        assert_eq!(expected, schema);
2059
2060        Ok(())
2061    }
2062
2063    #[test]
2064    fn test_fixed_schema_with_documentation() -> TestResult {
2065        let schema = Schema::parse_str(
2066            r#"{"type": "fixed", "name": "test", "size": 16, "doc": "FixedSchema documentation"}"#,
2067        )?;
2068
2069        let expected = Schema::Fixed(FixedSchema {
2070            name: Name::new("test")?,
2071            aliases: None,
2072            doc: Some(String::from("FixedSchema documentation")),
2073            size: 16_usize,
2074            attributes: Default::default(),
2075        });
2076
2077        assert_eq!(expected, schema);
2078
2079        Ok(())
2080    }
2081
2082    #[test]
2083    fn test_no_documentation() -> TestResult {
2084        let schema = Schema::parse_str(
2085            r#"{"type": "enum", "name": "Coin", "symbols": ["heads", "tails"]}"#,
2086        )?;
2087
2088        let doc = match schema {
2089            Schema::Enum(EnumSchema { doc, .. }) => doc,
2090            _ => unreachable!(),
2091        };
2092
2093        assert!(doc.is_none());
2094
2095        Ok(())
2096    }
2097
2098    #[test]
2099    fn test_documentation() -> TestResult {
2100        let schema = Schema::parse_str(
2101            r#"{"type": "enum", "name": "Coin", "doc": "Some documentation", "symbols": ["heads", "tails"]}"#,
2102        )?;
2103
2104        let doc = match schema {
2105            Schema::Enum(EnumSchema { doc, .. }) => doc,
2106            _ => None,
2107        };
2108
2109        assert_eq!("Some documentation".to_owned(), doc.unwrap());
2110
2111        Ok(())
2112    }
2113
2114    // Tests to ensure Schema is Send + Sync. These tests don't need to _do_ anything, if they can
2115    // compile, they pass.
2116    #[test]
2117    fn test_schema_is_send() {
2118        fn send<S: Send>(_s: S) {}
2119
2120        let schema = Schema::Null;
2121        send(schema);
2122    }
2123
2124    #[test]
2125    fn test_schema_is_sync() {
2126        fn sync<S: Sync>(_s: S) {}
2127
2128        let schema = Schema::Null;
2129        sync(&schema);
2130        sync(schema);
2131    }
2132
2133    #[test]
2134    fn test_schema_fingerprint() -> TestResult {
2135        use crate::rabin::Rabin;
2136        use md5::Md5;
2137        use sha2::Sha256;
2138
2139        let raw_schema = r#"
2140    {
2141        "type": "record",
2142        "name": "test",
2143        "fields": [
2144            {"name": "a", "type": "long", "default": 42},
2145            {"name": "b", "type": "string"},
2146            {"name": "c", "type": {"type": "long", "logicalType": "timestamp-micros"}}
2147        ]
2148    }
2149"#;
2150
2151        let schema = Schema::parse_str(raw_schema)?;
2152        assert_eq!(
2153            "7eb3b28d73dfc99bdd9af1848298b40804a2f8ad5d2642be2ecc2ad34842b987",
2154            format!("{}", schema.fingerprint::<Sha256>())
2155        );
2156
2157        assert_eq!(
2158            "cb11615e412ee5d872620d8df78ff6ae",
2159            format!("{}", schema.fingerprint::<Md5>())
2160        );
2161        assert_eq!(
2162            "92f2ccef718c6754",
2163            format!("{}", schema.fingerprint::<Rabin>())
2164        );
2165
2166        Ok(())
2167    }
2168
2169    #[test]
2170    fn test_logical_types() -> TestResult {
2171        let schema = Schema::parse_str(r#"{"type": "int", "logicalType": "date"}"#)?;
2172        assert_eq!(schema, Schema::Date);
2173
2174        let schema = Schema::parse_str(r#"{"type": "long", "logicalType": "timestamp-micros"}"#)?;
2175        assert_eq!(schema, Schema::TimestampMicros);
2176
2177        Ok(())
2178    }
2179
2180    #[test]
2181    fn test_nullable_logical_type() -> TestResult {
2182        let schema = Schema::parse_str(
2183            r#"{"type": ["null", {"type": "long", "logicalType": "timestamp-micros"}]}"#,
2184        )?;
2185        assert_eq!(
2186            schema,
2187            Schema::Union(UnionSchema::new(vec![
2188                Schema::Null,
2189                Schema::TimestampMicros,
2190            ])?)
2191        );
2192
2193        Ok(())
2194    }
2195
2196    #[test]
2197    fn test_avro_3374_preserve_namespace_for_primitive() -> TestResult {
2198        let schema = Schema::parse_str(
2199            r#"
2200            {
2201              "type" : "record",
2202              "name" : "ns.int",
2203              "fields" : [
2204                {"name" : "value", "type" : "int"},
2205                {"name" : "next", "type" : [ "null", "ns.int" ]}
2206              ]
2207            }
2208            "#,
2209        )?;
2210
2211        let json = schema.canonical_form();
2212        assert_eq!(
2213            json,
2214            r#"{"name":"ns.int","type":"record","fields":[{"name":"value","type":"int"},{"name":"next","type":["null","ns.int"]}]}"#
2215        );
2216
2217        Ok(())
2218    }
2219
2220    #[test]
2221    fn test_avro_3433_preserve_schema_refs_in_json() -> TestResult {
2222        let schema = r#"
2223    {
2224      "name": "test.test",
2225      "type": "record",
2226      "fields": [
2227        {
2228          "name": "bar",
2229          "type": { "name": "test.foo", "type": "record", "fields": [{ "name": "id", "type": "long" }] }
2230        },
2231        { "name": "baz", "type": "test.foo" }
2232      ]
2233    }
2234    "#;
2235
2236        let schema = Schema::parse_str(schema)?;
2237
2238        let expected = r#"{"name":"test.test","type":"record","fields":[{"name":"bar","type":{"name":"test.foo","type":"record","fields":[{"name":"id","type":"long"}]}},{"name":"baz","type":"test.foo"}]}"#;
2239        assert_eq!(schema.canonical_form(), expected);
2240
2241        Ok(())
2242    }
2243
2244    #[test]
2245    fn test_read_namespace_from_name() -> TestResult {
2246        let schema = r#"
2247    {
2248      "name": "space.name",
2249      "type": "record",
2250      "fields": [
2251        {
2252          "name": "num",
2253          "type": "int"
2254        }
2255      ]
2256    }
2257    "#;
2258
2259        let schema = Schema::parse_str(schema)?;
2260        if let Schema::Record(RecordSchema { name, .. }) = schema {
2261            assert_eq!(name.name(), "name");
2262            assert_eq!(name.namespace(), Some("space"));
2263        } else {
2264            panic!("Expected a record schema!");
2265        }
2266
2267        Ok(())
2268    }
2269
2270    #[test]
2271    fn test_namespace_from_name_has_priority_over_from_field() -> TestResult {
2272        let schema = r#"
2273    {
2274      "name": "space1.name",
2275      "namespace": "space2",
2276      "type": "record",
2277      "fields": [
2278        {
2279          "name": "num",
2280          "type": "int"
2281        }
2282      ]
2283    }
2284    "#;
2285
2286        let schema = Schema::parse_str(schema)?;
2287        if let Schema::Record(RecordSchema { name, .. }) = schema {
2288            assert_eq!(name.namespace(), Some("space1"));
2289        } else {
2290            panic!("Expected a record schema!");
2291        }
2292
2293        Ok(())
2294    }
2295
2296    #[test]
2297    fn test_namespace_from_field() -> TestResult {
2298        let schema = r#"
2299    {
2300      "name": "name",
2301      "namespace": "space2",
2302      "type": "record",
2303      "fields": [
2304        {
2305          "name": "num",
2306          "type": "int"
2307        }
2308      ]
2309    }
2310    "#;
2311
2312        let schema = Schema::parse_str(schema)?;
2313        if let Schema::Record(RecordSchema { name, .. }) = schema {
2314            assert_eq!(name.namespace(), Some("space2"));
2315        } else {
2316            panic!("Expected a record schema!");
2317        }
2318
2319        Ok(())
2320    }
2321
2322    fn assert_avro_3512_aliases(aliases: &Aliases) {
2323        match aliases {
2324            Some(aliases) => {
2325                assert_eq!(aliases.len(), 3);
2326                assert_eq!(aliases[0], Alias::new("space.b").unwrap());
2327                assert_eq!(aliases[1], Alias::new("x.y").unwrap());
2328                assert_eq!(aliases[2], Alias::new(".c").unwrap());
2329            }
2330            None => {
2331                panic!("'aliases' must be Some");
2332            }
2333        }
2334    }
2335
2336    #[test]
2337    fn avro_3512_alias_with_null_namespace_record() -> TestResult {
2338        let schema = Schema::parse_str(
2339            r#"
2340            {
2341              "type": "record",
2342              "name": "a",
2343              "namespace": "space",
2344              "aliases": ["b", "x.y", ".c"],
2345              "fields" : [
2346                {"name": "time", "type": "long"}
2347              ]
2348            }
2349        "#,
2350        )?;
2351
2352        if let Schema::Record(RecordSchema { ref aliases, .. }) = schema {
2353            assert_avro_3512_aliases(aliases);
2354        } else {
2355            panic!("The Schema should be a record: {schema:?}");
2356        }
2357
2358        Ok(())
2359    }
2360
2361    #[test]
2362    fn avro_3512_alias_with_null_namespace_enum() -> TestResult {
2363        let schema = Schema::parse_str(
2364            r#"
2365            {
2366              "type": "enum",
2367              "name": "a",
2368              "namespace": "space",
2369              "aliases": ["b", "x.y", ".c"],
2370              "symbols" : [
2371                "symbol1", "symbol2"
2372              ]
2373            }
2374        "#,
2375        )?;
2376
2377        if let Schema::Enum(EnumSchema { ref aliases, .. }) = schema {
2378            assert_avro_3512_aliases(aliases);
2379        } else {
2380            panic!("The Schema should be an enum: {schema:?}");
2381        }
2382
2383        Ok(())
2384    }
2385
2386    #[test]
2387    fn avro_3512_alias_with_null_namespace_fixed() -> TestResult {
2388        let schema = Schema::parse_str(
2389            r#"
2390            {
2391              "type": "fixed",
2392              "name": "a",
2393              "namespace": "space",
2394              "aliases": ["b", "x.y", ".c"],
2395              "size" : 12
2396            }
2397        "#,
2398        )?;
2399
2400        if let Schema::Fixed(FixedSchema { ref aliases, .. }) = schema {
2401            assert_avro_3512_aliases(aliases);
2402        } else {
2403            panic!("The Schema should be a fixed: {schema:?}");
2404        }
2405
2406        Ok(())
2407    }
2408
2409    #[test]
2410    fn avro_3518_serialize_aliases_record() -> TestResult {
2411        let schema = Schema::parse_str(
2412            r#"
2413            {
2414              "type": "record",
2415              "name": "a",
2416              "namespace": "space",
2417              "aliases": ["b", "x.y", ".c"],
2418              "fields" : [
2419                {
2420                    "name": "time",
2421                    "type": "long",
2422                    "doc": "The documentation is serialized",
2423                    "default": 123,
2424                    "aliases": ["time1", "ns.time2"]
2425                }
2426              ]
2427            }
2428        "#,
2429        )?;
2430
2431        let value = serde_json::to_value(&schema)?;
2432        let serialized = serde_json::to_string(&value)?;
2433        assert_eq!(
2434            r#"{"aliases":["space.b","x.y","c"],"fields":[{"aliases":["time1","ns.time2"],"default":123,"doc":"The documentation is serialized","name":"time","type":"long"}],"name":"a","namespace":"space","type":"record"}"#,
2435            &serialized
2436        );
2437        assert_eq!(schema, Schema::parse_str(&serialized)?);
2438
2439        Ok(())
2440    }
2441
2442    #[test]
2443    fn avro_3518_serialize_aliases_enum() -> TestResult {
2444        let schema = Schema::parse_str(
2445            r#"
2446            {
2447              "type": "enum",
2448              "name": "a",
2449              "namespace": "space",
2450              "aliases": ["b", "x.y", ".c"],
2451              "symbols" : [
2452                "symbol1", "symbol2"
2453              ]
2454            }
2455        "#,
2456        )?;
2457
2458        let value = serde_json::to_value(&schema)?;
2459        let serialized = serde_json::to_string(&value)?;
2460        assert_eq!(
2461            r#"{"aliases":["space.b","x.y","c"],"name":"a","namespace":"space","symbols":["symbol1","symbol2"],"type":"enum"}"#,
2462            &serialized
2463        );
2464        assert_eq!(schema, Schema::parse_str(&serialized)?);
2465
2466        Ok(())
2467    }
2468
2469    #[test]
2470    fn avro_3518_serialize_aliases_fixed() -> TestResult {
2471        let schema = Schema::parse_str(
2472            r#"
2473            {
2474              "type": "fixed",
2475              "name": "a",
2476              "namespace": "space",
2477              "aliases": ["b", "x.y", ".c"],
2478              "size" : 12
2479            }
2480        "#,
2481        )?;
2482
2483        let value = serde_json::to_value(&schema)?;
2484        let serialized = serde_json::to_string(&value)?;
2485        assert_eq!(
2486            r#"{"aliases":["space.b","x.y","c"],"name":"a","namespace":"space","size":12,"type":"fixed"}"#,
2487            &serialized
2488        );
2489        assert_eq!(schema, Schema::parse_str(&serialized)?);
2490
2491        Ok(())
2492    }
2493
2494    #[test]
2495    fn avro_3130_parse_anonymous_union_type() -> TestResult {
2496        let schema_str = r#"
2497        {
2498            "type": "record",
2499            "name": "AccountEvent",
2500            "fields": [
2501                {"type":
2502                  ["null",
2503                   { "name": "accountList",
2504                      "type": {
2505                        "type": "array",
2506                        "items": "long"
2507                      }
2508                  }
2509                  ],
2510                 "name":"NullableLongArray"
2511               }
2512            ]
2513        }
2514        "#;
2515        let schema = Schema::parse_str(schema_str)?;
2516
2517        if let Schema::Record(RecordSchema { name, fields, .. }) = schema {
2518            assert_eq!(name, Name::new("AccountEvent")?);
2519
2520            let field = &fields[0];
2521            assert_eq!(&field.name, "NullableLongArray");
2522
2523            if let Schema::Union(ref union) = field.schema {
2524                assert_eq!(union.schemas[0], Schema::Null);
2525
2526                if let Schema::Array(ref array_schema) = union.schemas[1] {
2527                    if let Schema::Long = *array_schema.items {
2528                        // OK
2529                    } else {
2530                        panic!("Expected a Schema::Array of type Long");
2531                    }
2532                } else {
2533                    panic!("Expected Schema::Array");
2534                }
2535            } else {
2536                panic!("Expected Schema::Union");
2537            }
2538        } else {
2539            panic!("Expected Schema::Record");
2540        }
2541
2542        Ok(())
2543    }
2544
2545    #[test]
2546    fn avro_custom_attributes_schema_without_attributes() -> TestResult {
2547        let schemata_str = [
2548            r#"
2549            {
2550                "type": "record",
2551                "name": "Rec",
2552                "doc": "A Record schema without custom attributes",
2553                "fields": []
2554            }
2555            "#,
2556            r#"
2557            {
2558                "type": "enum",
2559                "name": "Enum",
2560                "doc": "An Enum schema without custom attributes",
2561                "symbols": []
2562            }
2563            "#,
2564            r#"
2565            {
2566                "type": "fixed",
2567                "name": "Fixed",
2568                "doc": "A Fixed schema without custom attributes",
2569                "size": 0
2570            }
2571            "#,
2572        ];
2573        for schema_str in schemata_str.iter() {
2574            let schema = Schema::parse_str(schema_str)?;
2575            assert_eq!(schema.custom_attributes(), Some(&Default::default()));
2576        }
2577
2578        Ok(())
2579    }
2580
2581    const CUSTOM_ATTRS_SUFFIX: &str = r#"
2582            "string_key": "value",
2583            "number_key": 1.23,
2584            "null_key": null,
2585            "array_key": [1, 2, 3],
2586            "object_key": {
2587                "key": "value"
2588            }
2589        "#;
2590
2591    #[test]
2592    fn avro_3609_custom_attributes_schema_with_attributes() -> TestResult {
2593        let schemata_str = [
2594            r#"
2595            {
2596                "type": "record",
2597                "name": "Rec",
2598                "namespace": "ns",
2599                "doc": "A Record schema with custom attributes",
2600                "fields": [],
2601                {{{}}}
2602            }
2603            "#,
2604            r#"
2605            {
2606                "type": "enum",
2607                "name": "Enum",
2608                "namespace": "ns",
2609                "doc": "An Enum schema with custom attributes",
2610                "symbols": [],
2611                {{{}}}
2612            }
2613            "#,
2614            r#"
2615            {
2616                "type": "fixed",
2617                "name": "Fixed",
2618                "namespace": "ns",
2619                "doc": "A Fixed schema with custom attributes",
2620                "size": 2,
2621                {{{}}}
2622            }
2623            "#,
2624        ];
2625
2626        for schema_str in schemata_str.iter() {
2627            let schema = Schema::parse_str(
2628                schema_str
2629                    .to_owned()
2630                    .replace("{{{}}}", CUSTOM_ATTRS_SUFFIX)
2631                    .as_str(),
2632            )?;
2633
2634            assert_eq!(
2635                schema.custom_attributes(),
2636                Some(&expected_custom_attributes())
2637            );
2638        }
2639
2640        Ok(())
2641    }
2642
2643    fn expected_custom_attributes() -> BTreeMap<String, JsonValue> {
2644        let mut expected_attributes: BTreeMap<String, JsonValue> = Default::default();
2645        expected_attributes.insert(
2646            "string_key".to_string(),
2647            JsonValue::String("value".to_string()),
2648        );
2649        expected_attributes.insert("number_key".to_string(), json!(1.23));
2650        expected_attributes.insert("null_key".to_string(), JsonValue::Null);
2651        expected_attributes.insert(
2652            "array_key".to_string(),
2653            JsonValue::Array(vec![json!(1), json!(2), json!(3)]),
2654        );
2655        let mut object_value: HashMap<String, JsonValue> = HashMap::new();
2656        object_value.insert("key".to_string(), JsonValue::String("value".to_string()));
2657        expected_attributes.insert("object_key".to_string(), json!(object_value));
2658        expected_attributes
2659    }
2660
2661    #[test]
2662    fn avro_3609_custom_attributes_record_field_without_attributes() -> TestResult {
2663        let schema_str = String::from(
2664            r#"
2665            {
2666                "type": "record",
2667                "name": "Rec",
2668                "doc": "A Record schema without custom attributes",
2669                "fields": [
2670                    {
2671                        "name": "field_one",
2672                        "type": "float",
2673                        {{{}}}
2674                    }
2675                ]
2676            }
2677        "#,
2678        );
2679
2680        let schema = Schema::parse_str(schema_str.replace("{{{}}}", CUSTOM_ATTRS_SUFFIX).as_str())?;
2681
2682        match schema {
2683            Schema::Record(RecordSchema { name, fields, .. }) => {
2684                assert_eq!(name, Name::new("Rec")?);
2685                assert_eq!(fields.len(), 1);
2686                let field = &fields[0];
2687                assert_eq!(&field.name, "field_one");
2688                assert_eq!(field.custom_attributes, expected_custom_attributes());
2689            }
2690            _ => panic!("Expected Schema::Record"),
2691        }
2692
2693        Ok(())
2694    }
2695
2696    #[test]
2697    fn avro_3625_null_is_first() -> TestResult {
2698        let schema_str = String::from(
2699            r#"
2700            {
2701                "type": "record",
2702                "name": "union_schema_test",
2703                "fields": [
2704                    {"name": "a", "type": ["null", "long"], "default": null}
2705                ]
2706            }
2707        "#,
2708        );
2709
2710        let schema = Schema::parse_str(&schema_str)?;
2711
2712        match schema {
2713            Schema::Record(RecordSchema { name, fields, .. }) => {
2714                assert_eq!(name, Name::new("union_schema_test")?);
2715                assert_eq!(fields.len(), 1);
2716                let field = &fields[0];
2717                assert_eq!(&field.name, "a");
2718                assert_eq!(&field.default, &Some(JsonValue::Null));
2719                match &field.schema {
2720                    Schema::Union(union) => {
2721                        assert_eq!(union.variants().len(), 2);
2722                        assert!(union.is_nullable());
2723                        assert_eq!(union.variants()[0], Schema::Null);
2724                        assert_eq!(union.variants()[1], Schema::Long);
2725                    }
2726                    _ => panic!("Expected Schema::Union"),
2727                }
2728            }
2729            _ => panic!("Expected Schema::Record"),
2730        }
2731
2732        Ok(())
2733    }
2734
2735    #[test]
2736    fn avro_3625_null_is_last() -> TestResult {
2737        let schema_str = String::from(
2738            r#"
2739            {
2740                "type": "record",
2741                "name": "union_schema_test",
2742                "fields": [
2743                    {"name": "a", "type": ["long","null"], "default": 123}
2744                ]
2745            }
2746        "#,
2747        );
2748
2749        let schema = Schema::parse_str(&schema_str)?;
2750
2751        match schema {
2752            Schema::Record(RecordSchema { name, fields, .. }) => {
2753                assert_eq!(name, Name::new("union_schema_test")?);
2754                assert_eq!(fields.len(), 1);
2755                let field = &fields[0];
2756                assert_eq!(&field.name, "a");
2757                assert_eq!(&field.default, &Some(json!(123)));
2758                match &field.schema {
2759                    Schema::Union(union) => {
2760                        assert_eq!(union.variants().len(), 2);
2761                        assert_eq!(union.variants()[0], Schema::Long);
2762                        assert_eq!(union.variants()[1], Schema::Null);
2763                    }
2764                    _ => panic!("Expected Schema::Union"),
2765                }
2766            }
2767            _ => panic!("Expected Schema::Record"),
2768        }
2769
2770        Ok(())
2771    }
2772
2773    #[test]
2774    fn avro_3625_null_is_the_middle() -> TestResult {
2775        let schema_str = String::from(
2776            r#"
2777            {
2778                "type": "record",
2779                "name": "union_schema_test",
2780                "fields": [
2781                    {"name": "a", "type": ["long","null","int"], "default": 123}
2782                ]
2783            }
2784        "#,
2785        );
2786
2787        let schema = Schema::parse_str(&schema_str)?;
2788
2789        match schema {
2790            Schema::Record(RecordSchema { name, fields, .. }) => {
2791                assert_eq!(name, Name::new("union_schema_test")?);
2792                assert_eq!(fields.len(), 1);
2793                let field = &fields[0];
2794                assert_eq!(&field.name, "a");
2795                assert_eq!(&field.default, &Some(json!(123)));
2796                match &field.schema {
2797                    Schema::Union(union) => {
2798                        assert_eq!(union.variants().len(), 3);
2799                        assert_eq!(union.variants()[0], Schema::Long);
2800                        assert_eq!(union.variants()[1], Schema::Null);
2801                        assert_eq!(union.variants()[2], Schema::Int);
2802                    }
2803                    _ => panic!("Expected Schema::Union"),
2804                }
2805            }
2806            _ => panic!("Expected Schema::Record"),
2807        }
2808
2809        Ok(())
2810    }
2811
2812    #[test]
2813    fn avro_3649_default_notintfirst() -> TestResult {
2814        let schema_str = String::from(
2815            r#"
2816            {
2817                "type": "record",
2818                "name": "union_schema_test",
2819                "fields": [
2820                    {"name": "a", "type": ["string", "int"], "default": 123}
2821                ]
2822            }
2823        "#,
2824        );
2825
2826        let schema = Schema::parse_str(&schema_str)?;
2827
2828        match schema {
2829            Schema::Record(RecordSchema { name, fields, .. }) => {
2830                assert_eq!(name, Name::new("union_schema_test")?);
2831                assert_eq!(fields.len(), 1);
2832                let field = &fields[0];
2833                assert_eq!(&field.name, "a");
2834                assert_eq!(&field.default, &Some(json!(123)));
2835                match &field.schema {
2836                    Schema::Union(union) => {
2837                        assert_eq!(union.variants().len(), 2);
2838                        assert_eq!(union.variants()[0], Schema::String);
2839                        assert_eq!(union.variants()[1], Schema::Int);
2840                    }
2841                    _ => panic!("Expected Schema::Union"),
2842                }
2843            }
2844            _ => panic!("Expected Schema::Record"),
2845        }
2846
2847        Ok(())
2848    }
2849
2850    #[test]
2851    fn avro_3709_parsing_of_record_field_aliases() -> TestResult {
2852        let schema = r#"
2853        {
2854          "name": "rec",
2855          "type": "record",
2856          "fields": [
2857            {
2858              "name": "num",
2859              "type": "int",
2860              "aliases": ["num1", "num2"]
2861            }
2862          ]
2863        }
2864        "#;
2865
2866        let schema = Schema::parse_str(schema)?;
2867        if let Schema::Record(RecordSchema { fields, .. }) = schema {
2868            let num_field = &fields[0];
2869            assert_eq!(num_field.name, "num");
2870            assert_eq!(
2871                num_field.aliases,
2872                vec!["num1".to_string(), "num2".to_string()]
2873            );
2874        } else {
2875            panic!("Expected a record schema!");
2876        }
2877
2878        Ok(())
2879    }
2880
2881    #[test]
2882    fn avro_3735_parse_enum_namespace() -> TestResult {
2883        let schema = r#"
2884        {
2885            "type": "record",
2886            "name": "Foo",
2887            "namespace": "name.space",
2888            "fields":
2889            [
2890                {
2891                    "name": "barInit",
2892                    "type":
2893                    {
2894                        "type": "enum",
2895                        "name": "Bar",
2896                        "symbols":
2897                        [
2898                            "bar0",
2899                            "bar1"
2900                        ]
2901                    }
2902                },
2903                {
2904                    "name": "barUse",
2905                    "type": "Bar"
2906                }
2907            ]
2908        }
2909        "#;
2910
2911        #[derive(
2912            Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, serde::Deserialize, serde::Serialize,
2913        )]
2914        pub enum Bar {
2915            #[serde(rename = "bar0")]
2916            Bar0,
2917            #[serde(rename = "bar1")]
2918            Bar1,
2919        }
2920
2921        #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize)]
2922        pub struct Foo {
2923            #[serde(rename = "barInit")]
2924            pub bar_init: Bar,
2925            #[serde(rename = "barUse")]
2926            pub bar_use: Bar,
2927        }
2928
2929        let schema = Schema::parse_str(schema)?;
2930
2931        let foo = Foo {
2932            bar_init: Bar::Bar0,
2933            bar_use: Bar::Bar1,
2934        };
2935
2936        let avro_value = crate::to_value(foo)?;
2937        assert!(avro_value.validate(&schema));
2938
2939        let mut writer = crate::Writer::new(&schema, Vec::new())?;
2940
2941        // schema validation happens here
2942        writer.append_value(avro_value)?;
2943
2944        Ok(())
2945    }
2946
2947    #[test]
2948    fn avro_3755_deserialize() -> TestResult {
2949        #[derive(
2950            Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, serde::Deserialize, serde::Serialize,
2951        )]
2952        pub enum Bar {
2953            #[serde(rename = "bar0")]
2954            Bar0,
2955            #[serde(rename = "bar1")]
2956            Bar1,
2957            #[serde(rename = "bar2")]
2958            Bar2,
2959        }
2960
2961        #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize)]
2962        pub struct Foo {
2963            #[serde(rename = "barInit")]
2964            pub bar_init: Bar,
2965            #[serde(rename = "barUse")]
2966            pub bar_use: Bar,
2967        }
2968
2969        let writer_schema = r#"{
2970            "type": "record",
2971            "name": "Foo",
2972            "fields":
2973            [
2974                {
2975                    "name": "barInit",
2976                    "type":
2977                    {
2978                        "type": "enum",
2979                        "name": "Bar",
2980                        "symbols":
2981                        [
2982                            "bar0",
2983                            "bar1"
2984                        ]
2985                    }
2986                },
2987                {
2988                    "name": "barUse",
2989                    "type": "Bar"
2990                }
2991            ]
2992            }"#;
2993
2994        let reader_schema = r#"{
2995            "type": "record",
2996            "name": "Foo",
2997            "namespace": "name.space",
2998            "fields":
2999            [
3000                {
3001                    "name": "barInit",
3002                    "type":
3003                    {
3004                        "type": "enum",
3005                        "name": "Bar",
3006                        "symbols":
3007                        [
3008                            "bar0",
3009                            "bar1",
3010                            "bar2"
3011                        ]
3012                    }
3013                },
3014                {
3015                    "name": "barUse",
3016                    "type": "Bar"
3017                }
3018            ]
3019            }"#;
3020
3021        let writer_schema = Schema::parse_str(writer_schema)?;
3022        let foo = Foo {
3023            bar_init: Bar::Bar0,
3024            bar_use: Bar::Bar1,
3025        };
3026        let avro_value = crate::to_value(foo)?;
3027        assert!(
3028            avro_value.validate(&writer_schema),
3029            "value is valid for schema",
3030        );
3031        let datum = GenericDatumWriter::builder(&writer_schema)
3032            .build()?
3033            .write_value_to_vec(avro_value)?;
3034        let mut x = &datum[..];
3035        let reader_schema = Schema::parse_str(reader_schema)?;
3036        let deser_value = GenericDatumReader::builder(&writer_schema)
3037            .reader_schema(&reader_schema)
3038            .build()?
3039            .read_value(&mut x)?;
3040        match deser_value {
3041            types::Value::Record(fields) => {
3042                assert_eq!(fields.len(), 2);
3043                assert_eq!(fields[0].0, "barInit");
3044                assert_eq!(fields[0].1, types::Value::Enum(0, "bar0".to_string()));
3045                assert_eq!(fields[1].0, "barUse");
3046                assert_eq!(fields[1].1, types::Value::Enum(1, "bar1".to_string()));
3047            }
3048            _ => panic!("Expected Value::Record"),
3049        }
3050
3051        Ok(())
3052    }
3053
3054    #[test]
3055    fn test_avro_3780_decimal_schema_type_with_fixed() -> TestResult {
3056        let schema = json!(
3057        {
3058          "type": "record",
3059          "name": "recordWithDecimal",
3060          "fields": [
3061            {
3062                "name": "decimal",
3063                "type": {
3064                    "type": "fixed",
3065                    "name": "nestedFixed",
3066                    "size": 8,
3067                    "logicalType": "decimal",
3068                    "precision": 4
3069                }
3070            }
3071          ]
3072        });
3073
3074        let parse_result = Schema::parse(&schema);
3075        assert!(
3076            parse_result.is_ok(),
3077            "parse result must be ok, got: {parse_result:?}"
3078        );
3079
3080        Ok(())
3081    }
3082
3083    #[test]
3084    fn test_avro_3772_enum_default_wrong_type() -> TestResult {
3085        let schema = r#"
3086        {
3087          "type": "record",
3088          "name": "test",
3089          "fields": [
3090            {"name": "a", "type": "long", "default": 42},
3091            {"name": "b", "type": "string"},
3092            {
3093              "name": "c",
3094              "type": {
3095                "type": "enum",
3096                "name": "suit",
3097                "symbols": ["diamonds", "spades", "clubs", "hearts"],
3098                "default": 123
3099              }
3100            }
3101          ]
3102        }
3103        "#;
3104
3105        match Schema::parse_str(schema) {
3106            Err(err) => {
3107                assert_eq!(
3108                    err.to_string(),
3109                    "Default value for an enum must be a string! Got: 123"
3110                );
3111            }
3112            _ => panic!("Expected an error"),
3113        }
3114        Ok(())
3115    }
3116
3117    #[test]
3118    fn test_avro_3812_handle_null_namespace_properly() -> TestResult {
3119        let schema_str = r#"
3120        {
3121          "namespace": "",
3122          "type": "record",
3123          "name": "my_schema",
3124          "fields": [
3125            {
3126              "name": "a",
3127              "type": {
3128                "type": "enum",
3129                "name": "my_enum",
3130                "namespace": "",
3131                "symbols": ["a", "b"]
3132              }
3133            },  {
3134              "name": "b",
3135              "type": {
3136                "type": "fixed",
3137                "name": "my_fixed",
3138                "namespace": "",
3139                "size": 10
3140              }
3141            }
3142          ]
3143         }
3144         "#;
3145
3146        let expected = r#"{"name":"my_schema","type":"record","fields":[{"name":"a","type":{"name":"my_enum","type":"enum","symbols":["a","b"]}},{"name":"b","type":{"name":"my_fixed","type":"fixed","size":10}}]}"#;
3147        let schema = Schema::parse_str(schema_str)?;
3148        let canonical_form = schema.canonical_form();
3149        assert_eq!(canonical_form, expected);
3150
3151        let name = Name::new("my_name")?;
3152        let fullname = name.fullname(Some(""));
3153        assert_eq!(fullname, "my_name");
3154        let qname = name.fully_qualified_name(Some("")).to_string();
3155        assert_eq!(qname, "my_name");
3156
3157        Ok(())
3158    }
3159
3160    #[test]
3161    fn test_avro_3818_inherit_enclosing_namespace() -> TestResult {
3162        // Enclosing namespace is specified but inner namespaces are not.
3163        let schema_str = r#"
3164        {
3165          "namespace": "my_ns",
3166          "type": "record",
3167          "name": "my_schema",
3168          "fields": [
3169            {
3170              "name": "f1",
3171              "type": {
3172                "name": "enum1",
3173                "type": "enum",
3174                "symbols": ["a"]
3175              }
3176            },  {
3177              "name": "f2",
3178              "type": {
3179                "name": "fixed1",
3180                "type": "fixed",
3181                "size": 1
3182              }
3183            }
3184          ]
3185        }
3186        "#;
3187
3188        let expected = r#"{"name":"my_ns.my_schema","type":"record","fields":[{"name":"f1","type":{"name":"my_ns.enum1","type":"enum","symbols":["a"]}},{"name":"f2","type":{"name":"my_ns.fixed1","type":"fixed","size":1}}]}"#;
3189        let schema = Schema::parse_str(schema_str)?;
3190        let canonical_form = schema.canonical_form();
3191        assert_eq!(canonical_form, expected);
3192
3193        // Enclosing namespace and inner namespaces are specified
3194        // but inner namespaces are ""
3195        let schema_str = r#"
3196        {
3197          "namespace": "my_ns",
3198          "type": "record",
3199          "name": "my_schema",
3200          "fields": [
3201            {
3202              "name": "f1",
3203              "type": {
3204                "name": "enum1",
3205                "type": "enum",
3206                "namespace": "",
3207                "symbols": ["a"]
3208              }
3209            },  {
3210              "name": "f2",
3211              "type": {
3212                "name": "fixed1",
3213                "type": "fixed",
3214                "namespace": "",
3215                "size": 1
3216              }
3217            }
3218          ]
3219        }
3220        "#;
3221
3222        let expected = r#"{"name":"my_ns.my_schema","type":"record","fields":[{"name":"f1","type":{"name":"enum1","type":"enum","symbols":["a"]}},{"name":"f2","type":{"name":"fixed1","type":"fixed","size":1}}]}"#;
3223        let schema = Schema::parse_str(schema_str)?;
3224        let canonical_form = schema.canonical_form();
3225        assert_eq!(canonical_form, expected);
3226
3227        // Enclosing namespace is "" and inner non-empty namespaces are specified.
3228        let schema_str = r#"
3229        {
3230          "namespace": "",
3231          "type": "record",
3232          "name": "my_schema",
3233          "fields": [
3234            {
3235              "name": "f1",
3236              "type": {
3237                "name": "enum1",
3238                "type": "enum",
3239                "namespace": "f1.ns",
3240                "symbols": ["a"]
3241              }
3242            },  {
3243              "name": "f2",
3244              "type": {
3245                "name": "f2.ns.fixed1",
3246                "type": "fixed",
3247                "size": 1
3248              }
3249            }
3250          ]
3251        }
3252        "#;
3253
3254        let expected = r#"{"name":"my_schema","type":"record","fields":[{"name":"f1","type":{"name":"f1.ns.enum1","type":"enum","symbols":["a"]}},{"name":"f2","type":{"name":"f2.ns.fixed1","type":"fixed","size":1}}]}"#;
3255        let schema = Schema::parse_str(schema_str)?;
3256        let canonical_form = schema.canonical_form();
3257        assert_eq!(canonical_form, expected);
3258
3259        // Nested complex types with non-empty enclosing namespace.
3260        let schema_str = r#"
3261        {
3262          "type": "record",
3263          "name": "my_ns.my_schema",
3264          "fields": [
3265            {
3266              "name": "f1",
3267              "type": {
3268                "name": "inner_record1",
3269                "type": "record",
3270                "fields": [
3271                  {
3272                    "name": "f1_1",
3273                    "type": {
3274                      "name": "enum1",
3275                      "type": "enum",
3276                      "symbols": ["a"]
3277                    }
3278                  }
3279                ]
3280              }
3281            },  {
3282              "name": "f2",
3283                "type": {
3284                "name": "inner_record2",
3285                "type": "record",
3286                "namespace": "inner_ns",
3287                "fields": [
3288                  {
3289                    "name": "f2_1",
3290                    "type": {
3291                      "name": "enum2",
3292                      "type": "enum",
3293                      "symbols": ["a"]
3294                    }
3295                  }
3296                ]
3297              }
3298            }
3299          ]
3300        }
3301        "#;
3302
3303        let expected = r#"{"name":"my_ns.my_schema","type":"record","fields":[{"name":"f1","type":{"name":"my_ns.inner_record1","type":"record","fields":[{"name":"f1_1","type":{"name":"my_ns.enum1","type":"enum","symbols":["a"]}}]}},{"name":"f2","type":{"name":"inner_ns.inner_record2","type":"record","fields":[{"name":"f2_1","type":{"name":"inner_ns.enum2","type":"enum","symbols":["a"]}}]}}]}"#;
3304        let schema = Schema::parse_str(schema_str)?;
3305        let canonical_form = schema.canonical_form();
3306        assert_eq!(canonical_form, expected);
3307
3308        Ok(())
3309    }
3310
3311    #[test]
3312    fn test_avro_3779_bigdecimal_schema() -> TestResult {
3313        let schema = json!(
3314            {
3315                "name": "decimal",
3316                "type": "bytes",
3317                "logicalType": "big-decimal"
3318            }
3319        );
3320
3321        let parse_result = Schema::parse(&schema);
3322        assert!(
3323            parse_result.is_ok(),
3324            "parse result must be ok, got: {parse_result:?}"
3325        );
3326        match parse_result? {
3327            Schema::BigDecimal => (),
3328            other => panic!("Expected Schema::BigDecimal but got: {other:?}"),
3329        }
3330
3331        Ok(())
3332    }
3333
3334    #[test]
3335    fn test_avro_3820_deny_invalid_field_names() -> TestResult {
3336        let schema_str = r#"
3337        {
3338          "name": "my_record",
3339          "type": "record",
3340          "fields": [
3341            {
3342              "name": "f1.x",
3343              "type": {
3344                "name": "my_enum",
3345                "type": "enum",
3346                "symbols": ["a"]
3347              }
3348            },  {
3349              "name": "f2",
3350              "type": {
3351                "name": "my_fixed",
3352                "type": "fixed",
3353                "size": 1
3354              }
3355            }
3356          ]
3357        }
3358        "#;
3359
3360        match Schema::parse_str(schema_str).map_err(Error::into_details) {
3361            Err(Details::FieldName(x)) if x == "f1.x" => Ok(()),
3362            other => Err(format!("Expected Details::FieldName, got {other:?}").into()),
3363        }
3364    }
3365
3366    #[test]
3367    fn test_avro_3827_disallow_duplicate_field_names() -> TestResult {
3368        let schema_str = r#"
3369        {
3370          "name": "my_schema",
3371          "type": "record",
3372          "fields": [
3373            {
3374              "name": "f1",
3375              "type": {
3376                "name": "a",
3377                "type": "record",
3378                "fields": []
3379              }
3380            },  {
3381              "name": "f1",
3382              "type": {
3383                "name": "b",
3384                "type": "record",
3385                "fields": []
3386              }
3387            }
3388          ]
3389        }
3390        "#;
3391
3392        match Schema::parse_str(schema_str).map_err(Error::into_details) {
3393            Err(Details::FieldNameDuplicate(_)) => (),
3394            other => {
3395                return Err(format!("Expected Details::FieldNameDuplicate, got {other:?}").into());
3396            }
3397        };
3398
3399        let schema_str = r#"
3400        {
3401          "name": "my_schema",
3402          "type": "record",
3403          "fields": [
3404            {
3405              "name": "f1",
3406              "type": {
3407                "name": "a",
3408                "type": "record",
3409                "fields": [
3410                  {
3411                    "name": "f1",
3412                    "type": {
3413                      "name": "b",
3414                      "type": "record",
3415                      "fields": []
3416                    }
3417                  }
3418                ]
3419              }
3420            }
3421          ]
3422        }
3423        "#;
3424
3425        let expected = r#"{"name":"my_schema","type":"record","fields":[{"name":"f1","type":{"name":"a","type":"record","fields":[{"name":"f1","type":{"name":"b","type":"record","fields":[]}}]}}]}"#;
3426        let schema = Schema::parse_str(schema_str)?;
3427        let canonical_form = schema.canonical_form();
3428        assert_eq!(canonical_form, expected);
3429
3430        Ok(())
3431    }
3432
3433    #[test]
3434    fn test_avro_3830_null_namespace_in_fully_qualified_names() -> TestResult {
3435        // Check whether all the named types don't refer to the namespace field
3436        // if their name starts with a dot.
3437        let schema_str = r#"
3438        {
3439          "name": ".record1",
3440          "namespace": "ns1",
3441          "type": "record",
3442          "fields": [
3443            {
3444              "name": "f1",
3445              "type": {
3446                "name": ".enum1",
3447                "namespace": "ns2",
3448                "type": "enum",
3449                "symbols": ["a"]
3450              }
3451            },  {
3452              "name": "f2",
3453              "type": {
3454                "name": ".fxed1",
3455                "namespace": "ns3",
3456                "type": "fixed",
3457                "size": 1
3458              }
3459            }
3460          ]
3461        }
3462        "#;
3463
3464        let expected = r#"{"name":"record1","type":"record","fields":[{"name":"f1","type":{"name":"enum1","type":"enum","symbols":["a"]}},{"name":"f2","type":{"name":"fxed1","type":"fixed","size":1}}]}"#;
3465        let schema = Schema::parse_str(schema_str)?;
3466        let canonical_form = schema.canonical_form();
3467        assert_eq!(canonical_form, expected);
3468
3469        // Check whether inner types don't inherit ns1.
3470        let schema_str = r#"
3471        {
3472          "name": ".record1",
3473          "namespace": "ns1",
3474          "type": "record",
3475          "fields": [
3476            {
3477              "name": "f1",
3478              "type": {
3479                "name": "enum1",
3480                "type": "enum",
3481                "symbols": ["a"]
3482              }
3483            },  {
3484              "name": "f2",
3485              "type": {
3486                "name": "fxed1",
3487                "type": "fixed",
3488                "size": 1
3489              }
3490            }
3491          ]
3492        }
3493        "#;
3494
3495        let expected = r#"{"name":"record1","type":"record","fields":[{"name":"f1","type":{"name":"enum1","type":"enum","symbols":["a"]}},{"name":"f2","type":{"name":"fxed1","type":"fixed","size":1}}]}"#;
3496        let schema = Schema::parse_str(schema_str)?;
3497        let canonical_form = schema.canonical_form();
3498        assert_eq!(canonical_form, expected);
3499
3500        let name = Name::new(".my_name")?;
3501        let fullname = name.fullname(None);
3502        assert_eq!(fullname, "my_name");
3503        let qname = name.fully_qualified_name(None).to_string();
3504        assert_eq!(qname, "my_name");
3505
3506        Ok(())
3507    }
3508
3509    #[test]
3510    fn test_avro_3814_schema_resolution_failure() -> TestResult {
3511        // Define a reader schema: a nested record with an optional field.
3512        let reader_schema = json!(
3513            {
3514                "type": "record",
3515                "name": "MyOuterRecord",
3516                "fields": [
3517                    {
3518                        "name": "inner_record",
3519                        "type": [
3520                            "null",
3521                            {
3522                                "type": "record",
3523                                "name": "MyRecord",
3524                                "fields": [
3525                                    {"name": "a", "type": "string"}
3526                                ]
3527                            }
3528                        ],
3529                        "default": null
3530                    }
3531                ]
3532            }
3533        );
3534
3535        // Define a writer schema: a nested record with an optional field, which
3536        // may optionally contain an enum.
3537        let writer_schema = json!(
3538            {
3539                "type": "record",
3540                "name": "MyOuterRecord",
3541                "fields": [
3542                    {
3543                        "name": "inner_record",
3544                        "type": [
3545                            "null",
3546                            {
3547                                "type": "record",
3548                                "name": "MyRecord",
3549                                "fields": [
3550                                    {"name": "a", "type": "string"},
3551                                    {
3552                                        "name": "b",
3553                                        "type": [
3554                                            "null",
3555                                            {
3556                                                "type": "enum",
3557                                                "name": "MyEnum",
3558                                                "symbols": ["A", "B", "C"],
3559                                                "default": "C"
3560                                            }
3561                                        ],
3562                                        "default": null
3563                                    },
3564                                ]
3565                            }
3566                        ]
3567                    }
3568                ],
3569                "default": null
3570            }
3571        );
3572
3573        // Use different structs to represent the "Reader" and the "Writer"
3574        // to mimic two different versions of a producer & consumer application.
3575        #[derive(Serialize, Deserialize, Debug)]
3576        struct MyInnerRecordReader {
3577            a: String,
3578        }
3579
3580        #[derive(Serialize, Deserialize, Debug)]
3581        struct MyRecordReader {
3582            inner_record: Option<MyInnerRecordReader>,
3583        }
3584
3585        #[derive(Serialize, Deserialize, Debug)]
3586        enum MyEnum {
3587            A,
3588            B,
3589            C,
3590        }
3591
3592        #[derive(Serialize, Deserialize, Debug)]
3593        struct MyInnerRecordWriter {
3594            a: String,
3595            b: Option<MyEnum>,
3596        }
3597
3598        #[derive(Serialize, Deserialize, Debug)]
3599        struct MyRecordWriter {
3600            inner_record: Option<MyInnerRecordWriter>,
3601        }
3602
3603        let s = MyRecordWriter {
3604            inner_record: Some(MyInnerRecordWriter {
3605                a: "foo".to_string(),
3606                b: None,
3607            }),
3608        };
3609
3610        // Serialize using the writer schema.
3611        let writer_schema = Schema::parse(&writer_schema)?;
3612        let avro_value = crate::to_value(s)?;
3613        assert!(
3614            avro_value.validate(&writer_schema),
3615            "value is valid for schema",
3616        );
3617        let datum = GenericDatumWriter::builder(&writer_schema)
3618            .build()?
3619            .write_value_to_vec(avro_value)?;
3620
3621        // Now, attempt to deserialize using the reader schema.
3622        let reader_schema = Schema::parse(&reader_schema)?;
3623        let mut x = &datum[..];
3624
3625        // Deserialization should succeed and we should be able to resolve the schema.
3626        let deser_value = GenericDatumReader::builder(&writer_schema)
3627            .reader_schema(&reader_schema)
3628            .build()?
3629            .read_value(&mut x)?;
3630        assert!(deser_value.validate(&reader_schema));
3631
3632        // Verify that we can read a field from the record.
3633        let d: MyRecordReader = crate::from_value(&deser_value)?;
3634        assert_eq!(d.inner_record.unwrap().a, "foo".to_string());
3635        Ok(())
3636    }
3637
3638    #[test]
3639    fn test_avro_3837_disallow_invalid_namespace() -> TestResult {
3640        // Valid namespace #1 (Single name portion)
3641        let schema_str = r#"
3642        {
3643          "name": "record1",
3644          "namespace": "ns1",
3645          "type": "record",
3646          "fields": []
3647        }
3648        "#;
3649
3650        let expected = r#"{"name":"ns1.record1","type":"record","fields":[]}"#;
3651        let schema = Schema::parse_str(schema_str)?;
3652        let canonical_form = schema.canonical_form();
3653        assert_eq!(canonical_form, expected);
3654
3655        // Valid namespace #2 (multiple name portions).
3656        let schema_str = r#"
3657        {
3658          "name": "enum1",
3659          "namespace": "ns1.foo.bar",
3660          "type": "enum",
3661          "symbols": ["a"]
3662        }
3663        "#;
3664
3665        let expected = r#"{"name":"ns1.foo.bar.enum1","type":"enum","symbols":["a"]}"#;
3666        let schema = Schema::parse_str(schema_str)?;
3667        let canonical_form = schema.canonical_form();
3668        assert_eq!(canonical_form, expected);
3669
3670        // Invalid namespace #1 (a name portion starts with dot)
3671        let schema_str = r#"
3672        {
3673          "name": "fixed1",
3674          "namespace": ".ns1.a.b",
3675          "type": "fixed",
3676          "size": 1
3677        }
3678        "#;
3679
3680        match Schema::parse_str(schema_str).map_err(Error::into_details) {
3681            Err(Details::InvalidNamespace(_, _)) => (),
3682            other => {
3683                return Err(format!("Expected Details::InvalidNamespace, got {other:?}").into());
3684            }
3685        };
3686
3687        // Invalid namespace #2 (invalid character in a name portion)
3688        let schema_str = r#"
3689        {
3690          "name": "record1",
3691          "namespace": "ns1.a*b.c",
3692          "type": "record",
3693          "fields": []
3694        }
3695        "#;
3696
3697        match Schema::parse_str(schema_str).map_err(Error::into_details) {
3698            Err(Details::InvalidNamespace(_, _)) => (),
3699            other => {
3700                return Err(format!("Expected Details::InvalidNamespace, got {other:?}").into());
3701            }
3702        };
3703
3704        // Invalid namespace #3 (a name portion starts with a digit)
3705        let schema_str = r#"
3706        {
3707          "name": "fixed1",
3708          "namespace": "ns1.1a.b",
3709          "type": "fixed",
3710          "size": 1
3711        }
3712        "#;
3713
3714        match Schema::parse_str(schema_str).map_err(Error::into_details) {
3715            Err(Details::InvalidNamespace(_, _)) => (),
3716            other => {
3717                return Err(format!("Expected Details::InvalidNamespace, got {other:?}").into());
3718            }
3719        };
3720
3721        // Invalid namespace #4 (a name portion is missing - two dots in a row)
3722        let schema_str = r#"
3723        {
3724          "name": "fixed1",
3725          "namespace": "ns1..a",
3726          "type": "fixed",
3727          "size": 1
3728        }
3729        "#;
3730
3731        match Schema::parse_str(schema_str).map_err(Error::into_details) {
3732            Err(Details::InvalidNamespace(_, _)) => (),
3733            other => {
3734                return Err(format!("Expected Details::InvalidNamespace, got {other:?}").into());
3735            }
3736        };
3737
3738        // Invalid namespace #5 (a name portion is missing - ends with a dot)
3739        let schema_str = r#"
3740        {
3741          "name": "fixed1",
3742          "namespace": "ns1.a.",
3743          "type": "fixed",
3744          "size": 1
3745        }
3746        "#;
3747
3748        match Schema::parse_str(schema_str).map_err(Error::into_details) {
3749            Err(Details::InvalidNamespace(_, _)) => (),
3750            other => {
3751                return Err(format!("Expected Details::InvalidNamespace, got {other:?}").into());
3752            }
3753        };
3754
3755        Ok(())
3756    }
3757
3758    #[test]
3759    fn test_avro_3851_validate_default_value_of_simple_record_field() -> TestResult {
3760        let schema_str = r#"
3761        {
3762            "name": "record1",
3763            "namespace": "ns",
3764            "type": "record",
3765            "fields": [
3766                {
3767                    "name": "f1",
3768                    "type": "int",
3769                    "default": "invalid"
3770                }
3771            ]
3772        }
3773        "#;
3774        assert_eq!(
3775            Schema::parse_str(schema_str).unwrap_err().to_string(),
3776            r#"`default`'s value type of field `f1` in `ns.record1` must be a `"int"`. Got: String("invalid")"#
3777        );
3778
3779        Ok(())
3780    }
3781
3782    #[test]
3783    fn test_avro_3851_validate_default_value_of_nested_record_field() -> TestResult {
3784        let schema_str = r#"
3785        {
3786            "name": "record1",
3787            "namespace": "ns",
3788            "type": "record",
3789            "fields": [
3790                {
3791                    "name": "f1",
3792                    "type": {
3793                        "name": "record2",
3794                        "type": "record",
3795                        "fields": [
3796                            {
3797                                "name": "f1_1",
3798                                "type": "int"
3799                            }
3800                        ]
3801                    },
3802                    "default": "invalid"
3803                }
3804            ]
3805        }
3806        "#;
3807        assert_eq!(
3808            Schema::parse_str(schema_str).unwrap_err().to_string(),
3809            r#"`default`'s value type of field `f1` in `ns.record1` must be a `{"name":"ns.record2","type":"record","fields":[{"name":"f1_1","type":"int"}]}`. Got: String("invalid")"#
3810        );
3811
3812        Ok(())
3813    }
3814
3815    #[test]
3816    fn test_avro_3851_validate_default_value_of_enum_record_field() -> TestResult {
3817        let schema_str = r#"
3818        {
3819            "name": "record1",
3820            "namespace": "ns",
3821            "type": "record",
3822            "fields": [
3823                {
3824                    "name": "f1",
3825                    "type": {
3826                        "name": "enum1",
3827                        "type": "enum",
3828                        "symbols": ["a", "b", "c"]
3829                    },
3830                    "default": "invalid"
3831                }
3832            ]
3833        }
3834        "#;
3835        assert_eq!(
3836            Schema::parse_str(schema_str).unwrap_err().to_string(),
3837            r#"`default`'s value type of field `f1` in `ns.record1` must be a `{"name":"ns.enum1","type":"enum","symbols":["a","b","c"]}`. Got: String("invalid")"#
3838        );
3839
3840        Ok(())
3841    }
3842
3843    #[test]
3844    fn test_avro_3851_validate_default_value_of_fixed_record_field() -> TestResult {
3845        let schema_str = r#"
3846        {
3847            "name": "record1",
3848            "namespace": "ns",
3849            "type": "record",
3850            "fields": [
3851                {
3852                    "name": "f1",
3853                    "type": {
3854                        "name": "fixed1",
3855                        "type": "fixed",
3856                        "size": 3
3857                    },
3858                    "default": 100
3859                }
3860            ]
3861        }
3862        "#;
3863        assert_eq!(
3864            Schema::parse_str(schema_str).unwrap_err().to_string(),
3865            r#"`default`'s value type of field `f1` in `ns.record1` must be a `{"name":"ns.fixed1","type":"fixed","size":3}`. Got: Number(100)"#
3866        );
3867
3868        Ok(())
3869    }
3870
3871    #[test]
3872    fn test_avro_3851_validate_default_value_of_array_record_field() -> TestResult {
3873        let schema_str = r#"
3874        {
3875            "name": "record1",
3876            "namespace": "ns",
3877            "type": "record",
3878            "fields": [
3879                {
3880                    "name": "f1",
3881                    "type": {
3882                        "type": "array",
3883                        "items": "int"
3884                    },
3885                    "default": "invalid"
3886                }
3887            ]
3888        }
3889        "#;
3890
3891        let result = Schema::parse_str(schema_str);
3892        assert!(result.is_err());
3893        let err = result
3894            .map_err(|e| e.to_string())
3895            .err()
3896            .unwrap_or_else(|| "unexpected".to_string());
3897        assert_eq!(
3898            r#"`default`'s value type of field `f1` in `ns.record1` must be a `{"type":"array","items":"int"}`. Got: String("invalid")"#,
3899            err
3900        );
3901
3902        Ok(())
3903    }
3904
3905    #[test]
3906    fn test_avro_3851_validate_default_value_of_map_record_field() -> TestResult {
3907        let schema_str = r#"
3908        {
3909            "name": "record1",
3910            "namespace": "ns",
3911            "type": "record",
3912            "fields": [
3913                {
3914                    "name": "f1",
3915                    "type": {
3916                        "type": "map",
3917                        "values": "string"
3918                    },
3919                    "default": "invalid"
3920                }
3921            ]
3922        }
3923        "#;
3924
3925        let result = Schema::parse_str(schema_str);
3926        assert!(result.is_err());
3927        let err = result
3928            .map_err(|e| e.to_string())
3929            .err()
3930            .unwrap_or_else(|| "unexpected".to_string());
3931        assert_eq!(
3932            r#"`default`'s value type of field `f1` in `ns.record1` must be a `{"type":"map","values":"string"}`. Got: String("invalid")"#,
3933            err
3934        );
3935
3936        Ok(())
3937    }
3938
3939    #[test]
3940    fn test_avro_3851_validate_default_value_of_ref_record_field() -> TestResult {
3941        let schema_str = r#"
3942        {
3943            "name": "record1",
3944            "namespace": "ns",
3945            "type": "record",
3946            "fields": [
3947                {
3948                    "name": "f1",
3949                    "type": {
3950                        "name": "record2",
3951                        "type": "record",
3952                        "fields": [
3953                            {
3954                                "name": "f1_1",
3955                                "type": "int"
3956                            }
3957                        ]
3958                    }
3959                },  {
3960                    "name": "f2",
3961                    "type": "ns.record2",
3962                    "default": { "f1_1": true }
3963                }
3964            ]
3965        }
3966        "#;
3967        assert_eq!(
3968            Schema::parse_str(schema_str).unwrap_err().to_string(),
3969            r#"`default`'s value type of field `f2` in `ns.record1` must be a `{"name":"ns.record2","type":"record","fields":[{"name":"f1_1","type":"int"}]}`. Got: Object {"f1_1": Bool(true)}"#
3970        );
3971
3972        Ok(())
3973    }
3974
3975    #[test]
3976    fn test_avro_3851_validate_default_value_of_enum() -> TestResult {
3977        let schema_str = r#"
3978        {
3979            "name": "enum1",
3980            "namespace": "ns",
3981            "type": "enum",
3982            "symbols": ["a", "b", "c"],
3983            "default": 100
3984        }
3985        "#;
3986        let expected = Details::EnumDefaultWrongType(100.into()).to_string();
3987        let result = Schema::parse_str(schema_str);
3988        assert!(result.is_err());
3989        let err = result
3990            .map_err(|e| e.to_string())
3991            .err()
3992            .unwrap_or_else(|| "unexpected".to_string());
3993        assert_eq!(expected, err);
3994
3995        let schema_str = r#"
3996        {
3997            "name": "enum1",
3998            "namespace": "ns",
3999            "type": "enum",
4000            "symbols": ["a", "b", "c"],
4001            "default": "d"
4002        }
4003        "#;
4004        let expected = Details::GetEnumDefault {
4005            symbol: "d".to_string(),
4006            symbols: vec!["a".to_string(), "b".to_string(), "c".to_string()],
4007        }
4008        .to_string();
4009        let result = Schema::parse_str(schema_str);
4010        assert!(result.is_err());
4011        let err = result
4012            .map_err(|e| e.to_string())
4013            .err()
4014            .unwrap_or_else(|| "unexpected".to_string());
4015        assert_eq!(expected, err);
4016
4017        Ok(())
4018    }
4019
4020    #[test]
4021    fn test_avro_3862_get_aliases() -> TestResult {
4022        // Test for Record
4023        let schema_str = r#"
4024        {
4025            "name": "record1",
4026            "namespace": "ns1",
4027            "type": "record",
4028            "aliases": ["r1", "ns2.r2"],
4029            "fields": [
4030                { "name": "f1", "type": "int" },
4031                { "name": "f2", "type": "string" }
4032            ]
4033        }
4034        "#;
4035        let schema = Schema::parse_str(schema_str)?;
4036        let expected = vec![Alias::new("ns1.r1")?, Alias::new("ns2.r2")?];
4037        match schema.aliases() {
4038            Some(aliases) => assert_eq!(aliases, &expected),
4039            None => panic!("Expected Some({expected:?}), got None"),
4040        }
4041
4042        let schema_str = r#"
4043        {
4044            "name": "record1",
4045            "namespace": "ns1",
4046            "type": "record",
4047            "fields": [
4048                { "name": "f1", "type": "int" },
4049                { "name": "f2", "type": "string" }
4050            ]
4051        }
4052        "#;
4053        let schema = Schema::parse_str(schema_str)?;
4054        match schema.aliases() {
4055            None => (),
4056            some => panic!("Expected None, got {some:?}"),
4057        }
4058
4059        // Test for Enum
4060        let schema_str = r#"
4061        {
4062            "name": "enum1",
4063            "namespace": "ns1",
4064            "type": "enum",
4065            "aliases": ["en1", "ns2.en2"],
4066            "symbols": ["a", "b", "c"]
4067        }
4068        "#;
4069        let schema = Schema::parse_str(schema_str)?;
4070        let expected = vec![Alias::new("ns1.en1")?, Alias::new("ns2.en2")?];
4071        match schema.aliases() {
4072            Some(aliases) => assert_eq!(aliases, &expected),
4073            None => panic!("Expected Some({expected:?}), got None"),
4074        }
4075
4076        let schema_str = r#"
4077        {
4078            "name": "enum1",
4079            "namespace": "ns1",
4080            "type": "enum",
4081            "symbols": ["a", "b", "c"]
4082        }
4083        "#;
4084        let schema = Schema::parse_str(schema_str)?;
4085        match schema.aliases() {
4086            None => (),
4087            some => panic!("Expected None, got {some:?}"),
4088        }
4089
4090        // Test for Fixed
4091        let schema_str = r#"
4092        {
4093            "name": "fixed1",
4094            "namespace": "ns1",
4095            "type": "fixed",
4096            "aliases": ["fx1", "ns2.fx2"],
4097            "size": 10
4098        }
4099        "#;
4100        let schema = Schema::parse_str(schema_str)?;
4101        let expected = vec![Alias::new("ns1.fx1")?, Alias::new("ns2.fx2")?];
4102        match schema.aliases() {
4103            Some(aliases) => assert_eq!(aliases, &expected),
4104            None => panic!("Expected Some({expected:?}), got None"),
4105        }
4106
4107        let schema_str = r#"
4108        {
4109            "name": "fixed1",
4110            "namespace": "ns1",
4111            "type": "fixed",
4112            "size": 10
4113        }
4114        "#;
4115        let schema = Schema::parse_str(schema_str)?;
4116        match schema.aliases() {
4117            None => (),
4118            some => panic!("Expected None, got {some:?}"),
4119        }
4120
4121        // Test for non-named type
4122        let schema = Schema::Int;
4123        match schema.aliases() {
4124            None => (),
4125            some => panic!("Expected None, got {some:?}"),
4126        }
4127
4128        Ok(())
4129    }
4130
4131    #[test]
4132    fn test_avro_3862_get_doc() -> TestResult {
4133        // Test for Record
4134        let schema_str = r#"
4135        {
4136            "name": "record1",
4137            "type": "record",
4138            "doc": "Record Document",
4139            "fields": [
4140                { "name": "f1", "type": "int" },
4141                { "name": "f2", "type": "string" }
4142            ]
4143        }
4144        "#;
4145        let schema = Schema::parse_str(schema_str)?;
4146        let expected = "Record Document";
4147        match schema.doc() {
4148            Some(doc) => assert_eq!(doc, expected),
4149            None => panic!("Expected Some({expected:?}), got None"),
4150        }
4151
4152        let schema_str = r#"
4153        {
4154            "name": "record1",
4155            "type": "record",
4156            "fields": [
4157                { "name": "f1", "type": "int" },
4158                { "name": "f2", "type": "string" }
4159            ]
4160        }
4161        "#;
4162        let schema = Schema::parse_str(schema_str)?;
4163        match schema.doc() {
4164            None => (),
4165            some => panic!("Expected None, got {some:?}"),
4166        }
4167
4168        // Test for Enum
4169        let schema_str = r#"
4170        {
4171            "name": "enum1",
4172            "type": "enum",
4173            "doc": "Enum Document",
4174            "symbols": ["a", "b", "c"]
4175        }
4176        "#;
4177        let schema = Schema::parse_str(schema_str)?;
4178        let expected = "Enum Document";
4179        match schema.doc() {
4180            Some(doc) => assert_eq!(doc, expected),
4181            None => panic!("Expected Some({expected:?}), got None"),
4182        }
4183
4184        let schema_str = r#"
4185        {
4186            "name": "enum1",
4187            "type": "enum",
4188            "symbols": ["a", "b", "c"]
4189        }
4190        "#;
4191        let schema = Schema::parse_str(schema_str)?;
4192        match schema.doc() {
4193            None => (),
4194            some => panic!("Expected None, got {some:?}"),
4195        }
4196
4197        // Test for Fixed
4198        let schema_str = r#"
4199        {
4200            "name": "fixed1",
4201            "type": "fixed",
4202            "doc": "Fixed Document",
4203            "size": 10
4204        }
4205        "#;
4206        let schema = Schema::parse_str(schema_str)?;
4207        let expected = "Fixed Document";
4208        match schema.doc() {
4209            Some(doc) => assert_eq!(doc, expected),
4210            None => panic!("Expected Some({expected:?}), got None"),
4211        }
4212
4213        let schema_str = r#"
4214        {
4215            "name": "fixed1",
4216            "type": "fixed",
4217            "size": 10
4218        }
4219        "#;
4220        let schema = Schema::parse_str(schema_str)?;
4221        match schema.doc() {
4222            None => (),
4223            some => panic!("Expected None, got {some:?}"),
4224        }
4225
4226        // Test for non-named type
4227        let schema = Schema::Int;
4228        match schema.doc() {
4229            None => (),
4230            some => panic!("Expected None, got {some:?}"),
4231        }
4232
4233        Ok(())
4234    }
4235
4236    #[test]
4237    fn avro_3886_serialize_attributes() -> TestResult {
4238        let attributes = BTreeMap::from([
4239            ("string_key".into(), "value".into()),
4240            ("number_key".into(), 1.23.into()),
4241            ("null_key".into(), JsonValue::Null),
4242            (
4243                "array_key".into(),
4244                JsonValue::Array(vec![1.into(), 2.into(), 3.into()]),
4245            ),
4246            ("object_key".into(), JsonValue::Object(Map::default())),
4247        ]);
4248
4249        // Test serialize enum attributes
4250        let schema = Schema::Enum(EnumSchema {
4251            name: Name::new("a")?,
4252            aliases: None,
4253            doc: None,
4254            symbols: vec![],
4255            default: None,
4256            attributes: attributes.clone(),
4257        });
4258        let serialized = serde_json::to_string(&schema)?;
4259        assert_eq!(
4260            r#"{"type":"enum","name":"a","symbols":[],"array_key":[1,2,3],"null_key":null,"number_key":1.23,"object_key":{},"string_key":"value"}"#,
4261            &serialized
4262        );
4263
4264        // Test serialize fixed custom_attributes
4265        let schema = Schema::Fixed(FixedSchema {
4266            name: Name::new("a")?,
4267            aliases: None,
4268            doc: None,
4269            size: 1,
4270            attributes: attributes.clone(),
4271        });
4272        let serialized = serde_json::to_string(&schema)?;
4273        assert_eq!(
4274            r#"{"type":"fixed","name":"a","size":1,"array_key":[1,2,3],"null_key":null,"number_key":1.23,"object_key":{},"string_key":"value"}"#,
4275            &serialized
4276        );
4277
4278        // Test serialize record custom_attributes
4279        let schema = Schema::Record(RecordSchema {
4280            name: Name::new("a")?,
4281            aliases: None,
4282            doc: None,
4283            fields: vec![],
4284            lookup: BTreeMap::new(),
4285            attributes,
4286        });
4287        let serialized = serde_json::to_string(&schema)?;
4288        assert_eq!(
4289            r#"{"type":"record","name":"a","fields":[],"array_key":[1,2,3],"null_key":null,"number_key":1.23,"object_key":{},"string_key":"value"}"#,
4290            &serialized
4291        );
4292
4293        Ok(())
4294    }
4295
4296    #[test]
4297    fn test_avro_3896_decimal_schema() -> TestResult {
4298        // bytes decimal, represented as native logical type.
4299        let schema = json!(
4300        {
4301          "type": "bytes",
4302          "name": "BytesDecimal",
4303          "logicalType": "decimal",
4304          "size": 38,
4305          "precision": 9,
4306          "scale": 2
4307        });
4308        let parse_result = Schema::parse(&schema)?;
4309        assert!(matches!(
4310            parse_result,
4311            Schema::Decimal(DecimalSchema {
4312                precision: 9,
4313                scale: 2,
4314                ..
4315            })
4316        ));
4317
4318        // long decimal, represents as native complex type.
4319        let schema = json!(
4320        {
4321          "type": "long",
4322          "name": "LongDecimal",
4323          "logicalType": "decimal"
4324        });
4325        let parse_result = Schema::parse(&schema)?;
4326        // assert!(matches!(parse_result, Schema::Long));
4327        assert_eq!(parse_result, Schema::Long);
4328
4329        Ok(())
4330    }
4331
4332    #[test]
4333    fn avro_3896_uuid_schema_for_string() -> TestResult {
4334        // string uuid, represents as native logical type.
4335        let schema = json!(
4336        {
4337          "type": "string",
4338          "name": "StringUUID",
4339          "logicalType": "uuid"
4340        });
4341        let parse_result = Schema::parse(&schema)?;
4342        assert_eq!(parse_result, Schema::Uuid(UuidSchema::String));
4343
4344        Ok(())
4345    }
4346
4347    #[test]
4348    fn avro_3926_uuid_schema_for_fixed_with_size_16() -> TestResult {
4349        let schema = json!(
4350        {
4351            "type": "fixed",
4352            "name": "FixedUUID",
4353            "size": 16,
4354            "logicalType": "uuid"
4355        });
4356        let parse_result = Schema::parse(&schema)?;
4357        assert_eq!(
4358            parse_result,
4359            Schema::Uuid(UuidSchema::Fixed(FixedSchema {
4360                name: Name::new("FixedUUID")?,
4361                aliases: None,
4362                doc: None,
4363                size: 16,
4364                attributes: Default::default(),
4365            }))
4366        );
4367        assert_not_logged(
4368            r#"Ignoring uuid logical type for a Fixed schema because its size (6) is not 16! Schema: Fixed(FixedSchema { name: Name { name: "FixedUUID", namespace: None }, aliases: None, doc: None, size: 6, attributes: {"logicalType": String("uuid")} })"#,
4369        );
4370
4371        Ok(())
4372    }
4373
4374    #[test]
4375    fn uuid_schema_bytes() -> TestResult {
4376        let schema = json!(
4377        {
4378          "type": "bytes",
4379          "name": "BytesUUID",
4380          "logicalType": "uuid"
4381        });
4382        let parse_result = Schema::parse(&schema)?;
4383        assert_eq!(parse_result, Schema::Uuid(UuidSchema::Bytes));
4384
4385        Ok(())
4386    }
4387
4388    #[test]
4389    fn avro_3926_uuid_schema_for_fixed_with_size_different_than_16() -> TestResult {
4390        let schema = json!(
4391        {
4392            "type": "fixed",
4393            "name": "FixedUUID",
4394            "size": 6,
4395            "logicalType": "uuid"
4396        });
4397        let parse_result = Schema::parse(&schema)?;
4398
4399        assert_eq!(
4400            parse_result,
4401            Schema::Fixed(FixedSchema {
4402                name: Name::new("FixedUUID")?,
4403                aliases: None,
4404                doc: None,
4405                size: 6,
4406                attributes: BTreeMap::new(),
4407            })
4408        );
4409        assert_logged(
4410            r#"Ignoring uuid logical type for a Fixed schema because its size (6) is not 16! Schema: Fixed(FixedSchema { name: Name { name: "FixedUUID", .. }, size: 6, .. })"#,
4411        );
4412
4413        Ok(())
4414    }
4415
4416    #[test]
4417    fn test_avro_3896_timestamp_millis_schema() -> TestResult {
4418        // long timestamp-millis, represents as native logical type.
4419        let schema = json!(
4420        {
4421          "type": "long",
4422          "name": "LongTimestampMillis",
4423          "logicalType": "timestamp-millis"
4424        });
4425        let parse_result = Schema::parse(&schema)?;
4426        assert_eq!(parse_result, Schema::TimestampMillis);
4427
4428        // int timestamp-millis, represents as native complex type.
4429        let schema = json!(
4430        {
4431            "type": "int",
4432            "name": "IntTimestampMillis",
4433            "logicalType": "timestamp-millis"
4434        });
4435        let parse_result = Schema::parse(&schema)?;
4436        assert_eq!(parse_result, Schema::Int);
4437
4438        Ok(())
4439    }
4440
4441    #[test]
4442    fn test_avro_3896_custom_bytes_schema() -> TestResult {
4443        // log type, represents as complex type.
4444        let schema = json!(
4445        {
4446            "type": "bytes",
4447            "name": "BytesLog",
4448            "logicalType": "custom"
4449        });
4450        let parse_result = Schema::parse(&schema)?;
4451        assert_eq!(parse_result, Schema::Bytes);
4452        assert_eq!(parse_result.custom_attributes(), None);
4453
4454        Ok(())
4455    }
4456
4457    #[test]
4458    fn test_avro_3899_parse_decimal_type() -> TestResult {
4459        let schema = Schema::parse_str(
4460            r#"{
4461             "name": "InvalidDecimal",
4462             "type": "fixed",
4463             "size": 16,
4464             "logicalType": "decimal",
4465             "precision": 2,
4466             "scale": 3
4467         }"#,
4468        )?;
4469        match schema {
4470            Schema::Fixed(fixed_schema) => {
4471                let attrs = fixed_schema.attributes;
4472                let precision = attrs
4473                    .get("precision")
4474                    .expect("The 'precision' attribute is missing");
4475                let scale = attrs
4476                    .get("scale")
4477                    .expect("The 'scale' attribute is missing");
4478                assert_logged(&format!(
4479                    "Ignoring invalid decimal logical type: The decimal precision ({precision}) must be bigger or equal to the scale ({scale})"
4480                ));
4481            }
4482            _ => unreachable!("Expected Schema::Fixed, got {:?}", schema),
4483        }
4484
4485        let schema = Schema::parse_str(
4486            r#"{
4487            "name": "ValidDecimal",
4488             "type": "bytes",
4489             "logicalType": "decimal",
4490             "precision": 3,
4491             "scale": 2
4492         }"#,
4493        )?;
4494        match schema {
4495            Schema::Decimal(_) => {
4496                assert_not_logged(
4497                    "Ignoring invalid decimal logical type: The decimal precision (2) must be bigger or equal to the scale (3)",
4498                );
4499            }
4500            _ => unreachable!("Expected Schema::Decimal, got {:?}", schema),
4501        }
4502
4503        Ok(())
4504    }
4505
4506    #[test]
4507    fn avro_3920_serialize_record_with_custom_attributes() -> TestResult {
4508        let expected = {
4509            let mut lookup = BTreeMap::new();
4510            lookup.insert("value".to_owned(), 0);
4511            Schema::Record(RecordSchema {
4512                name: Name::new("LongList")?,
4513                aliases: Some(vec![Alias::new("LinkedLongs").unwrap()]),
4514                doc: None,
4515                fields: vec![
4516                    RecordField::builder()
4517                        .name("value".to_string())
4518                        .schema(Schema::Long)
4519                        .custom_attributes(BTreeMap::from([("field-id".to_string(), 1.into())]))
4520                        .build(),
4521                ],
4522                lookup,
4523                attributes: BTreeMap::from([("custom-attribute".to_string(), "value".into())]),
4524            })
4525        };
4526
4527        let value = serde_json::to_value(&expected)?;
4528        let serialized = serde_json::to_string(&value)?;
4529        assert_eq!(
4530            r#"{"aliases":["LinkedLongs"],"custom-attribute":"value","fields":[{"field-id":1,"name":"value","type":"long"}],"name":"LongList","type":"record"}"#,
4531            &serialized
4532        );
4533        assert_eq!(expected, Schema::parse_str(&serialized)?);
4534
4535        Ok(())
4536    }
4537
4538    #[test]
4539    fn test_avro_3925_serialize_decimal_inner_fixed() -> TestResult {
4540        let schema = Schema::Decimal(DecimalSchema {
4541            precision: 36,
4542            scale: 10,
4543            inner: InnerDecimalSchema::Fixed(FixedSchema {
4544                name: Name::new("decimal_36_10").unwrap(),
4545                aliases: None,
4546                doc: None,
4547                size: 16,
4548                attributes: Default::default(),
4549            }),
4550        });
4551
4552        let serialized_json = serde_json::to_string_pretty(&schema)?;
4553
4554        let expected_json = r#"{
4555  "type": "fixed",
4556  "name": "decimal_36_10",
4557  "size": 16,
4558  "logicalType": "decimal",
4559  "scale": 10,
4560  "precision": 36
4561}"#;
4562
4563        assert_eq!(serialized_json, expected_json);
4564
4565        Ok(())
4566    }
4567
4568    #[test]
4569    fn test_avro_3925_serialize_decimal_inner_bytes() -> TestResult {
4570        let schema = Schema::Decimal(DecimalSchema {
4571            precision: 36,
4572            scale: 10,
4573            inner: InnerDecimalSchema::Bytes,
4574        });
4575
4576        let serialized_json = serde_json::to_string_pretty(&schema)?;
4577
4578        let expected_json = r#"{
4579  "type": "bytes",
4580  "logicalType": "decimal",
4581  "scale": 10,
4582  "precision": 36
4583}"#;
4584
4585        assert_eq!(serialized_json, expected_json);
4586
4587        Ok(())
4588    }
4589
4590    #[test]
4591    fn test_avro_3927_serialize_array_with_custom_attributes() -> TestResult {
4592        let expected = Schema::array(Schema::Long)
4593            .attributes(BTreeMap::from([("field-id".to_string(), "1".into())]))
4594            .build();
4595
4596        let value = serde_json::to_value(&expected)?;
4597        let serialized = serde_json::to_string(&value)?;
4598        assert_eq!(
4599            r#"{"field-id":"1","items":"long","type":"array"}"#,
4600            &serialized
4601        );
4602        let actual_schema = Schema::parse_str(&serialized)?;
4603        assert_eq!(expected, actual_schema);
4604        assert_eq!(
4605            expected.custom_attributes(),
4606            actual_schema.custom_attributes()
4607        );
4608
4609        Ok(())
4610    }
4611
4612    #[test]
4613    fn test_avro_3927_serialize_map_with_custom_attributes() -> TestResult {
4614        let expected = Schema::map(Schema::Long)
4615            .attributes(BTreeMap::from([("field-id".to_string(), "1".into())]))
4616            .build();
4617
4618        let value = serde_json::to_value(&expected)?;
4619        let serialized = serde_json::to_string(&value)?;
4620        assert_eq!(
4621            r#"{"field-id":"1","type":"map","values":"long"}"#,
4622            &serialized
4623        );
4624        let actual_schema = Schema::parse_str(&serialized)?;
4625        assert_eq!(expected, actual_schema);
4626        assert_eq!(
4627            expected.custom_attributes(),
4628            actual_schema.custom_attributes()
4629        );
4630
4631        Ok(())
4632    }
4633
4634    #[test]
4635    fn avro_3928_parse_int_based_schema_with_default() -> TestResult {
4636        let schema = r#"
4637        {
4638          "type": "record",
4639          "name": "DateLogicalType",
4640          "fields": [ {
4641            "name": "birthday",
4642            "type": {"type": "int", "logicalType": "date"},
4643            "default": 1681601653
4644          } ]
4645        }"#;
4646
4647        match Schema::parse_str(schema)? {
4648            Schema::Record(record_schema) => {
4649                assert_eq!(record_schema.fields.len(), 1);
4650                let field = record_schema.fields.first().unwrap();
4651                assert_eq!(field.name, "birthday");
4652                assert_eq!(field.schema, Schema::Date);
4653                assert_eq!(
4654                    types::Value::try_from(field.default.clone().unwrap())?,
4655                    types::Value::Int(1681601653)
4656                );
4657            }
4658            _ => unreachable!("Expected Schema::Record"),
4659        }
4660
4661        Ok(())
4662    }
4663
4664    #[test]
4665    fn avro_3946_union_with_single_type() -> TestResult {
4666        let schema = r#"
4667        {
4668          "type": "record",
4669          "name": "Issue",
4670          "namespace": "invalid.example",
4671          "fields": [
4672            {
4673              "name": "myField",
4674              "type": ["long"]
4675            }
4676          ]
4677        }"#;
4678
4679        let _ = Schema::parse_str(schema)?;
4680
4681        assert_logged(
4682            "Union schema with just one member! Consider dropping the union! \
4683                    Please enable debug logging to find out which Record schema \
4684                    declares the union with 'RUST_LOG=apache_avro::schema=debug'.",
4685        );
4686
4687        Ok(())
4688    }
4689
4690    #[test]
4691    fn avro_3946_union_without_any_types() -> TestResult {
4692        let schema = r#"
4693        {
4694          "type": "record",
4695          "name": "Issue",
4696          "namespace": "invalid.example",
4697          "fields": [
4698            {
4699              "name": "myField",
4700              "type": []
4701            }
4702          ]
4703        }"#;
4704
4705        let _ = Schema::parse_str(schema)?;
4706
4707        assert_logged(
4708            "Union schemas should have at least two members! \
4709                    Please enable debug logging to find out which Record schema \
4710                    declares the union with 'RUST_LOG=apache_avro::schema=debug'.",
4711        );
4712
4713        Ok(())
4714    }
4715
4716    #[test]
4717    fn avro_4004_canonical_form_strip_logical_types() -> TestResult {
4718        let schema_str = r#"
4719      {
4720        "type": "record",
4721        "name": "test",
4722        "fields": [
4723            {"name": "a", "type": "long", "default": 42, "doc": "The field a"},
4724            {"name": "b", "type": "string", "namespace": "test.a"},
4725            {"name": "c", "type": {"type": "long", "logicalType": "timestamp-micros"}}
4726        ]
4727    }"#;
4728
4729        let schema = Schema::parse_str(schema_str)?;
4730        let canonical_form = schema.canonical_form();
4731        let fp_rabin = schema.fingerprint::<Rabin>();
4732        assert_eq!(
4733            r#"{"name":"test","type":"record","fields":[{"name":"a","type":"long"},{"name":"b","type":"string"},{"name":"c","type":{"type":"long"}}]}"#,
4734            canonical_form
4735        );
4736        assert_eq!("92f2ccef718c6754", fp_rabin.to_string());
4737        Ok(())
4738    }
4739
4740    #[test]
4741    fn avro_4055_should_fail_to_parse_invalid_schema() -> TestResult {
4742        // This is invalid because the record type should be inside the type field.
4743        let invalid_schema_str = r#"
4744        {
4745        "type": "record",
4746        "name": "SampleSchema",
4747        "fields": [
4748            {
4749            "name": "order",
4750            "type": "record",
4751            "fields": [
4752                {
4753                "name": "order_number",
4754                "type": ["null", "string"],
4755                "default": null
4756                },
4757                { "name": "order_date", "type": "string" }
4758            ]
4759            }
4760        ]
4761        }"#;
4762
4763        let schema = Schema::parse_str(invalid_schema_str);
4764        assert!(schema.is_err());
4765        assert_eq!(
4766            schema.unwrap_err().to_string(),
4767            "Invalid schema: There is no type called 'record', if you meant to define a non-primitive schema, it should be defined inside `type` attribute."
4768        );
4769
4770        let valid_schema = r#"
4771        {
4772            "type": "record",
4773            "name": "SampleSchema",
4774            "fields": [
4775                {
4776                "name": "order",
4777                "type": {
4778                    "type": "record",
4779                    "name": "Order",
4780                    "fields": [
4781                    {
4782                        "name": "order_number",
4783                        "type": ["null", "string"],
4784                        "default": null
4785                    },
4786                    { "name": "order_date", "type": "string" }
4787                    ]
4788                }
4789                }
4790            ]
4791        }"#;
4792        let schema = Schema::parse_str(valid_schema);
4793        assert!(schema.is_ok());
4794
4795        Ok(())
4796    }
4797
4798    #[test]
4799    fn avro_rs_292_array_items_should_be_ignored_in_custom_attributes() -> TestResult {
4800        let raw_schema = r#"{
4801                    "type": "array",
4802                    "items": {
4803                        "name": "foo",
4804                        "type": "record",
4805                        "fields": [
4806                            {
4807                                "name": "bar",
4808                                "type": {
4809                                    "type": "array",
4810                                    "items": {
4811                                        "type": "record",
4812                                        "name": "baz",
4813                                        "fields": [
4814                                            {
4815                                                "name": "quux",
4816                                                "type": "int"
4817                                            }
4818                                        ]
4819                                    }
4820                                }
4821                            }
4822                        ]
4823                    }
4824                }"#;
4825
4826        let schema1 = Schema::parse_str(raw_schema)?;
4827        match &schema1 {
4828            Schema::Array(ArraySchema { items, attributes }) => {
4829                assert!(attributes.is_empty());
4830
4831                match **items {
4832                    Schema::Record(RecordSchema {
4833                        ref name,
4834                        aliases: _,
4835                        doc: _,
4836                        ref fields,
4837                        lookup: _,
4838                        ref attributes,
4839                    }) => {
4840                        assert_eq!(name.to_string(), "foo");
4841                        assert_eq!(fields.len(), 1);
4842                        assert!(attributes.is_empty());
4843
4844                        match &fields[0].schema {
4845                            Schema::Array(ArraySchema {
4846                                items: _,
4847                                attributes,
4848                            }) => {
4849                                assert!(attributes.is_empty());
4850                            }
4851                            _ => panic!("Expected ArraySchema. got: {}", &fields[0].schema),
4852                        }
4853                    }
4854                    _ => panic!("Expected RecordSchema. got: {}", &items),
4855                }
4856            }
4857            _ => panic!("Expected ArraySchema. got: {}", &schema1),
4858        }
4859        let canonical_form1 = schema1.canonical_form();
4860        let schema2 = Schema::parse_str(&canonical_form1)?;
4861        let canonical_form2 = schema2.canonical_form();
4862
4863        assert_eq!(canonical_form1, canonical_form2);
4864
4865        Ok(())
4866    }
4867
4868    #[test]
4869    fn avro_rs_292_map_values_should_be_ignored_in_custom_attributes() -> TestResult {
4870        let raw_schema = r#"{
4871                    "type": "array",
4872                    "items": {
4873                        "name": "foo",
4874                        "type": "record",
4875                        "fields": [
4876                            {
4877                                "name": "bar",
4878                                "type": {
4879                                    "type": "map",
4880                                    "values": {
4881                                        "type": "record",
4882                                        "name": "baz",
4883                                        "fields": [
4884                                            {
4885                                                "name": "quux",
4886                                                "type": "int"
4887                                            }
4888                                        ]
4889                                    }
4890                                }
4891                            }
4892                        ]
4893                    }
4894                }"#;
4895
4896        let schema1 = Schema::parse_str(raw_schema)?;
4897        match &schema1 {
4898            Schema::Array(ArraySchema { items, attributes }) => {
4899                assert!(attributes.is_empty());
4900
4901                match **items {
4902                    Schema::Record(RecordSchema {
4903                        ref name,
4904                        aliases: _,
4905                        doc: _,
4906                        ref fields,
4907                        lookup: _,
4908                        ref attributes,
4909                    }) => {
4910                        assert_eq!(name.to_string(), "foo");
4911                        assert_eq!(fields.len(), 1);
4912                        assert!(attributes.is_empty());
4913
4914                        match &fields[0].schema {
4915                            Schema::Map(MapSchema {
4916                                types: _,
4917                                attributes,
4918                            }) => {
4919                                assert!(attributes.is_empty());
4920                            }
4921                            _ => panic!("Expected MapSchema. got: {}", &fields[0].schema),
4922                        }
4923                    }
4924                    _ => panic!("Expected RecordSchema. got: {}", &items),
4925                }
4926            }
4927            _ => panic!("Expected ArraySchema. got: {}", &schema1),
4928        }
4929        let canonical_form1 = schema1.canonical_form();
4930        let schema2 = Schema::parse_str(&canonical_form1)?;
4931        let canonical_form2 = schema2.canonical_form();
4932
4933        assert_eq!(canonical_form1, canonical_form2);
4934
4935        Ok(())
4936    }
4937
4938    #[test]
4939    fn avro_rs_382_serialize_duration_schema() -> TestResult {
4940        let schema = Schema::Duration(FixedSchema {
4941            name: Name::try_from("Duration")?,
4942            aliases: None,
4943            doc: None,
4944            size: 12,
4945            attributes: BTreeMap::new(),
4946        });
4947
4948        let expected_schema_json = json!({
4949            "type": "fixed",
4950            "logicalType": "duration",
4951            "name": "Duration",
4952            "size": 12
4953        });
4954
4955        let schema_json = serde_json::to_value(&schema)?;
4956
4957        assert_eq!(&schema_json, &expected_schema_json);
4958
4959        Ok(())
4960    }
4961
4962    #[test]
4963    fn avro_rs_395_logical_type_written_once_for_duration() -> TestResult {
4964        let schema = Schema::parse_str(
4965            r#"{
4966            "type": "fixed",
4967            "logicalType": "duration",
4968            "name": "Duration",
4969            "size": 12
4970        }"#,
4971        )?;
4972
4973        let schema_json_str = serde_json::to_string(&schema)?;
4974
4975        assert_eq!(
4976            schema_json_str.matches("logicalType").count(),
4977            1,
4978            "Expected serialized schema to contain only one logicalType key: {schema_json_str}"
4979        );
4980
4981        Ok(())
4982    }
4983
4984    #[test]
4985    fn avro_rs_395_logical_type_written_once_for_uuid_fixed() -> TestResult {
4986        let schema = Schema::parse_str(
4987            r#"{
4988            "type": "fixed",
4989            "logicalType": "uuid",
4990            "name": "UUID",
4991            "size": 16
4992        }"#,
4993        )?;
4994
4995        let schema_json_str = serde_json::to_string(&schema)?;
4996
4997        assert_eq!(
4998            schema_json_str.matches("logicalType").count(),
4999            1,
5000            "Expected serialized schema to contain only one logicalType key: {schema_json_str}"
5001        );
5002
5003        Ok(())
5004    }
5005
5006    #[test]
5007    fn avro_rs_395_logical_type_written_once_for_decimal_fixed() -> TestResult {
5008        let schema = Schema::parse_str(
5009            r#"{
5010            "type": "fixed",
5011            "logicalType": "decimal",
5012            "scale": 4,
5013            "precision": 8,
5014            "name": "FixedDecimal16",
5015            "size": 16
5016        }"#,
5017        )?;
5018
5019        let schema_json_str = serde_json::to_string(&schema)?;
5020
5021        assert_eq!(
5022            schema_json_str.matches("logicalType").count(),
5023            1,
5024            "Expected serialized schema to contain only one logicalType key: {schema_json_str}"
5025        );
5026
5027        Ok(())
5028    }
5029
5030    #[test]
5031    fn avro_rs_420_independent_canonical_form() -> TestResult {
5032        let (record, schemata) = Schema::parse_str_with_list(
5033            r#"{
5034            "name": "root",
5035            "type": "record",
5036            "fields": [{
5037                "name": "node",
5038                "type": "node"
5039            }]
5040        }"#,
5041            [r#"{
5042            "name": "node",
5043            "type": "record",
5044            "fields": [{
5045                "name": "children",
5046                "type": ["null", "node"]
5047            }]
5048        }"#],
5049        )?;
5050        let icf = record.independent_canonical_form(&schemata)?;
5051        assert_eq!(
5052            icf,
5053            r#"{"name":"root","type":"record","fields":[{"name":"node","type":{"name":"node","type":"record","fields":[{"name":"children","type":["null","node"]}]}}]}"#
5054        );
5055        Ok(())
5056    }
5057
5058    #[test]
5059    fn avro_rs_456_bool_instead_of_boolean() -> TestResult {
5060        let error = Schema::parse_str(
5061            r#"{
5062            "type": "record",
5063            "name": "defaults",
5064            "fields": [
5065                {"name": "boolean", "type": "bool", "default": true}
5066            ]
5067        }"#,
5068        )
5069        .unwrap_err()
5070        .into_details()
5071        .to_string();
5072        assert_eq!(
5073            error,
5074            Details::ParsePrimitiveSimilar("bool".to_string(), "boolean").to_string()
5075        );
5076
5077        Ok(())
5078    }
5079
5080    #[test]
5081    fn avro_rs_460_fixed_default_in_custom_attributes() -> TestResult {
5082        let schema = Schema::parse_str(
5083            r#"{
5084            "name": "fixed_with_default",
5085            "type": "fixed",
5086            "size": 1,
5087            "default": "\u0000",
5088            "doc": "a docstring"
5089        }"#,
5090        )?;
5091
5092        assert_eq!(schema.custom_attributes().unwrap().len(), 1);
5093
5094        let json = serde_json::to_string(&schema)?;
5095        let schema2 = Schema::parse_str(&json)?;
5096
5097        assert_eq!(schema2.custom_attributes().unwrap().len(), 1);
5098
5099        Ok(())
5100    }
5101
5102    #[test]
5103    fn avro_rs_460_enum_default_not_in_custom_attributes() -> TestResult {
5104        let schema = Schema::parse_str(
5105            r#"{
5106            "name": "enum_with_default",
5107            "type": "enum",
5108            "symbols": ["A", "B", "C"],
5109            "default": "A",
5110            "doc": "a docstring"
5111        }"#,
5112        )?;
5113
5114        assert_eq!(schema.custom_attributes().unwrap(), &BTreeMap::new());
5115
5116        let json = serde_json::to_string(&schema)?;
5117        let schema2 = Schema::parse_str(&json)?;
5118
5119        let Schema::Enum(enum_schema) = schema2 else {
5120            panic!("Expected Schema::Enum, got {schema2:?}");
5121        };
5122        assert!(enum_schema.default.is_some());
5123        assert!(enum_schema.doc.is_some());
5124
5125        Ok(())
5126    }
5127
5128    #[test]
5129    fn avro_rs_476_enum_cannot_be_directly_in_field() -> TestResult {
5130        let schema_str = r#"{
5131            "type": "record",
5132            "name": "ExampleEnum",
5133            "namespace": "com.schema",
5134            "fields": [
5135                {
5136                "name": "wrong_enum",
5137                "type": "enum",
5138                "symbols": ["INSERT", "UPDATE"]
5139                }
5140            ]
5141        }"#;
5142        let result = Schema::parse_str(schema_str).unwrap_err();
5143        assert_eq!(
5144            result.to_string(),
5145            "Invalid schema: There is no type called 'enum', if you meant to define a non-primitive schema, it should be defined inside `type` attribute."
5146        );
5147        Ok(())
5148    }
5149
5150    #[test]
5151    fn avro_rs_509_default_must_be_in_custom_attributes_for_map_and_enum() -> TestResult {
5152        let schema = Schema::parse_str(
5153            r#"{
5154            "name": "ignore_defaults",
5155            "type": "record",
5156            "fields": [
5157                {"name": "a", "type": { "type": "map", "values": "string", "default": null }},
5158                {"name": "b", "type": { "type": "array", "items": "string", "default": null }}
5159            ]
5160        }"#,
5161        )?;
5162
5163        let Schema::Record(record) = schema else {
5164            panic!("Expected Schema::Record, got {schema:?}");
5165        };
5166        let Schema::Map(map) = &record.fields[0].schema else {
5167            panic!("Expected Schema::Map for first field of {record:?}");
5168        };
5169        assert_eq!(map.attributes.len(), 1);
5170        assert_eq!(
5171            map.attributes.get("default"),
5172            Some(&serde_json::Value::Null)
5173        );
5174
5175        let Schema::Array(array) = &record.fields[1].schema else {
5176            panic!("Expected Schema::Array for second field of {record:?}");
5177        };
5178        assert_eq!(array.attributes.len(), 1);
5179        assert_eq!(
5180            array.attributes.get("default"),
5181            Some(&serde_json::Value::Null)
5182        );
5183
5184        Ok(())
5185    }
5186
5187    #[test]
5188    fn avro_rs_512_unique_normalized_name_must_be_unique() -> TestResult {
5189        let one = Schema::Ref {
5190            name: "a.b.c".parse()?,
5191        };
5192        let two = Schema::Ref {
5193            name: "a_b_c".parse()?,
5194        };
5195        let three = Schema::Ref {
5196            name: "a__b__c".parse()?,
5197        };
5198        let four = Schema::Ref {
5199            name: "a._b._c".parse()?,
5200        };
5201
5202        assert_ne!(one.unique_normalized_name(), two.unique_normalized_name());
5203        assert_ne!(one.unique_normalized_name(), three.unique_normalized_name());
5204        assert_ne!(one.unique_normalized_name(), four.unique_normalized_name());
5205        assert_ne!(two.unique_normalized_name(), three.unique_normalized_name());
5206        assert_ne!(two.unique_normalized_name(), four.unique_normalized_name());
5207        assert_ne!(
5208            three.unique_normalized_name(),
5209            four.unique_normalized_name()
5210        );
5211
5212        Ok(())
5213    }
5214}