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