This is an automated email from the ASF dual-hosted git repository.
mgrigorov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/avro.git
The following commit(s) were added to refs/heads/master by this push:
new 598911d80 AVRO-3814: Fix schema resolution for records in union types
(#2441)
598911d80 is described below
commit 598911d80ea2feda984624632679b0e6e2696b9b
Author: Martin Grigorov <[email protected]>
AuthorDate: Tue Aug 15 14:32:22 2023 +0300
AVRO-3814: Fix schema resolution for records in union types (#2441)
* AVRO-3814: Add a minimal test-case to reproduce
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
* AVRO-3814: Fix schema resolution for records in union types
The logic for validation records in Value::validate_internal() would be
too strict when resolving union types containing a record. This could
lead to a situation where schema resolution would fail because the
correct schema to use for a union type could not be identified.
This commit fixes this by passing a boolean `schema_resolution` to
`Value::validate_internal()` which governs whether schema_resolution
rules should be applied.
* AVRO-3814: Ensure to validate the deserialized value against the schema
* AVRO-3814: Extend test case for validate_record
* AVRO-3814: Revert whitespace changes
* AVRO-3814: Remove confusing comments
* AVRO-3786: Add test-cases and fix for AVRO-3786
* AVRO-3786: Revert change to UnionSchema::find_schema_with_known_schemata
* AVRO-3814: [Rust] Use types::Value::resolve_internal() instead of
validate_internal()
... when looking for the matching schema in an union
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
* AVRO-3814: Revert changes to validate_internal()
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
* AVRO-3814: Remove obsolete rustdoc for arguments
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
---------
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
Co-authored-by: Rik Heijdens <[email protected]>
---
lang/rust/avro/src/schema.rs | 130 ++++++-
lang/rust/avro/src/types.rs | 4 +-
lang/rust/avro/tests/avro-3786.rs | 727 ++++++++++++++++++++++++++++++++++++++
3 files changed, 858 insertions(+), 3 deletions(-)
diff --git a/lang/rust/avro/src/schema.rs b/lang/rust/avro/src/schema.rs
index 26ed04cb8..d688473bd 100644
--- a/lang/rust/avro/src/schema.rs
+++ b/lang/rust/avro/src/schema.rs
@@ -837,9 +837,11 @@ impl UnionSchema {
// extend known schemas with just resolved names
collected_names.extend(resolved_names);
let namespace = &schema.namespace().or_else(||
enclosing_namespace.clone());
+
value
- .validate_internal(schema, &collected_names, namespace)
- .is_none()
+ .clone()
+ .resolve_internal(schema, &collected_names, namespace,
&None)
+ .is_ok()
})
}
}
@@ -5209,4 +5211,128 @@ mod tests {
Ok(())
}
+
+ #[test]
+ fn test_avro_3814_schema_resolution_failure() -> TestResult {
+ // Define a reader schema: a nested record with an optional field.
+ let reader_schema = json!(
+ {
+ "type": "record",
+ "name": "MyOuterRecord",
+ "fields": [
+ {
+ "name": "inner_record",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "MyRecord",
+ "fields": [
+ {"name": "a", "type": "string"}
+ ]
+ }
+ ],
+ "default": null
+ }
+ ]
+ }
+ );
+
+ // Define a writer schema: a nested record with an optional field,
which
+ // may optionally contain an enum.
+ let writer_schema = json!(
+ {
+ "type": "record",
+ "name": "MyOuterRecord",
+ "fields": [
+ {
+ "name": "inner_record",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "MyRecord",
+ "fields": [
+ {"name": "a", "type": "string"},
+ {
+ "name": "b",
+ "type": [
+ "null",
+ {
+ "type": "enum",
+ "name": "MyEnum",
+ "symbols": ["A", "B", "C"],
+ "default": "C"
+ }
+ ],
+ "default": null
+ },
+ ]
+ }
+ ]
+ }
+ ],
+ "default": null
+ }
+ );
+
+ // Use different structs to represent the "Reader" and the "Writer"
+ // to mimic two different versions of a producer & consumer
application.
+ #[derive(Serialize, Deserialize, Debug)]
+ struct MyInnerRecordReader {
+ a: String,
+ }
+
+ #[derive(Serialize, Deserialize, Debug)]
+ struct MyRecordReader {
+ inner_record: Option<MyInnerRecordReader>,
+ }
+
+ #[derive(Serialize, Deserialize, Debug)]
+ enum MyEnum {
+ A,
+ B,
+ C,
+ }
+
+ #[derive(Serialize, Deserialize, Debug)]
+ struct MyInnerRecordWriter {
+ a: String,
+ b: Option<MyEnum>,
+ }
+
+ #[derive(Serialize, Deserialize, Debug)]
+ struct MyRecordWriter {
+ inner_record: Option<MyInnerRecordWriter>,
+ }
+
+ let s = MyRecordWriter {
+ inner_record: Some(MyInnerRecordWriter {
+ a: "foo".to_string(),
+ b: None,
+ }),
+ };
+
+ // Serialize using the writer schema.
+ let writer_schema = Schema::parse(&writer_schema)?;
+ let avro_value = crate::to_value(s)?;
+ assert!(
+ avro_value.validate(&writer_schema),
+ "value is valid for schema",
+ );
+ let datum = crate::to_avro_datum(&writer_schema, avro_value)?;
+
+ // Now, attempt to deserialize using the reader schema.
+ let reader_schema = Schema::parse(&reader_schema)?;
+ let mut x = &datum[..];
+
+ // Deserialization should succeed and we should be able to resolve the
schema.
+ let deser_value = crate::from_avro_datum(&writer_schema, &mut x,
Some(&reader_schema))?;
+ assert!(deser_value.validate(&reader_schema));
+
+ // Verify that we can read a field from the record.
+ let d: MyRecordReader = crate::from_value(&deser_value)?;
+ assert_eq!(d.inner_record.unwrap().a, "foo".to_string());
+ Ok(())
+ }
}
diff --git a/lang/rust/avro/src/types.rs b/lang/rust/avro/src/types.rs
index fbc4fa004..07a51c377 100644
--- a/lang/rust/avro/src/types.rs
+++ b/lang/rust/avro/src/types.rs
@@ -377,6 +377,7 @@ impl Value {
}
}
+ /// Validates the value against the provided schema.
pub(crate) fn validate_internal<S: std::borrow::Borrow<Schema> + Debug>(
&self,
schema: &Schema,
@@ -516,6 +517,7 @@ impl Value {
let non_nullable_fields_count =
fields.iter().filter(|&rf| !rf.is_nullable()).count();
+ // If the record contains fewer fields as required fields by
the schema, it is invalid.
if record_fields.len() < non_nullable_fields_count {
return Some(format!(
"The value's records length ({}) doesn't match the
schema ({} non-nullable fields)",
@@ -603,7 +605,7 @@ impl Value {
self.resolve_internal(schema, rs.get_names(), &enclosing_namespace,
&None)
}
- fn resolve_internal(
+ pub(crate) fn resolve_internal(
mut self,
schema: &Schema,
names: &NamesRef,
diff --git a/lang/rust/avro/tests/avro-3786.rs
b/lang/rust/avro/tests/avro-3786.rs
index f36b163c4..d27e0c4e5 100644
--- a/lang/rust/avro/tests/avro-3786.rs
+++ b/lang/rust/avro/tests/avro-3786.rs
@@ -157,3 +157,730 @@ fn
avro_3786_deserialize_union_with_different_enum_order() -> TestResult {
}
Ok(())
}
+
+#[test]
+fn avro_3786_deserialize_union_with_different_enum_order_defined_in_record()
-> TestResult {
+ #[derive(
+ Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone,
serde::Deserialize, serde::Serialize,
+ )]
+ pub enum Bar {
+ #[serde(rename = "bar0")]
+ Bar0,
+ #[serde(rename = "bar1")]
+ Bar1,
+ #[serde(rename = "bar2")]
+ Bar2,
+ }
+ #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize,
serde::Serialize)]
+ pub struct BarParent {
+ pub bar: Bar,
+ }
+ #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize,
serde::Serialize)]
+ pub struct Foo {
+ #[serde(rename = "barParent")]
+ pub bar_parent: Option<BarParent>,
+ }
+ let writer_schema = r#"{
+ "type": "record",
+ "name": "Foo",
+ "namespace": "com.rallyhealth.devices.canonical.avro.model.v6_0",
+ "fields":
+ [
+ {
+ "name": "barParent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "BarParent",
+ "fields": [
+ {
+ "name": "bar",
+ "type": {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar0",
+ "bar1",
+ "bar2"
+ ],
+ "default": "bar0"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+ let reader_schema = r#"{
+ "type": "record",
+ "name": "Foo",
+ "namespace": "com.rallyhealth.devices.canonical.avro.model.v6_0",
+ "fields":
+ [
+ {
+ "name": "barParent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "BarParent",
+ "fields": [
+ {
+ "name": "bar",
+ "type": {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar0",
+ "bar2"
+ ],
+ "default": "bar0"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+ let writer_schema = Schema::parse_str(writer_schema)?;
+ let foo1 = Foo {
+ bar_parent: Some(BarParent { bar: Bar::Bar0 }),
+ };
+ let avro_value = crate::to_value(foo1)?;
+ assert!(
+ avro_value.validate(&writer_schema),
+ "value is valid for schema",
+ );
+ let datum = crate::to_avro_datum(&writer_schema, avro_value)?;
+ let mut x = &datum[..];
+ let reader_schema = Schema::parse_str(reader_schema)?;
+ let deser_value = crate::from_avro_datum(&writer_schema, &mut x,
Some(&reader_schema))?;
+ match deser_value {
+ types::Value::Record(fields) => {
+ assert_eq!(fields.len(), 1);
+ assert_eq!(fields[0].0, "barParent");
+ // TODO: better validation
+ }
+ _ => panic!("Expected Value::Record"),
+ }
+ Ok(())
+}
+
+#[test]
+fn
test_avro_3786_deserialize_union_with_different_enum_order_defined_in_record_v1()
-> TestResult {
+ #[derive(
+ Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone,
serde::Deserialize, serde::Serialize,
+ )]
+ pub enum Bar {
+ #[serde(rename = "bar0")]
+ Bar0,
+ #[serde(rename = "bar1")]
+ Bar1,
+ #[serde(rename = "bar2")]
+ Bar2,
+ }
+ #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize,
serde::Serialize)]
+ pub struct BarParent {
+ pub bar: Bar,
+ }
+ #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize,
serde::Serialize)]
+ pub struct Foo {
+ #[serde(rename = "barParent")]
+ pub bar_parent: Option<BarParent>,
+ }
+ let writer_schema = r#"{
+ "type": "record",
+ "name": "Foo",
+ "namespace": "com.rallyhealth.devices.canonical.avro.model.v6_0",
+ "fields":
+ [
+ {
+ "name": "barParent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "BarParent",
+ "fields": [
+ {
+ "name": "bar",
+ "type": {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar0",
+ "bar1",
+ "bar2"
+ ],
+ "default": "bar0"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+ let reader_schema = r#"{
+ "type": "record",
+ "name": "Foo",
+ "namespace": "com.rallyhealth.devices.canonical.avro.model.v6_0",
+ "fields":
+ [
+ {
+ "name": "barParent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "BarParent",
+ "fields": [
+ {
+ "name": "bar",
+ "type": {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar0",
+ "bar2"
+ ],
+ "default": "bar0"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+ let writer_schema = Schema::parse_str(writer_schema)?;
+ let foo1 = Foo {
+ bar_parent: Some(BarParent { bar: Bar::Bar1 }),
+ };
+ let avro_value = crate::to_value(foo1)?;
+ assert!(
+ avro_value.validate(&writer_schema),
+ "value is valid for schema",
+ );
+ let datum = crate::to_avro_datum(&writer_schema, avro_value)?;
+ let mut x = &datum[..];
+ let reader_schema = Schema::parse_str(reader_schema)?;
+ let deser_value = crate::from_avro_datum(&writer_schema, &mut x,
Some(&reader_schema))?;
+ match deser_value {
+ types::Value::Record(fields) => {
+ assert_eq!(fields.len(), 1);
+ assert_eq!(fields[0].0, "barParent");
+ // TODO: better validation
+ }
+ _ => panic!("Expected Value::Record"),
+ }
+ Ok(())
+}
+
+#[test]
+fn
test_avro_3786_deserialize_union_with_different_enum_order_defined_in_record_v2()
-> TestResult {
+ #[derive(
+ Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone,
serde::Deserialize, serde::Serialize,
+ )]
+ pub enum Bar {
+ #[serde(rename = "bar0")]
+ Bar0,
+ #[serde(rename = "bar1")]
+ Bar1,
+ #[serde(rename = "bar2")]
+ Bar2,
+ }
+ #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize,
serde::Serialize)]
+ pub struct BarParent {
+ pub bar: Bar,
+ }
+ #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize,
serde::Serialize)]
+ pub struct Foo {
+ #[serde(rename = "barParent")]
+ pub bar_parent: Option<BarParent>,
+ }
+ let writer_schema = r#"{
+ "type": "record",
+ "name": "Foo",
+ "namespace": "com.rallyhealth.devices.canonical.avro.model.v6_0",
+ "fields":
+ [
+ {
+ "name": "barParent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "BarParent",
+ "fields": [
+ {
+ "name": "bar",
+ "type": {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar0",
+ "bar1",
+ "bar2"
+ ],
+ "default": "bar2"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+ let reader_schema = r#"{
+ "type": "record",
+ "name": "Foo",
+ "namespace": "com.rallyhealth.devices.canonical.avro.model.v6_0",
+ "fields":
+ [
+ {
+ "name": "barParent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "BarParent",
+ "fields": [
+ {
+ "name": "bar",
+ "type": {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar1",
+ "bar2"
+ ],
+ "default": "bar2"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+ let writer_schema = Schema::parse_str(writer_schema)?;
+ let foo1 = Foo {
+ bar_parent: Some(BarParent { bar: Bar::Bar1 }),
+ };
+ let avro_value = crate::to_value(foo1)?;
+ assert!(
+ avro_value.validate(&writer_schema),
+ "value is valid for schema",
+ );
+ let datum = crate::to_avro_datum(&writer_schema, avro_value)?;
+ let mut x = &datum[..];
+ let reader_schema = Schema::parse_str(reader_schema)?;
+ let deser_value = crate::from_avro_datum(&writer_schema, &mut x,
Some(&reader_schema))?;
+ match deser_value {
+ types::Value::Record(fields) => {
+ assert_eq!(fields.len(), 1);
+ assert_eq!(fields[0].0, "barParent");
+ // TODO: better validation
+ }
+ _ => panic!("Expected Value::Record"),
+ }
+ Ok(())
+}
+
+#[test]
+fn deserialize_union_with_different_enum_order_defined_in_record() ->
TestResult {
+ #[derive(
+ Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone,
serde::Deserialize, serde::Serialize,
+ )]
+ pub enum Bar {
+ #[serde(rename = "bar0")]
+ Bar0,
+ #[serde(rename = "bar1")]
+ Bar1,
+ #[serde(rename = "bar2")]
+ Bar2,
+ }
+ #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize,
serde::Serialize)]
+ pub struct BarParent {
+ pub bar: Bar,
+ }
+ #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize,
serde::Serialize)]
+ pub struct Foo {
+ #[serde(rename = "barParent")]
+ pub bar_parent: Option<BarParent>,
+ }
+ let writer_schema = r#"{
+ "type": "record",
+ "name": "Foo",
+ "namespace": "com.rallyhealth.devices.canonical.avro.model.v6_0",
+ "fields":
+ [
+ {
+ "name": "barParent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "BarParent",
+ "fields": [
+ {
+ "name": "bar",
+ "type": {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar0",
+ "bar1",
+ "bar2"
+ ],
+ "default": "bar0"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+ let reader_schema = r#"{
+ "type": "record",
+ "name": "Foo",
+ "namespace": "com.rallyhealth.devices.canonical.avro.model.v6_0",
+ "fields":
+ [
+ {
+ "name": "barParent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "BarParent",
+ "fields": [
+ {
+ "name": "bar",
+ "type": {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar0",
+ "bar2"
+ ],
+ "default": "bar0"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+ let writer_schema = Schema::parse_str(writer_schema)?;
+ let foo1 = Foo {
+ bar_parent: Some(BarParent { bar: Bar::Bar2 }),
+ };
+ let avro_value = crate::to_value(foo1)?;
+ assert!(
+ avro_value.validate(&writer_schema),
+ "value is valid for schema",
+ );
+ let datum = crate::to_avro_datum(&writer_schema, avro_value)?;
+ let mut x = &datum[..];
+ let reader_schema = Schema::parse_str(reader_schema)?;
+ let deser_value = crate::from_avro_datum(&writer_schema, &mut x,
Some(&reader_schema))?;
+ match deser_value {
+ types::Value::Record(fields) => {
+ assert_eq!(fields.len(), 1);
+ assert_eq!(fields[0].0, "barParent");
+ // TODO: better validation
+ }
+ _ => panic!("Expected Value::Record"),
+ }
+ Ok(())
+}
+
+#[test]
+fn
deserialize_union_with_record_with_enum_defined_inline_reader_has_different_indices(
+) -> TestResult {
+ #[derive(
+ Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone,
serde::Deserialize, serde::Serialize,
+ )]
+ pub enum DefinedInRecord {
+ #[serde(rename = "val0")]
+ Val0,
+ #[serde(rename = "val1")]
+ Val1,
+ #[serde(rename = "val2")]
+ Val2,
+ #[serde(rename = "UNKNOWN")]
+ Unknown,
+ }
+ #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize,
serde::Serialize)]
+ pub struct Parent {
+ pub date: i64,
+ #[serde(rename = "barUse")]
+ pub bar_use: Bar,
+ #[serde(rename = "bazUse")]
+ pub baz_use: Option<Vec<Baz>>,
+ #[serde(rename = "definedInRecord")]
+ pub defined_in_record: DefinedInRecord,
+ #[serde(rename = "optionalString")]
+ pub optional_string: Option<String>,
+ }
+ #[derive(
+ Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone,
serde::Deserialize, serde::Serialize,
+ )]
+ pub enum Baz {
+ #[serde(rename = "baz0")]
+ Baz0,
+ #[serde(rename = "baz1")]
+ Baz1,
+ #[serde(rename = "baz2")]
+ Baz2,
+ }
+ #[derive(
+ Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone,
serde::Deserialize, serde::Serialize,
+ )]
+ pub enum Bar {
+ #[serde(rename = "bar0")]
+ Bar0,
+ #[serde(rename = "bar1")]
+ Bar1,
+ #[serde(rename = "bar2")]
+ Bar2,
+ }
+ #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize,
serde::Serialize)]
+ pub struct Foo {
+ #[serde(rename = "barInit")]
+ pub bar_init: Bar,
+ pub baz: Baz,
+ pub parent: Option<Parent>,
+ }
+ let writer_schema = r#"{
+ "type": "record",
+ "name": "Foo",
+ "namespace": "fake",
+ "fields":
+ [
+ {
+ "name": "barInit",
+ "type":
+ {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar0",
+ "bar1",
+ "bar2"
+ ],
+ "default": "bar0"
+ }
+ },
+ {
+ "name": "baz",
+ "type":
+ {
+ "type": "enum",
+ "name": "Baz",
+ "symbols":
+ [
+ "baz0",
+ "baz1",
+ "baz2"
+ ],
+ "default": "baz0"
+ }
+ },
+ {
+ "name": "parent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "Parent",
+ "fields": [
+ {
+ "name": "date",
+ "type": {
+ "type": "long",
+ "avro.java.long": "Long"
+ }
+ },
+ {
+ "name": "barUse",
+ "type": "Bar"
+ },
+ {
+ "name": "bazUse",
+ "type": [
+ "null",
+ {
+ "type": "array",
+ "items": {
+ "type": "Baz"
+ }
+ }
+ ]
+ },
+ {
+ "name": "definedInRecord",
+ "type": {
+ "name": "DefinedInRecord",
+ "type": "enum",
+ "symbols": [
+ "val0",
+ "val1",
+ "val2",
+ "UNKNOWN"
+ ],
+ "default": "UNKNOWN"
+ }
+ },
+ {
+ "name": "optionalString",
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+ let reader_schema = r#"{
+ "type": "record",
+ "name": "Foo",
+ "namespace": "fake",
+ "fields":
+ [
+ {
+ "name": "barInit",
+ "type":
+ {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar0",
+ "bar2"
+ ],
+ "default": "bar0"
+ }
+ },
+ {
+ "name": "baz",
+ "type":
+ {
+ "type": "enum",
+ "name": "Baz",
+ "symbols":
+ [
+ "baz0",
+ "baz2"
+ ],
+ "default": "baz0"
+ }
+ },
+ {
+ "name": "parent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "Parent",
+ "fields": [
+ {
+ "name": "date",
+ "type": {
+ "type": "long",
+ "avro.java.long": "Long"
+ }
+ },
+ {
+ "name": "barUse",
+ "type": "Bar"
+ },
+ {
+ "name": "bazUse",
+ "type": [
+ "null",
+ {
+ "type": "array",
+ "items": {
+ "type": "Baz"
+ }
+ }
+ ]
+ },
+ {
+ "name": "definedInRecord",
+ "type": {
+ "name": "DefinedInRecord",
+ "type": "enum",
+ "symbols": [
+ "val1",
+ "val2",
+ "UNKNOWN"
+ ],
+ "default": "UNKNOWN"
+ }
+ },
+ {
+ "name": "optionalString",
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+ let writer_schema = Schema::parse_str(writer_schema)?;
+ let foo1 = Foo {
+ bar_init: Bar::Bar0,
+ baz: Baz::Baz0,
+ parent: Some(Parent {
+ bar_use: Bar::Bar0,
+ baz_use: Some(vec![Baz::Baz0]),
+ optional_string: Some("test".to_string()),
+ date: 1689197893,
+ defined_in_record: DefinedInRecord::Val1,
+ }),
+ };
+ let avro_value = crate::to_value(foo1)?;
+ assert!(
+ avro_value.validate(&writer_schema),
+ "value is valid for schema",
+ );
+ let datum = crate::to_avro_datum(&writer_schema, avro_value)?;
+ let mut x = &datum[..];
+ let reader_schema = Schema::parse_str(reader_schema)?;
+ let deser_value = crate::from_avro_datum(&writer_schema, &mut x,
Some(&reader_schema))?;
+ match deser_value {
+ types::Value::Record(fields) => {
+ assert_eq!(fields.len(), 3);
+ assert_eq!(fields[0].0, "barInit");
+ assert_eq!(fields[0].1, types::Value::Enum(0, "bar0".to_string()));
+ // TODO: better validation
+ }
+ _ => panic!("Expected Value::Record"),
+ }
+ Ok(())
+}