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