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

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


The following commit(s) were added to refs/heads/main by this push:
     new 7e04c388b AVRO-4014: [Rust] Add value and schema to 
ValidationWithReason error class (#3007)
7e04c388b is described below

commit 7e04c388bb41ca7fe205febb34d36a666489e0a4
Author: Matt Tanous <[email protected]>
AuthorDate: Wed Jul 10 13:26:47 2024 -0400

    AVRO-4014: [Rust] Add value and schema to ValidationWithReason error class 
(#3007)
---
 lang/rust/avro/src/error.rs  | 12 ++++++----
 lang/rust/avro/src/types.rs  | 16 +++++++++-----
 lang/rust/avro/src/writer.rs | 52 +++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 67 insertions(+), 13 deletions(-)

diff --git a/lang/rust/avro/src/error.rs b/lang/rust/avro/src/error.rs
index b436f403f..6e0de0dcb 100644
--- a/lang/rust/avro/src/error.rs
+++ b/lang/rust/avro/src/error.rs
@@ -16,8 +16,8 @@
 // under the License.
 
 use crate::{
-    schema::{Name, SchemaKind},
-    types::ValueKind,
+    schema::{Name, Schema, SchemaKind},
+    types::{Value, ValueKind},
 };
 use std::{error::Error as _, fmt};
 
@@ -55,8 +55,12 @@ pub enum Error {
     Validation,
 
     /// Describes errors happened while validating Avro data.
-    #[error("Value does not match schema: Reason: {0}")]
-    ValidationWithReason(String),
+    #[error("Value {value:?} does not match schema {schema:?}: Reason: 
{reason}")]
+    ValidationWithReason {
+        value: Value,
+        schema: Schema,
+        reason: String,
+    },
 
     #[error("Unable to allocate {desired} bytes (maximum allowed: {maximum})")]
     MemoryAllocation { desired: usize, maximum: usize },
diff --git a/lang/rust/avro/src/types.rs b/lang/rust/avro/src/types.rs
index 47daab503..241f1bf51 100644
--- a/lang/rust/avro/src/types.rs
+++ b/lang/rust/avro/src/types.rs
@@ -124,6 +124,7 @@ pub enum Value {
     /// Universally unique identifier.
     Uuid(Uuid),
 }
+
 /// Any structure implementing the [ToAvro](trait.ToAvro.html) trait will be 
usable
 /// from a [Writer](../writer/struct.Writer.html).
 #[deprecated(
@@ -612,7 +613,10 @@ impl Value {
                     }
                 })
             }
-            (_v, _s) => Some("Unsupported value-schema 
combination".to_string()),
+            (v, s) => Some(format!(
+                "Unsupported value-schema combination! Value: {:?}, schema: 
{:?}",
+                v, s
+            )),
         }
     }
 
