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 2d25087 fix: Support recursive types for
`Schema::independent_canonical_form` (#420)
2d25087 is described below
commit 2d250879a639e67c82315b65a51602819c081fa6
Author: Kriskras99 <[email protected]>
AuthorDate: Wed Jan 21 15:26:40 2026 +0100
fix: Support recursive types for `Schema::independent_canonical_form` (#420)
* fix: Support recursive types for `Schema::independent_canonical_form`
* fix: Make denormalizing support future named types
---------
Co-authored-by: default <[email protected]>
---
avro/src/schema/mod.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 51 insertions(+), 7 deletions(-)
diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs
index 7f453f4..4d0c8a9 100644
--- a/avro/src/schema/mod.rs
+++ b/avro/src/schema/mod.rs
@@ -614,7 +614,7 @@ impl Schema {
///
https://avro.apache.org/docs/current/specification/#parsing-canonical-form-for-schemas
pub fn independent_canonical_form(&self, schemata: &[Schema]) ->
Result<String, Error> {
let mut this = self.clone();
- this.denormalize(schemata)?;
+ this.denormalize(schemata, &mut
HashSet::with_capacity(schemata.len()))?;
Ok(this.canonical_form())
}
@@ -870,7 +870,19 @@ impl Schema {
UnionSchema::new(schemas).map(Schema::Union)
}
- fn denormalize(&mut self, schemata: &[Schema]) -> AvroResult<()> {
+ fn denormalize(
+ &mut self,
+ schemata: &[Schema],
+ defined_names: &mut HashSet<Name>,
+ ) -> AvroResult<()> {
+ // If this name already exists in this schema we can reference it.
+ // This makes the denormalized form as small as possible and prevent
infinite loops for recursive types.
+ if let Some(name) = self.name()
+ && defined_names.contains(name)
+ {
+ *self = Schema::Ref { name: name.clone() };
+ return Ok(());
+ }
match self {
Schema::Ref { name } => {
let replacement_schema = schemata
@@ -878,28 +890,32 @@ impl Schema {
.find(|s| s.name().map(|n| *n == *name).unwrap_or(false));
if let Some(schema) = replacement_schema {
let mut denorm = schema.clone();
- denorm.denormalize(schemata)?;
+ denorm.denormalize(schemata, defined_names)?;
*self = denorm;
} else {
return
Err(Details::SchemaResolutionError(name.clone()).into());
}
}
Schema::Record(record_schema) => {
+ defined_names.insert(record_schema.name.clone());
for field in &mut record_schema.fields {
- field.schema.denormalize(schemata)?;
+ field.schema.denormalize(schemata, defined_names)?;
}
}
Schema::Array(array_schema) => {
- array_schema.items.denormalize(schemata)?;
+ array_schema.items.denormalize(schemata, defined_names)?;
}
Schema::Map(map_schema) => {
- map_schema.types.denormalize(schemata)?;
+ map_schema.types.denormalize(schemata, defined_names)?;
}
Schema::Union(union_schema) => {
for schema in &mut union_schema.schemas {
- schema.denormalize(schemata)?;
+ schema.denormalize(schemata, defined_names)?;
}
}
+ schema if schema.is_named() => {
+ defined_names.insert(schema.name().expect("Schema is
named").clone());
+ }
_ => (),
}
Ok(())
@@ -6453,4 +6469,32 @@ mod tests {
Ok(())
}
+
+ #[test]
+ fn avro_rs_420_independent_canonical_form() -> TestResult {
+ let (record, schemata) = Schema::parse_str_with_list(
+ r#"{
+ "name": "root",
+ "type": "record",
+ "fields": [{
+ "name": "node",
+ "type": "node"
+ }]
+ }"#,
+ [r#"{
+ "name": "node",
+ "type": "record",
+ "fields": [{
+ "name": "children",
+ "type": ["null", "node"]
+ }]
+ }"#],
+ )?;
+ let icf = record.independent_canonical_form(&schemata)?;
+ assert_eq!(
+ icf,
+
r#"{"name":"root","type":"record","fields":[{"name":"node","type":{"name":"node","type":"record","fields":[{"name":"children","type":["null","node"]}]}}]}"#
+ );
+ Ok(())
+ }
}