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