@@ -1221,7 +1225,7 @@ mod tests {
                 Value::Int(42),
                 Schema::Boolean,
                 false,
-                "Invalid value: Int(42) for schema: Boolean. Reason: 
Unsupported value-schema combination",
+                "Invalid value: Int(42) for schema: Boolean. Reason: 
Unsupported value-schema combination! Value: Int(42), schema: Boolean",
             ),
             (
                 Value::Union(0, Box::new(Value::Null)),
@@ -1239,7 +1243,7 @@ mod tests {
                 Value::Union(0, Box::new(Value::Null)),
                 Schema::Union(UnionSchema::new(vec![Schema::Double, 
Schema::Int])?),
                 false,
-                "Invalid value: Union(0, Null) for schema: Union(UnionSchema { 
schemas: [Double, Int], variant_index: {Int: 1, Double: 0} }). Reason: 
Unsupported value-schema combination",
+                "Invalid value: Union(0, Null) for schema: Union(UnionSchema { 
schemas: [Double, Int], variant_index: {Int: 1, Double: 0} }). Reason: 
Unsupported value-schema combination! Value: Null, schema: Double",
             ),
             (
                 Value::Union(3, Box::new(Value::Int(42))),
@@ -1279,9 +1283,9 @@ mod tests {
                 Value::Array(vec![Value::Boolean(true)]),
                 Schema::array(Schema::Long),
                 false,
-                "Invalid value: Array([Boolean(true)]) for schema: 
Array(ArraySchema { items: Long, attributes: {} }). Reason: Unsupported 
value-schema combination",
+                "Invalid value: Array([Boolean(true)]) for schema: 
Array(ArraySchema { items: Long, attributes: {} }). Reason: Unsupported 
value-schema combination! Value: Boolean(true), schema: Long",
             ),
-            (Value::Record(vec![]), Schema::Null, false, "Invalid value: 
Record([]) for schema: Null. Reason: Unsupported value-schema combination"),
+            (Value::Record(vec![]), Schema::Null, false, "Invalid value: 
Record([]) for schema: Null. Reason: Unsupported value-schema combination! 
Value: Record([]), schema: Null"),
             (
                 Value::Fixed(12, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]),
                 Schema::Duration,
@@ -1551,7 +1555,7 @@ mod tests {
         ]);
         assert!(!value.validate(&schema));
         assert_logged(
-            r#"Invalid value: Record([("a", Boolean(false)), ("b", 
String("foo"))]) for schema: Record(RecordSchema { name: Name { name: 
"some_record", namespace: None }, aliases: None, doc: None, fields: 
[RecordField { name: "a", doc: None, aliases: None, default: None, schema: 
Long, order: Ascending, position: 0, custom_attributes: {} }, RecordField { 
name: "b", doc: None, aliases: None, default: None, schema: String, order: 
Ascending, position: 1, custom_attributes: {} }, RecordField  [...]
+            r#"Invalid value: Record([("a", Boolean(false)), ("b", 
String("foo"))]) for schema: Record(RecordSchema { name: Name { name: 
"some_record", namespace: None }, aliases: None, doc: None, fields: 
[RecordField { name: "a", doc: None, aliases: None, default: None, schema: 
Long, order: Ascending, position: 0, custom_attributes: {} }, RecordField { 
name: "b", doc: None, aliases: None, default: None, schema: String, order: 
Ascending, position: 1, custom_attributes: {} }, RecordField  [...]
         );
 
         let value = Value::Record(vec![
diff --git a/lang/rust/avro/src/writer.rs b/lang/rust/avro/src/writer.rs
index 90bb2da4c..02938d694 100644
--- a/lang/rust/avro/src/writer.rs
+++ b/lang/rust/avro/src/writer.rs
@@ -542,7 +542,11 @@ fn write_value_ref_resolved(
     buffer: &mut Vec<u8>,
 ) -> AvroResult<()> {
     match value.validate_internal(schema, resolved_schema.get_names(), 
&schema.namespace()) {
-        Some(err) => Err(Error::ValidationWithReason(err)),
+        Some(reason) => Err(Error::ValidationWithReason {
+            value: value.clone(),
+            schema: schema.clone(),
+            reason,
+        }),
         None => encode_internal(
             value,
             schema,
@@ -559,12 +563,16 @@ fn write_value_ref_owned_resolved(
     buffer: &mut Vec<u8>,
 ) -> AvroResult<()> {
     let root_schema = resolved_schema.get_root_schema();
-    if let Some(err) = value.validate_internal(
+    if let Some(reason) = value.validate_internal(
         root_schema,
         resolved_schema.get_names(),
         &root_schema.namespace(),
     ) {
-        return Err(Error::ValidationWithReason(err));
+        return Err(Error::ValidationWithReason {
+            value: value.clone(),
+            schema: root_schema.clone(),
+            reason,
+        });
     }
     encode_internal(
         value,
@@ -1374,4 +1382,42 @@ mod tests {
 
         Ok(())
     }
+
+    #[test]
+    fn avro_4014_validation_returns_a_detailed_error() -> TestResult {
+        const SCHEMA: &str = r#"
+  {
+      "type": "record",
+      "name": "Conference",
+      "fields": [
+          {"type": "string", "name": "name"},
+          {"type": ["null", "long"], "name": "date", "aliases" : [ "time2", 
"time" ]}
+      ]
+  }"#;
+
+        #[derive(Debug, PartialEq, Clone, Serialize)]
+        pub struct Conference {
+            pub name: String,
+            pub time: Option<f64>, // wrong type: f64 instead of i64
+        }
+
+        let conf = Conference {
+            name: "RustConf".to_string(),
+            time: Some(12345678.90),
+        };
+
+        let schema = Schema::parse_str(SCHEMA)?;
+        let mut writer = Writer::new(&schema, Vec::new());
+
+        match writer.append_ser(conf) {
+            Ok(bytes) => panic!("Expected an error, but got {} bytes written", 
bytes),
+            Err(e) => {
+                assert_eq!(
+                    e.to_string(),
+                    r#"Value Record([("name", String("RustConf")), ("time", 
Union(1, Double(12345678.9)))]) does not match schema Record(RecordSchema { 
name: Name { name: "Conference", namespace: None }, aliases: None, doc: None, 
fields: [RecordField { name: "name", doc: None, aliases: None, default: None, 
schema: String, order: Ascending, position: 0, custom_attributes: {} }, 
RecordField { name: "date", doc: None, aliases: Some(["time2", "time"]), 
default: None, schema: Union(UnionSchem [...]
+                );
+            }
+        }
+        Ok(())
+    }
 }

Reply via email to