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