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 2022/10/10 20:10:10 UTC

[avro] branch branch-1.11 updated: AVRO-3632: [Rust] Handle defaults in unions according to the spec (#1901)

This is an automated email from the ASF dual-hosted git repository.

mgrigorov pushed a commit to branch branch-1.11
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/branch-1.11 by this push:
     new 21f60bafa AVRO-3632: [Rust] Handle defaults in unions according to the spec (#1901)
21f60bafa is described below

commit 21f60bafab505fd051568a9d28a731130c127ab5
Author: Santiago Fraire Willemoes <sa...@gmail.com>
AuthorDate: Mon Oct 10 22:09:47 2022 +0200

    AVRO-3632: [Rust] Handle defaults in unions according to the spec (#1901)
    
    (cherry picked from commit 92d57060f98f60de2a5e6eb95a91bc2bc5a61db6)
---
 lang/rust/avro/src/error.rs    |  3 +++
 lang/rust/avro/src/schema.rs   | 32 ++++++++++++++++++++++++++++----
 lang/rust/avro/tests/schema.rs | 29 +++++++++++++++++++++++++++++
 3 files changed, 60 insertions(+), 4 deletions(-)

diff --git a/lang/rust/avro/src/error.rs b/lang/rust/avro/src/error.rs
index ec6e22aee..4f7098d2f 100644
--- a/lang/rust/avro/src/error.rs
+++ b/lang/rust/avro/src/error.rs
@@ -229,6 +229,9 @@ pub enum Error {
     #[error("Unions cannot contain duplicate types")]
     GetUnionDuplicate,
 
+    #[error("Union's first type {0:?} must match the `default`'s value type {1:?}")]
+    GetDefaultUnion(SchemaKind, ValueKind),
+
     #[error("JSON value {0} claims to be u64 but cannot be converted")]
     GetU64FromJson(serde_json::Number),
 
diff --git a/lang/rust/avro/src/schema.rs b/lang/rust/avro/src/schema.rs
index bbf13138d..f29406ffd 100644
--- a/lang/rust/avro/src/schema.rs
+++ b/lang/rust/avro/src/schema.rs
@@ -866,7 +866,7 @@ impl Parser {
         match *value {
             Value::String(ref t) => self.parse_known_schema(t.as_str(), enclosing_namespace),
             Value::Object(ref data) => self.parse_complex(data, enclosing_namespace),
-            Value::Array(ref data) => self.parse_union(data, enclosing_namespace),
+            Value::Array(ref data) => self.parse_union(data, enclosing_namespace, None),
             _ => Err(Error::ParseSchemaFromValidJson),
         }
     }
@@ -1148,7 +1148,10 @@ impl Parser {
                 other => self.parse_known_schema(other, enclosing_namespace),
             },
             Some(&Value::Object(ref data)) => self.parse_complex(data, enclosing_namespace),
-            Some(&Value::Array(ref variants)) => self.parse_union(variants, enclosing_namespace),
+            Some(&Value::Array(ref variants)) => {
+                let default = complex.get("default");
+                self.parse_union(variants, enclosing_namespace, default)
+            }
             Some(unknown) => Err(Error::GetComplexType(unknown.clone())),
             None => Err(Error::GetComplexTypeField),
         }
@@ -1373,11 +1376,32 @@ impl Parser {
         &mut self,
         items: &[Value],
         enclosing_namespace: &Namespace,
+        default: Option<&Value>,
     ) -> AvroResult<Schema> {
         items
             .iter()
             .map(|v| self.parse(v, enclosing_namespace))
             .collect::<Result<Vec<_>, _>>()
+            .and_then(|schemas| {
+                if let Some(default_value) = default.cloned() {
+                    let avro_value = types::Value::from(default_value);
+                    let first_schema = schemas.first();
+                    if let Some(schema) = first_schema {
+                        // Try to resolve the schema
+                        let resolved_value = avro_value.to_owned().resolve(schema);
+                        match resolved_value {
+                            Ok(_) => {}
+                            Err(_) => {
+                                return Err(Error::GetDefaultUnion(
+                                    SchemaKind::from(schema),
+                                    types::ValueKind::from(avro_value),
+                                ));
+                            }
+                        }
+                    }
+                }
+                Ok(schemas)
+            })
             .and_then(|schemas| Ok(Schema::Union(UnionSchema::new(schemas)?)))
     }
 
@@ -2182,7 +2206,7 @@ mod tests {
             "name": "OptionA",
             "type": "record",
             "fields": [
-                {"name": "field_one",  "type": ["null", "A"], "default": "null"}
+                {"name": "field_one",  "type": ["null", "A"], "default": null}
             ]
         }"#;
 
@@ -2199,7 +2223,7 @@ mod tests {
             fields: vec![RecordField {
                 name: "field_one".to_string(),
                 doc: None,
-                default: Some(Value::String("null".to_string())),
+                default: Some(Value::Null),
                 schema: Schema::Union(
                     UnionSchema::new(vec![
                         Schema::Null,
diff --git a/lang/rust/avro/tests/schema.rs b/lang/rust/avro/tests/schema.rs
index 044a78e55..0fc18921f 100644
--- a/lang/rust/avro/tests/schema.rs
+++ b/lang/rust/avro/tests/schema.rs
@@ -133,6 +133,35 @@ const UNION_EXAMPLES: &[(&str, bool)] = &[
             ]"#,
         false,
     ),
+    // Unions with default values
+    (
+        r#"{"name": "foo", "type": ["string", "long"], "default": "bar"}"#,
+        true,
+    ),
+    (
+        r#"{"name": "foo", "type": ["long", "string"], "default": 1}"#,
+        true,
+    ),
+    (
+        r#"{"name": "foo", "type": ["null", "string"], "default": null}"#,
+        true,
+    ),
+    (
+        r#"{"name": "foo", "type": ["string", "long"], "default": 1}"#,
+        false,
+    ),
+    (
+        r#"{"name": "foo", "type": ["string", "null"], "default": null}"#,
+        false,
+    ),
+    (
+        r#"{"name": "foo", "type": ["null", "string"], "default": "null"}"#,
+        false,
+    ),
+    (
+        r#"{"name": "foo", "type": ["long", "string"], "default": "str"}"#,
+        false,
+    ),
 ];
 
 const RECORD_EXAMPLES: &[(&str, bool)] = &[