This is an automated email from the ASF dual-hosted git repository.

Kriskras99 pushed a commit to branch refactor/derive_implementation
in repository https://gitbox.apache.org/repos/asf/avro-rs.git

commit c601ca952abf5fdd191d05f3f4fd7ffb419fe6bb
Author: Kriskras99 <[email protected]>
AuthorDate: Sat Jun 20 17:25:18 2026 +0200

    refactor: Centralize generating the actual `impl` in the `Implementation` 
struct
    
    This makes it clearer how everything fits together, and allows extending 
shared
    logic further (for example, adding generated tests). And because it is not
    a `TokenStream`, accidentally returning only a schema expression instead of 
the
    entire implementation results in a compilation error.
---
 avro_derive/src/attributes/mod.rs                  |  29 +++---
 avro_derive/src/enums/mod.rs                       |  58 ++++-------
 avro_derive/src/enums/plain.rs                     |  68 +++++++++---
 avro_derive/src/fields.rs                          |   4 +-
 avro_derive/src/implementation.rs                  | 114 +++++++++++++++++++++
 avro_derive/src/lib.rs                             |  99 ++++--------------
 avro_derive/src/structs.rs                         |  82 ++++++++++-----
 avro_derive/tests/derive.rs                        |  26 +++++
 .../avro_3687_basic_enum_with_default_twice.stderr |  14 ++-
 .../tests/ui/avro_rs_373_rename_all_fields.stderr  |  11 +-
 .../tests/ui/avro_rs_501_non_basic_enum.stderr     |  11 +-
 .../tests/ui/avro_rs_501_tuple_struct.stderr       |   4 +-
 .../tests/ui/avro_rs_501_unit_struct.stderr        |   4 +-
 13 files changed, 336 insertions(+), 188 deletions(-)

