This is an automated email from the ASF dual-hosted git repository.
Kriskras99 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/avro-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 3c1decb fix!: Don't log potentially sensitive value in
`Value::validate` (#666)
3c1decb is described below
commit 3c1decb54c612dce292a4345838cc470d66d8c6c
Author: Kriskras99 <[email protected]>
AuthorDate: Mon Sep 28 13:23:13 2026 +0200
fix!: Don't log potentially sensitive value in `Value::validate` (#666)
* fix!: Don't log potentially sensitive value in `Value::validate`
This is done by removing the logging completely and returing an error
instead of a boolean. The code will also no longer panic when it fails
to resolve the schema.
This is a breaking change, users can fix their code by adding `.is_ok()`
to get the previous behaviour.
Reported-by: CodeQL
* feat: Display the full path of the Value in `validate` error messages
* feat: Display the full path of the Schema in `validate` error messages
* fix: Show union variant type that does not exist in the schema
* fix issues from bad merge
* Remove unused dependency on uuid
Reported by nightly
* uuid is needed as a dev-dependency
* Fix the schema path for UuidSchema types
---------
Co-authored-by: Martin Grigorov <[email protected]>
Co-authored-by: Martin Tzvetanov Grigorov <[email protected]>
---
avro/src/error.rs | 6 +
avro/src/schema/mod.rs | 14 +-
avro/src/serde/with.rs | 2 +-
avro/src/types.rs | 1047 ++++++++++++++++++++++++--------------
avro/src/writer/datum.rs | 9 +-
avro/src/writer/mod.rs | 7 +-
avro/src/writer/single_object.rs | 3 +
avro/tests/avro-3786.rs | 30 +-
avro/tests/avro-3787.rs | 10 +-
avro/tests/io.rs | 5 +-
avro/tests/schema.rs | 5 +-
avro_derive/Cargo.toml | 2 +-
12 files changed, 697 insertions(+), 443 deletions(-)
diff --git a/avro/src/error.rs b/avro/src/error.rs
index 6811cfe..88a2ecb 100644
--- a/avro/src/error.rs
+++ b/avro/src/error.rs
@@ -744,6 +744,12 @@ pub enum Details {
position: usize,
total_elements: usize,
},
+
+ #[error("The value is invalid for the given schema: {0}")]
+ InvalidValueForSchema(String),
+
+ #[error("The value is invalid for all the given schemas: {0:?}")]
+ InvalidValueForAllSchemas(Vec<String>),
}
#[derive(thiserror::Error, PartialEq)]
diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs
index 9c0de17..bd2a871 100644
--- a/avro/src/schema/mod.rs
+++ b/avro/src/schema/mod.rs
@@ -2975,7 +2975,7 @@ mod tests {
};
let avro_value = crate::to_value(foo)?;
- assert!(avro_value.validate(&schema));
+ avro_value.validate(&schema)?;
let mut writer = crate::Writer::new(&schema, Vec::new())?;
@@ -3065,10 +3065,7 @@ mod tests {
bar_use: Bar::Bar1,
};
let avro_value = crate::to_value(foo)?;
- assert!(
- avro_value.validate(&writer_schema),
- "value is valid for schema",
- );
+ avro_value.validate(&writer_schema)?;
let datum = GenericDatumWriter::builder(&writer_schema)
.build()?
.write_value_to_vec(avro_value)?;
@@ -3651,10 +3648,7 @@ mod tests {
// Serialize using the writer schema.
let writer_schema = Schema::parse(writer_schema)?;
let avro_value = crate::to_value(s)?;
- assert!(
- avro_value.validate(&writer_schema),
- "value is valid for schema",
- );
+ avro_value.validate(&writer_schema)?;
let datum = GenericDatumWriter::builder(&writer_schema)
.build()?
.write_value_to_vec(avro_value)?;
@@ -3668,7 +3662,7 @@ mod tests {
.reader_schema(&reader_schema)
.build()?
.read_value(&mut x)?;
- assert!(deser_value.validate(&reader_schema));
+ deser_value.validate(&reader_schema)?;
// Verify that we can read a field from the record.
let d: MyRecordReader = crate::from_value(&deser_value)?;
diff --git a/avro/src/serde/with.rs b/avro/src/serde/with.rs
index 13f199f..6059f66 100644
--- a/avro/src/serde/with.rs
+++ b/avro/src/serde/with.rs
@@ -881,7 +881,7 @@ mod tests {
}"#,
)
.unwrap();
- assert!(value.validate(&schema));
+ value.validate(&schema).unwrap();
}
#[test]
diff --git a/avro/src/types.rs b/avro/src/types.rs
index 9d5819a..78289c7 100644
--- a/avro/src/types.rs
+++ b/avro/src/types.rs
@@ -32,6 +32,7 @@ use crate::{
use bigdecimal::BigDecimal;
use log::{debug, error};
use serde_json::{Number, Value as JsonValue};
+use std::fmt::Formatter;
use std::{
borrow::Borrow,
collections::{BTreeMap, HashMap},
@@ -39,6 +40,7 @@ use std::{
hash::BuildHasher,
str::FromStr,
};
+use strum::IntoDiscriminant;
use uuid::Uuid;
/// Compute the maximum decimal value precision of a byte array of length
`len` could hold.
@@ -368,6 +370,154 @@ impl TryFrom<Value> for JsonValue {
}
}
+pub(crate) enum ValuePath<'a> {
+ Start,
+ Value(&'a Value, &'a ValuePath<'a>),
+ Index(usize, &'a ValuePath<'a>),
+ Key(&'a str, &'a ValuePath<'a>),
+ Field(&'a str, &'a ValuePath<'a>),
+}
+
+impl<'a> std::fmt::Display for ValuePath<'a> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ValuePath::Start => {}
+ ValuePath::Value(value, prev) => {
+ prev.fmt(f)?;
+ match value {
+ Value::Null => write!(f, "Null")?,
+ Value::Boolean(_) => write!(f, "Boolean")?,
+ Value::Int(_) => write!(f, "Int")?,
+ Value::Long(_) => write!(f, "Long")?,
+ Value::Float(_) => write!(f, "Float")?,
+ Value::Double(_) => write!(f, "Double")?,
+ Value::Bytes(_) => write!(f, "Bytes")?,
+ Value::String(_) => write!(f, "String")?,
+ Value::Fixed(_, _) => write!(f, "Fixed")?,
+ Value::Enum(_, _) => write!(f, "Enum")?,
+ Value::Union(_, _) => write!(f, "Union")?,
+ Value::Array(_) => write!(f, "Array")?,
+ Value::Map(_) => write!(f, "Map")?,
+ Value::Record(_) => write!(f, "Record")?,
+ Value::Date(_) => write!(f, "Date")?,
+ Value::Decimal(_) => write!(f, "Decimal")?,
+ Value::BigDecimal(_) => write!(f, "BigDecimal")?,
+ Value::TimeMillis(_) => write!(f, "TimeMillis")?,
+ Value::TimeMicros(_) => write!(f, "TimeMicros")?,
+ Value::TimestampMillis(_) => write!(f, "TimestampMillis")?,
+ Value::TimestampMicros(_) => write!(f, "TimestampMicros")?,
+ Value::TimestampNanos(_) => write!(f, "TimestampNanos")?,
+ Value::LocalTimestampMillis(_) => write!(f,
"LocalTimestampMillis")?,
+ Value::LocalTimestampMicros(_) => write!(f,
"LocalTimestampMicros")?,
+ Value::LocalTimestampNanos(_) => write!(f,
"LocalTimestampNanos")?,
+ Value::Duration(_) => write!(f, "Duration")?,
+ Value::Uuid(_) => write!(f, "Uuid")?,
+ }
+ }
+ ValuePath::Index(index, prev) => {
+ prev.fmt(f)?;
+ write!(f, "[{index}].")?;
+ }
+ ValuePath::Key(key, prev) => {
+ prev.fmt(f)?;
+ write!(f, r#"["{key}"]."#)?;
+ }
+ ValuePath::Field(field, prev) => {
+ prev.fmt(f)?;
+ write!(f, ".{field}.")?;
+ }
+ }
+ Ok(())
+ }
+}
+
+pub(crate) enum SchemaPath<'a> {
+ Start,
+ Schema(&'a Schema, &'a SchemaPath<'a>),
+ Index(usize, &'a SchemaPath<'a>),
+ Field(&'a str, &'a SchemaPath<'a>),
+}
+
+impl<'a> std::fmt::Display for SchemaPath<'a> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ self.fmt_inner(f).map(|_| ())
+ }
+}
+
+impl<'a> SchemaPath<'a> {
+ fn fmt_inner(&self, f: &mut Formatter<'_>) -> Result<NamespaceRef<'_>,
std::fmt::Error> {
+ match self {
+ SchemaPath::Start => Ok(None),
+ SchemaPath::Schema(schema, prev) => {
+ let enclosing_namespace = prev.fmt_inner(f)?;
+ match schema {
+ Schema::Null => write!(f, "Null")?,
+ Schema::Boolean => write!(f, "Boolean")?,
+ Schema::Int => write!(f, "Int")?,
+ Schema::Long => write!(f, "Long")?,
+ Schema::Float => write!(f, "Float")?,
+ Schema::Double => write!(f, "Double")?,
+ Schema::Bytes => write!(f, "Bytes")?,
+ Schema::String => write!(f, "String")?,
+ Schema::Array(_) => write!(f, "Array")?,
+ Schema::Map(_) => write!(f, "Map")?,
+ Schema::Union(_) => write!(f, "Union")?,
+ Schema::Record(_) => write!(f, "Record")?,
+ Schema::Enum(_) => write!(f, "Enum")?,
+ Schema::Fixed(_) => write!(f, "Fixed")?,
+ Schema::Decimal(DecimalSchema {
+ inner: InnerDecimalSchema::Fixed(_),
+ ..
+ }) => write!(f, "Decimal(Fixed)")?,
+ Schema::Decimal(DecimalSchema {
+ inner: InnerDecimalSchema::Bytes,
+ ..
+ }) => write!(f, "Decimal(Bytes)")?,
+ Schema::BigDecimal => write!(f, "BigDecimal")?,
+ Schema::Uuid(UuidSchema::Fixed(_)) => write!(f,
"Uuid(Fixed)")?,
+ Schema::Uuid(UuidSchema::Bytes) => write!(f,
"Uuid(Bytes)")?,
+ Schema::Uuid(UuidSchema::String) => write!(f,
"Uuid(String)")?,
+ Schema::Date => write!(f, "Date")?,
+ Schema::TimeMillis => write!(f, "TimeMillis")?,
+ Schema::TimeMicros => write!(f, "TimeMicros")?,
+ Schema::TimestampMillis => write!(f, "TimestampMillis")?,
+ Schema::TimestampMicros => write!(f, "TimestampMicros")?,
+ Schema::TimestampNanos => write!(f, "TimestampNanos")?,
+ Schema::LocalTimestampMillis => write!(f,
"LocalTimestampMillis")?,
+ Schema::LocalTimestampMicros => write!(f,
"LocalTimestampMicros")?,
+ Schema::LocalTimestampNanos => write!(f,
"LocalTimestampNanos")?,
+ Schema::Duration(_) => write!(f, "Duration")?,
+ Schema::Ref { .. } => write!(f, "&")?,
+ }
+
+ if schema.discriminant() != SchemaKind::Ref
+ && let Some(name) = schema.name()
+ {
+ if name.namespace().is_none() || name.namespace() ==
enclosing_namespace {
+ write!(f, "{{{}}}", name.name())?;
+ Ok(enclosing_namespace)
+ } else {
+ write!(f, "{{{name}}}")?;
+ Ok(name.namespace())
+ }
+ } else {
+ Ok(enclosing_namespace)
+ }
+ }
+ SchemaPath::Index(index, prev) => {
+ let enclosing_namespace = prev.fmt_inner(f)?;
+ write!(f, "[{index}].")?;
+ Ok(enclosing_namespace)
+ }
+ SchemaPath::Field(field, prev) => {
+ let enclosing_namespace = prev.fmt_inner(f)?;
+ write!(f, ".{field}.")?;
+ Ok(enclosing_namespace)
+ }
+ }
+ }
+}
+
impl Value {
/// Validate the value against the given [`Schema`].
///
@@ -377,11 +527,18 @@ impl Value {
///
/// See the [Avro
specification](https://avro.apache.org/docs/++version++/specification)
/// for the full set of rules of schema validation.
- ///
- /// # Panics
- /// Will panic if the schema contain unresolved references or duplicate
named types.
- pub fn validate(&self, schema: &Schema) -> bool {
- self.validate_schemata(&[schema])
+ pub fn validate(&self, schema: &Schema) -> AvroResult<()> {
+ let rs = ResolvedSchema::new(schema)?;
+ match self.validate_internal(
+ schema,
+ rs.get_names(),
+ None,
+ &ValuePath::Start,
+ &SchemaPath::Start,
+ ) {
+ Some(reason) => Err(Details::InvalidValueForSchema(reason).into()),
+ None => Ok(()),
+ }
}
/// Validate the value against the given schemata.
@@ -392,28 +549,31 @@ impl Value {
///
/// See the [Avro
specification](https://avro.apache.org/docs/++version++/specification)
/// for the full set of rules of schema validation.
- ///
- /// # Panics
- /// Will panic if the schemata contain unresolved references or duplicate
schemas.
- pub fn validate_schemata(&self, schemata: &[&Schema]) -> bool {
- let rs = ResolvedSchema::try_from(schemata.to_vec())
- .expect("Schemata didn't successfully resolve");
+ pub fn validate_schemata(&self, schemata: &[&Schema]) -> AvroResult<()> {
+ let rs = ResolvedSchema::try_from(schemata.to_vec())?;
let schemata_len = schemata.len();
- schemata.iter().any(
- |schema| match self.validate_internal(schema, rs.get_names(),
None) {
+ let mut errors = Vec::with_capacity(schemata_len);
+ let found = schemata.iter().enumerate().any(|(index, schema)| {
+ match self.validate_internal(
+ schema,
+ rs.get_names(),
+ None,
+ &ValuePath::Start,
+ &SchemaPath::Index(index, &SchemaPath::Start),
+ ) {
Some(reason) => {
- let log_message =
- format!("Invalid value: {self:?} for schema:
{schema:?}. Reason: {reason}");
- if schemata_len == 1 {
- error!("{log_message}");
- } else {
- debug!("{log_message}");
- };
+ errors.push(reason);
false
}
None => true,
- },
- )
+ }
+ });
+
+ if found {
+ Ok(())
+ } else {
+ Err(Details::InvalidValueForAllSchemas(errors).into())
+ }
}
/// Validate the value against the given schema using `names` to resolve
any references.
@@ -424,13 +584,10 @@ impl Value {
&self,
schema: &Schema,
names: &HashMap<Name, S>,
- ) -> bool {
- match self.validate_internal(schema, names, None) {
- Some(reason) => {
- error!("Invalid value: {self:?} for schema: {schema:?}.
Reason: {reason}");
- false
- }
- None => true,
+ ) -> AvroResult<()> {
+ match self.validate_internal(schema, names, None, &ValuePath::Start,
&SchemaPath::Start) {
+ Some(reason) => Err(Details::InvalidValueForSchema(reason).into()),
+ None => Ok(()),
}
}
@@ -449,6 +606,8 @@ impl Value {
schema: &Schema,
names: &HashMap<Name, S>,
enclosing_namespace: NamespaceRef,
+ value_path: &ValuePath,
+ schema_path: &SchemaPath,
) -> Option<String> {
match (self, schema) {
(_, Schema::Ref { name }) => {
@@ -461,7 +620,15 @@ impl Value {
names.keys()
))
},
- |s| self.validate_internal(s.borrow(), names,
name.namespace()),
+ |s| {
+ self.validate_internal(
+ s.borrow(),
+ names,
+ name.namespace(),
+ value_path,
+ &SchemaPath::Schema(schema, schema_path),
+ )
+ },
)
}
(&Value::Null, &Schema::Null) => None,
@@ -495,10 +662,12 @@ impl Value {
(&Value::Bytes(_), &Schema::Bytes) => None,
(&Value::Bytes(_), &Schema::Decimal { .. }) => None,
(Value::Bytes(bytes), &Schema::Uuid(UuidSchema::Bytes)) => {
+ let value_path = ValuePath::Value(self, value_path);
if bytes.len() != 16 {
Some(format!(
- "The value's size ({}) is not the right length for a
bytes UUID (16)",
- bytes.len()
+ "Size of {value_path} ({}) is not the right length for
{} (16)",
+ bytes.len(),
+ SchemaPath::Schema(schema, schema_path)
))
} else {
None
@@ -506,54 +675,67 @@ impl Value {
}
(&Value::String(_), &Schema::String) => None,
(Value::String(string), &Schema::Uuid(UuidSchema::String)) => {
- // Non-hyphenated is 32 characters, hyphenated is longer
- if string.len() < 32 {
+ let value_path = ValuePath::Value(self, value_path);
+ if string.len() < uuid::fmt::Simple::LENGTH || string.len() >
uuid::fmt::Urn::LENGTH
+ {
Some(format!(
- "The value's size ({}) is not the right length for a
string UUID (>=32)",
- string.len()
+ "Size of {value_path} ({}) is not the right length for
{} ({}..={})",
+ string.len(),
+ SchemaPath::Schema(schema, schema_path),
+ uuid::fmt::Simple::LENGTH,
+ uuid::fmt::Urn::LENGTH,
))
} else {
None
}
}
(&Value::Fixed(n, _), &Schema::Fixed(FixedSchema { size, .. })) =>
{
+ let value_path = ValuePath::Value(self, value_path);
if n != size {
Some(format!(
- "The value's size ({n}) is different than the schema's
size ({size})"
+ "Size of {value_path} ({n}) is different than {}
({size})",
+ SchemaPath::Schema(schema, schema_path),
))
} else {
None
}
}
(Value::Bytes(b), &Schema::Fixed(FixedSchema { size, .. })) => {
+ let value_path = ValuePath::Value(self, value_path);
if b.len() != size {
Some(format!(
- "The bytes' length ({}) is different than the schema's
size ({})",
+ "Size of {value_path} ({}) is different than {} ({})",
b.len(),
- size
+ size,
+ SchemaPath::Schema(schema, schema_path),
))
} else {
None
}
}
(&Value::Fixed(n, _), &Schema::Duration(_)) => {
+ let value_path = ValuePath::Value(self, value_path);
if n != 12 {
Some(format!(
- "The value's size ('{n}') must be exactly 12 to be a
Duration"
+ "Size of {value_path} ({n}) must be exactly 12 for {}",
+ SchemaPath::Schema(schema, schema_path),
))
} else {
None
}
}
(&Value::Fixed(n, _), Schema::Uuid(UuidSchema::Fixed(size, ..)))
=> {
+ let value_path = ValuePath::Value(self, value_path);
if size.size != 16 {
Some(format!(
- "The schema's size ('{}') must be exactly 16 to be a
Uuid",
- size.size
+ "Invalid schema: {} must be exactly 16 not {}",
+ SchemaPath::Schema(schema, schema_path),
+ size.size,
))
} else if n != 16 {
Some(format!(
- "The value's size ('{n}') must be exactly 16 to be a
Uuid"
+ "Size of {value_path} ({n}) must be exactly 16 for {}",
+ SchemaPath::Schema(schema, schema_path),
))
} else {
None
@@ -562,8 +744,13 @@ impl Value {
// TODO: check precision against n
(&Value::Fixed(_n, _), &Schema::Decimal { .. }) => None,
(Value::String(s), Schema::Enum(EnumSchema { symbols, .. })) => {
+ let value_path = ValuePath::Value(self, value_path);
if !symbols.contains(s) {
- Some(format!("'{s}' is not a member of the possible
symbols"))
+ // By doing s:? we get an escaped string
+ Some(format!(
+ "{value_path}({s:?}) is not a symbol in {}",
+ SchemaPath::Schema(schema, schema_path),
+ ))
} else {
None
}
@@ -573,42 +760,85 @@ impl Value {
Schema::Enum(EnumSchema {
symbols, default, ..
}),
- ) => symbols
- .get(i as usize)
- .map(|ref symbol| {
- if symbol != &s {
- Some(format!("Symbol '{s}' is not at position '{i}'"))
- } else {
- None
- }
- })
- .unwrap_or_else(|| match default {
- Some(_) => None,
- None => Some(format!("No symbol at position '{i}'")),
- }),
- // (&Value::Union(None), &Schema::Union(_)) => None,
- (&Value::Union(i, ref value), Schema::Union(inner)) => inner
- .variants()
- .get(i as usize)
- .map(|schema| value.validate_internal(schema, names,
enclosing_namespace))
- .unwrap_or_else(|| Some(format!("No schema in the union at
position '{i}'"))),
+ ) => {
+ let value_path = ValuePath::Value(self, value_path);
+ symbols
+ .get(i as usize)
+ .map(|ref symbol| {
+ if symbol != &s {
+ Some(format!("{value_path}({s:?}) does not exist
at {i} in {}",
+ SchemaPath::Schema(schema,
schema_path),))
+ } else {
+ None
+ }
+ })
+ .unwrap_or_else(|| match default {
+ Some(_) => None,
+ None => Some(format!("{value_path}({s:?}) is at
position {i} but that position does not exist in {}",
+ SchemaPath::Schema(schema,
schema_path),)),
+ })
+ }
+ (&Value::Union(i, ref value), Schema::Union(inner)) => {
+ let value_path = ValuePath::Value(self, value_path);
+ let schema_path = SchemaPath::Schema(schema, schema_path);
+ inner
+ .variants()
+ .get(i as usize)
+ .map(|schema| {
+ value.validate_internal(
+ schema,
+ names,
+ enclosing_namespace,
+ &ValuePath::Index(i as usize, &value_path),
+ &SchemaPath::Index(i as usize, &schema_path),
+ )
+ })
+ .unwrap_or_else(|| {
+ Some(format!(
+ "{} is at position {i} but that position does not
exist in {schema_path}",
+ ValuePath::Value(value, &ValuePath::Index(i as
usize, &value_path)),
+ ))
+ })
+ }
(v, Schema::Union(inner)) => {
match inner.find_schema_with_known_schemata(v, Some(names),
enclosing_namespace) {
Some(_) => None,
- None => Some("Could not find matching type in
union".to_string()),
+ None => Some(format!(
+ "Could not find type matching {} in {}",
+ ValuePath::Value(v, value_path),
+ SchemaPath::Schema(schema, schema_path),
+ )),
}
}
- (Value::Array(items), Schema::Array(inner)) =>
items.iter().fold(None, |acc, item| {
- Value::accumulate(
- acc,
- item.validate_internal(&inner.items, names,
enclosing_namespace),
- )
- }),
+ (Value::Array(items), Schema::Array(inner)) => {
+ let value_path = ValuePath::Value(self, value_path);
+ let schema_path = SchemaPath::Schema(schema, schema_path);
+ items.iter().enumerate().fold(None, |acc, (index, item)| {
+ Value::accumulate(
+ acc,
+ item.validate_internal(
+ &inner.items,
+ names,
+ enclosing_namespace,
+ &ValuePath::Index(index, &value_path),
+ &SchemaPath::Index(index, &schema_path),
+ ),
+ )
+ })
+ }
(Value::Map(items), Schema::Map(inner)) => {
- items.iter().fold(None, |acc, (_, value)| {
+ let value_path = ValuePath::Value(self, value_path);
+ let schema_path = SchemaPath::Schema(schema, schema_path);
+ items.iter().fold(None, |acc, (key, value)| {
Value::accumulate(
acc,
- value.validate_internal(&inner.types, names,
enclosing_namespace),
+ value.validate_internal(
+ &inner.types,
+ names,
+ enclosing_namespace,
+ &ValuePath::Key(key, &value_path),
+ &schema_path,
+ ),
)
})
}
@@ -621,19 +851,21 @@ impl Value {
..
}),
) => {
+ let value_path = ValuePath::Value(self, value_path);
+ let schema_path = SchemaPath::Schema(schema, schema_path);
let non_nullable_fields_count =
fields.iter().filter(|&rf| !rf.is_nullable()).count();
// If the record contains fewer fields as required fields by
the schema, it is invalid.
if record_fields.len() < non_nullable_fields_count {
return Some(format!(
- "The value's records length ({}) doesn't match the
schema ({} non-nullable fields)",
+ "{value_path} has {} fields which doesn't match
{schema_path} ({} non-nullable fields)",
record_fields.len(),
non_nullable_fields_count
));
} else if record_fields.len() > fields.len() {
return Some(format!(
- "The value's records length ({}) is greater than the
schema's ({} fields)",
+ "{value_path} has {} fields which is greater than
{schema_path} ({} fields)",
record_fields.len(),
fields.len(),
));
@@ -652,27 +884,38 @@ impl Value {
&field.schema,
names,
record_namespace,
+ &ValuePath::Field(&field.name,
&value_path),
+ &SchemaPath::Field(&field.name,
&schema_path),
),
)
}
None => Value::accumulate(
acc,
- Some(format!("There is no schema field for
field '{field_name}'")),
+ Some(format!("{value_path} has a field
'{field_name}' but that does not exist in {schema_path}")),
),
}
})
}
(Value::Map(items), Schema::Record(RecordSchema { fields, .. }))
=> {
+ let value_path = ValuePath::Value(self, value_path);
+ let schema_path = SchemaPath::Schema(schema, schema_path);
fields.iter().fold(None, |acc, field| {
if let Some(item) = items.get(&field.name) {
- let res = item.validate_internal(&field.schema, names,
enclosing_namespace);
+ // ValuePath is a Key, because the Value is a Map not
a Record
+ let res = item.validate_internal(
+ &field.schema,
+ names,
+ enclosing_namespace,
+ &ValuePath::Key(&field.name, &value_path),
+ &SchemaPath::Field(&field.name, &schema_path),
+ );
Value::accumulate(acc, res)
} else if !field.is_nullable() {
Value::accumulate(
acc,
Some(format!(
- "Field with name '{:?}' is not a member of the
map items",
- field.name
+ "{} is not a key in {value_path}",
+ SchemaPath::Field(&field.name, &schema_path),
)),
)
} else {
@@ -680,9 +923,13 @@ impl Value {
}
})
}
- (v, s) => Some(format!(
- "Unsupported value-schema combination! Value: {v:?}, schema:
{s:?}"
- )),
+ (_, _) => {
+ let value_path = ValuePath::Value(self, value_path);
+ let schema_path = SchemaPath::Schema(schema, schema_path);
+ Some(format!(
+ "Unsupported value-schema combination! Value:
{value_path}, schema: {schema_path}"
+ ))
+ }
}
}
@@ -1369,10 +1616,7 @@ mod tests {
error::Details,
to_value,
};
- use apache_avro_test_helper::{
- TestResult,
- logger::{assert_logged, assert_not_logged},
- };
+ use apache_avro_test_helper::TestResult;
use num_bigint::BigInt;
use pretty_assertions::assert_eq;
use serde_json::json;
@@ -1424,40 +1668,38 @@ mod tests {
]),
)]);
- assert!(value.validate(&schema));
+ value.validate(&schema)?;
Ok(())
}
#[test]
fn validate() -> TestResult {
let value_schema_valid = vec![
- (Value::Int(42), Schema::Int, true, ""),
- (Value::Int(43), Schema::Long, true, ""),
- (Value::Float(43.2), Schema::Float, true, ""),
- (Value::Float(45.9), Schema::Double, true, ""),
+ (Value::Int(42), Schema::Int, None),
+ (Value::Int(43), Schema::Long, None),
+ (Value::Float(43.2), Schema::Float, None),
+ (Value::Float(45.9), Schema::Double, None),
(
Value::Int(42),
Schema::Boolean,
- false,
- "Invalid value: Int(42) for schema: Boolean. Reason:
Unsupported value-schema combination! Value: Int(42), schema: Boolean",
+ Some("Unsupported value-schema combination! Value: Int,
schema: Boolean"),
),
(
Value::Union(0, Box::new(Value::Null)),
Schema::Union(UnionSchema::new(vec![Schema::Null,
Schema::Int])?),
- true,
- "",
+ None,
),
(
Value::Union(1, Box::new(Value::Int(42))),
Schema::Union(UnionSchema::new(vec![Schema::Null,
Schema::Int])?),
- true,
- "",
+ None,
),
(
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] }). Reason: Unsupported value-schema combination! Value:
Null, schema: Double",
+ Some(
+ "Unsupported value-schema combination! Value:
Union[0].Null, schema: Union[0].Double",
+ ),
),
(
Value::Union(3, Box::new(Value::Int(42))),
@@ -1467,8 +1709,7 @@ mod tests {
Schema::String,
Schema::Int,
])?),
- true,
- "",
+ None,
),
(
Value::Union(1, Box::new(Value::Long(42i64))),
@@ -1476,32 +1717,36 @@ mod tests {
Schema::Null,
Schema::TimestampMillis,
])?),
- true,
- "",
+ None,
),
(
Value::Union(2, Box::new(Value::Long(1_i64))),
Schema::Union(UnionSchema::new(vec![Schema::Null,
Schema::Int])?),
- false,
- "Invalid value: Union(2, Long(1)) for schema:
Union(UnionSchema { schemas: [Null, Int] }). Reason: No schema in the union at
position '2'",
+ Some("Union[2].Long is at position 2 but that position does
not exist in Union"),
),
(
Value::Array(vec![Value::Long(42i64)]),
Schema::array(Schema::Long).build(),
- true,
- "",
+ None,
),
(
Value::Array(vec![Value::Boolean(true)]),
Schema::array(Schema::Long).build(),
- false,
- "Invalid value: Array([Boolean(true)]) for schema:
Array(ArraySchema { items: Long, .. }). Reason: Unsupported value-schema
combination! Value: Boolean(true), schema: Long",
+ Some(
+ "Unsupported value-schema combination! Value:
Array[0].Boolean, schema: Array[0].Long",
+ ),
+ ),
+ (
+ Value::Array(vec![Value::Long(42), Value::Boolean(true)]),
+ Schema::array(Schema::Long).build(),
+ Some(
+ "Unsupported value-schema combination! Value:
Array[1].Boolean, schema: Array[1].Long",
+ ),
),
(
Value::Record(vec![]),
Schema::Null,
- false,
- "Invalid value: Record([]) for schema: Null. Reason:
Unsupported value-schema combination! Value: Record([]), schema: Null",
+ Some("Unsupported value-schema combination! Value: Record,
schema: Null"),
),
(
Value::Fixed(12, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]),
@@ -1512,8 +1757,7 @@ mod tests {
size: 12,
attributes: BTreeMap::new(),
}),
- true,
- "",
+ None,
),
(
Value::Fixed(11, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
@@ -1524,8 +1768,7 @@ mod tests {
size: 12,
attributes: BTreeMap::new(),
}),
- false,
- r#"Invalid value: Fixed(11, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10]) for schema: Duration(FixedSchema { name: Name { name: "TestName", .. },
size: 12, .. }). Reason: The value's size ('11') must be exactly 12 to be a
Duration"#,
+ Some("Size of Fixed (11) must be exactly 12 for
Duration{TestName}"),
),
(
Value::Record(vec![("unknown_field_name".to_string(),
Value::Null)]),
@@ -1542,8 +1785,9 @@ mod tests {
lookup: Default::default(),
attributes: Default::default(),
}),
- false,
- r#"Invalid value: Record([("unknown_field_name", Null)]) for
schema: Record(RecordSchema { name: Name { name: "record_name", .. }, fields:
[RecordField { name: "field_name", schema: Int, .. }], .. }). Reason: There is
no schema field for field 'unknown_field_name'"#,
+ Some(
+ "Record has a field 'unknown_field_name' but that does not
exist in Record{record_name}",
+ ),
),
(
Value::Record(vec![("field_name".to_string(), Value::Null)]),
@@ -1562,23 +1806,21 @@ mod tests {
lookup: [("field_name".to_string(),
0)].iter().cloned().collect(),
attributes: Default::default(),
}),
- false,
- r#"Invalid value: Record([("field_name", Null)]) for schema:
Record(RecordSchema { name: Name { name: "record_name", .. }, fields:
[RecordField { name: "field_name", schema: Ref { name: Name { name: "missing",
.. } }, .. }], .. }). Reason: Unresolved schema reference: 'Name { name:
"missing", .. }'. Parsed names: []"#,
+ Some(
+ r#"Unresolved schema reference: 'Name { name: "missing",
.. }'. Parsed names: []"#,
+ ),
),
];
- for (value, schema, valid, expected_err_message) in
value_schema_valid.into_iter() {
- let err_message = value.validate_internal::<Schema>(&schema,
&HashMap::default(), None);
- assert_eq!(valid, err_message.is_none());
- if !valid {
- let full_err_message = format!(
- "Invalid value: {:?} for schema: {:?}. Reason: {}",
- value,
- schema,
- err_message.unwrap()
- );
- assert_eq!(expected_err_message, full_err_message);
- }
+ for (value, schema, expected_err_message) in
value_schema_valid.into_iter() {
+ let err_message = value.validate_internal::<Schema>(
+ &schema,
+ &HashMap::default(),
+ None,
+ &ValuePath::Start,
+ &SchemaPath::Start,
+ );
+ assert_eq!(err_message.as_deref(), expected_err_message);
}
Ok(())
@@ -1594,26 +1836,22 @@ mod tests {
attributes: Default::default(),
});
- assert!(Value::Fixed(4, vec![0, 0, 0, 0]).validate(&schema));
+ Value::Fixed(4, vec![0, 0, 0, 0]).validate(&schema)?;
let value = Value::Fixed(5, vec![0, 0, 0, 0, 0]);
- assert!(!value.validate(&schema));
- assert_logged(
- format!(
- "Invalid value: {:?} for schema: {:?}. Reason: {}",
- value, schema, "The value's size (5) is different than the
schema's size (4)"
- )
- .as_str(),
+ assert_eq!(
+ value.validate(&schema).unwrap_err().to_string(),
+ "The value is invalid for the given schema: Size of Fixed (5) is
different than Fixed{some_fixed} (4)"
+ );
+ assert_eq!(
+ value.validate(&schema).unwrap_err().to_string(),
+ "The value is invalid for the given schema: Size of Fixed (5) is
different than Fixed{some_fixed} (4)"
);
- assert!(Value::Bytes(vec![0, 0, 0, 0]).validate(&schema));
+ Value::Bytes(vec![0, 0, 0, 0]).validate(&schema)?;
let value = Value::Bytes(vec![0, 0, 0, 0, 0]);
- assert!(!value.validate(&schema));
- assert_logged(
- format!(
- "Invalid value: {:?} for schema: {:?}. Reason: {}",
- value, schema, "The bytes' length (5) is different than the
schema's size (4)"
- )
- .as_str(),
+ assert_eq!(
+ value.validate(&schema).unwrap_err().to_string(),
+ "The value is invalid for the given schema: Size of Bytes (5) is
different than 4 (Fixed{some_fixed})"
);
Ok(())
@@ -1635,37 +1873,25 @@ mod tests {
attributes: Default::default(),
});
- assert!(Value::Enum(0, "spades".to_string()).validate(&schema));
- assert!(Value::String("spades".to_string()).validate(&schema));
+ Value::Enum(0, "spades".to_string()).validate(&schema)?;
+ Value::String("spades".to_string()).validate(&schema)?;
let value = Value::Enum(1, "spades".to_string());
- assert!(!value.validate(&schema));
- assert_logged(
- format!(
- "Invalid value: {:?} for schema: {:?}. Reason: {}",
- value, schema, "Symbol 'spades' is not at position '1'"
- )
- .as_str(),
+ assert_eq!(
+ value.validate(&schema).unwrap_err().to_string(),
+ r#"The value is invalid for the given schema: Enum("spades") does
not exist at 1 in Enum{some_enum}"#
);
let value = Value::Enum(1000, "spades".to_string());
- assert!(!value.validate(&schema));
- assert_logged(
- format!(
- "Invalid value: {:?} for schema: {:?}. Reason: {}",
- value, schema, "No symbol at position '1000'"
- )
- .as_str(),
+ assert_eq!(
+ value.validate(&schema).unwrap_err().to_string(),
+ r#"The value is invalid for the given schema: Enum("spades") is at
position 1000 but that position does not exist in Enum{some_enum}"#
);
let value = Value::String("lorem".to_string());
- assert!(!value.validate(&schema));
- assert_logged(
- format!(
- "Invalid value: {:?} for schema: {:?}. Reason: {}",
- value, schema, "'lorem' is not a member of the possible
symbols"
- )
- .as_str(),
+ assert_eq!(
+ value.validate(&schema).unwrap_err().to_string(),
+ r#"The value is invalid for the given schema: String("lorem") is
not a symbol in Enum{some_enum}"#
);
let other_schema = Schema::Enum(EnumSchema {
@@ -1683,13 +1909,9 @@ mod tests {
});
let value = Value::Enum(0, "spades".to_string());
- assert!(!value.validate(&other_schema));
- assert_logged(
- format!(
- "Invalid value: {:?} for schema: {:?}. Reason: {}",
- value, other_schema, "Symbol 'spades' is not at position '0'"
- )
- .as_str(),
+ assert_eq!(
+ value.validate(&other_schema).unwrap_err().to_string(),
+ r#"The value is invalid for the given schema: Enum("spades") does
not exist at 0 in Enum{some_other_enum}"#
);
Ok(())
@@ -1742,48 +1964,43 @@ mod tests {
attributes: Default::default(),
});
- assert!(
- Value::Record(vec![
- ("a".to_string(), Value::Long(42i64)),
- ("b".to_string(), Value::String("foo".to_string())),
- ])
- .validate(&schema)
- );
+ Value::Record(vec![
+ ("a".to_string(), Value::Long(42i64)),
+ ("b".to_string(), Value::String("foo".to_string())),
+ ])
+ .validate(&schema)?;
let value = Value::Record(vec![
("b".to_string(), Value::String("foo".to_string())),
("a".to_string(), Value::Long(42i64)),
]);
- assert!(value.validate(&schema));
+ value.validate(&schema)?;
let value = Value::Record(vec![
("a".to_string(), Value::Boolean(false)),
("b".to_string(), Value::String("foo".to_string())),
]);
- 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", .. }, fields: [RecordField { name: "a", schema: Long, .. },
RecordField { name: "b", schema: String, .. }, RecordField { name: "c",
default: Null, schema: Union(UnionSchema { schemas: [Null, Int] }), .. }], ..
}). Reason: Unsupported value-schema combination! Value: Boolean(false),
schema: Long"#,
+ assert_eq!(
+ value.validate(&schema).unwrap_err().to_string(),
+ "The value is invalid for the given schema: Unsupported
value-schema combination! Value: Record.a.Boolean, schema:
Record{some_record}.a.Long"
);
let value = Value::Record(vec![
("a".to_string(), Value::Long(42i64)),
("c".to_string(), Value::String("foo".to_string())),
]);
- assert!(!value.validate(&schema));
- assert_logged(
- r#"Invalid value: Record([("a", Long(42)), ("c", String("foo"))])
for schema: Record(RecordSchema { name: Name { name: "some_record", .. },
fields: [RecordField { name: "a", schema: Long, .. }, RecordField { name: "b",
schema: String, .. }, RecordField { name: "c", default: Null, schema:
Union(UnionSchema { schemas: [Null, Int] }), .. }], .. }). Reason: Could not
find matching type in union"#,
- );
- assert_not_logged(
- r#"Invalid value: String("foo") for schema: Int. Reason:
Unsupported value-schema combination"#,
+ assert_eq!(
+ value.validate(&schema).unwrap_err().to_string(),
+ "The value is invalid for the given schema: Could not find type
matching Record.c.String in Record{some_record}.c.Union",
);
let value = Value::Record(vec![
("a".to_string(), Value::Long(42i64)),
("d".to_string(), Value::String("foo".to_string())),
]);
- assert!(!value.validate(&schema));
- assert_logged(
- r#"Invalid value: Record([("a", Long(42)), ("d", String("foo"))])
for schema: Record(RecordSchema { name: Name { name: "some_record", .. },
fields: [RecordField { name: "a", schema: Long, .. }, RecordField { name: "b",
schema: String, .. }, RecordField { name: "c", default: Null, schema:
Union(UnionSchema { schemas: [Null, Int] }), .. }], .. }). Reason: There is no
schema field for field 'd'"#,
+ assert_eq!(
+ value.validate(&schema).unwrap_err().to_string(),
+ "The value is invalid for the given schema: Record has a field 'd'
but that does not exist in Record{some_record}",
);
let value = Value::Record(vec![
@@ -1792,63 +2009,56 @@ mod tests {
("c".to_string(), Value::Null),
("d".to_string(), Value::Null),
]);
- assert!(!value.validate(&schema));
- assert_logged(
- r#"Invalid value: Record([("a", Long(42)), ("b", String("foo")),
("c", Null), ("d", Null)]) for schema: Record(RecordSchema { name: Name { name:
"some_record", .. }, fields: [RecordField { name: "a", schema: Long, .. },
RecordField { name: "b", schema: String, .. }, RecordField { name: "c",
default: Null, schema: Union(UnionSchema { schemas: [Null, Int] }), .. }], ..
}). Reason: The value's records length (4) is greater than the schema's (3
fields)"#,
+ assert_eq!(
+ value.validate(&schema).unwrap_err().to_string(),
+ "The value is invalid for the given schema: Record has 4 fields
which is greater than Record{some_record} (3 fields)",
);
- assert!(
- Value::Map(
- vec![
- ("a".to_string(), Value::Long(42i64)),
- ("b".to_string(), Value::String("foo".to_string())),
- ]
- .into_iter()
- .collect()
- )
- .validate(&schema)
- );
+ Value::Map(
+ vec![
+ ("a".to_string(), Value::Long(42i64)),
+ ("b".to_string(), Value::String("foo".to_string())),
+ ]
+ .into_iter()
+ .collect(),
+ )
+ .validate(&schema)?;
- assert!(
- !Value::Map(
+ assert_eq!(
+ Value::Map(
vec![("d".to_string(), Value::Long(123_i64)),]
.into_iter()
.collect()
)
.validate(&schema)
- );
- assert_logged(
- r#"Invalid value: Map({"d": Long(123)}) for schema:
Record(RecordSchema { name: Name { name: "some_record", .. }, fields:
[RecordField { name: "a", schema: Long, .. }, RecordField { name: "b", schema:
String, .. }, RecordField { name: "c", default: Null, schema: Union(UnionSchema
{ schemas: [Null, Int] }), .. }], .. }). Reason: Field with name '"a"' is not a
member of the map items
-Field with name '"b"' is not a member of the map items"#,
+ .unwrap_err()
+ .to_string(),
+ "The value is invalid for the given schema: Record{some_record}.a.
is not a key in Map\nRecord{some_record}.b. is not a key in Map",
);
let union_schema = Schema::Union(UnionSchema::new(vec![Schema::Null,
schema])?);
- assert!(
- Value::Union(
- 1,
- Box::new(Value::Record(vec![
+ Value::Union(
+ 1,
+ Box::new(Value::Record(vec![
+ ("a".to_string(), Value::Long(42i64)),
+ ("b".to_string(), Value::String("foo".to_string())),
+ ])),
+ )
+ .validate(&union_schema)?;
+
+ Value::Union(
+ 1,
+ Box::new(Value::Map(
+ vec![
("a".to_string(), Value::Long(42i64)),
("b".to_string(), Value::String("foo".to_string())),
- ]))
- )
- .validate(&union_schema)
- );
-
- assert!(
- Value::Union(
- 1,
- Box::new(Value::Map(
- vec![
- ("a".to_string(), Value::Long(42i64)),
- ("b".to_string(), Value::String("foo".to_string())),
- ]
- .into_iter()
- .collect()
- ))
- )
- .validate(&union_schema)
- );
+ ]
+ .into_iter()
+ .collect(),
+ )),
+ )
+ .validate(&union_schema)?;
Ok(())
}
@@ -1889,7 +2099,10 @@ Field with name '"b"' is not a member of the map items"#,
#[test]
fn resolve_bytes_failure() {
let value = Value::Array(vec![Value::Int(2000), Value::Int(-42)]);
- assert!(value.resolve(&Schema::Bytes).is_err());
+ assert_eq!(
+ value.resolve(&Schema::Bytes).unwrap_err().to_string(),
+ "Unable to convert to u8, got Int(2000)"
+ );
}
#[test]
@@ -1900,7 +2113,10 @@ Field with name '"b"' is not a member of the map items"#,
scale: 4,
inner: InnerDecimalSchema::Bytes,
}))?;
- assert!(value.resolve(&Schema::String).is_err());
+ assert_eq!(
+ value.resolve(&Schema::String).unwrap_err().to_string(),
+ "Expected Value::String, Value::Bytes or Value::Fixed, got:
Decimal(Decimal { value: 4328719365, len: 5 })"
+ );
Ok(())
}
@@ -1930,14 +2146,16 @@ Field with name '"b"' is not a member of the map
items"#,
);
let value = Value::String("\u{0100}".to_string());
- assert!(
+ assert_eq!(
value
.resolve(&Schema::Decimal(DecimalSchema {
precision: NonZero::new(10).unwrap(),
scale: 4,
inner: InnerDecimalSchema::Bytes,
}))
- .is_err()
+ .unwrap_err()
+ .to_string(),
+ r#"Expected Value::Decimal, Value::Bytes, Value::Fixed or
Value::String, got: String("Ā")"#
);
Ok(())
@@ -1971,132 +2189,202 @@ Field with name '"b"' is not a member of the map
items"#,
#[test]
fn resolve_decimal_invalid_scale() {
let value = Value::Decimal(Decimal::new([1, 2]).unwrap());
- assert!(
+ assert_eq!(
value
.resolve(&Schema::Decimal(DecimalSchema {
precision: NonZero::new(2).unwrap(),
scale: 3,
inner: InnerDecimalSchema::Bytes,
}))
- .is_err()
+ .unwrap_err()
+ .to_string(),
+ "Scale 3 is greater than precision 2"
);
}
#[test]
fn resolve_decimal_invalid_precision_for_length() {
let value =
Value::Decimal(Decimal::new((1u8..=8u8).rev().collect::<Vec<_>>()).unwrap());
- assert!(
- value
- .resolve(&Schema::Decimal(DecimalSchema {
- precision: NonZero::new(1).unwrap(),
- scale: 0,
- inner: InnerDecimalSchema::Bytes,
- }))
- .is_ok()
- );
+ value
+ .resolve(&Schema::Decimal(DecimalSchema {
+ precision: NonZero::new(1).unwrap(),
+ scale: 0,
+ inner: InnerDecimalSchema::Bytes,
+ }))
+ .unwrap();
}
#[test]
fn resolve_decimal_fixed() {
let value = Value::Decimal(Decimal::new([1, 2, 3, 4, 5]).unwrap());
- assert!(
- value
- .clone()
- .resolve(&Schema::Decimal(DecimalSchema {
- precision: NonZero::new(10).unwrap(),
- scale: 1,
- inner: InnerDecimalSchema::Fixed(FixedSchema {
- name: Name::new("decimal").unwrap(),
- aliases: None,
- size: 20,
- doc: None,
- attributes: Default::default(),
- })
- }))
- .is_ok()
+ value
+ .clone()
+ .resolve(&Schema::Decimal(DecimalSchema {
+ precision: NonZero::new(10).unwrap(),
+ scale: 1,
+ inner: InnerDecimalSchema::Fixed(FixedSchema {
+ name: Name::new("decimal").unwrap(),
+ aliases: None,
+ size: 20,
+ doc: None,
+ attributes: Default::default(),
+ }),
+ }))
+ .unwrap();
+ assert_eq!(
+ value.resolve(&Schema::String).unwrap_err().to_string(),
+ "Expected Value::String, Value::Bytes or Value::Fixed, got:
Decimal(Decimal { value: 4328719365, len: 5 })"
);
- assert!(value.resolve(&Schema::String).is_err());
}
#[test]
fn resolve_date() {
let value = Value::Date(2345);
- assert!(value.clone().resolve(&Schema::Date).is_ok());
- assert!(value.resolve(&Schema::String).is_err());
+ value.clone().resolve(&Schema::Date).unwrap();
+ assert_eq!(
+ value.resolve(&Schema::String).unwrap_err().to_string(),
+ "Expected Value::String, Value::Bytes or Value::Fixed, got:
Date(2345)"
+ );
}
#[test]
fn resolve_time_millis() {
let value = Value::TimeMillis(10);
- assert!(value.clone().resolve(&Schema::TimeMillis).is_ok());
- assert!(value.resolve(&Schema::TimeMicros).is_err());
+ value.clone().resolve(&Schema::TimeMillis).unwrap();
+ assert_eq!(
+ value.resolve(&Schema::TimeMicros).unwrap_err().to_string(),
+ "Expected Value::TimeMicros, Value::Long or Value::Int, got:
TimeMillis(10)"
+ );
}
#[test]
fn resolve_time_micros() {
let value = Value::TimeMicros(10);
- assert!(value.clone().resolve(&Schema::TimeMicros).is_ok());
- assert!(value.resolve(&Schema::TimeMillis).is_err());
+ value.clone().resolve(&Schema::TimeMicros).unwrap();
+ assert_eq!(
+ value.resolve(&Schema::TimeMillis).unwrap_err().to_string(),
+ "Expected Value::TimeMillis or Value::Int, got: TimeMicros(10)"
+ );
}
#[test]
fn resolve_timestamp_millis() {
let value = Value::TimestampMillis(10);
- assert!(value.clone().resolve(&Schema::TimestampMillis).is_ok());
- assert!(value.resolve(&Schema::Float).is_err());
+ value.clone().resolve(&Schema::TimestampMillis).unwrap();
+ assert_eq!(
+ value.resolve(&Schema::Float).unwrap_err().to_string(),
+ r#"Expected Value::Float, Value::Double, Value::Int, Value::Long
or Value::String ("NaN", "INF", "Infinity", "-INF" or "-Infinity"), got:
TimestampMillis(10)"#
+ );
let value = Value::Float(10.0f32);
- assert!(value.resolve(&Schema::TimestampMillis).is_err());
+ assert_eq!(
+ value
+ .resolve(&Schema::TimestampMillis)
+ .unwrap_err()
+ .to_string(),
+ "Expected Value::TimestampMillis, Value::Long or Value::Int, got:
Float(10.0)"
+ );
}
#[test]
fn resolve_timestamp_micros() {
let value = Value::TimestampMicros(10);
- assert!(value.clone().resolve(&Schema::TimestampMicros).is_ok());
- assert!(value.resolve(&Schema::Int).is_err());
+ value.clone().resolve(&Schema::TimestampMicros).unwrap();
+ assert_eq!(
+ value.resolve(&Schema::Int).unwrap_err().to_string(),
+ "Expected Value::Int, got: TimestampMicros(10)"
+ );
let value = Value::Double(10.0);
- assert!(value.resolve(&Schema::TimestampMicros).is_err());
+ assert_eq!(
+ value
+ .resolve(&Schema::TimestampMicros)
+ .unwrap_err()
+ .to_string(),
+ "Expected Value::TimestampMicros, Value::Long or Value::Int, got:
Double(10.0)"
+ );
}
#[test]
fn test_avro_3914_resolve_timestamp_nanos() {
let value = Value::TimestampNanos(10);
- assert!(value.clone().resolve(&Schema::TimestampNanos).is_ok());
- assert!(value.resolve(&Schema::Int).is_err());
+ value.clone().resolve(&Schema::TimestampNanos).unwrap();
+ assert_eq!(
+ value.resolve(&Schema::Int).unwrap_err().to_string(),
+ "Expected Value::Int, got: TimestampNanos(10)"
+ );
let value = Value::Double(10.0);
- assert!(value.resolve(&Schema::TimestampNanos).is_err());
+ assert_eq!(
+ value
+ .resolve(&Schema::TimestampNanos)
+ .unwrap_err()
+ .to_string(),
+ "Expected Value::TimestampNanos, Value::Long or Value::Int, got:
Double(10.0)"
+ );
}
#[test]
fn test_avro_3853_resolve_timestamp_millis() {
let value = Value::LocalTimestampMillis(10);
- assert!(value.clone().resolve(&Schema::LocalTimestampMillis).is_ok());
- assert!(value.resolve(&Schema::Float).is_err());
+ value
+ .clone()
+ .resolve(&Schema::LocalTimestampMillis)
+ .unwrap();
+ assert_eq!(
+ value.resolve(&Schema::Float).unwrap_err().to_string(),
+ r#"Expected Value::Float, Value::Double, Value::Int, Value::Long
or Value::String ("NaN", "INF", "Infinity", "-INF" or "-Infinity"), got:
LocalTimestampMillis(10)"#
+ );
let value = Value::Float(10.0f32);
- assert!(value.resolve(&Schema::LocalTimestampMillis).is_err());
+ assert_eq!(
+ value
+ .resolve(&Schema::LocalTimestampMillis)
+ .unwrap_err()
+ .to_string(),
+ "Expected Value::LocalTimestampMillis, Value::Long or Value::Int,
got: Float(10.0)"
+ );
}
#[test]
fn test_avro_3853_resolve_timestamp_micros() {
let value = Value::LocalTimestampMicros(10);
- assert!(value.clone().resolve(&Schema::LocalTimestampMicros).is_ok());
- assert!(value.resolve(&Schema::Int).is_err());
+ value
+ .clone()
+ .resolve(&Schema::LocalTimestampMicros)
+ .unwrap();
+ assert_eq!(
+ value.resolve(&Schema::Int).unwrap_err().to_string(),
+ "Expected Value::Int, got: LocalTimestampMicros(10)"
+ );
let value = Value::Double(10.0);
- assert!(value.resolve(&Schema::LocalTimestampMicros).is_err());
+ assert_eq!(
+ value
+ .resolve(&Schema::LocalTimestampMicros)
+ .unwrap_err()
+ .to_string(),
+ "Expected Value::LocalTimestampMicros, Value::Long or Value::Int,
got: Double(10.0)"
+ );
}
#[test]
fn test_avro_3916_resolve_timestamp_nanos() {
let value = Value::LocalTimestampNanos(10);
- assert!(value.clone().resolve(&Schema::LocalTimestampNanos).is_ok());
- assert!(value.resolve(&Schema::Int).is_err());
+ value.clone().resolve(&Schema::LocalTimestampNanos).unwrap();
+ assert_eq!(
+ value.resolve(&Schema::Int).unwrap_err().to_string(),
+ "Expected Value::Int, got: LocalTimestampNanos(10)"
+ );
let value = Value::Double(10.0);
- assert!(value.resolve(&Schema::LocalTimestampNanos).is_err());
+ assert_eq!(
+ value
+ .resolve(&Schema::LocalTimestampNanos)
+ .unwrap_err()
+ .to_string(),
+ "Expected Value::LocalTimestampNanos, Value::Long or Value::Int,
got: Double(10.0)"
+ );
}
#[test]
@@ -2106,20 +2394,24 @@ Field with name '"b"' is not a member of the map
items"#,
Days::new(5),
Millis::new(3000),
));
- assert!(
+ value
+ .clone()
+ .resolve(&Schema::Duration(FixedSchema {
+ name: Name::try_from("TestName").expect("Name is valid"),
+ aliases: None,
+ doc: None,
+ size: 12,
+ attributes: BTreeMap::new(),
+ }))
+ .unwrap();
+ assert_eq!(
value
- .clone()
- .resolve(&Schema::Duration(FixedSchema {
- name: Name::try_from("TestName").expect("Name is valid"),
- aliases: None,
- doc: None,
- size: 12,
- attributes: BTreeMap::new()
- }))
- .is_ok()
+ .resolve(&Schema::TimestampMicros)
+ .unwrap_err()
+ .to_string(),
+ "Expected Value::TimestampMicros, Value::Long or Value::Int, got:
Duration(Duration { months: Months(10), days: Days(5), millis: Millis(3000) })"
);
- assert!(value.resolve(&Schema::TimestampMicros).is_err());
- assert!(
+ assert_eq!(
Value::Long(1i64)
.resolve(&Schema::Duration(FixedSchema {
name: Name::try_from("TestName").expect("Name is valid"),
@@ -2128,38 +2420,33 @@ Field with name '"b"' is not a member of the map
items"#,
size: 12,
attributes: BTreeMap::new()
}))
- .is_err()
+ .unwrap_err()
+ .to_string(),
+ "Expected Value::Duration or Value::Fixed(12), got: Long(1)"
);
}
#[test]
fn resolve_uuid() -> TestResult {
let value =
Value::Uuid(Uuid::parse_str("1481531d-ccc9-46d9-a56f-5b67459c0537")?);
- assert!(
- value
- .clone()
- .resolve(&Schema::Uuid(UuidSchema::String))
- .is_ok()
- );
- assert!(
- value
- .clone()
- .resolve(&Schema::Uuid(UuidSchema::Bytes))
- .is_ok()
- );
- assert!(
+ value.clone().resolve(&Schema::Uuid(UuidSchema::String))?;
+ value.clone().resolve(&Schema::Uuid(UuidSchema::Bytes))?;
+ value
+ .clone()
+ .resolve(&Schema::Uuid(UuidSchema::Fixed(FixedSchema {
+ name: Name::new("some_name")?,
+ aliases: None,
+ doc: None,
+ size: 16,
+ attributes: Default::default(),
+ })))?;
+ assert_eq!(
value
- .clone()
- .resolve(&Schema::Uuid(UuidSchema::Fixed(FixedSchema {
- name: Name::new("some_name")?,
- aliases: None,
- doc: None,
- size: 16,
- attributes: Default::default(),
- })))
- .is_ok()
+ .resolve(&Schema::TimestampMicros)
+ .unwrap_err()
+ .to_string(),
+ "Expected Value::TimestampMicros, Value::Long or Value::Int, got:
Uuid(1481531d-ccc9-46d9-a56f-5b67459c0537)"
);
- assert!(value.resolve(&Schema::TimestampMicros).is_err());
Ok(())
}
@@ -2167,7 +2454,7 @@ Field with name '"b"' is not a member of the map items"#,
#[test]
fn avro_3678_resolve_float_to_double() {
let value = Value::Float(2345.1);
- assert!(value.resolve(&Schema::Double).is_ok());
+ value.resolve(&Schema::Double).unwrap();
}
#[test]
@@ -2210,13 +2497,16 @@ Field with name '"b"' is not a member of the map
items"#,
"event".to_string(),
Value::Record(vec![("amount".to_string(), Value::Int(200))]),
)]);
- assert!(value.resolve(&schema).is_ok());
+ value.resolve(&schema)?;
let value = Value::Record(vec![(
"event".to_string(),
Value::Record(vec![("size".to_string(), Value::Int(1))]),
)]);
- assert!(value.resolve(&schema).is_err());
+ assert_eq!(
+ value.resolve(&schema).unwrap_err().to_string(),
+ r#"Could not find matching type in UnionSchema { schemas: [Null,
Record(RecordSchema { name: Name { name: "event", .. }, fields: [RecordField {
name: "amount", schema: Int, .. }, RecordField { name: "size", default: Null,
schema: Union(UnionSchema { schemas: [Null, Int] }), .. }], .. })] } for
Record([("size", Int(1))])"#
+ );
Ok(())
}
@@ -2975,14 +3265,14 @@ Field with name '"b"' is not a member of the map
items"#,
("b".into(), inner_value_wrong2),
]);
- assert!(
- !outer1.validate(&schema),
- "field b record is invalid against the schema"
- ); // this should pass, but doesn't
- assert!(
- !outer2.validate(&schema),
- "field b record is invalid against the schema"
- ); // this should pass, but doesn't
+ assert_eq!(
+ outer1.validate(&schema).unwrap_err().to_string(),
+ "The value is invalid for the given schema: Unsupported
value-schema combination! Value: Record.b.Record.z.Null, schema:
Record{TestStruct}.b.&Record{Inner}.z.Int",
+ );
+ assert_eq!(
+ outer2.validate(&schema).unwrap_err().to_string(),
+ "The value is invalid for the given schema: Record.b.Record has a
field 'a' but that does not exist in Record{TestStruct}.b.&Record{Inner}"
+ );
Ok(())
}
@@ -3057,17 +3347,17 @@ Field with name '"b"' is not a member of the map
items"#,
let test_outer2: Value = to_value(test_outer2)?;
let test_outer3: Value = to_value(test_outer3)?;
- assert!(
- !test_outer1.validate(&schema),
- "field b record is invalid against the schema"
+ assert_eq!(
+ test_outer1.validate(&schema).unwrap_err().to_string(),
+ "The value is invalid for the given schema: Unsupported
value-schema combination! Value: Record.b.String, schema:
Record{TestStruct}.b.&Record{Inner}"
);
- assert!(
- !test_outer2.validate(&schema),
- "field b record is invalid against the schema"
+ assert_eq!(
+ test_outer2.validate(&schema).unwrap_err().to_string(),
+ "The value is invalid for the given schema: Unsupported
value-schema combination! Value: Record.b.Int, schema:
Record{TestStruct}.b.&Record{Inner}"
);
- assert!(
- !test_outer3.validate(&schema),
- "field b record is invalid against the schema"
+ assert_eq!(
+ test_outer3.validate(&schema).unwrap_err().to_string(),
+ "The value is invalid for the given schema: Unsupported
value-schema combination! Value: Record.b.Union, schema:
Record{TestStruct}.b.&Record{Inner}"
);
Ok(())
@@ -3145,11 +3435,8 @@ Field with name '"b"' is not a member of the map items"#,
};
let test_value: Value = to_value(msg)?;
- assert!(test_value.validate(&schema), "test_value should validate");
- assert!(
- test_value.resolve(&schema).is_ok(),
- "test_value should resolve"
- );
+ test_value.validate(&schema)?;
+ test_value.resolve(&schema)?;
Ok(())
}
@@ -3226,11 +3513,8 @@ Field with name '"b"' is not a member of the map items"#,
};
let test_value: Value = to_value(msg)?;
- assert!(test_value.validate(&schema), "test_value should validate");
- assert!(
- test_value.resolve(&schema).is_ok(),
- "test_value should resolve"
- );
+ test_value.validate(&schema)?;
+ test_value.resolve(&schema)?;
Ok(())
}
@@ -3266,17 +3550,11 @@ Field with name '"b"' is not a member of the map
items"#,
let main_schema = schemas.first().unwrap();
let schemata: Vec<_> = schemas.iter().skip(1).collect();
- let resolve_result = avro_value.clone().resolve_schemata(main_schema,
schemata);
+ avro_value.clone().resolve_schemata(main_schema, schemata)?;
- assert!(
- resolve_result.is_ok(),
- "result of resolving with schemata should be ok, got:
{resolve_result:?}"
- );
-
- let resolve_result = avro_value.resolve(main_schema);
- assert!(
- resolve_result.is_err(),
- "result of resolving without schemata should be err, got:
{resolve_result:?}"
+ assert_eq!(
+ avro_value.resolve(main_schema).unwrap_err().to_string(),
+ "Unresolved schema reference: enumForReference"
);
Ok(())
@@ -3309,10 +3587,7 @@ Field with name '"b"' is not a member of the map items"#,
let resolve_result = avro_value.resolve_schemata(main_schema,
other_schemata)?;
let schemata_ref = schemata.iter().collect::<Vec<_>>();
- assert!(
- resolve_result.validate_schemata(&schemata_ref),
- "result of validation with schemata should be true"
- );
+ resolve_result.validate_schemata(&schemata_ref)?;
Ok(())
}
@@ -3325,11 +3600,7 @@ Field with name '"b"' is not a member of the map items"#,
BigInt::from(12345678u32).to_signed_bytes_be(),
)?);
let schema = Schema::parse_str(schema)?;
- let resolve_result = avro_value.resolve(&schema);
- assert!(
- resolve_result.is_ok(),
- "resolve result must be ok, got: {resolve_result:?}"
- );
+ avro_value.resolve(&schema)?;
Ok(())
}
@@ -3341,11 +3612,7 @@ Field with name '"b"' is not a member of the map items"#,
let avro_value = Value::BigDecimal(BigDecimal::from(12345678u32));
let schema = Schema::parse_str(schema)?;
- let resolve_result: AvroResult<Value> = avro_value.resolve(&schema);
- assert!(
- resolve_result.is_ok(),
- "resolve result must be ok, got: {resolve_result:?}"
- );
+ avro_value.resolve(&schema)?;
Ok(())
}
@@ -3365,7 +3632,7 @@ Field with name '"b"' is not a member of the map items"#,
);
let value = Value::Bytes(vec![97, 99]);
- assert!(
+ assert_eq!(
value
.resolve(&Schema::Fixed(FixedSchema {
name: "test".try_into()?,
@@ -3374,11 +3641,13 @@ Field with name '"b"' is not a member of the map
items"#,
size: 3,
attributes: Default::default()
}))
- .is_err(),
+ .unwrap_err()
+ .to_string(),
+ "Fixed size mismatch, expected: 3, got: 2"
);
let value = Value::Bytes(vec![97, 98, 99, 100]);
- assert!(
+ assert_eq!(
value
.resolve(&Schema::Fixed(FixedSchema {
name: "test".try_into()?,
@@ -3387,7 +3656,9 @@ Field with name '"b"' is not a member of the map items"#,
size: 3,
attributes: Default::default()
}))
- .is_err(),
+ .unwrap_err()
+ .to_string(),
+ "Fixed size mismatch, expected: 3, got: 4"
);
Ok(())
diff --git a/avro/src/writer/datum.rs b/avro/src/writer/datum.rs
index 8f2c264..2df0a0c 100644
--- a/avro/src/writer/datum.rs
+++ b/avro/src/writer/datum.rs
@@ -19,6 +19,7 @@ use bon::bon;
use serde::Serialize;
use std::io::Write;
+use crate::types::{SchemaPath, ValuePath};
use crate::{
AvroResult, Schema,
encode::encode_internal,
@@ -125,7 +126,13 @@ impl GenericDatumWriter<'_> {
pub fn write_value_ref<W: Write>(&self, writer: &mut W, value: &Value) ->
AvroResult<usize> {
if self.validate
&& value
- .validate_internal(self.schema, self.resolved.get_names(),
None)
+ .validate_internal(
+ self.schema,
+ self.resolved.get_names(),
+ None,
+ &ValuePath::Start,
+ &SchemaPath::Start,
+ )
.is_some()
{
return Err(Details::Validation.into());
diff --git a/avro/src/writer/mod.rs b/avro/src/writer/mod.rs
index 528b3e7..efe4337 100644
--- a/avro/src/writer/mod.rs
+++ b/avro/src/writer/mod.rs
@@ -16,6 +16,7 @@
// under the License.
//! Logic handling writing in Avro format at user level.
+use crate::types::{SchemaPath, ValuePath};
use crate::{
AvroResult, Codec, Error,
encode::{encode, encode_internal, encode_to_vec},
@@ -220,6 +221,8 @@ impl<'a, W: Write> Writer<'a, W> {
self.schema,
self.resolved_schema.get_names(),
self.schema.namespace(),
+ &ValuePath::Start,
+ &SchemaPath::Start,
) {
return Err(Details::ValidationWithReason {
value: value.clone(),
@@ -1239,12 +1242,12 @@ mod tests {
let err = writer.append_value_ref(&value).unwrap_err();
assert_eq!(
err.to_string(),
- "Value Int(1) does not match schema String: Reason: Unsupported
value-schema combination! Value: Int(1), schema: String"
+ "Value Int(1) does not match schema String: Reason: Unsupported
value-schema combination! Value: Int, schema: String"
);
let err = writer.append_value(value).unwrap_err();
assert_eq!(
err.to_string(),
- "Value Int(1) does not match schema String: Reason: Unsupported
value-schema combination! Value: Int(1), schema: String"
+ "Value Int(1) does not match schema String: Reason: Unsupported
value-schema combination! Value: Int, schema: String"
);
Ok(())
diff --git a/avro/src/writer/single_object.rs b/avro/src/writer/single_object.rs
index 2fdab1b..8df3f7a 100644
--- a/avro/src/writer/single_object.rs
+++ b/avro/src/writer/single_object.rs
@@ -23,6 +23,7 @@ use serde::Serialize;
use crate::Error;
use crate::encode::encode_internal;
use crate::serde::ser_schema::{Config, SchemaAwareSerializer};
+use crate::types::{SchemaPath, ValuePath};
use crate::util::is_human_readable;
use crate::{
AvroResult, AvroSchema, Schema,
@@ -237,6 +238,8 @@ fn write_value_ref_owned_resolved<W: Write>(
root_schema,
resolved_schema.get_names(),
root_schema.namespace(),
+ &ValuePath::Start,
+ &SchemaPath::Start,
) {
return Err(Details::ValidationWithReason {
value: value.clone(),
diff --git a/avro/tests/avro-3786.rs b/avro/tests/avro-3786.rs
index 9719fd0..1308873 100644
--- a/avro/tests/avro-3786.rs
+++ b/avro/tests/avro-3786.rs
@@ -129,10 +129,7 @@ fn avro_3786_deserialize_union_with_different_enum_order()
-> TestResult {
bar_use_parent: Some(BarUseParent { bar_use: Bar::Bar1 }),
};
let avro_value = to_value(foo1)?;
- assert!(
- avro_value.validate(&writer_schema),
- "value is valid for schema",
- );
+ avro_value.validate(&writer_schema)?;
let datum = GenericDatumWriter::builder(&writer_schema)
.build()?
.write_value_to_vec(avro_value)?;
@@ -258,10 +255,7 @@ fn
avro_3786_deserialize_union_with_different_enum_order_defined_in_record() ->
bar_parent: Some(BarParent { bar: Bar::Bar0 }),
};
let avro_value = to_value(foo1)?;
- assert!(
- avro_value.validate(&writer_schema),
- "value is valid for schema",
- );
+ avro_value.validate(&writer_schema)?;
let datum = GenericDatumWriter::builder(&writer_schema)
.build()?
.write_value_to_vec(avro_value)?;
@@ -376,10 +370,7 @@ fn
test_avro_3786_deserialize_union_with_different_enum_order_defined_in_record_
bar_parent: Some(BarParent { bar: Bar::Bar1 }),
};
let avro_value = to_value(foo1)?;
- assert!(
- avro_value.validate(&writer_schema),
- "value is valid for schema",
- );
+ avro_value.validate(&writer_schema)?;
let datum = GenericDatumWriter::builder(&writer_schema)
.build()?
.write_value_to_vec(avro_value)?;
@@ -494,10 +485,7 @@ fn
test_avro_3786_deserialize_union_with_different_enum_order_defined_in_record_
bar_parent: Some(BarParent { bar: Bar::Bar1 }),
};
let avro_value = to_value(foo1)?;
- assert!(
- avro_value.validate(&writer_schema),
- "value is valid for schema",
- );
+ avro_value.validate(&writer_schema)?;
let datum = GenericDatumWriter::builder(&writer_schema)
.build()?
.write_value_to_vec(avro_value)?;
@@ -612,10 +600,7 @@ fn
deserialize_union_with_different_enum_order_defined_in_record() -> TestResult
bar_parent: Some(BarParent { bar: Bar::Bar2 }),
};
let avro_value = to_value(foo1)?;
- assert!(
- avro_value.validate(&writer_schema),
- "value is valid for schema",
- );
+ avro_value.validate(&writer_schema)?;
let datum = GenericDatumWriter::builder(&writer_schema)
.build()?
.write_value_to_vec(avro_value)?;
@@ -891,10 +876,7 @@ fn
deserialize_union_with_record_with_enum_defined_inline_reader_has_different_i
}),
};
let avro_value = to_value(foo1)?;
- assert!(
- avro_value.validate(&writer_schema),
- "value is valid for schema",
- );
+ avro_value.validate(&writer_schema)?;
let datum = GenericDatumWriter::builder(&writer_schema)
.build()?
.write_value_to_vec(avro_value)?;
diff --git a/avro/tests/avro-3787.rs b/avro/tests/avro-3787.rs
index b1558d9..844b3c8 100644
--- a/avro/tests/avro-3787.rs
+++ b/avro/tests/avro-3787.rs
@@ -130,10 +130,7 @@ fn avro_3787_deserialize_union_with_unknown_symbol() ->
TestResult {
bar_use_parent: Some(BarUseParent { bar_use: Bar::Bar2 }),
};
let avro_value = to_value(foo1)?;
- assert!(
- avro_value.validate(&writer_schema),
- "value is valid for schema",
- );
+ avro_value.validate(&writer_schema)?;
let datum = GenericDatumWriter::builder(&writer_schema)
.build()?
.write_value_to_vec(avro_value)?;
@@ -266,10 +263,7 @@ fn
avro_3787_deserialize_union_with_unknown_symbol_no_ref() -> TestResult {
bar_parent: Some(BarParent { bar: Bar::Bar2 }),
};
let avro_value = to_value(foo2)?;
- assert!(
- avro_value.validate(&writer_schema),
- "value is valid for schema",
- );
+ avro_value.validate(&writer_schema)?;
let datum = GenericDatumWriter::builder(&writer_schema)
.build()?
.write_value_to_vec(avro_value)?;
diff --git a/avro/tests/io.rs b/avro/tests/io.rs
index 2998a2c..9fcf60b 100644
--- a/avro/tests/io.rs
+++ b/avro/tests/io.rs
@@ -219,10 +219,7 @@ fn long_record_datum() -> &'static Value {
fn test_validate() -> TestResult {
for (raw_schema, value) in schemas_to_validate().iter() {
let schema = Schema::parse_str(raw_schema)?;
- assert!(
- value.validate(&schema),
- "value {value:?} does not validate schema: {raw_schema}"
- );
+ value.validate(&schema)?;
}
Ok(())
diff --git a/avro/tests/schema.rs b/avro/tests/schema.rs
index 1419138..690be0f 100644
--- a/avro/tests/schema.rs
+++ b/avro/tests/schema.rs
@@ -952,10 +952,7 @@ fn
test_avro_3785_deserialize_namespace_with_nullable_type_containing_reference_
bar_use_parent: Some(BarUseParent { bar_use: Bar::Bar1 }),
};
let avro_value = to_value(foo1)?;
- assert!(
- avro_value.validate(&writer_schema),
- "value is valid for schema",
- );
+ avro_value.validate(&writer_schema)?;
let datum = GenericDatumWriter::builder(&writer_schema)
.build()?
.write_value_to_vec(avro_value)?;
diff --git a/avro_derive/Cargo.toml b/avro_derive/Cargo.toml
index eac66a8..e1c10e4 100644
--- a/avro_derive/Cargo.toml
+++ b/avro_derive/Cargo.toml
@@ -36,7 +36,6 @@ proc-macro2 = { default-features = false, version = "1.0.106"
}
quote = { default-features = false, version = "1.0.45" }
serde_json = { workspace = true }
syn = { default-features = false, version = "3.0.3", features = ["full",
"fold"] }
-uuid = { workspace = true }
[dev-dependencies]
apache-avro = { default-features = false, path = "../avro", features =
["derive"] }
@@ -46,6 +45,7 @@ proptest = { default-features = false, version = "1.11.0",
features = ["std"] }
rustversion = "1.0.22"
serde = { workspace = true }
trybuild = "1.0.116"
+uuid = { workspace = true }
[build-dependencies]
rustversion = "1.0.22"