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