Skip to content

Commit

Permalink
AVRO-4014: [Rust] Add value and schema to ValidationWithReason error …
Browse files Browse the repository at this point in the history
…class (#3007)
  • Loading branch information
CodingAnarchy committed Jul 10, 2024
1 parent 8281e61 commit 7e04c38
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 13 deletions.
12 changes: 8 additions & 4 deletions lang/rust/avro/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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 },
Expand Down
16 changes: 10 additions & 6 deletions lang/rust/avro/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
)),
}
}

Expand Down Expand Up @@ -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)),
Expand All @@ -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))),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 { name: "c", doc: None, aliases: None, default: Some(Null), schema: Union(UnionSchema { schemas: [Null, Int], variant_index: {Null: 0, Int: 1} }), order: Ascending, position: 2, custom_attributes: {} }], lookup: {"a": 0, "b": 1, "c": 2}, attributes: {} }). Reason: Unsupported value-schema combination"#,
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 { name: "c", doc: None, aliases: None, default: Some(Null), schema: Union(UnionSchema { schemas: [Null, Int], variant_index: {Null: 0, Int: 1} }), order: Ascending, position: 2, custom_attributes: {} }], lookup: {"a": 0, "b": 1, "c": 2}, attributes: {} }). Reason: Unsupported value-schema combination! Value: Boolean(false), schema: Long"#,
);

let value = Value::Record(vec![
Expand Down
52 changes: 49 additions & 3 deletions lang/rust/avro/src/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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(UnionSchema { schemas: [Null, Long], variant_index: {Null: 0, Long: 1} }), order: Ascending, position: 1, custom_attributes: {} }], lookup: {"date": 1, "name": 0, "time": 1, "time2": 1}, attributes: {} }): Reason: Unsupported value-schema combination! Value: Double(12345678.9), schema: Long"#
);
}
}
Ok(())
}
}

0 comments on commit 7e04c38

Please sign in to comment.