This is an automated email from the ASF dual-hosted git repository.
kriskras99 pushed a commit to branch feat/enums
in repository https://gitbox.apache.org/repos/asf/avro-rs.git
The following commit(s) were added to refs/heads/feat/enums by this push:
new 58b9af3 naming tuples and arrays sucks
58b9af3 is described below
commit 58b9af3a02df5ea07fa7f13fb8a5ceaf5269590e
Author: Kriskras99 <[email protected]>
AuthorDate: Tue Mar 10 21:35:36 2026 +0100
naming tuples and arrays sucks
---
avro_derive/src/enums/bare_union.rs | 4 +-
avro_derive/src/enums/record_tag_content.rs | 4 +-
avro_derive/src/enums/union_of_records.rs | 6 +-
avro_derive/src/lib.rs | 6 +-
avro_derive/src/tuple.rs | 124 +++++++++++++++++++++++++++-
5 files changed, 130 insertions(+), 14 deletions(-)
diff --git a/avro_derive/src/enums/bare_union.rs
b/avro_derive/src/enums/bare_union.rs
index 75cb558..c465fb2 100644
--- a/avro_derive/src/enums/bare_union.rs
+++ b/avro_derive/src/enums/bare_union.rs
@@ -1,5 +1,5 @@
use crate::attributes::{NamedTypeOptions, VariantOptions};
-use crate::tuple::tuple_to_record_schema;
+use crate::tuple::tuple_struct_variant_to_record_schema;
use crate::{named_to_record_fields, type_to_schema_expr};
use proc_macro2::{Span, TokenStream};
use quote::quote;
@@ -50,7 +50,7 @@ pub fn get_data_enum_schema_def(
let schema_expr = type_to_schema_expr(&only_one.ty)?;
variant_expr.push(schema_expr);
} else if unnamed.unnamed.len() > 1 {
- let schema_expr = tuple_to_record_schema(unnamed, &name,
&[])?;
+ let schema_expr =
tuple_struct_variant_to_record_schema(unnamed, &name, &[])?;
variant_expr.push(schema_expr);
}
diff --git a/avro_derive/src/enums/record_tag_content.rs
b/avro_derive/src/enums/record_tag_content.rs
index 2565cc9..e39177e 100644
--- a/avro_derive/src/enums/record_tag_content.rs
+++ b/avro_derive/src/enums/record_tag_content.rs
@@ -1,5 +1,5 @@
use crate::attributes::{NamedTypeOptions, VariantOptions};
-use crate::tuple::tuple_to_record_schema;
+use crate::tuple::tuple_struct_variant_to_record_schema;
use crate::{aliases, named_to_record_fields, preserve_optional,
type_to_schema_expr};
use proc_macro2::TokenStream;
use quote::quote;
@@ -53,7 +53,7 @@ pub fn get_data_enum_schema_def(
let field_schema_expr = type_to_schema_expr(&only_one.ty)?;
schema_definitions.push(field_schema_expr);
} else if unnamed.unnamed.len() > 1 {
- let schema_expr = tuple_to_record_schema(unnamed, &name,
&[])?;
+ let schema_expr =
tuple_struct_variant_to_record_schema(unnamed, &name, &[])?;
schema_definitions.push(schema_expr);
}
diff --git a/avro_derive/src/enums/union_of_records.rs
b/avro_derive/src/enums/union_of_records.rs
index 8786abc..c0f8902 100644
--- a/avro_derive/src/enums/union_of_records.rs
+++ b/avro_derive/src/enums/union_of_records.rs
@@ -1,6 +1,6 @@
use crate::attributes::{NamedTypeOptions, VariantOptions};
use crate::named_to_record_fields;
-use crate::tuple::tuple_to_record_schema;
+use crate::tuple::tuple_struct_variant_to_record_schema;
use proc_macro2::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
@@ -39,13 +39,13 @@ pub fn get_data_enum_schema_def(
}
Fields::Unnamed(unnamed) => {
let schema_expr = if unnamed.unnamed.len() == 1 {
- tuple_to_record_schema(
+ tuple_struct_variant_to_record_schema(
unnamed,
&name,
&["org.apache.avro.rust.union_of_records"],
)?
} else {
- tuple_to_record_schema(unnamed, &name, &[])?
+ tuple_struct_variant_to_record_schema(unnamed, &name, &[])?
};
variant_expr.push(schema_expr);
diff --git a/avro_derive/src/lib.rs b/avro_derive/src/lib.rs
index ccdffa3..84daff0 100644
--- a/avro_derive/src/lib.rs
+++ b/avro_derive/src/lib.rs
@@ -41,6 +41,7 @@ use syn::{
parse_macro_input, spanned::Spanned,
};
+use crate::tuple::tuple_to_schema;
use crate::{
attributes::{FieldOptions, NamedTypeOptions, With},
case::RenameRule,
@@ -364,10 +365,7 @@ fn type_to_schema_expr(ty: &Type) -> Result<TokenStream,
Vec<syn::Error>> {
ty,
"AvroSchema: derive does not support raw pointers",
)]),
- Type::Tuple(_) => Err(vec![syn::Error::new_spanned(
- ty,
- "AvroSchema: derive does not support tuples",
- )]),
+ Type::Tuple(tuple) => tuple_to_schema(tuple),
_ => Err(vec![syn::Error::new_spanned(
ty,
format!(
diff --git a/avro_derive/src/tuple.rs b/avro_derive/src/tuple.rs
index 387bee5..adf56c4 100644
--- a/avro_derive/src/tuple.rs
+++ b/avro_derive/src/tuple.rs
@@ -1,6 +1,6 @@
use proc_macro2::TokenStream;
-use quote::quote;
-use syn::{FieldsUnnamed, spanned::Spanned};
+use quote::{ToTokens, quote};
+use syn::{Expr, ExprLit, FieldsUnnamed, Lit, TypeArray, TypeTuple,
spanned::Spanned};
use crate::{FieldOptions, doc_into_tokenstream, field_aliases,
type_to_schema_expr};
@@ -10,7 +10,7 @@ use crate::{FieldOptions, doc_into_tokenstream,
field_aliases, type_to_schema_ex
///
/// The schema will have the attribute `org.apache.avro.rust.tuple` any any
other specified in `extra_attributes`.
/// All attributes will have a value of `true`.
-pub fn tuple_to_record_schema(
+pub fn tuple_struct_variant_to_record_schema(
unnamed: FieldsUnnamed,
name: &str,
extra_attributes: &[&str],
@@ -32,6 +32,124 @@ pub fn tuple_to_record_schema(
})
}
+/// Create a schema definition for a tuple.
+///
+/// # Mapping
+/// - `0-tuple` => `Schema::Null`,
+/// - `1-tuple` => Schema of the only element,
+/// - `n-tuple` => `Schema::Record`.
+pub fn tuple_to_schema(tuple: &TypeTuple) -> Result<TokenStream,
Vec<syn::Error>> {
+ if tuple.elems.is_empty() {
+ Ok(quote! {::apache_avro::schema::Schema::Null})
+ } else if tuple.elems.len() == 1 {
+ type_to_schema_expr(&tuple.elems.iter().next().unwrap())
+ } else {
+ let mut fields = Vec::with_capacity(tuple.elems.len());
+
+ for (index, elem) in tuple.elems.iter().enumerate() {
+ let name = format!("field_{index}");
+ let field_schema_expr = type_to_schema_expr(elem)?;
+ fields.push(quote! {
+ ::apache_avro::schema::RecordField::builder()
+ .name(#name.to_string())
+ .schema(#field_schema_expr)
+ .build()
+ });
+ }
+
+ // Try to create a unique name for this record, this is done in a best
effort way and the
+ // name is NOT recorded in `names`.
+ // This will always start and end with a `_` as `(` and `)` are not
valid characters
+ let tuple_as_valid_name = tuple
+ .to_token_stream()
+ .to_string()
+ .chars()
+ .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
+ .collect::<String>();
+
+ let name = format!("tuple_{}{tuple_as_valid_name}", tuple.elems.len());
+
+ Ok(quote! {
+
::apache_avro::schema::Schema::Record(::apache_avro::schema::RecordSchema::builder()
+
.name(::apache_avro::schema::Name::new_with_enclosing_namespace(#name,
enclosing_namespace).expect(&format!("Unable to parse variant record name for
schema {}", #name)[..]))
+ .fields(vec![
+ #(#fields, )*
+ ])
+ .attributes(
+ [
+ ("org.apache.avro.rust.tuple".to_string(),
::serde_json::value::Value::Bool(true)),
+ ].into()
+ )
+ .build()
+ )
+ })
+ }
+}
+
+/// Create a schema definition for an array.
+///
+/// # Mapping
+/// - `[T; 0]` => `Schema::Null`,
+/// - `[T; 1]` => Schema of `T`,
+/// - `[T; N]` => `Schema::Record`.
+pub fn array_to_schema(array: &TypeArray) -> Result<TokenStream,
Vec<syn::Error>> {
+ let Expr::Lit(ExprLit {
+ lit: Lit::Int(lit), ..
+ }) = &array.len
+ else {
+ return Err(vec![syn::Error::new(
+ array.span(),
+ "AvroSchema: Expected a integer literal for the array length",
+ )]);
+ };
+ // This should always work as the length always needs to fit in a usize
+ let len: usize = lit.base10_parse().map_err(|e| vec![e])?;
+
+ if len == 0 {
+ Ok(quote! {::apache_avro::schema::Schema::Null})
+ } else if len == 1 {
+ type_to_schema_expr(&array.elem)
+ } else {
+ let t_schema_expr = type_to_schema_expr(&array.elem)?;
+ let fields = (0..len).map(|index| {
+ let name = format!("field_{index}");
+ quote! {
+ ::apache_avro::schema::RecordField::builder()
+ .name(#name.to_string())
+ .schema(#t_schema_expr)
+ .build()
+ }
+ });
+
+ // Try to create a unique name for this record, this is done as best
effort and the
+ // name is NOT recorded in `names`.
+ let array_elem_as_valid_name = array
+ .elem
+ .to_token_stream()
+ .to_string()
+ .chars()
+ .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
+ .collect::<String>();
+
+ let name = format!("array_{len}_{array_elem_as_valid_name}");
+
+ Ok(quote! {
+
::apache_avro::schema::Schema::Record(::apache_avro::schema::RecordSchema::builder()
+
.name(::apache_avro::schema::Name::new_with_enclosing_namespace(#name,
enclosing_namespace).expect(&format!("Unable to parse variant record name for
schema {}", #name)[..]))
+ .fields(vec![
+ #(#fields, )*
+ ])
+ .attributes(
+ [
+ ("org.apache.avro.rust.tuple".to_string(),
::serde_json::value::Value::Bool(true)),
+ ].into()
+ )
+ .build()
+ )
+ })
+ }
+}
+
/// Create a vector of `RecordField`s named `field_{field_index}`.
pub fn unnamed_to_record_fields(unnamed: FieldsUnnamed) -> Result<TokenStream,
Vec<syn::Error>> {
let mut fields = Vec::with_capacity(unnamed.unnamed.len());