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 efe9aa7fb AVRO-3688: Fix UnionSchema resolution if a UnionSchema 
contains a reference (#2011)
efe9aa7fb is described below

commit efe9aa7fbe457805dc894a9186e1267603d82dd2
Author: Rik Heijdens <[email protected]>
AuthorDate: Tue Dec 13 14:14:15 2022 +0100

    AVRO-3688: Fix UnionSchema resolution if a UnionSchema contains a reference 
(#2011)
    
    * AVRO-3688: Add test-case to reproduce
    
    * AVRO-3688: Implement fix for resolving Union schemas
    
    A UnionSchema might contain a schema reference in the form of a
    `Schema::Ref`, in order to properly support resolution of UnionSchemas
    we must ensure we first resolve the `Schema::Ref` into the resolved
    schema prior to resolving the union fields itself.
    
    * AVRO-3688: Fix clippy lint
---
 lang/rust/avro/src/types.rs | 108 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 104 insertions(+), 4 deletions(-)

diff --git a/lang/rust/avro/src/types.rs b/lang/rust/avro/src/types.rs
index 9ee6b7b2d..940ca17aa 100644
--- a/lang/rust/avro/src/types.rs
+++ b/lang/rust/avro/src/types.rs
@@ -372,7 +372,6 @@ impl Value {
         match (self, schema) {
             (_, &Schema::Ref { ref name }) => {
                 let name = name.fully_qualified_name(enclosing_namespace);
-
                 names.get(&name).map_or_else(
                     || {
                         Some(format!(
@@ -870,9 +869,29 @@ impl Value {
             v => v,
         };
 
-        // Find the first match in the reader schema.
-        // FIXME: this might be wrong when the union consists of multiple same 
records that have different names
-        let (i, inner) = 
schema.find_schema(&v).ok_or(Error::FindUnionVariant)?;
+        // A union might contain references to another schema in the form of a 
Schema::Ref,
+        // resolve these prior to finding the schema.
+        let resolved_schemas: Vec<Schema> = schema
+            .schemas
+            .iter()
+            .cloned()
+            .map(|schema| match schema {
+                Schema::Ref { name } => {
+                    let name = name.fully_qualified_name(enclosing_namespace);
+                    names
+                        .get(&name)
+                        .map(|s| (**s).clone())
+                        .ok_or_else(|| 
Error::SchemaResolutionError(name.clone()))
+                }
+                schema => Ok(schema),
+            })
+            .collect::<Result<Vec<Schema>, Error>>()?;
+
+        let resolved_union_schema = 
UnionSchema::new(resolved_schemas).unwrap();
+        let (i, inner) = resolved_union_schema
+            .find_schema(&v)
+            .ok_or(Error::FindUnionVariant)?;
+
         Ok(Value::Union(
             i as u32,
             Box::new(v.resolve_internal(inner, names, enclosing_namespace)?),
@@ -2540,4 +2559,85 @@ Field with name '"b"' is not a member of the map items"#,
     fn test_avro_3674_validate_with_namespace_resolution() {
         avro_3674_with_or_without_namespace(true);
     }
+
+    fn avro_3688_schema_resolution_panic(set_field_b: bool) {
+        use crate::ser::Serializer;
+        use serde::{Deserialize, Serialize};
+
+        let schema_str = r#"{
+            "type": "record",
+            "name": "Message",
+            "fields": [
+                {
+                    "name": "field_a",
+                    "type": [
+                        "null",
+                        {
+                            "name": "Inner",
+                            "type": "record",
+                            "fields": [
+                                {
+                                    "name": "inner_a",
+                                    "type": "string"
+                                }
+                            ]
+                        }
+                    ],
+                    "default": null
+                },
+                {
+                    "name": "field_b",
+                    "type": [
+                        "null",
+                        "Inner"
+                    ],
+                    "default": null
+                }
+            ]
+        }"#;
+
+        #[derive(Serialize, Deserialize)]
+        struct Inner {
+            inner_a: String,
+        }
+
+        #[derive(Serialize, Deserialize)]
+        struct Message {
+            field_a: Option<Inner>,
+            field_b: Option<Inner>,
+        }
+
+        let schema = Schema::parse_str(schema_str).unwrap();
+
+        let msg = Message {
+            field_a: Some(Inner {
+                inner_a: "foo".to_string(),
+            }),
+            field_b: if set_field_b {
+                Some(Inner {
+                    inner_a: "bar".to_string(),
+                })
+            } else {
+                None
+            },
+        };
+
+        let mut ser = Serializer::default();
+        let test_value: Value = msg.serialize(&mut ser).unwrap();
+        assert!(test_value.validate(&schema), "test_value should validate");
+        assert!(
+            test_value.resolve(&schema).is_ok(),
+            "test_value should resolve"
+        );
+    }
+
+    #[test]
+    fn test_avro_3688_field_b_not_set() {
+        avro_3688_schema_resolution_panic(false);
+    }
+
+    #[test]
+    fn test_avro_3688_field_b_set() {
+        avro_3688_schema_resolution_panic(true);
+    }
 }

Reply via email to