1mod 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
59pub type Documentation = Option<String>;
61
62pub 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#[derive(Clone, Debug, EnumDiscriminants, Display)]
87#[strum_discriminants(name(SchemaKind), derive(Hash, Ord, PartialOrd))]
88pub enum Schema {
89 Null,
91 Boolean,
93 Int,
95 Long,
97 Float,
99 Double,
101 Bytes,
104 String,
107 Array(ArraySchema),
110 Map(MapSchema),
114 Union(UnionSchema),
116 Record(RecordSchema),
118 Enum(EnumSchema),
120 Fixed(FixedSchema),
122 Decimal(DecimalSchema),
125 BigDecimal,
128 Uuid(UuidSchema),
130 Date,
133 TimeMillis,
136 TimeMicros,
139 TimestampMillis,
141 TimestampMicros,
143 TimestampNanos,
145 LocalTimestampMillis,
147 LocalTimestampMicros,
149 LocalTimestampNanos,
151 Duration(FixedSchema),
153 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 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#[derive(bon::Builder, Debug, Clone)]
244pub struct EnumSchema {
245 pub name: Name,
247 #[builder(default)]
249 pub aliases: Aliases,
250 #[builder(default)]
252 pub doc: Documentation,
253 pub symbols: Vec<String>,
255 pub default: Option<String>,
257 #[builder(default = BTreeMap::new())]
259 pub attributes: BTreeMap<String, Value>,
260}
261
262#[derive(bon::Builder, Debug, Clone)]
264pub struct FixedSchema {
265 pub name: Name,
267 #[builder(default)]
269 pub aliases: Aliases,
270 #[builder(default)]
272 pub doc: Documentation,
273 pub size: usize,
275 pub default: Option<String>,
277 #[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 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#[derive(Debug, Clone)]
331pub struct DecimalSchema {
332 pub precision: DecimalMetadata,
334 pub scale: DecimalMetadata,
336 pub inner: InnerDecimalSchema,
338}
339
340#[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#[derive(Debug, Clone)]
361pub enum UuidSchema {
362 Bytes,
367 String,
369 Fixed(FixedSchema),
371}
372
373type DecimalMetadata = usize;
374pub(crate) type Precision = DecimalMetadata;
375pub(crate) type Scale = DecimalMetadata;
376
377impl Schema {
378 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 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 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 pub fn parse_str(input: &str) -> Result<Schema, Error> {
416 let mut parser = Parser::default();
417 parser.parse_str(input)
418 }
419
420 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 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 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 pub fn parse(value: &Value) -> AvroResult<Schema> {
511 let mut parser = Parser::default();
512 parser.parse(value, &None)
513 }
514
515 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 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 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 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 pub fn namespace(&self) -> Namespace {
576 self.name().and_then(|n| n.namespace.clone())
577 }
578
579 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 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 pub fn map(types: Schema) -> Self {
613 Schema::Map(MapSchema {
614 types: Box::new(types),
615 attributes: Default::default(),
616 })
617 }
618
619 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 pub fn array(items: Schema) -> Self {
629 Schema::Array(ArraySchema {
630 items: Box::new(items),
631 attributes: Default::default(),
632 })
633 }
634
635 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 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 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
906fn 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 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 if schema.len() == 1 && k == "type" {
944 if let Value::String(s) = v {
946 return pcf_string(s);
947 }
948 }
949
950 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 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 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 fields.push((
980 k,
981 format!(
982 "{}:{}",
983 pcf_string(k),
984 parsing_canonical_form(v, defined_names)
985 ),
986 ));
987 }
988
989 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
1036fn 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 #[test]
1131 fn test_union_of_records() -> TestResult {
1132 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 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 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 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 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 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 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 #[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 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 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 #[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 } 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 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 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 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 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 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 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 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 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 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 #[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 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 let reader_schema = Schema::parse(&reader_schema)?;
3586 let mut x = &datum[..];
3587
3588 let deser_value = crate::from_avro_datum(&writer_schema, &mut x, Some(&reader_schema))?;
3590 assert!(deser_value.validate(&reader_schema));
3591
3592 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let schema = json!(
4327 {
4328 "type": "long",
4329 "name": "LongDecimal",
4330 "logicalType": "decimal"
4331 });
4332 let parse_result = Schema::parse(&schema)?;
4333 assert_eq!(parse_result, Schema::Long);
4335
4336 Ok(())
4337 }
4338
4339 #[test]
4340 fn avro_3896_uuid_schema_for_string() -> TestResult {
4341 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 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 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 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 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}