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 92a8908 only some tests to update
92a8908 is described below
commit 92a8908af0da60aa3a330d4611732d453cac0813
Author: Kriskras99 <[email protected]>
AuthorDate: Wed Mar 11 22:00:53 2026 +0100
only some tests to update
---
avro/src/schema/mod.rs | 70 ++++++++++-
avro/src/serde/derive.rs | 312 ++++++++++++++++++++++++++++++++++++++++++++++
avro_derive/src/fields.rs | 252 ++-----------------------------------
3 files changed, 389 insertions(+), 245 deletions(-)
diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs
index b9a75f5..06f56be 100644
--- a/avro/src/schema/mod.rs
+++ b/avro/src/schema/mod.rs
@@ -49,6 +49,7 @@ use serde::{
ser::{SerializeMap, SerializeSeq},
};
use serde_json::{Map, Value as JsonValue};
+use std::borrow::Cow;
use std::fmt::Formatter;
use std::{
collections::{BTreeMap, HashMap, HashSet},
@@ -417,9 +418,9 @@ impl FixedSchema {
#[derive(Debug, Clone)]
pub struct DecimalSchema {
/// The number of digits in the unscaled value
- pub precision: DecimalMetadata,
+ pub precision: Precision,
/// The number of digits to the right of the decimal point
- pub scale: DecimalMetadata,
+ pub scale: Scale,
/// The inner schema of the decimal (fixed or bytes)
pub inner: InnerDecimalSchema,
}
@@ -788,6 +789,71 @@ impl Schema {
}
Ok(())
}
+
+ /// Create a name for this schema.
+ ///
+ /// The name is a valid schema name and will be unique for different
+ /// schemas.
+ ///
+ /// Requires that named schemas have unique names.
+ pub(crate) fn unique_normalized_name(&self) -> Cow<'static, str> {
+ match self {
+ Schema::Null => Cow::Borrowed("null"),
+ Schema::Boolean => Cow::Borrowed("boolean"),
+ Schema::Int => Cow::Borrowed("int"),
+ Schema::Long => Cow::Borrowed("long"),
+ Schema::Float => Cow::Borrowed("float"),
+ Schema::Double => Cow::Borrowed("double"),
+ Schema::Bytes => Cow::Borrowed("bytes"),
+ Schema::String => Cow::Borrowed("string"),
+ Schema::Array(array) => {
+ Cow::Owned(format!("array_{}",
array.items.unique_normalized_name()))
+ }
+ Schema::Map(map) => Cow::Owned(format!("map_{}",
map.types.unique_normalized_name())),
+ Schema::Union(union) => {
+ let mut name = format!("union_{}", union.schemas.len());
+ for schema in &union.schemas {
+ name.push('_');
+ name.push_str(&schema.unique_normalized_name());
+ }
+ Cow::Owned(name)
+ }
+ Schema::BigDecimal => Cow::Borrowed("big_decimal"),
+ Schema::Date => Cow::Borrowed("date"),
+ Schema::TimeMillis => Cow::Borrowed("time_millis"),
+ Schema::TimeMicros => Cow::Borrowed("time_micros"),
+ Schema::TimestampMillis => Cow::Borrowed("timestamp_millis"),
+ Schema::TimestampMicros => Cow::Borrowed("timestamp_micros"),
+ Schema::TimestampNanos => Cow::Borrowed("timestamp_nanos"),
+ Schema::LocalTimestampMillis =>
Cow::Borrowed("local_timestamp_millis"),
+ Schema::LocalTimestampMicros =>
Cow::Borrowed("local_timestamp_micros"),
+ Schema::LocalTimestampNanos =>
Cow::Borrowed("local_timestamp_nanos"),
+ Schema::Decimal(DecimalSchema {
+ inner: InnerDecimalSchema::Bytes,
+ precision,
+ scale,
+ }) => Cow::Owned(format!("decimal_bytes_{precision}_{scale}")),
+ Schema::Uuid(UuidSchema::Bytes) => Cow::Borrowed("uuid_bytes"),
+ Schema::Uuid(UuidSchema::String) => Cow::Borrowed("uuid_string"),
+ Schema::Record(RecordSchema { name, .. })
+ | Schema::Enum(EnumSchema { name, .. })
+ | Schema::Fixed(FixedSchema { name, .. })
+ | Schema::Decimal(DecimalSchema {
+ inner: InnerDecimalSchema::Fixed(FixedSchema { name, .. }),
+ ..
+ })
+ | Schema::Uuid(UuidSchema::Fixed(FixedSchema { name, .. }))
+ | Schema::Duration(FixedSchema { name, .. })
+ | Schema::Ref { name } => {
+ let name: String = name
+ .to_string()
+ .chars()
+ .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
+ .collect();
+ Cow::Owned(format!("ref_{}_{}", name.len(), name))
+ }
+ }
+ }
}
impl Serialize for Schema {
diff --git a/avro/src/serde/derive.rs b/avro/src/serde/derive.rs
index 8ffc3de..5be4fed 100644
--- a/avro/src/serde/derive.rs
+++ b/avro/src/serde/derive.rs
@@ -19,6 +19,7 @@ use crate::Schema;
use crate::schema::{
FixedSchema, Name, NamespaceRef, RecordField, RecordSchema, UnionSchema,
UuidSchema,
};
+use serde_json::Value;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
@@ -768,6 +769,218 @@ impl AvroSchemaComponent for i128 {
}
}
+/// Schema definition for `[T; N]`.
+///
+/// Schema is defined as follows:
+/// - 0-sized arrays: [`Schema::Null`]
+/// - 1-sized arrays: `T::get_schema_in_ctxt`
+/// - N-sized arrays: [`Schema::Record`] with a field for every index
+///
+/// If you need or want a [`Schema::Array`] use [`apache_avro::serde::array`]
instead.
+///
+/// [`apache_avro::serde::array`]: crate::serde::array
+impl<const N: usize, T: AvroSchemaComponent> AvroSchemaComponent for [T; N] {
+ fn get_schema_in_ctxt(
+ named_schemas: &mut HashSet<Name>,
+ enclosing_namespace: NamespaceRef,
+ ) -> Schema {
+ if N == 0 {
+ Schema::Null
+ } else if N == 1 {
+ T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
+ } else {
+ let t_schema = T::get_schema_in_ctxt(named_schemas,
enclosing_namespace);
+ let name = Name::new_with_enclosing_namespace(
+ format!("array_{N}_{}", t_schema.unique_normalized_name()),
+ enclosing_namespace,
+ )
+ .expect("Name is valid");
+ if named_schemas.contains(&name) {
+ Schema::Ref { name }
+ } else {
+ named_schemas.insert(name.clone());
+
+ let t_default = T::field_default();
+ // For named types this should now be a reference
+ let t_schema_potential_ref =
+ T::get_schema_in_ctxt(named_schemas, enclosing_namespace);
+ let fields = std::iter::once(
+ RecordField::builder()
+ .name("field_0".to_string())
+ .schema(t_schema)
+ .maybe_default(t_default.clone())
+ .build(),
+ )
+ .chain((1..N).map(|n| {
+ RecordField::builder()
+ .name(format!("field_{n}"))
+ .schema(t_schema_potential_ref.clone())
+ .maybe_default(t_default.clone())
+ .build()
+ }))
+ .collect();
+
+
Schema::Record(RecordSchema::builder().name(name).fields(fields).build())
+ }
+ }
+ }
+
+ fn get_record_fields_in_ctxt(
+ named_schemas: &mut HashSet<Name>,
+ enclosing_namespace: NamespaceRef,
+ ) -> Option<Vec<RecordField>> {
+ if N == 0 {
+ None
+ } else if N == 1 {
+ T::get_record_fields_in_ctxt(named_schemas, enclosing_namespace)
+ } else {
+ let t_schema = T::get_schema_in_ctxt(named_schemas,
enclosing_namespace);
+ // For named types this should now be a reference
+ let t_schema_potential_ref = T::get_schema_in_ctxt(named_schemas,
enclosing_namespace);
+ let t_default = T::field_default();
+ Some(
+ std::iter::once(
+ RecordField::builder()
+ .name("field_0".to_string())
+ .schema(t_schema)
+ .maybe_default(t_default.clone())
+ .build(),
+ )
+ .chain((1..N).map(|n| {
+ RecordField::builder()
+ .name(format!("field_{n}"))
+ .schema(t_schema_potential_ref.clone())
+ .maybe_default(t_default.clone())
+ .build()
+ }))
+ .collect(),
+ )
+ }
+ }
+
+ fn field_default() -> Option<Value> {
+ if N == 0 {
+ Some(Value::Null)
+ } else if N == 1 {
+ T::field_default()
+ } else {
+ None
+ }
+ }
+}
+
+/// Schema definition for `(T₁, T₂, …, Tₙ)`.
+///
+/// Implement for tuples of up to 16 elements.
+///
+/// Schema is defined as follows:
+/// - 1-tuple: `T::get_schema_in_ctxt`
+/// - N-tuple: [`Schema::Record`] with a field for every element
+#[cfg_attr(docsrs, doc(fake_variadic))]
+impl<T> AvroSchemaComponent for (T,)
+where
+ T: AvroSchemaComponent,
+{
+ fn get_schema_in_ctxt(
+ named_schemas: &mut HashSet<Name>,
+ enclosing_namespace: NamespaceRef,
+ ) -> Schema {
+ T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
+ }
+
+ fn get_record_fields_in_ctxt(
+ named_schemas: &mut HashSet<Name>,
+ enclosing_namespace: NamespaceRef,
+ ) -> Option<Vec<RecordField>> {
+ T::get_record_fields_in_ctxt(named_schemas, enclosing_namespace)
+ }
+
+ fn field_default() -> Option<Value> {
+ T::field_default()
+ }
+}
+
+macro_rules! tuple_impls {
+ ($($len:expr => ($($name:ident)+))+) => {
+ $(
+ #[cfg_attr(docsrs, doc(hidden))]
+ impl<$($name),+> AvroSchemaComponent for ($($name,)+)
+ where
+ $($name: AvroSchemaComponent,)+
+ {
+ tuple_impl_body!($len => ($($name)+));
+ }
+ )+
+ };
+}
+
+macro_rules! tuple_impl_body {
+ ($len:expr => ($($name:ident)+)) => {
+ fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>,
enclosing_namespace: NamespaceRef) -> Schema {
+ let schemas: [Schema; $len] =
[$($name::get_schema_in_ctxt(named_schemas, enclosing_namespace), )+];
+
+ let mut name = format!("tuple_{}", $len);
+ for schema in &schemas {
+ name.push('_');
+ name.push_str(&schema.unique_normalized_name());
+ }
+
+ let name = Name::new_with_enclosing_namespace(name,
enclosing_namespace).expect("Name is valid");
+ if named_schemas.contains(&name) {
+ Schema::Ref { name }
+ } else {
+ named_schemas.insert(name.clone());
+
+ let defaults: [Option<Value>; $len] =
[$($name::field_default(), )+];
+
+ let fields =
schemas.into_iter().zip(defaults.into_iter()).enumerate().map(|(n, (schema,
default))| {
+ RecordField::builder()
+ .name(format!("field_{n}"))
+ .schema(schema)
+ .maybe_default(default)
+ .build()
+ }).collect();
+
+ Schema::Record(RecordSchema::builder()
+ .name(name)
+ .fields(fields)
+ .build()
+ )
+ }
+ }
+
+ fn get_record_fields_in_ctxt(named_schemas: &mut HashSet<Name>,
enclosing_namespace: NamespaceRef) -> Option<Vec<RecordField>> {
+ let schemas: [Schema; $len] =
[$($name::get_schema_in_ctxt(named_schemas, enclosing_namespace), )+];
+ let defaults: [Option<Value>; $len] = [$($name::field_default(),
)+];
+
Some(schemas.into_iter().zip(defaults.into_iter()).enumerate().map(|(n,
(schema, default))| {
+ RecordField::builder()
+ .name(format!("field_{n}"))
+ .schema(schema)
+ .maybe_default(default)
+ .build()
+ }).collect())
+ }
+ };
+}
+
+tuple_impls! {
+ 2 => (T0 T1)
+ 3 => (T0 T1 T2)
+ 4 => (T0 T1 T2 T3)
+ 5 => (T0 T1 T2 T3 T4)
+ 6 => (T0 T1 T2 T3 T4 T5)
+ 7 => (T0 T1 T2 T3 T4 T5 T6)
+ 8 => (T0 T1 T2 T3 T4 T5 T6 T7)
+ 9 => (T0 T1 T2 T3 T4 T5 T6 T7 T8)
+ 10 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9)
+ 11 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10)
+ 12 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11)
+ 13 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12)
+ 14 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13)
+ 15 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14)
+ 16 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15)
+}
+
#[cfg(test)]
mod tests {
use crate::{
@@ -775,6 +988,7 @@ mod tests {
schema::{FixedSchema, Name},
};
use apache_avro_test_helper::TestResult;
+ use uuid::Uuid;
#[test]
fn avro_rs_401_str() -> TestResult {
@@ -896,4 +1110,102 @@ mod tests {
fn avro_rs_489_option_option() {
<Option<Option<i32>>>::get_schema();
}
+
+ #[test]
+ fn avro_rs_xxx_0_array() -> TestResult {
+ let schema = Schema::parse_str(r#""null""#)?;
+
+ assert_eq!(schema, <[String; 0]>::get_schema());
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_xxx_1_array() -> TestResult {
+ let schema = Schema::parse_str(r#""string""#)?;
+
+ assert_eq!(schema, <[String; 1]>::get_schema());
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_xxx_n_array() -> TestResult {
+ let schema = Schema::parse_str(
+ r#"{
+ "type": "record",
+ "name": "array_5_string",
+ "fields": [
+ { "name": "field_0", "type": "string" },
+ { "name": "field_1", "type": "string" },
+ { "name": "field_2", "type": "string" },
+ { "name": "field_3", "type": "string" },
+ { "name": "field_4", "type": "string" }
+ ]
+ }"#,
+ )?;
+
+ assert_eq!(schema, <[String; 5]>::get_schema());
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_xxx_n_array_complex_type() -> TestResult {
+ let schema = Schema::parse_str(
+ r#"{
+ "type": "record",
+ "name": "array_2_union_2_null_ref_4_uuid",
+ "fields": [
+ { "name": "field_0", "type": ["null", {"type": "fixed",
"logicalType": "uuid", "size": 16, "name": "uuid"}], "default": null },
+ { "name": "field_1", "type": ["null", "uuid"], "default": null
}
+ ]
+ }"#,
+ )?;
+
+ assert_eq!(schema, <[Option<Uuid>; 2]>::get_schema());
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_xxx_1_tuple() -> TestResult {
+ let schema = Schema::parse_str(r#""string""#)?;
+
+ assert_eq!(schema, <(String,)>::get_schema());
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_xxx_n_tuple() -> TestResult {
+ let schema = Schema::parse_str(
+ r#"{
+ "type": "record",
+ "name": "tuple_5_string_int_long_boolean_null",
+ "fields": [
+ { "name": "field_0", "type": "string" },
+ { "name": "field_1", "type": "int" },
+ { "name": "field_2", "type": "long" },
+ { "name": "field_3", "type": "boolean" },
+ { "name": "field_4", "type": "null" }
+ ]
+ }"#,
+ )?;
+
+ assert_eq!(schema, <(String, i32, i64, bool, ())>::get_schema());
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_xxx_n_tuple_complex_type() -> TestResult {
+ let schema = Schema::parse_str(
+ r#"{
+ "type": "record",
+ "name": "tuple_2_union_2_null_ref_4_uuid_union_2_null_ref_4_uuid",
+ "fields": [
+ { "name": "field_0", "type": ["null", {"type": "fixed",
"logicalType": "uuid", "size": 16, "name": "uuid"}], "default": null },
+ { "name": "field_1", "type": ["null", "uuid"], "default": null
}
+ ]
+ }"#,
+ )?;
+
+ assert_eq!(schema, <(Option<Uuid>, Option<Uuid>)>::get_schema());
+ Ok(())
+ }
}
diff --git a/avro_derive/src/fields.rs b/avro_derive/src/fields.rs
index 8e4702d..a384818 100644
--- a/avro_derive/src/fields.rs
+++ b/avro_derive/src/fields.rs
@@ -1,9 +1,9 @@
use crate::RecordField;
use crate::attributes::{FieldDefault, With};
use crate::utils::{Schema, TypedTokenStream};
-use quote::{ToTokens, quote};
+use quote::quote;
use syn::spanned::Spanned;
-use syn::{Expr, ExprLit, Field, Lit, Type, TypeArray, TypeTuple};
+use syn::{Expr, Field, Type};
pub fn to_schema(field: &Field, with: With) ->
Result<TypedTokenStream<Schema>, Vec<syn::Error>> {
match with {
@@ -109,11 +109,11 @@ pub fn to_default(
/// An `Expr` that resolves to an instance of `Schema`.
fn type_to_schema_expr(ty: &Type) -> Result<TypedTokenStream<Schema>,
Vec<syn::Error>> {
match ty {
- Type::Slice(_) | Type::Path(_) | Type::Reference(_) =>
Ok(TypedTokenStream::<Schema>::new(
- quote! {<#ty as ::
apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)},
- )),
- Type::Tuple(tuple) => tuple_to_schema(tuple),
- Type::Array(array) => array_to_schema(array),
+ Type::Tuple(_) | Type::Array(_) | Type::Slice(_) | Type::Path(_) |
Type::Reference(_) => {
+ Ok(TypedTokenStream::<Schema>::new(
+ quote! {<#ty as ::
apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)},
+ ))
+ }
Type::Ptr(_) => Err(vec![syn::Error::new_spanned(
ty,
"AvroSchema: derive does not support raw pointers",
@@ -127,155 +127,15 @@ fn type_to_schema_expr(ty: &Type) ->
Result<TypedTokenStream<Schema>, Vec<syn::E
}
}
-/// Create a schema definition for a tuple.
-///
-/// # Mapping
-/// - `0-tuple` => `Schema::Null`,
-/// - `1-tuple` => Schema of the only element,
-/// - `n-tuple` => `Schema::Record`.
-///
-/// # `TokenStream`
-/// ## Context
-/// The token stream expects the following variables to be defined:
-/// - `named_schemas`: `&mut HashSet<Name>`
-/// - `enclosing_namespace`: `Option<&str>`
-/// ## Returns
-/// An `Expr` that resolves to an instance of `Schema`.
-fn tuple_to_schema(tuple: &TypeTuple) -> Result<TypedTokenStream<Schema>,
Vec<syn::Error>> {
- if tuple.elems.is_empty() {
- Ok(TypedTokenStream::<Schema>::new(
- 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(TypedTokenStream::<Schema>::new(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`.
-///
-/// # `TokenStream`
-/// ## Context
-/// The token stream expects the following variables to be defined:
-/// - `named_schemas`: `&mut HashSet<Name>`
-/// - `enclosing_namespace`: `Option<&str>`
-/// ## Returns
-/// An `Expr` that resolves to an instance of `Schema`.
-fn array_to_schema(array: &TypeArray) -> Result<TypedTokenStream<Schema>,
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(TypedTokenStream::<Schema>::new(
- 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(TypedTokenStream::<Schema>::new(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()
- )
- }))
- }
-}
-
fn type_to_record_fields(
ty: &Type,
) -> Result<TypedTokenStream<Option<Vec<RecordField>>>, Vec<syn::Error>> {
match ty {
- Type::Slice(_) | Type::Path(_) | Type::Reference(_) => {
+ Type::Tuple(_) | Type::Array(_) | Type::Slice(_) | Type::Path(_) |
Type::Reference(_) => {
Ok(TypedTokenStream::<Option<Vec<RecordField>>>::new(
quote! {<#ty as ::
apache_avro::AvroSchemaComponent>::get_record_fields_in_ctxt(named_schemas,
enclosing_namespace)},
))
}
- Type::Array(array) => array_to_record_fields(array),
- Type::Tuple(tuple) => tuple_to_record_fields(tuple),
Type::Ptr(_) => Err(vec![syn::Error::new_spanned(
ty,
"AvroSchema: derive does not support raw pointers",
@@ -289,104 +149,11 @@ fn type_to_record_fields(
}
}
-/// Create a schema definition for a tuple.
-///
-/// # Mapping
-/// - `0-tuple` => `Schema::Null`,
-/// - `1-tuple` => Schema of the only element,
-/// - `n-tuple` => `Schema::Record`.
-///
-/// # `TokenStream`
-/// ## Context
-/// The token stream expects the following variables to be defined:
-/// - `named_schemas`: `&mut HashSet<Name>`
-/// - `enclosing_namespace`: `Option<&str>`
-/// ## Returns
-/// An `Expr` that resolves to an instance of `Schema`.
-fn tuple_to_record_fields(
- tuple: &TypeTuple,
-) -> Result<TypedTokenStream<Option<Vec<RecordField>>>, Vec<syn::Error>> {
- if tuple.elems.is_empty() {
- Ok(TypedTokenStream::none())
- } else if tuple.elems.len() == 1 {
- type_to_record_fields(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()
- });
- }
-
- Ok(TypedTokenStream::new(quote! {
- ::std::option::Option::Some(vec![#(#fields, )*])
- }))
- }
-}
-/// Create a schema definition for an array.
-///
-/// # Mapping
-/// - `[T; 0]` => `Schema::Null`,
-/// - `[T; 1]` => Schema of `T`,
-/// - `[T; N]` => `Schema::Record`.
-///
-/// # `TokenStream`
-/// ## Context
-/// The token stream expects the following variables to be defined:
-/// - `named_schemas`: `&mut HashSet<Name>`
-/// - `enclosing_namespace`: `Option<&str>`
-/// ## Returns
-/// An `Expr` that resolves to an instance of `Schema`.
-fn array_to_record_fields(
- array: &TypeArray,
-) -> Result<TypedTokenStream<Option<Vec<RecordField>>>, 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(TypedTokenStream::<Option<_>>::new(
- quote! {::std::option::Option::None},
- ))
- } else if len == 1 {
- type_to_record_fields(&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()
- }
- });
-
- Ok(TypedTokenStream::<Option<Vec<RecordField>>>::new(quote! {
- ::std::option::Option::Some(vec![#(#fields, )*])
- }))
- }
-}
-
fn type_to_field_default(
ty: &Type,
) -> Result<TypedTokenStream<Option<serde_json::Value>>, Vec<syn::Error>> {
match ty {
- Type::Slice(_) | Type::Path(_) | Type::Reference(_) => {
+ Type::Tuple(_) | Type::Array(_) | Type::Slice(_) | Type::Path(_) |
Type::Reference(_) => {
Ok(TypedTokenStream::<Option<serde_json::Value>>::new(
quote! {<#ty as ::
apache_avro::AvroSchemaComponent>::field_default()},
))
@@ -395,7 +162,6 @@ fn type_to_field_default(
ty,
"AvroSchema: derive does not support raw pointers",
)]),
- Type::Tuple(_) | Type::Array(_) => Ok(TypedTokenStream::none()),
_ => Err(vec![syn::Error::new_spanned(
ty,
format!(