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

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


The following commit(s) were added to refs/heads/main by this push:
     new 791e77f  refactor: Centralize generating the actual `impl` in the 
`Implementation` struct (#561)
791e77f is described below

commit 791e77fc227f65d35234eb6f45017b0049b6857a
Author: Kriskras99 <[email protected]>
AuthorDate: Fri Jun 26 10:45:24 2026 +0200

    refactor: Centralize generating the actual `impl` in the `Implementation` 
struct (#561)
    
    * 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.
    
    * fix: Review feedback
    - Only allow the default attribute on the field of a transparent struct
    - Improve the plain enum schema generation
      - Skipped variants are now actually skipped
      - The container default attribute is used first, then comes the variant 
default attribute
    - `serde_json::Value`s are now constructed instead of parsed at runtime
    
    * fix: Match the plain enum check in the implementation
---
 avro/src/serde/ser_schema/mod.rs                   |   6 +-
 avro_derive/src/attributes/mod.rs                  |  40 +++---
 avro_derive/src/attributes/serde.rs                |   6 +-
 avro_derive/src/enums/mod.rs                       |  72 +++++------
 avro_derive/src/enums/plain.rs                     | 143 ++++++++++++++++-----
 avro_derive/src/fields.rs                          |  14 +-
 avro_derive/src/implementation.rs                  | 114 ++++++++++++++++
 avro_derive/src/lib.rs                             |  99 +++-----------
 avro_derive/src/structs.rs                         |  82 ++++++++----
 avro_derive/src/utils.rs                           |  63 +++++++++
 avro_derive/tests/derive.rs                        |  15 +++
 .../avro_3687_basic_enum_with_default.expanded.rs  |   4 +-
 .../avro_3709_record_field_attributes.expanded.rs  |  10 +-
 avro_derive/tests/serde.rs                         |   2 +-
 .../avro_3687_basic_enum_with_default_twice.stderr |   6 -
 .../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 +-
 .../tests/ui/avro_rs_561_transparent_default.rs    |  27 ++++
 .../ui/avro_rs_561_transparent_default.stderr      |   9 ++
 21 files changed, 509 insertions(+), 233 deletions(-)

diff --git a/avro/src/serde/ser_schema/mod.rs b/avro/src/serde/ser_schema/mod.rs
index 337268a..5aba68b 100644
--- a/avro/src/serde/ser_schema/mod.rs
+++ b/avro/src/serde/ser_schema/mod.rs
@@ -435,8 +435,12 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> Serializer for 
SchemaAwareSerializer<'
         match self.schema {
             Schema::Enum(enum_schema) => {
                 // Plain enum
-                if variant.as_ptr() == SERIALIZING_SCHEMA_DEFAULT.as_ptr() || 
enum_schema.symbols[variant_index as usize] == variant {
+                if variant.as_ptr() == SERIALIZING_SCHEMA_DEFAULT.as_ptr() || 
enum_schema.symbols.get(variant_index as usize).map(String::as_str) == 
Some(variant) {
+                    // Fast path for when the index matches our symbol index
                     zig_i32(variant_index as i32, &mut *self.writer)
+                } else if let Some((index, _)) = 
enum_schema.symbols.iter().enumerate().find(|(_, s)| s.as_str() == variant) {
+                    // Slow path for when `#[serde(skip)]` is used
+                    zig_i32(index as i32, &mut *self.writer)
                 } else {
                     Err(self.error("unit variant", format!(r#"Expected symbol 
"{variant}" at index {variant_index} in enum"#)))
                 }
diff --git a/avro_derive/src/attributes/mod.rs 
b/avro_derive/src/attributes/mod.rs
index e68bbbb..24e71f9 100644
--- a/avro_derive/src/attributes/mod.rs
+++ b/avro_derive/src/attributes/mod.rs
@@ -17,8 +17,8 @@
 
 use crate::case::RenameRule;
 use darling::{FromAttributes, FromMeta};
-use proc_macro2::{Span, TokenStream};
-use quote::quote;
+use proc_macro2::Span;
+use serde_json::Value;
 use syn::{AttrStyle, Attribute, Expr, Ident, Path, spanned::Spanned};
 
 mod avro;
@@ -31,7 +31,7 @@ pub struct NamedTypeOptions {
     pub aliases: Vec<String>,
     pub rename_all: RenameRule,
     pub transparent: bool,
-    pub default: TokenStream,
+    pub default: Option<Value>,
 }
 
 impl NamedTypeOptions {
@@ -88,6 +88,7 @@ impl NamedTypeOptions {
                 || avro.name.is_some()
                 || avro.namespace.is_some()
                 || avro.doc.is_some()
+                || avro.default.is_some()
                 || !avro.alias.is_empty()
                 || avro.rename_all != RenameRule::None
                 || serde.rename_all.serialize != RenameRule::None
@@ -112,20 +113,15 @@ 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 {
+            Some(serde_json::from_str(default_value.as_str()).map_err(|e| {
+                vec![syn::Error::new(
+                    ident.span(),
+                    format!("Invalid Avro `default` JSON: \n{e}"),
+                )]
+            })?)
+        } else {
+            None
         };
 
         Ok(Self {
@@ -141,6 +137,7 @@ impl NamedTypeOptions {
 
 pub struct VariantOptions {
     pub rename: Option<String>,
+    pub skip: bool,
 }
 
 impl VariantOptions {
@@ -177,6 +174,9 @@ impl VariantOptions {
 
         Ok(Self {
             rename: serde.rename,
+            // For variants we don't care about defaults for skipping, as 
Serde will error if a skipped
+            // variant is serialized or deserialized.
+            skip: serde.skip || (serde.skip_serializing && 
serde.skip_deserializing),
         })
     }
 }
@@ -232,12 +232,14 @@ pub enum FieldDefault {
     /// Don't set a default.
     Disabled,
     /// Use this JSON value.
-    Value(String),
+    Value(Value),
 }
 
 impl FromMeta for FieldDefault {
     fn from_string(value: &str) -> darling::Result<Self> {
-        Ok(Self::Value(value.to_string()))
+        Ok(Self::Value(serde_json::from_str(value).map_err(|e| {
+            darling::Error::custom(format!("Failed to parse field default: 
{e:?}"))
+        })?))
     }
 
     fn from_bool(value: bool) -> darling::Result<Self> {
diff --git a/avro_derive/src/attributes/serde.rs 
b/avro_derive/src/attributes/serde.rs
index 69d594b..f53efd4 100644
--- a/avro_derive/src/attributes/serde.rs
+++ b/avro_derive/src/attributes/serde.rs
@@ -163,13 +163,13 @@ pub struct VariantAttributes {
     pub _rename_all: RenameAll,
     /// Do not serialize or deserialize this variant.
     #[darling(default, rename = "skip")]
-    pub _skip: bool,
+    pub skip: bool,
     /// Do not serialize this variant.
     #[darling(default, rename = "skip_serializing")]
-    pub _skip_serializing: bool,
+    pub skip_serializing: bool,
     /// Do not deserialize this variant.
     #[darling(default, rename = "skip_deserializing")]
-    pub _skip_deserializing: bool,
+    pub skip_deserializing: bool,
     /// Use this function for serializing.
     #[darling(rename = "serialize_with")]
     pub _serialize_with: Option<String>,
diff --git a/avro_derive/src/enums/mod.rs b/avro_derive/src/enums/mod.rs
index 1511f65..6425745 100644
--- a/avro_derive/src/enums/mod.rs
+++ b/avro_derive/src/enums/mod.rs
@@ -17,51 +17,43 @@
 
 mod plain;
 
-use crate::attributes::NamedTypeOptions;
-use proc_macro2::{Ident, Span, TokenStream};
-use syn::{Attribute, DataEnum, Fields, Meta};
+use crate::attributes::{NamedTypeOptions, VariantOptions};
+use crate::implementation::Implementation;
+use proc_macro2::{Ident, Span};
+use syn::spanned::Spanned;
+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)
-    } else {
-        Err(vec![syn::Error::new(
-            ident_span,
-            "AvroSchema: derive does not work for enums with non unit structs",
-        )])
+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",
+        )]);
     }
-}
-
-fn default_enum_variant(
-    data_enum: &DataEnum,
-    error_span: Span,
-) -> Result<Option<String>, Vec<syn::Error>> {
-    match data_enum
+    if data
         .variants
         .iter()
-        .filter(|v| v.attrs.iter().any(is_default_attr))
-        .collect::<Vec<_>>()
+        .filter(|v| {
+            // Filter skipped variants, if the attributes fail to parse we'll 
also filter them out
+            // `plain` will throw proper errors for that.
+            VariantOptions::new(&v.attrs, v.span())
+                .map(|o| !o.skip)
+                .unwrap_or(false)
+        })
+        .all(|v| Fields::Unit == v.fields)
     {
-        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>>()
-            ),
-        )]),
+        plain::to_implementation(input_span, ident, generics, container_attrs, 
data)
+    } else {
+        Err(vec![syn::Error::new(
+            input_span,
+            "AvroSchema: derive does not work for enums with non unit structs",
+        )])
     }
 }
-
-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..8270e0f 100644
--- a/avro_derive/src/enums/plain.rs
+++ b/avro_derive/src/enums/plain.rs
@@ -17,49 +17,128 @@
 
 use crate::attributes::{NamedTypeOptions, VariantOptions};
 use crate::case::RenameRule;
-use crate::enums::default_enum_variant;
+use crate::implementation::Implementation;
+use crate::utils::json_value_expr;
 use crate::{aliases, preserve_optional};
-use proc_macro2::{Span, TokenStream};
+use proc_macro2::{Ident, Span};
 use quote::quote;
+use serde_json::Value;
 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)?;
-        let default = preserve_optional(default_value);
-        let mut symbols = Vec::new();
-        for variant in &data_enum.variants {
-            let field_attrs = VariantOptions::new(&variant.attrs, 
variant.span())?;
-            let name = match (field_attrs.rename, container_attrs.rename_all) {
-                (Some(rename), _) => rename,
+
+    let mut errors = Vec::new();
+    let mut symbols = Vec::new();
+    let mut default = match &container_attrs.default {
+        None => None,
+        Some(Value::String(s)) => Some(s.clone()),
+        Some(_) => {
+            errors.push(syn::Error::new(
+                input_span,
+                "Expected a JSON string for an enum default",
+            ));
+            None
+        }
+    };
+
+    for variant in data.variants {
+        let variant_attrs = match VariantOptions::new(&variant.attrs, 
variant.span()) {
+            Ok(attrs) => attrs,
+            Err(errs) => {
+                errors.extend(errs);
+                continue;
+            }
+        };
+
+        // Check the skip attribute before the check if it is a unit variant, 
because a skipped (un)named
+        // variant is not a problem
+        if variant_attrs.skip {
+            continue;
+        }
+
+        if !matches!(variant.fields, Fields::Unit) {
+            errors.push(syn::Error::new(
+                variant.span(),
+                "AvroSchema: derive does not work for enums with non unit 
structs",
+            ));
+            continue;
+        }
+
+        // When a variant has the `#[default]` attribute and we don't have a 
default yet, we assign
+        // it as the default. If an enum has multiple variants with the 
default attribute, the compiler
+        // throws an error anyway so we can ignore that situation. If the 
default attribute is on a
+        // skipped variant, we ignore it as Serde will throw an error if we 
try to deserialize that
+        // variant.
+        if default.is_none() && has_default_attr(&variant.attrs) {
+            let renamed = match (&variant_attrs.rename, 
container_attrs.rename_all) {
+                (Some(rename), _) => rename.clone(),
                 (None, rename_all) if !matches!(rename_all, RenameRule::None) 
=> {
                     rename_all.apply_to_variant(&variant.ident.to_string())
                 }
                 _ => variant.ident.to_string(),
             };
-            symbols.push(name);
+            default = Some(renamed);
         }
-        Ok(quote! {
-            
::apache_avro::schema::Schema::Enum(::apache_avro::schema::EnumSchema {
-                name,
-                aliases: #enum_aliases,
-                doc: #doc,
-                symbols: vec![#(#symbols.to_owned()),*],
-                default: #default,
-                attributes: ::std::collections::BTreeMap::new(),
-            })
-        })
-    } else {
-        Err(vec![syn::Error::new(
-            ident_span,
-            "AvroSchema: derive does not work for enums with non unit structs",
-        )])
+
+        let name = match (variant_attrs.rename, container_attrs.rename_all) {
+            (Some(rename), _) => rename,
+            (None, rename_all) if !matches!(rename_all, RenameRule::None) => {
+                rename_all.apply_to_variant(&variant.ident.to_string())
+            }
+            _ => variant.ident.to_string(),
+        };
+        symbols.push(name);
     }
+
+    if let Some(default) = &default
+        && symbols.iter().all(|n| default != n)
+    {
+        errors.push(syn::Error::new(
+            input_span,
+            format!("The enum default {default} is not one of the variants: 
{symbols:?}"),
+        ));
+    }
+
+    if !errors.is_empty() {
+        return Err(errors);
+    }
+
+    let default = preserve_optional(default.map(|d| quote! { 
::std::string::String::from(#d)}));
+    let schema_expr = quote! {
+        ::apache_avro::schema::Schema::Enum(::apache_avro::schema::EnumSchema {
+            name,
+            aliases: #enum_aliases,
+            doc: #doc,
+            symbols: vec![#(#symbols.to_owned()),*],
+            default: #default,
+            attributes: ::std::collections::BTreeMap::new(),
+        })
+    };
+
+    Ok(Implementation::named(
+        ident,
+        generics,
+        &container_attrs.name,
+        schema_expr,
+        None,
+        container_attrs
+            .default
+            .map(json_value_expr)
+            .map(|t| quote! { ::std::option::Option::Some(#t)}),
+    ))
+}
+
+fn has_default_attr(attrs: &[Attribute]) -> bool {
+    attrs.iter().any(|attr| {
+        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 f6bcb80..960c6aa 100644
--- a/avro_derive/src/fields.rs
+++ b/avro_derive/src/fields.rs
@@ -16,12 +16,13 @@
 // under the License.
 
 use crate::attributes::{FieldDefault, With};
+use crate::utils::json_value_expr;
 use proc_macro2::TokenStream;
 use quote::quote;
 use syn::spanned::Spanned;
 use syn::{Expr, Field, Type};
 
-pub fn field_to_schema_expr(field: &Field, with: With) -> Result<TokenStream, 
Vec<syn::Error>> {
+pub fn field_to_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 +48,7 @@ pub fn field_to_schema_expr(field: &Field, with: With) -> 
Result<TokenStream, Ve
 
 pub fn field_to_record_fields_expr(
     field: &Field,
-    with: With,
+    with: &With,
 ) -> Result<TokenStream, Vec<syn::Error>> {
     match with {
         With::Trait => Ok(type_to_record_fields_expr(&field.ty)?),
@@ -88,14 +89,9 @@ pub fn field_to_field_default_expr(
         FieldDefault::Disabled => Ok(quote! { ::std::option::Option::None }),
         FieldDefault::Trait => type_to_field_default_expr(&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}"),
-                )]
-            })?;
+            let value = json_value_expr(default_value);
             Ok(quote! {
-                
::std::option::Option::Some(::serde_json::from_str(#default_value).expect("Unreachable!
 This parsed successfully at compile time!"))
+                ::std::option::Option::Some(#value)
             })
         }
     }
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 db51bd1..78d0032 100644
--- a/avro_derive/src/structs.rs
+++ b/avro_derive/src/structs.rs
@@ -20,20 +20,36 @@ use crate::case::RenameRule;
 use crate::fields::{
     field_to_field_default_expr, field_to_record_fields_expr, 
field_to_schema_expr,
 };
-use crate::utils::{aliases, field_aliases, preserve_optional};
-use proc_macro2::{Span, TokenStream};
+use crate::implementation::Implementation;
+use crate::utils::{aliases, field_aliases, json_value_expr, preserve_optional};
+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, 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
@@ -60,7 +76,7 @@ pub fn get_struct_schema_def(
                 } 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 = 
field_to_record_fields_expr(&field, field_attrs.with)?;
+                    let get_record_fields = 
field_to_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);
@@ -74,7 +90,7 @@ pub fn get_struct_schema_def(
                 }
                 let default_value = field_to_field_default_expr(&field, 
field_attrs.default)?;
                 let aliases = field_aliases(&field_attrs.alias);
-                let schema_expr = field_to_schema_expr(&field, 
field_attrs.with)?;
+                let schema_expr = field_to_schema_expr(&field, 
&field_attrs.with)?;
                 record_field_exprs.push(quote! {
                     schema_fields.push(::apache_avro::schema::RecordField {
                         name: #name.to_string(),
@@ -89,13 +105,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",
             )]);
         }
@@ -109,7 +125,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)*
@@ -131,21 +147,32 @@ 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
+            .map(json_value_expr)
+            .map(|t| quote! { ::std::option::Option::Some(#t)}),
+    ))
 }
 
-/// 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,
+    data: DataStruct,
+) -> Result<Implementation, Vec<syn::Error>> {
+    match data.fields {
         Fields::Named(fields_named) => {
             let mut found = None;
             for field in fields_named.named {
@@ -162,9 +189,14 @@ pub fn get_transparent_struct_schema_def(
             }
 
             if let Some((field, attrs)) = found {
-                Ok((
-                    field_to_schema_expr(&field, attrs.with.clone())?,
-                    field_to_record_fields_expr(&field, attrs.with)?,
+                let field_default_expr = field_to_field_default_expr(&field, 
attrs.default)?;
+
+                Ok(Implementation::unnamed(
+                    ident,
+                    generics,
+                    field_to_schema_expr(&field, &attrs.with)?,
+                    Some(field_to_record_fields_expr(&field, &attrs.with)?),
+                    Some(field_default_expr),
                 ))
             } else {
                 Err(vec![syn::Error::new(
diff --git a/avro_derive/src/utils.rs b/avro_derive/src/utils.rs
index 6affff0..7699494 100644
--- a/avro_derive/src/utils.rs
+++ b/avro_derive/src/utils.rs
@@ -17,6 +17,7 @@
 
 use proc_macro2::TokenStream;
 use quote::quote;
+use serde_json::Value;
 
 /// Convert a list of errors into actual compiler errors.
 ///
@@ -65,3 +66,65 @@ pub fn field_aliases(op: &[impl AsRef<str>]) -> TokenStream {
         .collect();
     quote! {vec![#(#items),*]}
 }
+
+/// Convert a `Value` into a `TokenStream`.
+///
+/// The `Value` is constructed, not parsed from a JSON string.
+pub fn json_value_expr(value: Value) -> TokenStream {
+    match value {
+        Value::Null => quote! {
+            ::serde_json::Value::Null
+        },
+        Value::Bool(bool) => quote! {
+            ::serde_json::Value::Bool(#bool)
+        },
+        Value::Number(number) => {
+            if let Some(n) = number.as_u64() {
+                quote! {
+                    ::serde_json::Value::Number(::serde_json::Number::from(#n))
+                }
+            } else if let Some(n) = number.as_i64() {
+                quote! {
+                    ::serde_json::Value::Number(::serde_json::Number::from(#n))
+                }
+            } else if let Some(n) = number.as_f64() {
+                quote! {
+                    
::serde_json::Value::Number(::serde_json::Number::from_f64(#n).expect("Unreachable,
 f64 is finite"))
+                }
+            } else {
+                // This is needed when the arbitrary-precision feature flag is 
enabled on serde_json
+                let s = number.to_string();
+                quote! {
+                    
::serde_json::Value::Number(#s.parse().expect(concat!("This was a valid number 
at compile time: ", #s)))
+                }
+            }
+        }
+        Value::String(string) => quote! {
+            ::serde_json::Value::String(::std::string::String::from(#string))
+        },
+        Value::Array(array) => {
+            let array = array.into_iter().map(json_value_expr);
+            quote! {
+                ::serde_json::Value::Array(vec![#(#array),*])
+            }
+        }
+        Value::Object(object) => {
+            let len = object.len();
+
+            let mut keys = Vec::with_capacity(len);
+            let mut values = Vec::with_capacity(len);
+            for (key, value) in object {
+                keys.push(key);
+                values.push(json_value_expr(value));
+            }
+
+            quote! {
+                ::serde_json::Value::Object({
+                    let mut map = ::serde_json::Map::with_capacity(#len);
+                    #(map.insert(::std::string::String::from(#keys), 
#values);)*
+                    map
+                })
+            }
+        }
+    }
+}
diff --git a/avro_derive/tests/derive.rs b/avro_derive/tests/derive.rs
index e2509c3..837ef3d 100644
--- a/avro_derive/tests/derive.rs
+++ b/avro_derive/tests/derive.rs
@@ -2570,3 +2570,18 @@ fn 
avro_rs_476_skip_serializing_fielddefault_trait_none() {
         },
     }
 }
+
+#[test]
+fn avro_rs_561_transparent_struct_with_default_override() {
+    #[derive(AvroSchema)]
+    #[serde(transparent)]
+    struct T {
+        #[avro(default = "42")]
+        _field: i32,
+    }
+
+    let schema = T::get_schema();
+    let field_default = T::field_default();
+    assert_eq!(schema, Schema::Int);
+    assert_eq!(field_default, Some(serde_json::Value::Number(42i32.into())));
+}
diff --git 
a/avro_derive/tests/expanded/avro_3687_basic_enum_with_default.expanded.rs 
b/avro_derive/tests/expanded/avro_3687_basic_enum_with_default.expanded.rs
index 62fcff7..1db9146 100644
--- a/avro_derive/tests/expanded/avro_3687_basic_enum_with_default.expanded.rs
+++ b/avro_derive/tests/expanded/avro_3687_basic_enum_with_default.expanded.rs
@@ -35,7 +35,9 @@ impl ::apache_avro::AvroSchemaComponent for Basic {
                         ["A".to_owned(), "B".to_owned(), "C".to_owned(), 
"D".to_owned()],
                     ),
                 ),
-                default: ::std::option::Option::Some("A".into()),
+                default: ::std::option::Option::Some(
+                    ::std::string::String::from("A").into(),
+                ),
                 attributes: ::std::collections::BTreeMap::new(),
             })
         }
diff --git 
a/avro_derive/tests/expanded/avro_3709_record_field_attributes.expanded.rs 
b/avro_derive/tests/expanded/avro_3709_record_field_attributes.expanded.rs
index f65b9a0..11e668c 100644
--- a/avro_derive/tests/expanded/avro_3709_record_field_attributes.expanded.rs
+++ b/avro_derive/tests/expanded/avro_3709_record_field_attributes.expanded.rs
@@ -30,10 +30,9 @@ impl ::apache_avro::AvroSchemaComponent for A {
                         name: "a3".to_string(),
                         doc: ::std::option::Option::Some("a doc".into()),
                         default: ::std::option::Option::Some(
-                            ::serde_json::from_str("123")
-                                .expect(
-                                    "Unreachable! This parsed successfully at 
compile time!",
-                                ),
+                            ::serde_json::Value::Number(
+                                ::serde_json::Number::from(123u64),
+                            ),
                         ),
                         aliases: 
::alloc::boxed::box_assume_init_into_vec_unsafe(
                             ::alloc::intrinsics::write_box_via_move(
@@ -109,8 +108,7 @@ impl ::apache_avro::AvroSchemaComponent for A {
                 name: "a3".to_string(),
                 doc: ::std::option::Option::Some("a doc".into()),
                 default: ::std::option::Option::Some(
-                    ::serde_json::from_str("123")
-                        .expect("Unreachable! This parsed successfully at 
compile time!"),
+                    
::serde_json::Value::Number(::serde_json::Number::from(123u64)),
                 ),
                 aliases: ::alloc::boxed::box_assume_init_into_vec_unsafe(
                     ::alloc::intrinsics::write_box_via_move(
diff --git a/avro_derive/tests/serde.rs b/avro_derive/tests/serde.rs
index 0f03713..3b51074 100644
--- a/avro_derive/tests/serde.rs
+++ b/avro_derive/tests/serde.rs
@@ -508,7 +508,7 @@ mod variant_attributes {
             "type":"enum",
             "name":"Foo",
             "symbols": [
-                "One", "Two"
+                "Two"
             ]
         }
         "#;
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..7b8fe98 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,9 +1,3 @@
-error: Multiple defaults defined: ["A", "C"]
-  --> tests/ui/avro_3687_basic_enum_with_default_twice.rs:21:6
-   |
-21 | enum Basic {
-   |      ^^^^^
-
 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;
-   |        ^^^^^^^^^^^^
+   | ^^^^^^^^^^^^^^^^^^^^
diff --git a/avro_derive/tests/ui/avro_rs_561_transparent_default.rs 
b/avro_derive/tests/ui/avro_rs_561_transparent_default.rs
new file mode 100644
index 0000000..91d61db
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_561_transparent_default.rs
@@ -0,0 +1,27 @@
+// 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 apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+#[serde(transparent)]
+#[avro(default = "0")]
+struct T {
+    _field: i32,
+}
+
+fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_561_transparent_default.stderr 
b/avro_derive/tests/ui/avro_rs_561_transparent_default.stderr
new file mode 100644
index 0000000..a0ca2c1
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_561_transparent_default.stderr
@@ -0,0 +1,9 @@
+error: AvroSchema: #[serde(transparent)] is incompatible with all other 
attributes
+  --> tests/ui/avro_rs_561_transparent_default.rs:21:1
+   |
+21 | / #[serde(transparent)]
+22 | | #[avro(default = "0")]
+23 | | struct T {
+24 | |     _field: i32,
+25 | | }
+   | |_^


Reply via email to