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(())
+ }
}