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

commit f7f9e127396749f980848f01755bb4ec82b3efef
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());

Reply via email to