Skip to main content

apache_avro/schema/
mod.rs

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