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 bd2c7c9 make type_to_* private
bd2c7c9 is described below
commit bd2c7c9e2959b7e093c5cca2e6ee288843c4cb90
Author: Kriskras99 <[email protected]>
AuthorDate: Wed Mar 11 10:06:46 2026 +0100
make type_to_* private
---
avro_derive/src/attributes/mod.rs | 27 +-
avro_derive/src/enums/bare_union.rs | 7 +-
avro_derive/src/enums/record_internally_tagged.rs | 7 +-
avro_derive/src/enums/record_tag_content.rs | 9 +-
avro_derive/src/fields.rs | 390 ++++++++++++++++++++++
avro_derive/src/lib.rs | 177 +---------
avro_derive/src/tuple.rs | 148 ++------
7 files changed, 437 insertions(+), 328 deletions(-)
diff --git a/avro_derive/src/attributes/mod.rs
b/avro_derive/src/attributes/mod.rs
index 13fadfe..ab66911 100644
--- a/avro_derive/src/attributes/mod.rs
+++ b/avro_derive/src/attributes/mod.rs
@@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-use crate::{case::RenameRule, type_to_field_default_expr};
+use crate::case::RenameRule;
use darling::{FromAttributes, FromMeta};
use proc_macro2::{Span, TokenStream};
use quote::quote;
@@ -389,31 +389,6 @@ pub enum FieldDefault {
Value(String),
}
-impl FieldDefault {
- pub fn into_tokenstream(
- self,
- span: Span,
- field_type: &Type,
- ) -> Result<TokenStream, Vec<syn::Error>> {
- match self {
- FieldDefault::Disabled => Ok(quote! { ::std::option::Option::None
}),
- FieldDefault::Trait => type_to_field_default_expr(field_type),
- FieldDefault::Value(default_value) => {
- let _: serde_json::Value =
- serde_json::from_str(&default_value[..]).map_err(|e| {
- vec![syn::Error::new(
- span,
- format!("Invalid avro default json: \n{e}"),
- )]
- })?;
- Ok(quote! {
-
::std::option::Option::Some(::serde_json::from_str(#default_value).expect(format!("Invalid
JSON: {:?}", #default_value).as_str()))
- })
- }
- }
- }
-}
-
impl FromMeta for FieldDefault {
fn from_string(value: &str) -> darling::Result<Self> {
Ok(Self::Value(value.to_string()))
diff --git a/avro_derive/src/enums/bare_union.rs
b/avro_derive/src/enums/bare_union.rs
index c465fb2..7752b81 100644
--- a/avro_derive/src/enums/bare_union.rs
+++ b/avro_derive/src/enums/bare_union.rs
@@ -1,6 +1,6 @@
-use crate::attributes::{NamedTypeOptions, VariantOptions};
+use crate::attributes::{FieldOptions, NamedTypeOptions, VariantOptions};
use crate::tuple::tuple_struct_variant_to_record_schema;
-use crate::{named_to_record_fields, type_to_schema_expr};
+use crate::{fields, named_to_record_fields};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::spanned::Spanned;
@@ -47,7 +47,8 @@ pub fn get_data_enum_schema_def(
)]);
} else if unnamed.unnamed.len() == 1 {
let only_one = unnamed.unnamed.iter().next().expect("There
is one");
- let schema_expr = type_to_schema_expr(&only_one.ty)?;
+ let field_attrs = FieldOptions::new(&only_one.attrs,
only_one.span())?;
+ let schema_expr = fields::to_schema(&only_one,
field_attrs.with)?;
variant_expr.push(schema_expr);
} else if unnamed.unnamed.len() > 1 {
let schema_expr =
tuple_struct_variant_to_record_schema(unnamed, &name, &[])?;
diff --git a/avro_derive/src/enums/record_internally_tagged.rs
b/avro_derive/src/enums/record_internally_tagged.rs
index 7d09b0d..e9173cd 100644
--- a/avro_derive/src/enums/record_internally_tagged.rs
+++ b/avro_derive/src/enums/record_internally_tagged.rs
@@ -1,5 +1,5 @@
-use crate::attributes::{NamedTypeOptions, VariantOptions};
-use crate::{aliases, named_to_record_fields, preserve_optional,
type_to_schema_expr};
+use crate::attributes::{FieldOptions, NamedTypeOptions, VariantOptions};
+use crate::{aliases, fields, named_to_record_fields, preserve_optional};
use proc_macro2::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
@@ -36,7 +36,8 @@ pub fn get_data_enum_schema_def(
Fields::Unnamed(unnamed) => {
if unnamed.unnamed.len() == 1 {
let only_one = unnamed.unnamed.iter().next().expect("There
is one");
- let schema_expr = type_to_schema_expr(&only_one.ty)?;
+ let field_attrs = FieldOptions::new(&only_one.attrs,
only_one.span())?;
+ let schema_expr = fields::to_schema(&only_one,
field_attrs.with)?;
field_additions.push(quote! {
fields.push(#schema_expr);
});
diff --git a/avro_derive/src/enums/record_tag_content.rs
b/avro_derive/src/enums/record_tag_content.rs
index e39177e..a1aebf5 100644
--- a/avro_derive/src/enums/record_tag_content.rs
+++ b/avro_derive/src/enums/record_tag_content.rs
@@ -1,6 +1,6 @@
-use crate::attributes::{NamedTypeOptions, VariantOptions};
+use crate::attributes::{FieldOptions, NamedTypeOptions, VariantOptions};
use crate::tuple::tuple_struct_variant_to_record_schema;
-use crate::{aliases, named_to_record_fields, preserve_optional,
type_to_schema_expr};
+use crate::{aliases, fields, named_to_record_fields, preserve_optional};
use proc_macro2::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
@@ -50,8 +50,9 @@ pub fn get_data_enum_schema_def(
schema_definitions.push(schema_expr);
} else if unnamed.unnamed.len() == 1 {
let only_one = unnamed.unnamed.iter().next().expect("There
is one");
- let field_schema_expr = type_to_schema_expr(&only_one.ty)?;
- schema_definitions.push(field_schema_expr);
+ let field_attrs = FieldOptions::new(&only_one.attrs,
only_one.span())?;
+ let schema_expr = fields::to_schema(&only_one,
field_attrs.with)?;
+ schema_definitions.push(schema_expr);
} else if unnamed.unnamed.len() > 1 {
let schema_expr =
tuple_struct_variant_to_record_schema(unnamed, &name, &[])?;
diff --git a/avro_derive/src/fields.rs b/avro_derive/src/fields.rs
new file mode 100644
index 0000000..af5b3aa
--- /dev/null
+++ b/avro_derive/src/fields.rs
@@ -0,0 +1,390 @@
+use crate::attributes::{FieldDefault, With};
+use proc_macro2::TokenStream;
+use quote::{ToTokens, quote};
+use syn::spanned::Spanned;
+use syn::{Expr, ExprLit, Field, Lit, Type, TypeArray, TypeTuple};
+
+pub fn to_schema(field: &Field, with: With) -> Result<TokenStream,
Vec<syn::Error>> {
+ match with {
+ With::Trait => Ok(type_to_schema_expr(&field.ty)?),
+ With::Serde(path) => {
+ Ok(quote! { #path::get_schema_in_ctxt(named_schemas,
enclosing_namespace) })
+ }
+ With::Expr(Expr::Closure(closure)) => {
+ if closure.inputs.is_empty() {
+ Ok(quote! { (#closure)() })
+ } else {
+ Err(vec![syn::Error::new(
+ field.span(),
+ "Expected closure with 0 parameters",
+ )])
+ }
+ }
+ With::Expr(Expr::Path(path)) => Ok(quote! { #path(named_schemas,
enclosing_namespace) }),
+ With::Expr(_expr) => Err(vec![syn::Error::new(
+ field.span(),
+ "Invalid expression, expected function or closure",
+ )]),
+ }
+}
+
+/// Call `get_record_fields_in_ctxt` for this field.
+///
+/// # `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 `Option<Vec<RecordField>>`.
+pub fn to_record_fields(field: &Field, with: With) -> Result<TokenStream,
Vec<syn::Error>> {
+ match with {
+ With::Trait => Ok(type_to_record_fields(&field.ty)?),
+ With::Serde(path) => {
+ Ok(quote! { #path::get_record_fields_in_ctxt(named_schemas,
enclosing_namespace) })
+ }
+ With::Expr(Expr::Closure(closure)) => {
+ if closure.inputs.is_empty() {
+ Ok(quote! {
+ ::apache_avro::serde::get_record_fields_in_ctxt(
+ named_schemas,
+ enclosing_namespace,
+ |_, _| (#closure)(),
+ )
+ })
+ } else {
+ Err(vec![syn::Error::new(
+ field.span(),
+ "Expected closure with 0 parameters",
+ )])
+ }
+ }
+ With::Expr(Expr::Path(path)) => Ok(quote! {
+ ::apache_avro::serde::get_record_fields_in_ctxt(named_schemas,
enclosing_namespace, #path)
+ }),
+ With::Expr(_expr) => Err(vec![syn::Error::new(
+ field.span(),
+ "Invalid expression, expected function or closure",
+ )]),
+ }
+}
+
+pub fn to_default(field: &Field, default: FieldDefault) -> Result<TokenStream,
Vec<syn::Error>> {
+ match default {
+ FieldDefault::Disabled => Ok(quote! { ::std::option::Option::None }),
+ FieldDefault::Trait => type_to_field_default(&field.ty),
+ FieldDefault::Value(default_value) => {
+ let _: serde_json::Value =
serde_json::from_str(&default_value[..]).map_err(|e| {
+ vec![syn::Error::new(
+ field.span(),
+ format!("Invalid avro default json: \n{e}"),
+ )]
+ })?;
+ Ok(quote! {
+
::std::option::Option::Some(::serde_json::from_str(#default_value).expect("Unreachable!
Checked at compile time"))
+ })
+ }
+ }
+}
+
+/// Takes in the Tokens of a type and returns the tokens of an expression with
return type `Schema`
+///
+/// # `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 type_to_schema_expr(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
+ match ty {
+ Type::Slice(_) | Type::Path(_) | Type::Reference(_) => Ok(
+ 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::Ptr(_) => Err(vec![syn::Error::new_spanned(
+ ty,
+ "AvroSchema: derive does not support raw pointers",
+ )]),
+ _ => Err(vec![syn::Error::new_spanned(
+ ty,
+ format!(
+ "AvroSchema: Unexpected type encountered! Please open an issue
if this kind of type should be supported: {ty:?}"
+ ),
+ )]),
+ }
+}
+
+/// 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<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`.
+///
+/// # `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<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()
+ )
+ })
+ }
+}
+
+fn type_to_record_fields(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
+ match ty {
+ Type::Slice(_) | Type::Path(_) | Type::Reference(_) => Ok(
+ 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",
+ )]),
+ _ => Err(vec![syn::Error::new_spanned(
+ ty,
+ format!(
+ "AvroSchema: Unexpected type encountered! Please open an issue
if this kind of type should be supported: {ty:?}"
+ ),
+ )]),
+ }
+}
+
+/// 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<TokenStream,
Vec<syn::Error>> {
+ if tuple.elems.is_empty() {
+ Ok(quote! {::std::option::Option::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(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<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! {::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(quote! {
+ ::std::option::Option::Some(vec![#(#fields, )*])
+ })
+ }
+}
+
+fn type_to_field_default(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
+ match ty {
+ Type::Slice(_) | Type::Path(_) | Type::Reference(_) => {
+ Ok(quote! {<#ty as ::
apache_avro::AvroSchemaComponent>::field_default()})
+ }
+ Type::Ptr(_) => Err(vec![syn::Error::new_spanned(
+ ty,
+ "AvroSchema: derive does not support raw pointers",
+ )]),
+ Type::Tuple(_) | Type::Array(_) => Ok(quote! {
::std::option::Option::None }),
+ _ => Err(vec![syn::Error::new_spanned(
+ ty,
+ format!(
+ "AvroSchema: Unexpected type encountered! Please open an issue
if this kind of type should be supported: {ty:?}"
+ ),
+ )]),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn test_trait_cast() {
+
assert_eq!(type_to_schema_expr(&syn::parse2::<Type>(quote!{i32}).unwrap()).unwrap().to_string(),
quote!{<i32 as ::
apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}.to_string());
+
assert_eq!(type_to_schema_expr(&syn::parse2::<Type>(quote!{Vec<T>}).unwrap()).unwrap().to_string(),
quote!{<Vec<T> as ::
apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}.to_string());
+
assert_eq!(type_to_schema_expr(&syn::parse2::<Type>(quote!{AnyType}).unwrap()).unwrap().to_string(),
quote!{<AnyType as ::
apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}.to_string());
+ }
+}
diff --git a/avro_derive/src/lib.rs b/avro_derive/src/lib.rs
index 84daff0..ab3e8c2 100644
--- a/avro_derive/src/lib.rs
+++ b/avro_derive/src/lib.rs
@@ -32,18 +32,18 @@
mod attributes;
mod case;
mod enums;
+mod fields;
mod tuple;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
- DataStruct, DeriveInput, Expr, Field, Fields, FieldsNamed, Generics,
Ident, Type,
- parse_macro_input, spanned::Spanned,
+ DataStruct, DeriveInput, Fields, FieldsNamed, Generics, Ident, Type,
parse_macro_input,
+ spanned::Spanned,
};
-use crate::tuple::tuple_to_schema;
use crate::{
- attributes::{FieldOptions, NamedTypeOptions, With},
+ attributes::{FieldOptions, NamedTypeOptions},
case::RenameRule,
tuple::unnamed_to_record_fields,
};
@@ -235,8 +235,8 @@ fn get_transparent_struct_schema_def(
if let Some((field, attrs)) = found {
Ok((
- get_field_schema_expr(&field, attrs.with.clone())?,
- get_field_get_record_fields_expr(&field, attrs.with)?,
+ fields::to_schema(&field, attrs.with.clone())?,
+ fields::to_record_fields(&field, attrs.with)?,
))
} else {
Err(vec![syn::Error::new(
@@ -262,8 +262,8 @@ fn get_transparent_struct_schema_def(
if let Some((field, attrs)) = found {
Ok((
- get_field_schema_expr(&field, attrs.with.clone())?,
- get_field_get_record_fields_expr(&field, attrs.with)?,
+ fields::to_schema(&field, attrs.with.clone())?,
+ fields::to_record_fields(&field, attrs.with)?,
))
} else {
Err(vec![syn::Error::new(
@@ -279,146 +279,6 @@ fn get_transparent_struct_schema_def(
}
}
-fn get_field_schema_expr(field: &Field, with: With) -> Result<TokenStream,
Vec<syn::Error>> {
- match with {
- With::Trait => Ok(type_to_schema_expr(&field.ty)?),
- With::Serde(path) => {
- Ok(quote! { #path::get_schema_in_ctxt(named_schemas,
enclosing_namespace) })
- }
- With::Expr(Expr::Closure(closure)) => {
- if closure.inputs.is_empty() {
- Ok(quote! { (#closure)() })
- } else {
- Err(vec![syn::Error::new(
- field.span(),
- "Expected closure with 0 parameters",
- )])
- }
- }
- With::Expr(Expr::Path(path)) => Ok(quote! { #path(named_schemas,
enclosing_namespace) }),
- With::Expr(_expr) => Err(vec![syn::Error::new(
- field.span(),
- "Invalid expression, expected function or closure",
- )]),
- }
-}
-
-/// Call `get_record_fields_in_ctxt` for this field.
-///
-/// # `TokenStream`
-/// ## Context
-/// The token stream expects the following variables to be defined:
-/// - `named_schemas`: `&mut HashSet<Name>`
-/// - `enclosing_namespace`: `Option<&str>`
-/// ## Returns
-/// A call to a `get_record_fields_in_ctxt(named_schemas, enclosing_namespace)
-> Option<Vec<RecordField>>`
-fn get_field_get_record_fields_expr(
- field: &Field,
- with: With,
-) -> Result<TokenStream, Vec<syn::Error>> {
- match with {
- With::Trait => Ok(type_to_get_record_fields_expr(&field.ty)?),
- With::Serde(path) => {
- Ok(quote! { #path::get_record_fields_in_ctxt(named_schemas,
enclosing_namespace) })
- }
- With::Expr(Expr::Closure(closure)) => {
- if closure.inputs.is_empty() {
- Ok(quote! {
- ::apache_avro::serde::get_record_fields_in_ctxt(
- named_schemas,
- enclosing_namespace,
- |_, _| (#closure)(),
- )
- })
- } else {
- Err(vec![syn::Error::new(
- field.span(),
- "Expected closure with 0 parameters",
- )])
- }
- }
- With::Expr(Expr::Path(path)) => Ok(quote! {
- ::apache_avro::serde::get_record_fields_in_ctxt(named_schemas,
enclosing_namespace, #path)
- }),
- With::Expr(_expr) => Err(vec![syn::Error::new(
- field.span(),
- "Invalid expression, expected function or closure",
- )]),
- }
-}
-
-/// Takes in the Tokens of a type and returns the tokens of an expression with
return type `Schema`
-///
-/// # `TokenStream`
-/// ## Context
-/// The token stream expects the following variables to be defined:
-/// - `named_schemas`: `&mut HashSet<Name>`
-/// - `enclosing_namespace`: `Option<&str>`
-/// ## Returns
-/// A call to a `get_schema_in_ctxt(named_schemas, enclosing_namespace) ->
Schema`
-fn type_to_schema_expr(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
- match ty {
- Type::Array(_) | Type::Slice(_) | Type::Path(_) | Type::Reference(_)
=> Ok(
- 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",
- )]),
- Type::Tuple(tuple) => tuple_to_schema(tuple),
- _ => Err(vec![syn::Error::new_spanned(
- ty,
- format!(
- "AvroSchema: Unexpected type encountered! Please open an issue
if this kind of type should be supported: {ty:?}"
- ),
- )]),
- }
-}
-
-fn type_to_get_record_fields_expr(ty: &Type) -> Result<TokenStream,
Vec<syn::Error>> {
- match ty {
- Type::Array(_) | Type::Slice(_) | Type::Path(_) | Type::Reference(_)
=> Ok(
- quote! {<#ty as ::
apache_avro::AvroSchemaComponent>::get_record_fields_in_ctxt(named_schemas,
enclosing_namespace)},
- ),
- Type::Ptr(_) => Err(vec![syn::Error::new_spanned(
- ty,
- "AvroSchema: derive does not support raw pointers",
- )]),
- Type::Tuple(_) => Err(vec![syn::Error::new_spanned(
- ty,
- "AvroSchema: derive does not support tuples",
- )]),
- _ => Err(vec![syn::Error::new_spanned(
- ty,
- format!(
- "AvroSchema: Unexpected type encountered! Please open an issue
if this kind of type should be supported: {ty:?}"
- ),
- )]),
- }
-}
-
-fn type_to_field_default_expr(ty: &Type) -> Result<TokenStream,
Vec<syn::Error>> {
- match ty {
- Type::Array(_) | Type::Slice(_) | Type::Path(_) | Type::Reference(_)
=> {
- Ok(quote! {<#ty as ::
apache_avro::AvroSchemaComponent>::field_default()})
- }
- Type::Ptr(_) => Err(vec![syn::Error::new_spanned(
- ty,
- "AvroSchema: derive does not support raw pointers",
- )]),
- Type::Tuple(_) => Err(vec![syn::Error::new_spanned(
- ty,
- "AvroSchema: derive does not support tuples",
- )]),
- _ => Err(vec![syn::Error::new_spanned(
- ty,
- format!(
- "AvroSchema: Unexpected type encountered! Please open an issue
if this kind of type should be supported: {ty:?}"
- ),
- )]),
- }
-}
-
/// Create a vector of `RecordField`s.
fn named_to_record_fields(
named: FieldsNamed,
@@ -432,7 +292,7 @@ fn named_to_record_fields(
} else if field_attrs.flatten {
// Inline the fields of the child record at runtime, as we don't
have access to
// the schema here.
- let get_record_fields = get_field_get_record_fields_expr(&field,
field_attrs.with)?;
+ let get_record_fields = fields::to_record_fields(&field,
field_attrs.with)?;
fields.push(quote! {
if let Some(flattened_fields) = #get_record_fields {
fields.extend(flattened_fields);
@@ -461,12 +321,10 @@ fn named_to_record_fields(
}
_ => {}
}
- let default_value = field_attrs
- .default
- .into_tokenstream(field.ident.span(), &field.ty)?;
+ let default_value = fields::to_default(&field, field_attrs.default)?;
let aliases = field_aliases(&field_attrs.alias);
let doc = doc_into_tokenstream(field_attrs.doc);
- let field_schema_expr = get_field_schema_expr(&field,
field_attrs.with)?;
+ let field_schema_expr = fields::to_schema(&field, field_attrs.with)?;
fields.push(quote! {
fields.push(::apache_avro::schema::RecordField::builder()
.name(#name.to_string())
@@ -534,16 +392,3 @@ fn field_aliases(op: &[impl quote::ToTokens]) ->
TokenStream {
quote! {vec![#(#items),*]}
}
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use pretty_assertions::assert_eq;
-
- #[test]
- fn test_trait_cast() {
-
assert_eq!(type_to_schema_expr(&syn::parse2::<Type>(quote!{i32}).unwrap()).unwrap().to_string(),
quote!{<i32 as ::
apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}.to_string());
-
assert_eq!(type_to_schema_expr(&syn::parse2::<Type>(quote!{Vec<T>}).unwrap()).unwrap().to_string(),
quote!{<Vec<T> as ::
apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}.to_string());
-
assert_eq!(type_to_schema_expr(&syn::parse2::<Type>(quote!{AnyType}).unwrap()).unwrap().to_string(),
quote!{<AnyType as ::
apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}.to_string());
- }
-}
diff --git a/avro_derive/src/tuple.rs b/avro_derive/src/tuple.rs
index adf56c4..8253332 100644
--- a/avro_derive/src/tuple.rs
+++ b/avro_derive/src/tuple.rs
@@ -1,15 +1,23 @@
use proc_macro2::TokenStream;
-use quote::{ToTokens, quote};
-use syn::{Expr, ExprLit, FieldsUnnamed, Lit, TypeArray, TypeTuple,
spanned::Spanned};
+use quote::quote;
+use syn::{FieldsUnnamed, spanned::Spanned};
-use crate::{FieldOptions, doc_into_tokenstream, field_aliases,
type_to_schema_expr};
+use crate::{FieldOptions, doc_into_tokenstream, field_aliases, fields};
/// Create a `Schema::Record` from this tuple definition.
///
/// Fields are named `field_{field_index}` and the struct will have the
provided name.
///
-/// The schema will have the attribute `org.apache.avro.rust.tuple` any any
other specified in `extra_attributes`.
+/// The schema will have the attribute `org.apache.avro.rust.tuple` any other
specified in `extra_attributes`.
/// All attributes will have a value of `true`.
+///
+/// # `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`.
pub fn tuple_struct_variant_to_record_schema(
unnamed: FieldsUnnamed,
name: &str,
@@ -32,125 +40,15 @@ pub fn tuple_struct_variant_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}`.
+///
+/// # `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 `Vec<RecordField>`.
pub fn unnamed_to_record_fields(unnamed: FieldsUnnamed) -> Result<TokenStream,
Vec<syn::Error>> {
let mut fields = Vec::with_capacity(unnamed.unnamed.len());
for (index, field) in unnamed.unnamed.into_iter().enumerate() {
@@ -163,15 +61,13 @@ pub fn unnamed_to_record_fields(unnamed: FieldsUnnamed) ->
Result<TokenStream, V
"AvroSchema: `#[serde(flatten)]` is not supported on tuple
fields",
)]);
}
- let default_value = field_attrs
- .default
- .into_tokenstream(field.ident.span(), &field.ty)?;
+ let default_value = fields::to_default(&field, field_attrs.default)?;
let aliases = field_aliases(&field_attrs.alias);
let doc = doc_into_tokenstream(field_attrs.doc);
let name = field_attrs
.rename
.unwrap_or_else(|| format!("field_{index}"));
- let field_schema_expr = type_to_schema_expr(&field.ty)?;
+ let field_schema_expr = crate::fields::to_schema(&field,
field_attrs.with)?;
fields.push(quote! {
::apache_avro::schema::RecordField::builder()
.name(#name.to_string())