diff --git a/avro_derive/src/attributes/mod.rs 
b/avro_derive/src/attributes/mod.rs
index ebb2997..cb037cd 100644
--- a/avro_derive/src/attributes/mod.rs
+++ b/avro_derive/src/attributes/mod.rs
@@ -31,7 +31,7 @@ pub struct NamedTypeOptions {
     pub aliases: Vec<String>,
     pub rename_all: RenameRule,
     pub transparent: bool,
-    pub default: TokenStream,
+    pub default: Option<TokenStream>,
 }
 
 impl NamedTypeOptions {
@@ -118,20 +118,19 @@ impl NamedTypeOptions {
 
         let doc = avro.doc.or_else(|| extract_rustdoc(attributes));
 
-        let default = match avro.default {
-            None => quote! { None },
-            Some(default_value) => {
-                let _: serde_json::Value =
-                    serde_json::from_str(&default_value[..]).map_err(|e| {
-                        vec![syn::Error::new(
-                            ident.span(),
-                            format!("Invalid Avro `default` JSON: \n{e}"),
-                        )]
-                    })?;
-                quote! {
-                    
Some(::serde_json::from_str(#default_value).expect(format!("Invalid JSON: 
{:?}", #default_value).as_str()))
-                }
-            }
+        let default = if let Some(default_value) = avro.default {
+            let _: serde_json::Value =
+                serde_json::from_str(default_value.as_str()).map_err(|e| {
+                    vec![syn::Error::new(
+                        ident.span(),
+                        format!("Invalid Avro `default` JSON: \n{e}"),
+                    )]
+                })?;
+            Some(quote! {
+                
::std::option::Option::Some(::serde_json::from_str(#default_value).expect(format!("Invalid
 JSON: {:?}", #default_value).as_str()))
+            })
+        } else {
+            None
         };
 
         Ok(Self {
diff --git a/avro_derive/src/enums/mod.rs b/avro_derive/src/enums/mod.rs
index 1511f65..843b77b 100644
--- a/avro_derive/src/enums/mod.rs
+++ b/avro_derive/src/enums/mod.rs
@@ -18,50 +18,30 @@
 mod plain;
 
 use crate::attributes::NamedTypeOptions;
-use proc_macro2::{Ident, Span, TokenStream};
-use syn::{Attribute, DataEnum, Fields, Meta};
+use crate::implementation::Implementation;
+use proc_macro2::{Ident, Span};
+use syn::{DataEnum, Fields, Generics};
 
 /// Generate a schema definition for a enum.
-pub fn get_data_enum_schema_def(
-    container_attrs: &NamedTypeOptions,
-    data_enum: &DataEnum,
-    ident_span: Span,
-) -> Result<TokenStream, Vec<syn::Error>> {
-    if data_enum.variants.iter().all(|v| Fields::Unit == v.fields) {
-        plain::schema_def(container_attrs, data_enum, ident_span)
+pub fn to_implementation(
+    input_span: Span,
+    ident: Ident,
+    generics: Generics,
+    container_attrs: NamedTypeOptions,
+    data: DataEnum,
+) -> Result<Implementation, Vec<syn::Error>> {
+    if container_attrs.transparent {
+        return Err(vec![syn::Error::new(
+            input_span,
+            "AvroSchema: `#[serde(transparent)]` is only supported on structs",
+        )]);
+    }
+    if data.variants.iter().all(|v| Fields::Unit == v.fields) {
+        plain::to_implementation(input_span, ident, generics, container_attrs, 
data)
     } else {
         Err(vec![syn::Error::new(
-            ident_span,
+            input_span,
             "AvroSchema: derive does not work for enums with non unit structs",
         )])
     }
 }
-
-fn default_enum_variant(
-    data_enum: &DataEnum,
-    error_span: Span,
-) -> Result<Option<String>, Vec<syn::Error>> {
-    match data_enum
-        .variants
-        .iter()
-        .filter(|v| v.attrs.iter().any(is_default_attr))
-        .collect::<Vec<_>>()
-    {
-        variants if variants.is_empty() => Ok(None),
-        single if single.len() == 1 => Ok(Some(single[0].ident.to_string())),
-        multiple => Err(vec![syn::Error::new(
-            error_span,
-            format!(
-                "Multiple defaults defined: {:?}",
-                multiple
-                    .iter()
-                    .map(|v| v.ident.to_string())
-                    .collect::<Vec<String>>()
-            ),
-        )]),
-    }
-}
-
-fn is_default_attr(attr: &Attribute) -> bool {
-    matches!(attr, Attribute { meta: Meta::Path(path), .. } if 
path.get_ident().map(Ident::to_string).as_deref() == Some("default"))
-}
diff --git a/avro_derive/src/enums/plain.rs b/avro_derive/src/enums/plain.rs
index 4fb0adb..7dbcdcb 100644
--- a/avro_derive/src/enums/plain.rs
+++ b/avro_derive/src/enums/plain.rs
@@ -17,25 +17,27 @@
 
 use crate::attributes::{NamedTypeOptions, VariantOptions};
 use crate::case::RenameRule;
-use crate::enums::default_enum_variant;
+use crate::implementation::Implementation;
 use crate::{aliases, preserve_optional};
-use proc_macro2::{Span, TokenStream};
+use proc_macro2::{Ident, Span};
 use quote::quote;
 use syn::spanned::Spanned;
-use syn::{DataEnum, Fields};
+use syn::{Attribute, DataEnum, Fields, Generics, Meta};
 
-pub fn schema_def(
-    container_attrs: &NamedTypeOptions,
-    data_enum: &DataEnum,
-    ident_span: Span,
-) -> Result<TokenStream, Vec<syn::Error>> {
+pub fn to_implementation(
+    input_span: Span,
+    ident: Ident,
+    generics: Generics,
+    container_attrs: NamedTypeOptions,
+    data: DataEnum,
+) -> Result<Implementation, Vec<syn::Error>> {
     let doc = preserve_optional(container_attrs.doc.as_ref());
     let enum_aliases = aliases(&container_attrs.aliases);
-    if data_enum.variants.iter().all(|v| Fields::Unit == v.fields) {
-        let default_value = default_enum_variant(data_enum, ident_span)?;
+    if data.variants.iter().all(|v| Fields::Unit == v.fields) {
+        let default_value = default_enum_variant(&data, input_span)?;
         let default = preserve_optional(default_value);
         let mut symbols = Vec::new();
-        for variant in &data_enum.variants {
+        for variant in data.variants {
             let field_attrs = VariantOptions::new(&variant.attrs, 
variant.span())?;
             let name = match (field_attrs.rename, container_attrs.rename_all) {
                 (Some(rename), _) => rename,
@@ -46,7 +48,7 @@ pub fn schema_def(
             };
             symbols.push(name);
         }
-        Ok(quote! {
+        let schema_expr = quote! {
             
::apache_avro::schema::Schema::Enum(::apache_avro::schema::EnumSchema {
                 name,
                 aliases: #enum_aliases,
@@ -55,11 +57,49 @@ pub fn schema_def(
                 default: #default,
                 attributes: ::std::collections::BTreeMap::new(),
             })
-        })
+        };
+
+        Ok(Implementation::named(
+            ident,
+            generics,
+            &container_attrs.name,
+            schema_expr,
+            None,
+            container_attrs.default,
+        ))
     } else {
         Err(vec![syn::Error::new(
-            ident_span,
+            input_span,
             "AvroSchema: derive does not work for enums with non unit structs",
         )])
     }
 }
+
+fn default_enum_variant(
+    data_enum: &DataEnum,
+    error_span: Span,
+) -> Result<Option<String>, Vec<syn::Error>> {
+    match data_enum
+        .variants
+        .iter()
+        .filter(|v| v.attrs.iter().any(is_default_attr))
+        .collect::<Vec<_>>()
+    {
+        variants if variants.is_empty() => Ok(None),
+        single if single.len() == 1 => Ok(Some(single[0].ident.to_string())),
+        multiple => Err(vec![syn::Error::new(
+            error_span,
+            format!(
+                "AvroSchema: Multiple defaults defined: {:?}",
+                multiple
+                    .iter()
+                    .map(|v| v.ident.to_string())
+                    .collect::<Vec<String>>()
+            ),
+        )]),
+    }
+}
+
+fn is_default_attr(attr: &Attribute) -> bool {
+    matches!(attr, Attribute { meta: Meta::Path(path), .. } if 
path.get_ident().map(Ident::to_string).as_deref() == Some("default"))
+}
diff --git a/avro_derive/src/fields.rs b/avro_derive/src/fields.rs
index f9acbf1..66c3634 100644
--- a/avro_derive/src/fields.rs
+++ b/avro_derive/src/fields.rs
@@ -21,7 +21,7 @@ use quote::quote;
 use syn::spanned::Spanned;
 use syn::{Expr, Field, Type};
 
-pub fn get_field_schema_expr(field: &Field, with: With) -> Result<TokenStream, 
Vec<syn::Error>> {
+pub 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) => {
@@ -47,7 +47,7 @@ pub fn get_field_schema_expr(field: &Field, with: With) -> 
Result<TokenStream, V
 
 pub fn get_field_get_record_fields_expr(
     field: &Field,
-    with: With,
+    with: &With,
 ) -> Result<TokenStream, Vec<syn::Error>> {
     match with {
         With::Trait => Ok(type_to_get_record_fields_expr(&field.ty)?),
diff --git a/avro_derive/src/implementation.rs 
b/avro_derive/src/implementation.rs
new file mode 100644
index 0000000..0124ffd
--- /dev/null
+++ b/avro_derive/src/implementation.rs
@@ -0,0 +1,114 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use proc_macro2::{Ident, TokenStream};
+use quote::quote;
+use syn::Generics;
+
+pub struct Implementation {
+    ident: Ident,
+    generics: Generics,
+    /// An expression that resolves to a `Schema`
+    ///
+    /// The variables `named_schemas: &mut HashSet<Name>` and 
`enclosing_namespace: NamespaceRef` are
+    /// defined.
+    schema_expr: TokenStream,
+    /// An expression that resolves to a `Option<Vec<RecordField>>`
+    ///
+    /// The variables `named_schemas: &mut HashSet<Name>` and 
`enclosing_namespace: NamespaceRef` are
+    /// defined.
+    record_fields_expr: TokenStream,
+    /// An expression that resolves to a `Option<serde_json::Value>`
+    field_default_expr: TokenStream,
+}
+
+impl Implementation {
+    #[expect(
+        clippy::needless_pass_by_value,
+        reason = "Makes it match the `unnamed` variant"
+    )]
+    pub fn named(
+        ident: Ident,
+        generics: Generics,
+        name: &str,
+        schema_expr: TokenStream,
+        record_fields_expr: Option<TokenStream>,
+        field_default_expr: Option<TokenStream>,
+    ) -> Implementation {
+        let schema_expr = quote! {
+            let name = 
::apache_avro::schema::Name::new_with_enclosing_namespace(#name, 
enclosing_namespace).expect(concat!("Unable to parse schema name ", #name));
+            if named_schemas.contains(&name) {
+                ::apache_avro::schema::Schema::Ref{name}
+            } else {
+                let enclosing_namespace = name.namespace();
+                named_schemas.insert(name.clone());
+                #schema_expr
+            }
+        };
+
+        Self::unnamed(
+            ident,
+            generics,
+            schema_expr,
+            record_fields_expr,
+            field_default_expr,
+        )
+    }
+
+    pub fn unnamed(
+        ident: Ident,
+        generics: Generics,
+        schema_expr: TokenStream,
+        record_fields_expr: Option<TokenStream>,
+        field_default_expr: Option<TokenStream>,
+    ) -> Implementation {
+        Self {
+            ident,
+            generics,
+            schema_expr,
+            record_fields_expr: record_fields_expr
+                .unwrap_or_else(|| quote! { ::std::option::Option::None }),
+            field_default_expr: field_default_expr
+                .unwrap_or_else(|| quote! { ::std::option::Option::None }),
+        }
+    }
+
+    pub fn into_token_stream(self) -> TokenStream {
+        let (impl_generics, ty_generics, where_clause) = 
self.generics.split_for_impl();
+        let ident = self.ident;
+        let schema_expr = self.schema_expr;
+        let record_fields_expr = self.record_fields_expr;
+        let field_default_expr = self.field_default_expr;
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_generics ::apache_avro::AvroSchemaComponent for #ident 
#ty_generics #where_clause {
+                fn get_schema_in_ctxt(named_schemas: &mut 
::std::collections::HashSet<::apache_avro::schema::Name>, enclosing_namespace: 
::apache_avro::schema::NamespaceRef) -> ::apache_avro::schema::Schema {
+                    #schema_expr
+                }
+
+                fn get_record_fields_in_ctxt(named_schemas: &mut 
::std::collections::HashSet<::apache_avro::schema::Name>, enclosing_namespace: 
::apache_avro::schema::NamespaceRef) -> 
::std::option::Option<::std::vec::Vec<::apache_avro::schema::RecordField>> {
+                    #record_fields_expr
+                }
+
+                fn field_default() -> 
::std::option::Option<::serde_json::Value> {
+                    #field_default_expr
+                }
+            }
+        }
+    }
+}
diff --git a/avro_derive/src/lib.rs b/avro_derive/src/lib.rs
index e2bc329..2401eab 100644
--- a/avro_derive/src/lib.rs
+++ b/avro_derive/src/lib.rs
@@ -33,16 +33,14 @@ mod attributes;
 mod case;
 mod enums;
 mod fields;
+mod implementation;
 mod structs;
 mod utils;
 
-use proc_macro2::TokenStream;
-use quote::quote;
-use syn::{DeriveInput, Generics, Ident, parse_macro_input, spanned::Spanned};
+use syn::{DeriveInput, parse_macro_input, spanned::Spanned};
 
 use crate::attributes::NamedTypeOptions;
-use crate::enums::get_data_enum_schema_def;
-use crate::structs::{get_struct_schema_def, get_transparent_struct_schema_def};
+use crate::implementation::Implementation;
 use crate::utils::{aliases, preserve_optional, to_compile_errors};
 
 #[proc_macro_derive(AvroSchema, attributes(avro, serde))]
@@ -50,53 +48,35 @@ use crate::utils::{aliases, preserve_optional, 
to_compile_errors};
 pub fn proc_macro_derive_avro_schema(input: proc_macro::TokenStream) -> 
proc_macro::TokenStream {
     let input = parse_macro_input!(input as DeriveInput);
     derive_avro_schema(input)
+        .map(Implementation::into_token_stream)
         .unwrap_or_else(to_compile_errors)
         .into()
 }
 
-fn derive_avro_schema(input: DeriveInput) -> Result<TokenStream, 
Vec<syn::Error>> {
+fn derive_avro_schema(input: DeriveInput) -> Result<Implementation, 
Vec<syn::Error>> {
     // It would be nice to parse the attributes before the `match`, but we 
first need to validate that `input` is not a union.
     // Otherwise a user could get errors related to the attributes and after 
fixing those get an error because the attributes were on a union.
     let input_span = input.span();
     match input.data {
         syn::Data::Struct(data_struct) => {
             let named_type_options = NamedTypeOptions::new(&input.ident, 
&input.attrs, input_span)?;
-            let (get_schema_impl, get_record_fields_impl) = if 
named_type_options.transparent {
-                get_transparent_struct_schema_def(data_struct.fields, 
input_span)?
-            } else {
-                let (schema_def, record_fields) =
-                    get_struct_schema_def(&named_type_options, data_struct, 
input.ident.span())?;
-                (
-                    handle_named_schemas(&named_type_options.name, 
&schema_def),
-                    record_fields,
-                )
-            };
-            Ok(create_trait_definition(
-                &input.ident,
-                &input.generics,
-                &get_schema_impl,
-                &get_record_fields_impl,
-                &named_type_options.default,
-            ))
+            structs::to_implementation(
+                input_span,
+                input.ident,
+                input.generics,
+                named_type_options,
+                data_struct,
+            )
         }
         syn::Data::Enum(data_enum) => {
             let named_type_options = NamedTypeOptions::new(&input.ident, 
&input.attrs, input_span)?;
-            if named_type_options.transparent {
-                return Err(vec![syn::Error::new(
-                    input_span,
-                    "AvroSchema: `#[serde(transparent)]` is only supported on 
structs",
-                )]);
-            }
-            let schema_def =
-                get_data_enum_schema_def(&named_type_options, &data_enum, 
input.ident.span())?;
-            let inner = handle_named_schemas(&named_type_options.name, 
&schema_def);
-            Ok(create_trait_definition(
-                &input.ident,
-                &input.generics,
-                &inner,
-                &quote! { ::std::option::Option::None },
-                &named_type_options.default,
-            ))
+            enums::to_implementation(
+                input_span,
+                input.ident,
+                input.generics,
+                named_type_options,
+                data_enum,
+            )
         }
         syn::Data::Union(_) => Err(vec![syn::Error::new(
             input_span,
@@ -104,44 +84,3 @@ fn derive_avro_schema(input: DeriveInput) -> 
Result<TokenStream, Vec<syn::Error>
         )]),
     }
 }
-
-/// Generate the trait definition with the correct generics
-fn create_trait_definition(
-    ident: &Ident,
-    generics: &Generics,
-    get_schema_impl: &TokenStream,
-    get_record_fields_impl: &TokenStream,
-    field_default_impl: &TokenStream,
-) -> TokenStream {
-    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
-    quote! {
-        #[automatically_derived]
-        impl #impl_generics ::apache_avro::AvroSchemaComponent for #ident 
#ty_generics #where_clause {
-            fn get_schema_in_ctxt(named_schemas: &mut 
::std::collections::HashSet<::apache_avro::schema::Name>, enclosing_namespace: 
::apache_avro::schema::NamespaceRef) -> ::apache_avro::schema::Schema {
-                #get_schema_impl
-            }
-
-            fn get_record_fields_in_ctxt(named_schemas: &mut 
::std::collections::HashSet<::apache_avro::schema::Name>, enclosing_namespace: 
::apache_avro::schema::NamespaceRef) -> 
::std::option::Option<::std::vec::Vec<::apache_avro::schema::RecordField>> {
-                #get_record_fields_impl
-            }
-
-            fn field_default() -> ::std::option::Option<::serde_json::Value> {
-                ::std::option::Option::#field_default_impl
-            }
-        }
-    }
-}
-
-/// Generate the code to check `named_schemas` if this schema already exist
-fn handle_named_schemas(full_schema_name: &str, schema_def: &TokenStream) -> 
TokenStream {
-    quote! {
-        let name = 
::apache_avro::schema::Name::new_with_enclosing_namespace(#full_schema_name, 
enclosing_namespace).expect(concat!("Unable to parse schema name ", 
#full_schema_name));
-        if named_schemas.contains(&name) {
-            ::apache_avro::schema::Schema::Ref{name}
-        } else {
-            let enclosing_namespace = name.namespace();
-            named_schemas.insert(name.clone());
-            #schema_def
-        }
-    }
-}
diff --git a/avro_derive/src/structs.rs b/avro_derive/src/structs.rs
index 53bcbe5..af9f9da 100644
--- a/avro_derive/src/structs.rs
+++ b/avro_derive/src/structs.rs
@@ -20,20 +20,36 @@ use crate::case::RenameRule;
 use crate::fields::{
     get_field_field_default_expr, get_field_get_record_fields_expr, 
get_field_schema_expr,
 };
+use crate::implementation::Implementation;
 use crate::utils::{aliases, field_aliases, preserve_optional};
-use proc_macro2::{Span, TokenStream};
+use proc_macro2::{Ident, Span};
 use quote::quote;
 use syn::spanned::Spanned;
-use syn::{DataStruct, Fields};
+use syn::{DataStruct, Fields, Generics};
 
-/// Generate a schema definition for a struct.
-pub fn get_struct_schema_def(
-    container_attrs: &NamedTypeOptions,
-    data_struct: DataStruct,
-    ident_span: Span,
-) -> Result<(TokenStream, TokenStream), Vec<syn::Error>> {
+pub fn to_implementation(
+    input_span: Span,
+    ident: Ident,
+    generics: Generics,
+    container_attrs: NamedTypeOptions,
+    data: DataStruct,
+) -> Result<Implementation, Vec<syn::Error>> {
+    if container_attrs.transparent {
+        transparent(input_span, ident, generics, container_attrs, data)
+    } else {
+        normal(input_span, ident, generics, container_attrs, data)
+    }
+}
+
+fn normal(
+    input_span: Span,
+    ident: Ident,
+    generics: Generics,
+    container_attrs: NamedTypeOptions,
+    data: DataStruct,
+) -> Result<Implementation, Vec<syn::Error>> {
     let mut record_field_exprs = vec![];
-    match data_struct.fields {
+    match data.fields {
         Fields::Named(a) => {
             for field in a.named {
                 let mut name = field
@@ -61,7 +77,7 @@ pub fn get_struct_schema_def(
                     // 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)?;
+                        get_field_get_record_fields_expr(&field, 
&field_attrs.with)?;
                     record_field_exprs.push(quote! {
                         if let Some(flattened_fields) = #get_record_fields {
                             schema_fields.extend(flattened_fields);
@@ -75,7 +91,7 @@ pub fn get_struct_schema_def(
                 }
                 let default_value = get_field_field_default_expr(&field, 
field_attrs.default)?;
                 let aliases = field_aliases(&field_attrs.alias);
-                let schema_expr = get_field_schema_expr(&field, 
field_attrs.with)?;
+                let schema_expr = get_field_schema_expr(&field, 
&field_attrs.with)?;
                 record_field_exprs.push(quote! {
                     schema_fields.push(::apache_avro::schema::RecordField {
                         name: #name.to_string(),
@@ -90,13 +106,13 @@ pub fn get_struct_schema_def(
         }
         Fields::Unnamed(_) => {
             return Err(vec![syn::Error::new(
-                ident_span,
+                input_span,
                 "AvroSchema derive does not work for tuple structs",
             )]);
         }
         Fields::Unit => {
             return Err(vec![syn::Error::new(
-                ident_span,
+                input_span,
                 "AvroSchema derive does not work for unit structs",
             )]);
         }
@@ -110,7 +126,7 @@ pub fn get_struct_schema_def(
     // the most common case where there is no flatten.
     let minimum_fields = record_field_exprs.len();
 
-    let schema_def = quote! {
+    let schema_expr = quote! {
         {
             let mut schema_fields = 
::std::vec::Vec::with_capacity(#minimum_fields);
             #(#record_field_exprs)*
@@ -132,21 +148,30 @@ pub fn get_struct_schema_def(
             })
         }
     };
-    let record_fields = quote! {
+    let record_fields_expr = quote! {
         let mut schema_fields = 
::std::vec::Vec::with_capacity(#minimum_fields);
         #(#record_field_exprs)*
         ::std::option::Option::Some(schema_fields)
     };
 
-    Ok((schema_def, record_fields))
+    Ok(Implementation::named(
+        ident,
+        generics,
+        &container_attrs.name,
+        schema_expr,
+        Some(record_fields_expr),
+        container_attrs.default,
+    ))
 }
 
-/// Use the schema definition of the only field in the struct as the schema
-pub fn get_transparent_struct_schema_def(
-    fields: Fields,
+fn transparent(
     input_span: Span,
-) -> Result<(TokenStream, TokenStream), Vec<syn::Error>> {
-    match fields {
+    ident: Ident,
+    generics: Generics,
+    container_attrs: NamedTypeOptions,
+    data: DataStruct,
+) -> Result<Implementation, Vec<syn::Error>> {
+    match data.fields {
         Fields::Named(fields_named) => {
             let mut found = None;
             for field in fields_named.named {
@@ -163,9 +188,18 @@ pub 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)?,
+                let field_default_expr = if container_attrs.default.is_none() {
+                    Some(get_field_field_default_expr(&field, attrs.default)?)
+                } else {
+                    container_attrs.default
+                };
+
+                Ok(Implementation::unnamed(
+                    ident,
+                    generics,
+                    get_field_schema_expr(&field, &attrs.with)?,
+                    Some(get_field_get_record_fields_expr(&field, 
&attrs.with)?),
+                    field_default_expr,
                 ))
             } else {
                 Err(vec![syn::Error::new(
diff --git a/avro_derive/tests/derive.rs b/avro_derive/tests/derive.rs
index e2509c3..9da6851 100644
--- a/avro_derive/tests/derive.rs
+++ b/avro_derive/tests/derive.rs
@@ -2570,3 +2570,29 @@ fn 
avro_rs_476_skip_serializing_fielddefault_trait_none() {
         },
     }
 }
+
+#[test]
+fn avro_rs_561_transparent_struct_with_default_override() {
+    #[derive(AvroSchema)]
+    #[serde(transparent)]
+    struct Ta {
+        #[avro(default = "0")]
+        _field: i32,
+    }
+    #[derive(AvroSchema)]
+    #[serde(transparent)]
+    #[avro(default = "0")]
+    struct Tb {
+        _field: i32,
+    }
+
+    let schema = Ta::get_schema();
+    let field_default = Ta::field_default();
+    assert_eq!(schema, Schema::Int);
+    assert_eq!(field_default, Some(serde_json::Value::Number(0i32.into())));
+
+    let schema = Tb::get_schema();
+    let field_default = Tb::field_default();
+    assert_eq!(schema, Schema::Int);
+    assert_eq!(field_default, Some(serde_json::Value::Number(0i32.into())));
+}
diff --git 
a/avro_derive/tests/ui/avro_3687_basic_enum_with_default_twice.stderr 
b/avro_derive/tests/ui/avro_3687_basic_enum_with_default_twice.stderr
index 14a3798..e8e97e6 100644
--- a/avro_derive/tests/ui/avro_3687_basic_enum_with_default_twice.stderr
+++ b/avro_derive/tests/ui/avro_3687_basic_enum_with_default_twice.stderr
@@ -1,8 +1,14 @@
-error: Multiple defaults defined: ["A", "C"]
-  --> tests/ui/avro_3687_basic_enum_with_default_twice.rs:21:6
+error: AvroSchema: Multiple defaults defined: ["A", "C"]
+  --> tests/ui/avro_3687_basic_enum_with_default_twice.rs:21:1
    |
-21 | enum Basic {
-   |      ^^^^^
+21 | / enum Basic {
+22 | |     #[default]
+23 | |     A,
+24 | |     B,
+...  |
+27 | |     D
+28 | | }
+   | |_^
 
 error: multiple declared defaults
   --> tests/ui/avro_3687_basic_enum_with_default_twice.rs:20:22
diff --git a/avro_derive/tests/ui/avro_rs_373_rename_all_fields.stderr 
b/avro_derive/tests/ui/avro_rs_373_rename_all_fields.stderr
index 7a5a531..c779c05 100644
--- a/avro_derive/tests/ui/avro_rs_373_rename_all_fields.stderr
+++ b/avro_derive/tests/ui/avro_rs_373_rename_all_fields.stderr
@@ -1,5 +1,10 @@
 error: AvroSchema: derive does not work for enums with non unit structs
-  --> tests/ui/avro_rs_373_rename_all_fields.rs:22:6
+  --> tests/ui/avro_rs_373_rename_all_fields.rs:21:1
    |
-22 | enum Foo {
-   |      ^^^
+21 | / #[serde(rename_all_fields = "UPPERCASE")]
+22 | | enum Foo {
+23 | |     Bar {
+24 | |         a: String,
+...  |
+27 | | }
+   | |_^
diff --git a/avro_derive/tests/ui/avro_rs_501_non_basic_enum.stderr 
b/avro_derive/tests/ui/avro_rs_501_non_basic_enum.stderr
index 961253b..2ef79de 100644
--- a/avro_derive/tests/ui/avro_rs_501_non_basic_enum.stderr
+++ b/avro_derive/tests/ui/avro_rs_501_non_basic_enum.stderr
@@ -1,5 +1,10 @@
 error: AvroSchema: derive does not work for enums with non unit structs
-  --> tests/ui/avro_rs_501_non_basic_enum.rs:21:6
+  --> tests/ui/avro_rs_501_non_basic_enum.rs:21:1
    |
-21 | enum NonBasic {
-   |      ^^^^^^^^
+21 | / enum NonBasic {
+22 | |     A(i32),
+23 | |     B,
+24 | |     C,
+25 | |     D
+26 | | }
+   | |_^
diff --git a/avro_derive/tests/ui/avro_rs_501_tuple_struct.stderr 
b/avro_derive/tests/ui/avro_rs_501_tuple_struct.stderr
index 1091ec0..5051fc7 100644
--- a/avro_derive/tests/ui/avro_rs_501_tuple_struct.stderr
+++ b/avro_derive/tests/ui/avro_rs_501_tuple_struct.stderr
@@ -1,5 +1,5 @@
 error: AvroSchema derive does not work for tuple structs
-  --> tests/ui/avro_rs_501_tuple_struct.rs:21:8
+  --> tests/ui/avro_rs_501_tuple_struct.rs:21:1
    |
 21 | struct B(i32, String);
-   |        ^
+   | ^^^^^^^^^^^^^^^^^^^^^^
diff --git a/avro_derive/tests/ui/avro_rs_501_unit_struct.stderr 
b/avro_derive/tests/ui/avro_rs_501_unit_struct.stderr
index 2a16ef6..655af14 100644
--- a/avro_derive/tests/ui/avro_rs_501_unit_struct.stderr
+++ b/avro_derive/tests/ui/avro_rs_501_unit_struct.stderr
@@ -1,5 +1,5 @@
 error: AvroSchema derive does not work for unit structs
-  --> tests/ui/avro_rs_501_unit_struct.rs:21:8
+  --> tests/ui/avro_rs_501_unit_struct.rs:21:1
    |
 21 | struct AbsoluteUnit;
-   |        ^^^^^^^^^^^^
+   | ^^^^^^^^^^^^^^^^^^^^

Reply via email to