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 8155d08 feat: Allow to supply own names when using `Value::resolve`
and `Value::Validate` (#579)
8155d08 is described below
commit 8155d08a55675aec10b88e36e139d8ceb5889166
Author: Kriskras99 <[email protected]>
AuthorDate: Tue Jul 14 13:16:53 2026 +0200
feat: Allow to supply own names when using `Value::resolve` and
`Value::Validate` (#579)
* feat: Allow to supply own names when using `Value::resolve` and
`Value::Validate`
* fix: Simplify `format!` + `error!` to just `error!`
* fix: `resolve_internal` must use the records namespace before the
enclosing namespace
---
avro/src/reader/datum.rs | 2 +-
avro/src/schema/parser.rs | 2 +-
avro/src/schema/record/field.rs | 4 +-
avro/src/schema/union.rs | 6 +--
avro/src/types.rs | 106 ++++++++++++++++++++++++++++++++--------
5 files changed, 93 insertions(+), 27 deletions(-)
diff --git a/avro/src/reader/datum.rs b/avro/src/reader/datum.rs
index fa692e1..4f0c31e 100644
--- a/avro/src/reader/datum.rs
+++ b/avro/src/reader/datum.rs
@@ -132,7 +132,7 @@ impl<'s> GenericDatumReader<'s> {
pub fn read_value<R: Read>(&self, reader: &mut R) -> AvroResult<Value> {
let value = decode_internal(self.writer, self.resolved.get_names(),
None, reader)?;
if let Some((reader, resolved)) = &self.reader {
- value.resolve_internal(reader, resolved.get_names(), None, &None)
+ value.resolve_internal(reader, resolved.get_names(), None, None)
} else {
Ok(value)
}
diff --git a/avro/src/schema/parser.rs b/avro/src/schema/parser.rs
index 2310e08..f84766f 100644
--- a/avro/src/schema/parser.rs
+++ b/avro/src/schema/parser.rs
@@ -662,7 +662,7 @@ impl Parser {
if let Some(ref value) = default {
let resolved = types::Value::from(value.clone())
- .resolve_enum(&symbols, &Some(value.to_string()), &None)
+ .resolve_enum(&symbols, &Some(value.to_string()), None)
.is_ok();
if !resolved {
return Err(Details::GetEnumDefault {
diff --git a/avro/src/schema/record/field.rs b/avro/src/schema/record/field.rs
index 10f8856..2bd6468 100644
--- a/avro/src/schema/record/field.rs
+++ b/avro/src/schema/record/field.rs
@@ -146,7 +146,7 @@ impl RecordField {
let resolved = schemas.iter().any(|schema| {
avro_value
.to_owned()
- .resolve_internal(schema, names, schema.namespace(),
&None)
+ .resolve_internal(schema, names, schema.namespace(),
None)
.is_ok()
});
@@ -163,7 +163,7 @@ impl RecordField {
}
} else {
let resolved = avro_value
- .resolve_internal(field_schema, names,
field_schema.namespace(), &None)
+ .resolve_internal(field_schema, names,
field_schema.namespace(), None)
.is_ok();
if !resolved {
diff --git a/avro/src/schema/union.rs b/avro/src/schema/union.rs
index 1561c11..ca9a997 100644
--- a/avro/src/schema/union.rs
+++ b/avro/src/schema/union.rs
@@ -251,7 +251,7 @@ impl UnionSchema {
// TODO: Do this without the clone
value
.clone()
- .resolve_internal(schema, known_schemata, namespace,
&None)
+ .resolve_internal(schema, known_schemata, namespace,
None)
.ok()
.map(|_| (index, schema))
} else {
@@ -275,7 +275,7 @@ impl UnionSchema {
// TODO: Do this without the clone
value
.clone()
- .resolve_internal(schema, known_schemata, namespace,
&None)
+ .resolve_internal(schema, known_schemata, namespace,
None)
.is_ok()
})
});
@@ -293,7 +293,7 @@ impl UnionSchema {
// TODO: Do this without the clone
value
.clone()
- .resolve_internal(schema, known_schemata, namespace,
&None)
+ .resolve_internal(schema, known_schemata, namespace,
None)
.is_ok()
})
}
diff --git a/avro/src/types.rs b/avro/src/types.rs
index 503f6cb..c2e0b3a 100644
--- a/avro/src/types.rs
+++ b/avro/src/types.rs
@@ -370,6 +370,10 @@ impl TryFrom<Value> for JsonValue {
impl Value {
/// Validate the value against the given [`Schema`].
///
+ /// Note: This will first build a reference map from the schema (see
[`ResolvedSchema`]). If this
+ /// function is called more than once for the same schema, it is
recommended to use
+ /// [`validate_with_names`](Self::validate_with_names) instead.
+ ///
/// See the [Avro
specification](https://avro.apache.org/docs/++version++/specification)
/// for the full set of rules of schema validation.
///
@@ -381,6 +385,13 @@ impl Value {
/// Validate the value against the given schemata.
///
+ /// Note: This will first build a reference map from the schema (see
[`ResolvedSchema`]). If this
+ /// function is called more than once for the same schema, it is
recommended to use
+ /// [`validate_with_names`](Self::validate_with_names) instead.
+ ///
+ /// 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 {
@@ -404,6 +415,24 @@ impl Value {
)
}
+ /// Validate the value against the given schema using `names` to resolve
any references.
+ ///
+ /// See the [Avro
specification](https://avro.apache.org/docs/++version++/specification)
+ /// for the full set of rules of schema validation.
+ pub fn validate_with_names<S: Borrow<Schema> + Debug>(
+ &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,
+ }
+ }
+
fn accumulate(accumulator: Option<String>, other: Option<String>) ->
Option<String> {
match (accumulator, other) {
(None, None) => None,
@@ -656,30 +685,62 @@ impl Value {
}
}
- /// Attempt to perform schema resolution on the value, with the given
- /// [Schema](../schema/enum.Schema.html).
+ /// Resolve this value into a new schema.
+ ///
+ /// Note: This will first build a reference map from the schema (see
[`ResolvedSchema`]). If this
+ /// function is called more than once for the same schema, it is
recommended to use
+ /// [`resolve_with_names`](Self::resolve_with_names) instead.
///
/// See [Schema
Resolution](https://avro.apache.org/docs/++version++/specification/#schema-resolution)
- /// in the Avro specification for the full set of rules of schema
- /// resolution.
+ /// in the Avro specification for the full set of rules of schema
resolution.
pub fn resolve(self, schema: &Schema) -> AvroResult<Self> {
self.resolve_schemata(schema, Vec::with_capacity(0))
}
- /// Attempt to perform schema resolution on the value, with the given
- /// [Schema](../schema/enum.Schema.html) and set of schemas to use for
Refs resolution.
+ /// Resolve this value into a new schema using `schemata` to resolve any
references.
+ ///
+ /// `schemata` must contain all referenced schemas in `schema`, including
references to parts of
+ /// `schema` itself.
+ ///
+ /// Note: This will first build a reference map from the schema (see
[`ResolvedSchema`]). If this
+ /// function is called more than once for the same schema, it is
recommended to use
+ /// [`resolve_with_names`](Self::resolve_with_names) instead.
///
/// See [Schema
Resolution](https://avro.apache.org/docs/++version++/specification/#schema-resolution)
- /// in the Avro specification for the full set of rules of schema
- /// resolution.
+ /// in the Avro specification for the full set of rules of schema
resolution.
pub fn resolve_schemata(self, schema: &Schema, schemata: Vec<&Schema>) ->
AvroResult<Self> {
- let enclosing_namespace = schema.namespace();
let rs = if schemata.is_empty() {
ResolvedSchema::try_from(schema)?
} else {
ResolvedSchema::try_from(schemata)?
};
- self.resolve_internal(schema, rs.get_names(), enclosing_namespace,
&None)
+ self.resolve_internal(schema, rs.get_names(), None, None)
+ }
+
+ /// Resolve this value into a new schema using `names` to resolve any
references.
+ ///
+ /// See [Schema
Resolution](https://avro.apache.org/docs/++version++/specification/#schema-resolution)
+ /// in the Avro specification for the full set of rules of schema
resolution.
+ ///
+ /// # Example
+ /// ```
+ /// # use apache_avro::{types::Value, Error, Schema,
schema::ResolvedSchema};
+ /// # let values: [Value; 0] = [];
+ /// # let schema = Schema::Null;
+ /// let resolved_schema = ResolvedSchema::new(&schema)?;
+ ///
+ /// for value in values {
+ /// let resolved = value.resolve_with_names(&schema,
resolved_schema.get_names())?;
+ /// // Do something with the resolved value
+ /// }
+ /// # Ok::<(), Error>(())
+ /// ```
+ pub fn resolve_with_names<S: Borrow<Schema> + Debug>(
+ self,
+ schema: &Schema,
+ names: &HashMap<Name, S>,
+ ) -> AvroResult<Self> {
+ self.resolve_internal(schema, names, None, None)
}
pub(crate) fn resolve_internal<S: Borrow<Schema> + Debug>(
@@ -687,7 +748,7 @@ impl Value {
schema: &Schema,
names: &HashMap<Name, S>,
enclosing_namespace: NamespaceRef,
- field_default: &Option<JsonValue>,
+ field_default: Option<&JsonValue>,
) -> AvroResult<Self> {
// Check if this schema is a union, and if the reader schema is not.
if SchemaKind::from(&self) == SchemaKind::Union
@@ -729,8 +790,8 @@ impl Value {
}) => self.resolve_enum(symbols, default, field_default),
Schema::Array(inner) => self.resolve_array(&inner.items, names,
enclosing_namespace),
Schema::Map(inner) => self.resolve_map(&inner.types, names,
enclosing_namespace),
- Schema::Record(RecordSchema { fields, .. }) => {
- self.resolve_record(fields, names, enclosing_namespace)
+ Schema::Record(RecordSchema { fields, name, .. }) => {
+ self.resolve_record(fields, names,
name.namespace().or(enclosing_namespace))
}
Schema::Decimal(DecimalSchema {
scale,
@@ -1065,7 +1126,7 @@ impl Value {
self,
symbols: &[String],
enum_default: &Option<String>,
- _field_default: &Option<JsonValue>,
+ _field_default: Option<&JsonValue>,
) -> Result<Self, Error> {
let validate_symbol = |symbol: String, symbols: &[String]| {
if let Some(index) = symbols.iter().position(|item| item ==
&symbol) {
@@ -1104,7 +1165,7 @@ impl Value {
schema: &UnionSchema,
names: &HashMap<Name, S>,
enclosing_namespace: NamespaceRef,
- field_default: &Option<JsonValue>,
+ field_default: Option<&JsonValue>,
) -> Result<Self, Error> {
let v = match self {
// Both are unions case.
@@ -1135,7 +1196,7 @@ impl Value {
Value::Array(items) => Ok(Value::Array(
items
.into_iter()
- .map(|item| item.resolve_internal(schema, names,
enclosing_namespace, &None))
+ .map(|item| item.resolve_internal(schema, names,
enclosing_namespace, None))
.collect::<Result<_, _>>()?,
)),
other => Err(Details::GetArray {
@@ -1158,7 +1219,7 @@ impl Value {
.into_iter()
.map(|(key, value)| {
value
- .resolve_internal(schema, names,
enclosing_namespace, &None)
+ .resolve_internal(schema, names,
enclosing_namespace, None)
.map(|value| (key, value))
})
.collect::<Result<_, _>>()?,
@@ -1203,7 +1264,7 @@ impl Value {
}) => Value::try_from(value.clone())?.resolve_enum(
symbols,
default,
- &field.default.clone(),
+ field.default.as_ref(),
)?,
Schema::Union(ref union_schema) => {
let first = &union_schema.variants()[0];
@@ -1218,7 +1279,7 @@ impl Value {
first,
names,
enclosing_namespace,
- &field.default,
+ field.default.as_ref(),
)?,
),
),
@@ -1232,7 +1293,12 @@ impl Value {
},
};
value
- .resolve_internal(&field.schema, names,
enclosing_namespace, &field.default)
+ .resolve_internal(
+ &field.schema,
+ names,
+ enclosing_namespace,
+ field.default.as_ref(),
+ )
.map(|value| (field.name.clone(), value))
})
.collect::<Result<Vec<_>, _>>()?;