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 784294d fix: Allow serializing an enum inside a union (#591)
784294d is described below
commit 784294dd2cf7a6d6290bd08dda2b31a5ac4fa34f
Author: Kriskras99 <[email protected]>
AuthorDate: Sat Jul 25 15:52:51 2026 +0200
fix: Allow serializing an enum inside a union (#591)
* fix: Allow serializing an enum inside a union
* fix: Add more tests and resolve references
* fix: Require exact name for enum in union
---
avro/src/serde/ser_schema/mod.rs | 271 ++++++++++++++++++++++++++++++++++++-
avro/src/serde/ser_schema/union.rs | 36 ++++-
2 files changed, 300 insertions(+), 7 deletions(-)
diff --git a/avro/src/serde/ser_schema/mod.rs b/avro/src/serde/ser_schema/mod.rs
index 5aba68b..4369235 100644
--- a/avro/src/serde/ser_schema/mod.rs
+++ b/avro/src/serde/ser_schema/mod.rs
@@ -428,7 +428,7 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> Serializer for
SchemaAwareSerializer<'
fn serialize_unit_variant(
self,
- _name: &'static str,
+ name: &'static str,
variant_index: u32,
variant: &'static str,
) -> Result<Self::Ok, Self::Error> {
@@ -445,15 +445,17 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> Serializer for
SchemaAwareSerializer<'
Err(self.error("unit variant", format!(r#"Expected symbol
"{variant}" at index {variant_index} in enum"#)))
}
}
- Schema::Union(union) => match
self.get_resolved_union_variant(union, variant_index)? {
+ Schema::Union(union) if (variant_index as usize) <
union.variants().len() => match self.get_resolved_union_variant(union,
variant_index)? {
// Bare union
Schema::Null => zig_i32(variant_index as i32, &mut
*self.writer),
Schema::Record(record) if record.fields.is_empty() &&
record.name.name() == variant => {
// Union of records
zig_i32(variant_index as i32, &mut *self.writer)
}
- _ => Err(self.error("unit variant", format!("Expected
Schema::Null | Schema::Record(name: {variant}, fields: []) at index
{variant_index} in the union"))),
+ _ => UnionSerializer::new(self.writer, union,
self.config).serialize_unit_variant(name, variant_index, variant)
}
+ // This branch happens if the enum has more variants than the
union, which is valid if the target schema is a enum schema in the union
+ Schema::Union(union) => UnionSerializer::new(self.writer, union,
self.config).serialize_unit_variant(name, variant_index, variant),
_ => Err(self.error("unit variant", format!("Expected
Schema::Enum(symbols[{variant_index}] == {variant}) |
Schema::Union(variants[{variant_index}] == Schema::Null | Schema::Record(name:
{variant}, fields: []))"))),
}
}
@@ -2216,4 +2218,267 @@ mod tests {
Ok(())
}
+
+ #[test]
+ fn avro_rs_529_enum_larger_than_union_schema() -> TestResult {
+ #[derive(Debug, Serialize)]
+ enum TestEnum {
+ A,
+ B,
+ C,
+ }
+
+ let schema = Schema::parse_str(
+ r#"
+ [
+ "int",
+ {
+ "type": "enum",
+ "name": "TestEnum",
+ "symbols": ["A", "B", "C"]
+ }
+ ]
+ "#,
+ )?;
+ let names = HashMap::new();
+ assert_serialize(TestEnum::A, &schema, &names, &[2, 0]);
+ assert_serialize(TestEnum::B, &schema, &names, &[2, 2]);
+ assert_serialize(TestEnum::C, &schema, &names, &[2, 4]);
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_529_enum_equal_to_union_schema() -> TestResult {
+ #[derive(Debug, Serialize)]
+ enum TestEnum {
+ A,
+ B,
+ }
+
+ let schema = Schema::parse_str(
+ r#"
+ [
+ "int",
+ {
+ "type": "enum",
+ "name": "TestEnum",
+ "symbols": ["A", "B"]
+ }
+ ]
+ "#,
+ )?;
+ let names = HashMap::new();
+ assert_serialize(TestEnum::A, &schema, &names, &[2, 0]);
+ assert_serialize(TestEnum::B, &schema, &names, &[2, 2]);
+
+ Ok(())
+ }
+
+ #[test]
+ #[should_panic(expected = "assertion failed: `(left == right)`")]
+ fn avro_rs_529_enum_with_union_schema_with_null() {
+ #[derive(Debug, Serialize)]
+ enum TestEnum {
+ A,
+ B,
+ }
+
+ let schema = Schema::parse_str(
+ r#"
+ [
+ "null",
+ {
+ "type": "enum",
+ "name": "TestEnum",
+ "symbols": ["A", "B"]
+ }
+ ]
+ "#,
+ )
+ .unwrap();
+ let names = HashMap::new();
+ // This works because the value index does not contain a null
+ assert_serialize(TestEnum::B, &schema, &names, &[2, 2]);
+ // This doesn't work because the value index contains a null, so the
serializer uses that and
+ // writes [0, 0]
+ assert_serialize(TestEnum::A, &schema, &names, &[2, 0]);
+ }
+
+ #[test]
+ #[should_panic(expected = "assertion failed: `(left == right)`")]
+ fn avro_rs_529_enum_with_union_schema_with_empty_record_matching_variant()
{
+ #[derive(Debug, Serialize)]
+ enum TestEnum {
+ A,
+ B,
+ }
+
+ let schema = Schema::parse_str(
+ r#"
+ [
+ {
+ "type": "record",
+ "name": "A",
+ "fields": []
+ },
+ {
+ "type": "enum",
+ "name": "TestEnum",
+ "symbols": ["A", "B"]
+ }
+ ]
+ "#,
+ )
+ .unwrap();
+ let names = HashMap::new();
+ // This works because the value index does not contain a null
+ assert_serialize(TestEnum::B, &schema, &names, &[2, 2]);
+ // This doesn't work because the value index contains a null, so the
serializer uses that and
+ // writes [0, 0]
+ assert_serialize(TestEnum::A, &schema, &names, &[2, 0]);
+ }
+
+ #[test]
+ fn avro_rs_529_enum_with_union_schema_with_two_enums() {
+ #[derive(Debug, Serialize)]
+ enum TestEnum {
+ A,
+ B,
+ }
+
+ #[derive(Debug, Serialize)]
+ enum OtherEnum {
+ A,
+ B,
+ }
+
+ let schema = Schema::parse_str(
+ r#"
+ [
+ {
+ "type": "enum",
+ "name": "TestEnum",
+ "symbols": ["A", "B"]
+ },
+ {
+ "type": "enum",
+ "name": "OtherEnum",
+ "symbols": ["A", "B"]
+ }
+ ]
+ "#,
+ )
+ .unwrap();
+ let names = HashMap::new();
+ assert_serialize(TestEnum::A, &schema, &names, &[0, 0]);
+ assert_serialize(TestEnum::B, &schema, &names, &[0, 2]);
+
+ assert_serialize(OtherEnum::A, &schema, &names, &[2, 0]);
+ assert_serialize(OtherEnum::B, &schema, &names, &[2, 2]);
+ }
+
+ #[test]
+ fn avro_rs_529_enum_with_union_schema_with_reference() {
+ #[derive(Debug, Serialize)]
+ enum TestEnum {
+ A,
+ B,
+ }
+
+ let (schema, mut schemata) = Schema::parse_str_with_list(
+ r#"
+ [
+ {
+ "type": "TestEnum"
+ }
+ ]
+ "#,
+ [r#"
+ {
+ "type": "enum",
+ "name": "TestEnum",
+ "symbols": ["A", "B"]
+ }
+ "#],
+ )
+ .unwrap();
+ let mut names = HashMap::new();
+ let reference = schemata.pop().unwrap();
+ names.insert(reference.name().cloned().unwrap(), &reference);
+
+ assert_serialize(TestEnum::A, &schema, &names, &[0, 0]);
+ assert_serialize(TestEnum::B, &schema, &names, &[0, 2]);
+ }
+
+ #[test]
+ fn avro_rs_529_wrong_name() -> TestResult {
+ #[derive(Debug, Serialize)]
+ enum WrongName {
+ A,
+ B,
+ }
+
+ let schema = Schema::parse_str(
+ r#"
+ [
+ "int",
+ {
+ "type": "enum",
+ "name": "TestEnum",
+ "symbols": ["A", "B"]
+ }
+ ]
+ "#,
+ )?;
+ let names = HashMap::new();
+ assert_serialize_err(
+ WrongName::A,
+ &schema,
+ &names,
+ r#"Failed to serialize value of type `unit variant` using
Schema::Union(UnionSchema { schemas: [Int, Enum(EnumSchema { name: Name { name:
"TestEnum", .. }, symbols: ["A", "B"], .. })] }): Expected Schema::Enum(name:
"WrongName", symbols: [.., "A", ..]) in variants"#,
+ );
+ assert_serialize_err(
+ WrongName::B,
+ &schema,
+ &names,
+ r#"Failed to serialize value of type `unit variant` using
Schema::Union(UnionSchema { schemas: [Int, Enum(EnumSchema { name: Name { name:
"TestEnum", .. }, symbols: ["A", "B"], .. })] }): Expected Schema::Enum(name:
"WrongName", symbols: [.., "B", ..]) in variants"#,
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_529_unknown_variant() -> TestResult {
+ #[derive(Debug, Serialize)]
+ enum TestEnum {
+ A,
+ B,
+ C,
+ }
+
+ let schema = Schema::parse_str(
+ r#"
+ [
+ "int",
+ {
+ "type": "enum",
+ "name": "TestEnum",
+ "symbols": ["A", "B"]
+ }
+ ]
+ "#,
+ )?;
+ let names = HashMap::new();
+ assert_serialize(TestEnum::A, &schema, &names, &[2, 0]);
+ assert_serialize(TestEnum::B, &schema, &names, &[2, 2]);
+ assert_serialize_err(
+ TestEnum::C,
+ &schema,
+ &names,
+ r#"Failed to serialize value of type `unit variant` using
Schema::Union(UnionSchema { schemas: [Int, Enum(EnumSchema { name: Name { name:
"TestEnum", .. }, symbols: ["A", "B"], .. })] }): No variant named "C" in enum
schema"#,
+ );
+
+ Ok(())
+ }
}
diff --git a/avro/src/serde/ser_schema/union.rs
b/avro/src/serde/ser_schema/union.rs
index 4c02011..37c46b5 100644
--- a/avro/src/serde/ser_schema/union.rs
+++ b/avro/src/serde/ser_schema/union.rs
@@ -356,11 +356,39 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> Serializer for
UnionSerializer<'s, 'w,
fn serialize_unit_variant(
self,
- _: &'static str,
- _: u32,
- _: &'static str,
+ name: &'static str,
+ variant_index: u32,
+ variant: &'static str,
) -> Result<Self::Ok, Self::Error> {
- Err(self.error("unit variant", "Nested unions are not supported"))
+ if let Some((index, Schema::Enum(enum_schema))) =
+ self.union.find_named_schema(name, self.config.names)?
+ {
+ let variant_index = if enum_schema
+ .symbols
+ .get(variant_index as usize)
+ .map(String::as_str)
+ == Some(variant)
+ {
+ variant_index as i32
+ } else if let Some(symbol) =
enum_schema.symbols.iter().position(|s| s == variant) {
+ symbol as i32
+ } else {
+ return Err(self.error(
+ "unit variant",
+ format!(r#"No variant named "{variant}" in enum schema"#),
+ ));
+ };
+ let mut bytes_written = zig_i32(index as i32, &mut *self.writer)?;
+ bytes_written += zig_i32(variant_index, &mut *self.writer)?;
+ Ok(bytes_written)
+ } else {
+ Err(self.error(
+ "unit variant",
+ format!(
+ r#"Expected Schema::Enum(name: "{name}", symbols: [..,
"{variant}", ..]) in variants"#
+ ),
+ ))
+ }
}
fn serialize_newtype_struct<T>(