You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by mg...@apache.org on 2023/07/10 12:12:53 UTC
[avro] 01/01: AVRO-3787: [Rust] Deserialization fails to use default if an enum in a record in a union is given an unknown symbol
This is an automated email from the ASF dual-hosted git repository.
mgrigorov pushed a commit to branch avro-3787-enum-validation
in repository https://gitbox.apache.org/repos/asf/avro.git
commit 2b70b3392ed4db37c2611ffebc8dd440858ff136
Author: Martin Tzvetanov Grigorov <mg...@apache.org>
AuthorDate: Mon Jul 10 15:09:47 2023 +0300
AVRO-3787: [Rust] Deserialization fails to use default if an enum in a record in a union is given an unknown symbol
Do not fail the validation if the provided enum symbol is not in the
list of available ones if there schema defines a default symbol
Signed-off-by: Martin Tzvetanov Grigorov <mg...@apache.org>
---
lang/rust/avro/src/types.rs | 12 +-
lang/rust/avro/tests/avro-3787.rs | 262 ++++++++++++++++++++++++++++++++++++++
2 files changed, 272 insertions(+), 2 deletions(-)
diff --git a/lang/rust/avro/src/types.rs b/lang/rust/avro/src/types.rs
index 1b87f25e6..fc1e9130f 100644
--- a/lang/rust/avro/src/types.rs
+++ b/lang/rust/avro/src/types.rs
@@ -460,7 +460,12 @@ impl Value {
None
}
}
- (&Value::Enum(i, ref s), Schema::Enum(EnumSchema { symbols, .. })) => symbols
+ (
+ &Value::Enum(i, ref s),
+ Schema::Enum(EnumSchema {
+ symbols, default, ..
+ }),
+ ) => symbols
.get(i as usize)
.map(|ref symbol| {
if symbol != &s {
@@ -469,7 +474,10 @@ impl Value {
None
}
})
- .unwrap_or_else(|| Some(format!("No symbol at position '{i}'"))),
+ .unwrap_or_else(|| match default {
+ Some(_) => None,
+ None => Some(format!("No symbol at position '{i}'")),
+ }),
// (&Value::Union(None), &Schema::Union(_)) => None,
(&Value::Union(i, ref value), Schema::Union(inner)) => inner
.variants()
diff --git a/lang/rust/avro/tests/avro-3787.rs b/lang/rust/avro/tests/avro-3787.rs
new file mode 100644
index 000000000..3ec000721
--- /dev/null
+++ b/lang/rust/avro/tests/avro-3787.rs
@@ -0,0 +1,262 @@
+use apache_avro::{from_avro_datum, to_avro_datum, to_value, types, Schema};
+use apache_avro_test_helper::TestResult;
+
+#[test]
+fn avro_3787_deserialize_union_with_unknown_symbol() -> TestResult {
+ #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize)]
+ pub struct BarUseParent {
+ #[serde(rename = "barUse")]
+ pub bar_use: Bar,
+ }
+
+ #[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,
+ #[serde(rename = "barUseParent")]
+ pub bar_use_parent: Option<BarUseParent>,
+ }
+
+ let writer_schema = r#"{
+ "type": "record",
+ "name": "Foo",
+ "fields":
+ [
+ {
+ "name": "barInit",
+ "type":
+ {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar0",
+ "bar1",
+ "bar2"
+ ],
+ "default": "bar0"
+ }
+ },
+ {
+ "name": "barUseParent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "BarUseParent",
+ "fields": [
+ {
+ "name": "barUse",
+ "type": "Bar"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+
+ let reader_schema = r#"{
+ "type": "record",
+ "name": "Foo",
+ "fields":
+ [
+ {
+ "name": "barInit",
+ "type":
+ {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar0",
+ "bar1"
+ ],
+ "default": "bar0"
+ }
+ },
+ {
+ "name": "barUseParent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "BarUseParent",
+ "fields": [
+ {
+ "name": "barUse",
+ "type": "Bar"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+
+ let writer_schema = Schema::parse_str(writer_schema)?;
+ let foo1 = Foo {
+ bar_init: Bar::Bar1,
+ bar_use_parent: Some(BarUseParent { bar_use: Bar::Bar2 }),
+ };
+ let avro_value = to_value(foo1)?;
+ assert!(
+ avro_value.validate(&writer_schema),
+ "value is valid for schema",
+ );
+ let datum = to_avro_datum(&writer_schema, avro_value)?;
+ let mut x = &datum[..];
+ let reader_schema = Schema::parse_str(reader_schema)?;
+ let deser_value = from_avro_datum(&writer_schema, &mut x, Some(&reader_schema))?;
+ match deser_value {
+ types::Value::Record(fields) => {
+ assert_eq!(fields.len(), 2);
+ assert_eq!(fields[0].0, "barInit");
+ assert_eq!(fields[0].1, types::Value::Enum(1, "bar1".to_string()));
+ assert_eq!(fields[1].0, "barUseParent");
+ // TODO: test value
+ }
+ _ => panic!("Expected Value::Record"),
+ }
+
+ Ok(())
+}
+
+#[test]
+fn avro_3787_deserialize_union_with_unknown_symbol_no_ref() -> 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)]
+ #[serde(default)]
+ pub struct BarParent {
+ #[serde(rename = "Bar")]
+ pub bar: Bar,
+ }
+
+ #[inline(always)]
+ fn default_barparent_bar() -> Bar {
+ Bar::Bar0
+ }
+ impl Default for BarParent {
+ fn default() -> BarParent {
+ BarParent {
+ bar: default_barparent_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",
+ "fields":
+ [
+ {
+ "name": "barParent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "BarParent",
+ "fields": [
+ {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar0",
+ "bar1",
+ "bar2"
+ ],
+ "default": "bar0"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+
+ let reader_schema = r#"{
+ "type": "record",
+ "name": "Foo",
+ "fields":
+ [
+ {
+ "name": "barParent",
+ "type": [
+ "null",
+ {
+ "type": "record",
+ "name": "BarParent",
+ "fields": [
+ {
+ "type": "enum",
+ "name": "Bar",
+ "symbols":
+ [
+ "bar0",
+ "bar1"
+ ],
+ "default": "bar0"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }"#;
+
+ let writer_schema = Schema::parse_str(writer_schema)?;
+ let foo2 = Foo {
+ bar_parent: Some(BarParent { bar: Bar::Bar2 }),
+ };
+ let avro_value = to_value(foo2)?;
+ assert!(
+ avro_value.validate(&writer_schema),
+ "value is valid for schema",
+ );
+ let datum = to_avro_datum(&writer_schema, avro_value)?;
+ let mut x = &datum[..];
+ let reader_schema = Schema::parse_str(reader_schema)?;
+ let deser_value = 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, "barInit");
+ // assert_eq!(fields[0].1, types::Value::Enum(0, "bar0".to_string()));
+ assert_eq!(fields[0].0, "barParent");
+ // assert_eq!(fields[1].1, types::Value::Enum(1, "bar1".to_string()));
+ }
+ _ => panic!("Expected Value::Record"),
+ }
+
+ Ok(())
+}