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

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

commit 06bfaf48da37ac4e5cb6f1218ff5e86283926bcc
Author: Kriskras99 <[email protected]>
AuthorDate: Sun Mar 22 21:43:55 2026 +0100

    wip: derive
---
 Cargo.lock                          |   1 +
 avro_derive/Cargo.toml              |   1 +
 avro_derive/src/attributes/avro.rs  | 121 +++++++++-
 avro_derive/src/attributes/mod.rs   | 450 ++++++++++++++++++++++++++++--------
 avro_derive/src/attributes/serde.rs |  34 +--
 avro_derive/src/lib.rs              | 185 +++++++++------
 6 files changed, 604 insertions(+), 188 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 239fd96..8203033 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -103,6 +103,7 @@ dependencies = [
  "darling",
  "macrotest",
  "pretty_assertions",
+ "prettyplease",
  "proc-macro2",
  "proptest",
  "quote",
diff --git a/avro_derive/Cargo.toml b/avro_derive/Cargo.toml
index 15e6971..f6798f3 100644
--- a/avro_derive/Cargo.toml
+++ b/avro_derive/Cargo.toml
@@ -43,6 +43,7 @@ uuid = { workspace = true }
 apache-avro = { default-features = false, path = "../avro", features = 
["derive"] }
 macrotest = { version = "1.2.1", default-features = false }
 pretty_assertions = { workspace = true }
+prettyplease = "0.2.37"
 proptest = { default-features = false, version = "1.11.0", features = ["std"] }
 rustversion = "1.0.22"
 serde = { workspace = true }
diff --git a/avro_derive/src/attributes/avro.rs 
b/avro_derive/src/attributes/avro.rs
index e0a1433..5a0b328 100644
--- a/avro_derive/src/attributes/avro.rs
+++ b/avro_derive/src/attributes/avro.rs
@@ -21,12 +21,39 @@
 //! Although a user will mostly use the Serde attributes, there are some Avro 
specific attributes
 //! a user can use. These add extra metadata to the generated schema.
 
-use crate::attributes::FieldDefault;
 use crate::case::RenameRule;
 use darling::FromMeta;
 use proc_macro2::Span;
+use serde_json::Value;
 use syn::Expr;
 
+/// What `Schema` representation to generate for a type.
+#[derive(Debug, FromMeta, PartialEq, Clone, Copy)]
+pub enum Repr {
+    /// Generate a `Schema::Enum` for a `enum`.
+    ///
+    /// Only works for unit variants.
+    Enum,
+    /// Generate a `Schema::Union` for a `enum`.
+    ///
+    /// Requires `#[serde(untagged)]`
+    ///
+    /// There can only be one unit variant and every newtype/struct/tuple 
variant must be unique.
+    BareUnion,
+    /// Generate a `Schema::Union` with a `Schema::Record`  for a `enum`.
+    ///
+    /// This works for every enum as the records will have unique names for 
every variant.
+    UnionOfRecords,
+    /// Generate a `Schema::Record` with a tag and content field for a `enum`.
+    ///
+    /// Requires `#[serde(tag = "..", content = "..")]`.
+    RecordTagContent,
+    /// Generate a `Schema::Record` with a tag field and flattened variant 
fields for a `enum`.
+    ///
+    /// Requires `#[serde(tag = "..")]`
+    RecordInternallyTagged,
+}
+
 /// All the Avro attributes a container can have.
 #[derive(darling::FromAttributes)]
 #[darling(attributes(avro))]
@@ -57,6 +84,9 @@ pub struct ContainerAttributes {
     /// Set the default value if this schema is used as a field
     #[darling(default)]
     pub default: Option<String>,
+    /// Force the generator to use a certain schema representation
+    #[darling(default)]
+    pub repr: Option<Repr>,
 }
 
 impl ContainerAttributes {
@@ -78,6 +108,28 @@ impl ContainerAttributes {
     }
 }
 
+/// How to get the schema for a variant.
+#[derive(Debug, PartialEq, Default)]
+pub enum With {
+    /// Use `<T as AvroSchemaComponent>::get_schema_in_ctxt`.
+    #[default]
+    Trait,
+    /// Use `module::get_schema_in_ctxt` where the module is defined by 
Serde's `with` attribute.
+    Serde,
+    /// Call the function in this expression.
+    Expr(Expr),
+}
+
+impl FromMeta for With {
+    fn from_word() -> darling::Result<Self> {
+        Ok(Self::Serde)
+    }
+
+    fn from_expr(expr: &Expr) -> darling::Result<Self> {
+        Ok(Self::Expr(expr.clone()))
+    }
+}
+
 /// All the Avro attributes a variant can have.
 #[derive(darling::FromAttributes)]
 #[darling(attributes(avro))]
@@ -89,6 +141,19 @@ pub struct VariantAttributes {
     /// [`serde::VariantAttributes::rename`]: 
crate::attributes::serde::VariantAttributes::rename
     #[darling(default)]
     pub rename: Option<String>,
+    /// How to get the schema for a variant.
+    ///
+    /// By default uses `<T as AvroSchemaComponent>::get_schema_in_ctxt`.
+    ///
+    /// When it's provided without an argument (`#[avro(with)]`), it will use 
the function `get_schema_in_ctxt` defined
+    /// in the same module as the `#[serde(with = "some_module")]` attribute.
+    ///
+    /// When it's provided with an argument (`#[avro(with = some_fn)]`), it 
will use that function.
+    #[darling(default)]
+    pub with: With,
+    /// Adds a `doc` field to the schema.
+    #[darling(default)]
+    pub doc: Option<String>,
 }
 
 impl VariantAttributes {
@@ -103,21 +168,59 @@ impl VariantAttributes {
     }
 }
 
-/// How to get the schema for a field.
-#[derive(Debug, FromMeta, PartialEq, Default)]
-#[darling(from_expr = |expr| Ok(With::Expr(expr.clone())))]
-pub enum With {
-    /// Use `<T as AvroSchemaComponent>::get_schema_in_ctxt`.
+/// How to get the default value for a value.
+#[derive(Debug, PartialEq, Default)]
+pub enum FieldDefault {
+    /// Use `<T as AvroSchemaComponent>::field_default`.
     #[default]
-    #[darling(skip)]
     Trait,
-    /// Use `module::get_schema_in_ctxt` where the module is defined by 
Serde's `with` attribute.
-    #[darling(word, skip)]
+    /// Don't set a default.
+    Disabled,
+    /// Use `module::field_default` where the module is defined by Serde's 
`with` attribute.
     Serde,
+    /// Use this JSON value.
+    Value(Value),
     /// Call the function in this expression.
     Expr(Expr),
 }
 
+impl FromMeta for FieldDefault {
+    fn from_word() -> darling::Result<Self> {
+        Ok(Self::Serde)
+    }
+
+    fn from_expr(expr: &Expr) -> darling::Result<Self> {
+        match expr {
+            Expr::Lit(lit) => Self::from_value(&lit.lit),
+            Expr::Group(group) => {
+                // syn may generate this invisible group delimiter when the 
input to the darling
+                // proc macro (specifically, the attributes) are generated by a
+                // macro_rules! (e.g. propagating a macro_rules!'s expr)
+                // Since we want to basically ignore these invisible group 
delimiters,
+                // we just propagate the call to the inner expression.
+                Self::from_expr(&group.expr)
+            }
+            expr => Ok(Self::Expr(expr.clone())),
+        }
+    }
+
+    fn from_string(value: &str) -> darling::Result<Self> {
+        serde_json::from_str(value)
+            .map(Self::Value)
+            .map_err(darling::Error::custom)
+    }
+
+    fn from_bool(value: bool) -> darling::Result<Self> {
+        if value {
+            Err(darling::Error::custom(
+                "Expected `false` or a JSON string, got `true`",
+            ))
+        } else {
+            Ok(Self::Disabled)
+        }
+    }
+}
+
 /// All the Avro attributes a field can have.
 #[derive(darling::FromAttributes)]
 #[darling(attributes(avro))]
diff --git a/avro_derive/src/attributes/mod.rs 
b/avro_derive/src/attributes/mod.rs
index ebb2997..8d97b3f 100644
--- a/avro_derive/src/attributes/mod.rs
+++ b/avro_derive/src/attributes/mod.rs
@@ -19,19 +19,126 @@ use crate::case::RenameRule;
 use darling::{FromAttributes, FromMeta};
 use proc_macro2::{Span, TokenStream};
 use quote::quote;
-use syn::{AttrStyle, Attribute, Expr, Ident, Path, spanned::Spanned};
+use serde_json::Value;
+use std::cmp::PartialEq;
+use syn::{AttrStyle, Attribute, Expr, Field, Ident, Path, Type, 
spanned::Spanned};
 
 mod avro;
 mod serde;
 
-#[derive(Default)]
+/// What `Schema` representation to generate for a type.
+#[derive(Debug, PartialEq)]
+pub enum Repr {
+    /// Generate a `Schema::Enum` for a `enum`.
+    ///
+    /// Only works for unit variants.
+    Enum,
+    /// Generate a `Schema::Union` for a `enum`.
+    ///
+    /// Requires `#[serde(untagged)]`
+    ///
+    /// There can only be one unit variant and every newtype/struct/tuple 
variant must be unique.
+    BareUnion,
+    /// Generate a `Schema::Union` with a `Schema::Record`  for a `enum`.
+    ///
+    /// This works for every enum as the records will have unique names for 
every variant.
+    UnionOfRecords,
+    /// Generate a `Schema::Record` with a tag and content field for a `enum`.
+    ///
+    /// Requires `#[serde(tag = "..", content = "..")]`.
+    RecordTagContent { tag: String, content: String },
+    /// Generate a `Schema::Record` with a tag field and flattened variant 
fields for a `enum`.
+    ///
+    /// Requires `#[serde(tag = "..")]`
+    RecordInternallyTagged { tag: String },
+}
+
+impl Repr {
+    fn from_avro_and_serde(
+        avro: Option<avro::Repr>,
+        tag: Option<String>,
+        content: Option<String>,
+        untagged: bool,
+        span: Span,
+    ) -> Result<Option<Self>, syn::Error> {
+        match avro {
+            Some(avro::Repr::Enum) => {
+                if tag.is_some() || content.is_some() || untagged {
+                    Err(syn::Error::new(
+                        span,
+                        r#"AvroSchema: `#[avro(repr = "enum")]` is 
incompatible with `#[serde(tag = "..")]`, `#[serde(content = "..")]`, and 
`#[serde(untagged)]`"#,
+                    ))
+                } else {
+                    Ok(Some(Self::Enum))
+                }
+            }
+            Some(avro::Repr::BareUnion) => {
+                if tag.is_some() || content.is_some() {
+                    Err(syn::Error::new(
+                        span,
+                        r#"AvroSchema: `#[avro(repr = "bare_union")]` is 
incompatible with `#[serde(tag = "..")]` and `#[serde(content = "..")]`"#,
+                    ))
+                } else {
+                    Ok(Some(Self::BareUnion))
+                }
+            }
+            Some(avro::Repr::UnionOfRecords) => {
+                if tag.is_some() || content.is_some() || untagged {
+                    Err(syn::Error::new(
+                        span,
+                        r#"AvroSchema: `#[avro(repr = "union_of_records")]` is 
incompatible with `#[serde(tag = "..")]`, `#[serde(content = "..")]`, and 
`#[serde(untagged)]`"#,
+                    ))
+                } else {
+                    Ok(Some(Self::UnionOfRecords))
+                }
+            }
+            Some(avro::Repr::RecordTagContent) => {
+                if let Some(tag) = tag
+                    && let Some(content) = content
+                    && !untagged
+                {
+                    Ok(Some(Self::RecordTagContent { tag, content }))
+                } else {
+                    Err(syn::Error::new(
+                        span,
+                        r#"AvroSchema: `#[avro(repr = "record_tag_content")]` 
requires `#[serde(tag = "..", content = "..")]` and is incompatible with 
`#[serde(untagged)]`"#,
+                    ))
+                }
+            }
+            Some(avro::Repr::RecordInternallyTagged) => {
+                if let Some(tag) = tag
+                    && content.is_none()
+                    && !untagged
+                {
+                    Ok(Some(Self::RecordInternallyTagged { tag }))
+                } else {
+                    Err(syn::Error::new(
+                        span,
+                        r#"AvroSchema: `#[avro(repr = 
"record_internally_tagged")]` requires `#[serde(tag = "..")]` and is 
incompatible with `#[serde(content = "..")]` and `#[serde(untagged)]`"#,
+                    ))
+                }
+            }
+            None if tag.is_some() && content.is_some() => 
Ok(Some(Self::RecordTagContent {
+                tag: tag.unwrap(),
+                content: content.unwrap(),
+            })),
+            None if tag.is_some() => Ok(Some(Self::RecordInternallyTagged { 
tag: tag.unwrap() })),
+            None if untagged => Ok(Some(Self::BareUnion)),
+            None => Ok(None),
+        }
+    }
+}
+
 pub struct NamedTypeOptions {
     pub name: String,
     pub doc: Option<String>,
     pub aliases: Vec<String>,
     pub rename_all: RenameRule,
+    pub rename_all_fields: RenameRule,
     pub transparent: bool,
     pub default: TokenStream,
+    pub repr: Option<Repr>,
+    pub with: Option<Path>,
 }
 
 impl NamedTypeOptions {
@@ -52,27 +159,28 @@ impl NamedTypeOptions {
         let mut errors = Vec::new();
 
         // Check for any Serde attributes that are hard errors
-        if serde.tag.is_some()
-            || serde.content.is_some()
-            || serde.untagged
-            || serde.variant_identifier
-            || serde.field_identifier
-        {
+        if serde.variant_identifier || serde.field_identifier {
             errors.push(syn::Error::new(
                 span,
-                "AvroSchema derive does not support changing the tagging Serde 
generates (`tag`, `content`, `untagged`, `variant_identifier`, 
`field_identifier`)",
+                r#"AvroSchema: `#[serde(variant_identifier)]` and 
`#[serde(field_identifier)]` are not supported"#,
             ));
         }
-        if serde.remote.is_some() {
+        if serde.rename_all.deserialize != serde.rename_all.serialize {
             errors.push(syn::Error::new(
                 span,
-                "AvroSchema derive does not support the Serde `remote` 
attribute",
+                r#"AvroSchema: rename rules for serializing and deserializing 
must match (`rename_all(serialize = "..", deserialize = "..")`)"#
             ));
         }
-        if serde.rename_all.deserialize != serde.rename_all.serialize {
+        if serde.rename_all_fields.deserialize != 
serde.rename_all_fields.serialize {
             errors.push(syn::Error::new(
                 span,
-                r#"AvroSchema derive does not support different rename rules 
for serializing and deserializing (`rename_all(serialize = "..", deserialize = 
"..")`)"#
+                r#"AvroSchema: rename rules for serializing and deserializing 
must match (`rename_all_fields(serialize = "..", deserialize = "..")`)"#
+            ));
+        }
+        if serde.from != serde.into && serde.try_from != serde.into {
+            errors.push(syn::Error::new(
+                span,
+                r#"AvroSchema: `#[serde({try_,}from = "..")]` must match 
`#[serde(into = "..")]`"#,
             ));
         }
 
@@ -80,13 +188,13 @@ impl NamedTypeOptions {
         if avro.name.is_some() && avro.name != serde.rename {
             errors.push(syn::Error::new(
                 span,
-                r#"#[avro(name = "..")] must match #[serde(rename = "..")], 
it's also deprecated. Please use only `#[serde(rename = "..")]`"#,
+                r#"AvroSchema: #[avro(name = "..")] must match #[serde(rename 
= "..")] and it's deprecated. Please use only `#[serde(rename = "..")]`"#,
             ));
         }
         if avro.rename_all != RenameRule::None && serde.rename_all.serialize 
!= avro.rename_all {
             errors.push(syn::Error::new(
                 span,
-                r#"#[avro(rename_all = "..")] must match #[serde(rename_all = 
"..")], it's also deprecated. Please use only `#[serde(rename_all = "..")]`"#,
+                r#"AvroSchema: #[avro(rename_all = "..")] must match 
#[serde(rename_all = "..")] and it's deprecated. Please use only 
`#[serde(rename_all = "..")]`"#,
             ));
         }
         if serde.transparent
@@ -96,14 +204,86 @@ impl NamedTypeOptions {
                 || avro.doc.is_some()
                 || !avro.alias.is_empty()
                 || avro.rename_all != RenameRule::None
+                || avro.repr.is_some()
+                || serde.rename_all.serialize != RenameRule::None
+                || serde.rename_all.deserialize != RenameRule::None
+                || serde.rename_all_fields.serialize != RenameRule::None
+                || serde.rename_all_fields.deserialize != RenameRule::None
+                || serde.untagged
+                || serde.tag.is_some()
+                || serde.content.is_some()
+                || serde.into.is_some()
+                || serde.from.is_some()
+                || serde.try_from.is_some())
+        {
+            errors.push(syn::Error::new(
+                span,
+                "AvroSchema: `#[serde(transparent)]` is incompatible with all 
other attributes",
+            ));
+        }
+        if serde.into.is_some()
+            && (serde.rename.is_some()
+                || avro.name.is_some()
+                || avro.namespace.is_some()
+                || avro.doc.is_some()
+                || !avro.alias.is_empty()
+                || avro.rename_all != RenameRule::None
+                || avro.repr.is_some()
                 || serde.rename_all.serialize != RenameRule::None
-                || serde.rename_all.deserialize != RenameRule::None)
+                || serde.rename_all.deserialize != RenameRule::None
+                || serde.rename_all_fields.serialize != RenameRule::None
+                || serde.rename_all_fields.deserialize != RenameRule::None
+                || serde.untagged
+                || serde.tag.is_some()
+                || serde.content.is_some())
         {
             errors.push(syn::Error::new(
                 span,
-                "AvroSchema: #[serde(transparent)] is incompatible with all 
other attributes",
+                r#"AvroSchema: `#[serde({,try_}from = "..", into = "..")]` are 
incompatible with all other attributes"#,
             ));
         }
+        let repr = match Repr::from_avro_and_serde(
+            avro.repr,
+            serde.tag,
+            serde.content,
+            serde.untagged,
+            span,
+        ) {
+            Ok(repr) => repr,
+            Err(err) => {
+                errors.push(err);
+                None
+            }
+        };
+
+        let default = match avro.default {
+            None => quote! { None },
+            Some(default_value) => {
+                if let Err(err) = 
serde_json::from_str::<serde_json::Value>(&default_value[..]) {
+                    errors.push(syn::Error::new(
+                        span,
+                        format!("Invalid Avro `default` JSON: \n{err}"),
+                    ));
+                    quote! { ::std::option::Option::None }
+                } else {
+                    quote! {
+                        
::std::option::Option::Some(::serde_json::from_str(#default_value).expect(format!("Invalid
 JSON: {:?}", #default_value).as_str()))
+                    }
+                }
+            }
+        };
+
+        let with = match serde.into.as_deref().map(Path::from_string) {
+            Some(Ok(path)) => Some(path),
+            Some(Err(err)) => {
+                errors.push(syn::Error::new(
+                    span,
+                    format!(r#"AvroSchema: Expected a path for `#[serde(into = 
"..")]`: {err:?}"#),
+                ));
+                None
+            }
+            None => None,
+        };
 
         if !errors.is_empty() {
             return Err(errors);
@@ -118,35 +298,70 @@ 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()))
-                }
-            }
-        };
-
         Ok(Self {
             name: full_schema_name,
             doc,
             aliases: avro.alias,
             rename_all: serde.rename_all.serialize,
+            rename_all_fields: serde.rename_all_fields.serialize,
             transparent: serde.transparent,
             default,
+            repr,
+            with,
         })
     }
 }
 
+/// How to get the schema for this field or variant.
+#[derive(Debug, PartialEq, Default, Clone)]
+pub enum With {
+    /// Use `<T as AvroSchemaComponent>::get_schema_in_ctxt`.
+    #[default]
+    Trait,
+    /// Use `module::get_schema_in_ctxt` where the module is defined by 
Serde's `with` attribute.
+    Serde(Path),
+    /// Call the function in this expression.
+    Expr(Expr),
+}
+
+impl With {
+    fn from_avro_and_serde(
+        avro: &avro::With,
+        serde: &Option<String>,
+        span: Span,
+    ) -> Result<Self, syn::Error> {
+        match &avro {
+            avro::With::Trait => Ok(Self::Trait),
+            avro::With::Serde => {
+                if let Some(serde) = serde {
+                    let path = Path::from_string(serde).map_err(|err| {
+                        syn::Error::new(
+                            span,
+                            format!(
+                                r#"AvroSchema: Expected a path for 
`#[serde(with = "..")]`: {err:?}"#
+                            ),
+                        )
+                    })?;
+                    Ok(Self::Serde(path))
+                } else {
+                    Err(syn::Error::new(
+                        span,
+                        r#"`#[avro(with)]` requires `#[serde(with = 
"some_module")]` or provide a function to call `#[avro(with = some_fn)]`"#,
+                    ))
+                }
+            }
+            avro::With::Expr(expr) => Ok(Self::Expr(expr.clone())),
+        }
+    }
+}
+
 pub struct VariantOptions {
+    pub doc: Option<String>,
     pub rename: Option<String>,
+    pub rename_all: RenameRule,
+    pub aliases: Vec<String>,
+    pub skip: bool,
+    pub with: With,
 }
 
 impl VariantOptions {
@@ -165,7 +380,19 @@ impl VariantOptions {
         if serde.other || serde.untagged {
             errors.push(syn::Error::new(
                 span,
-                "AvroSchema derive does not support changing the tagging Serde 
generates (`other`, `untagged`)",
+                r#"AvroSchema: `#[serde(other)]` and `#[serde(untagged)]` are 
not supported"#,
+            ));
+        }
+        if serde.rename_all.deserialize != serde.rename_all.serialize {
+            errors.push(syn::Error::new(
+                span,
+                r#"AvroSchema: rename rules for serializing and deserializing 
must match (`rename_all(serialize = "..", deserialize = "..")`)"#
+            ));
+        }
+        if serde.skip_serializing != serde.skip_deserializing {
+            errors.push(syn::Error::new(
+                span,
+                "`#[serde(skip_serializing)]` must match 
`#[serde(skip_deserializing)]`",
             ));
         }
 
@@ -176,88 +403,120 @@ impl VariantOptions {
                 r#"`#[avro(rename = "..")]` must match `#[serde(rename = 
"..")]`, it's also deprecated. Please use only `#[serde(rename = "..")]`"#
             ));
         }
+        let with = match With::from_avro_and_serde(&avro.with, &serde.with, 
span) {
+            Ok(with) => with,
+            Err(error) => {
+                errors.push(error);
+                // This won't actually be used, but it does simplify the code
+                With::Trait
+            }
+        };
 
         if !errors.is_empty() {
             return Err(errors);
         }
 
+        let doc = avro.doc.or_else(|| extract_rustdoc(attributes));
+
         Ok(Self {
             rename: serde.rename,
+            rename_all: serde.rename_all.serialize,
+            aliases: serde.alias,
+            doc,
+            skip: serde.skip || serde.skip_serializing,
+            with,
         })
     }
 }
 
-/// How to get the schema for this field or variant.
-#[derive(Debug, PartialEq, Default, Clone)]
-pub enum With {
-    /// Use `<T as AvroSchemaComponent>::get_schema_in_ctxt`.
-    #[default]
+/// How to get the default value for a value.
+#[derive(PartialEq)]
+pub enum FieldDefault {
+    /// Use `<T as AvroSchemaComponent>::field_default`.
     Trait,
-    /// Use `module::get_schema_in_ctxt` where the module is defined by 
Serde's `with` attribute.
+    /// Don't set a default.
+    Disabled,
+    /// Use `module::field_default` where the module is defined by Serde's 
`with` attribute.
     Serde(Path),
+    /// Use this JSON value.
+    Value(Value),
     /// Call the function in this expression.
     Expr(Expr),
 }
 
-impl With {
+impl FieldDefault {
     fn from_avro_and_serde(
-        avro: &avro::With,
-        serde: Option<&String>,
+        avro: avro::FieldDefault,
+        with: &With,
         span: Span,
     ) -> Result<Self, syn::Error> {
-        match &avro {
-            avro::With::Trait => Ok(Self::Trait),
-            avro::With::Serde => {
-                if let Some(serde) = serde {
-                    let path = Path::from_string(serde).map_err(|err| {
-                        syn::Error::new(
-                            span,
-                            format!(
-                                r#"AvroSchema: Expected a path for 
`#[serde(with = "..")]`: {err:?}"#
-                            ),
-                        )
-                    })?;
-                    Ok(Self::Serde(path))
+        match avro {
+            // Only get the default from the type if the schema is also 
retrieved from the type
+            avro::FieldDefault::Trait if with == &With::Trait => 
Ok(Self::Trait),
+            avro::FieldDefault::Trait | avro::FieldDefault::Disabled => 
Ok(Self::Disabled),
+            avro::FieldDefault::Serde => {
+                if let With::Serde(path) = with {
+                    Ok(Self::Serde(path.clone()))
                 } else {
                     Err(syn::Error::new(
                         span,
-                        r#"`#[avro(with)]` requires `#[serde(with = 
"some_module")]` or provide a function to call `#[avro(with = some_fn)]`"#,
+                        r##"`#[avro(default)]` requires `#[serde(with = 
"some_module")]` or provide a function to call `#[avro(default = some_fn)]` or 
a JSON value `#[avro(default = r#""string""#)]`"##,
                     ))
                 }
             }
-            avro::With::Expr(expr) => Ok(Self::Expr(expr.clone())),
+            avro::FieldDefault::Value(value) => Ok(Self::Value(value)),
+            avro::FieldDefault::Expr(expr) => Ok(Self::Expr(expr)),
         }
     }
-}
-/// How to get the default value for a value.
-#[derive(Debug, PartialEq, Default)]
-pub enum FieldDefault {
-    /// Use `<T as AvroSchemaComponent>::field_default`.
-    #[default]
-    Trait,
-    /// Don't set a default.
-    Disabled,
-    /// Use this JSON value.
-    Value(String),
-}
-
-impl FromMeta for FieldDefault {
-    fn from_string(value: &str) -> darling::Result<Self> {
-        Ok(Self::Value(value.to_string()))
-    }
 
-    fn from_bool(value: bool) -> darling::Result<Self> {
-        if value {
-            Err(darling::Error::custom(
-                "Expected `false` or a JSON string, got `true`",
-            ))
-        } else {
-            Ok(Self::Disabled)
+    pub fn to_expr(&self, field: &Field) -> Result<TokenStream, syn::Error> {
+        match self {
+            FieldDefault::Trait => {
+                let ty = &field.ty;
+                match &field.ty {
+                    Type::Array(_) | Type::Slice(_) | Type::Path(_) | 
Type::Reference(_) => {
+                        Ok(quote! {<#ty as :: 
apache_avro::AvroSchemaComponent>::field_default()})
+                    }
+                    Type::Ptr(_) => Err(syn::Error::new(
+                        ty.span(),
+                        "AvroSchema: Raw pointers are not supported",
+                    )),
+                    _ => Err(syn::Error::new(
+                        ty.span(),
+                        format!("AvroSchema: Unexpected type encountered: 
{ty:?}"),
+                    )),
+                }
+            }
+            FieldDefault::Disabled => Ok(quote! { ::std::option::Option::None 
}),
+            FieldDefault::Serde(path) => Ok(quote! { #path::field_default() }),
+            FieldDefault::Value(value) => {
+                let value = serde_json::to_string(&value)
+                    .expect("serde_json::Value will always successfully 
serialize");
+                Ok(quote! {
+                    
::std::option::Option::Some(::serde_json::from_str(#value).expect("Unreachable! 
This is checked at compile time!"))
+                })
+            }
+            FieldDefault::Expr(Expr::Closure(closure)) => {
+                if closure.inputs.is_empty() {
+                    Ok(quote! { (#closure)() })
+                } else {
+                    Err(syn::Error::new(
+                        field.span(),
+                        "AvroSchema: Expected closure with 0 parameters for 
`#[avro(default = ..)]`",
+                    ))
+                }
+            }
+            FieldDefault::Expr(Expr::Path(path)) => {
+                Ok(quote! { #path(named_schemas, enclosing_namespace) })
+            }
+            FieldDefault::Expr(_expr) => Err(syn::Error::new(
+                field.span(),
+                "AvroSchema: Invalid expression, expected function or closure 
for `#[avro(default = ..)]`",
+            )),
         }
     }
 }
 
-#[derive(Default)]
 pub struct FieldOptions {
     pub doc: Option<String>,
     pub default: FieldDefault,
@@ -318,8 +577,7 @@ impl FieldOptions {
                 r#"`#[avro(alias = "..")]` must match `#[serde(alias = 
"..")]`, it's also deprecated. Please use only `#[serde(alias = "..")]`"#
             ));
         }
-
-        let with = match With::from_avro_and_serde(&avro.with, 
serde.with.as_ref(), span) {
+        let with = match With::from_avro_and_serde(&avro.with, &serde.with, 
span) {
             Ok(with) => with,
             Err(error) => {
                 errors.push(error);
@@ -327,19 +585,21 @@ impl FieldOptions {
                 With::Trait
             }
         };
-        // TODO: Implement a better way to do this (maybe if user specifies 
`#[avro(with)]` also use that for the default)
-        // Disable getting the field default, if the schema is not retrieved 
from the field type
-        if with != With::Trait && avro.default == FieldDefault::Trait {
-            avro.default = FieldDefault::Disabled;
-        }
-
+        let default = match FieldDefault::from_avro_and_serde(avro.default, 
&with, span) {
+            Ok(with) => with,
+            Err(error) => {
+                errors.push(error);
+                // This won't actually be used, but it does simplify the code
+                FieldDefault::Disabled
+            }
+        };
         if ((serde.skip_serializing && !serde.skip_deserializing)
             || serde.skip_serializing_if.is_some())
-            && avro.default == FieldDefault::Disabled
+            && default == FieldDefault::Disabled
         {
             errors.push(syn::Error::new(
                 span,
-                "`#[serde(skip_serializing)]` and 
`#[serde(skip_serializing_if)]` are incompatible with `#[avro(default = 
false)]`"
+                "`#[serde(skip_serializing)]` and 
`#[serde(skip_serializing_if)]` are incompatible with `#[avro(default = 
false)]` and `#[avro(with)]` without `#[avro(default)]`"
             ));
         }
 
@@ -351,7 +611,7 @@ impl FieldOptions {
 
         Ok(Self {
             doc,
-            default: avro.default,
+            default,
             alias: serde.alias,
             rename: serde.rename,
             skip: serde.skip || (serde.skip_serializing && 
serde.skip_deserializing),
@@ -396,7 +656,7 @@ fn darling_to_syn(e: darling::Error) -> Vec<syn::Error> {
 fn warn(span: Span, message: &str, help: &str) {
     proc_macro::Diagnostic::spanned(span.unwrap(), proc_macro::Level::Warning, 
message)
         .help(help)
-        .emit();
+        .emit()
 }
 
 #[cfg(not(nightly))]
diff --git a/avro_derive/src/attributes/serde.rs 
b/avro_derive/src/attributes/serde.rs
index b1ca6fa..c9ed8a5 100644
--- a/avro_derive/src/attributes/serde.rs
+++ b/avro_derive/src/attributes/serde.rs
@@ -92,8 +92,8 @@ pub struct ContainerAttributes {
     #[darling(default)]
     pub rename_all: RenameAll,
     /// Rename all the fields of the struct variants in this enum.
-    #[darling(default, rename = "rename_all_fields")]
-    pub _rename_all_fields: RenameAll,
+    #[darling(default)]
+    pub rename_all_fields: RenameAll,
     /// Error when encountering unknown fields when deserialising.
     #[darling(default, rename = "deny_unknown_fields")]
     pub _deny_unknown_fields: bool,
@@ -117,20 +117,20 @@ pub struct ContainerAttributes {
     pub _default: Option<SerdeDefault>,
     /// This type is the serde implementation for a "remote" type.
     ///
-    /// This makes the (de)serialisation use/return a different type.
-    pub remote: Option<String>,
+    /// Inverse of `#[serde(from = "..", into = "..")].
+    pub _remote: Option<String>,
     /// Directly use the inner type for (de)serialisation.
     #[darling(default)]
     pub transparent: bool,
     /// Deserialize using the given type and then convert to this type with 
`From`.
-    #[darling(default, rename = "from")]
-    pub _from: Option<String>,
+    #[darling(default)]
+    pub from: Option<String>,
     /// Deserialize using the given type and then convert to this type with 
`TryFrom`.
-    #[darling(default, rename = "try_from")]
-    pub _try_from: Option<String>,
+    #[darling(default)]
+    pub try_from: Option<String>,
     /// Convert this type to the given type using `Into` and then serialize 
using the given type.
-    #[darling(default, rename = "into")]
-    pub _into: Option<String>,
+    #[darling(default)]
+    pub into: Option<String>,
     /// Use the Serde API at this path.
     #[darling(default, rename = "crate")]
     pub _crate: Option<String>,
@@ -154,18 +154,18 @@ pub struct VariantAttributes {
     pub rename: Option<String>,
     /// Aliases for this variant, only used during deserialisation.
     #[darling(multiple, rename = "alias")]
-    pub _alias: Vec<String>,
-    #[darling(default, rename = "rename_all")]
-    pub _rename_all: RenameAll,
+    pub alias: Vec<String>,
+    #[darling(default)]
+    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>,
@@ -174,7 +174,7 @@ pub struct VariantAttributes {
     pub _deserialize_with: Option<String>,
     /// Use this module for (de)serializing.
     #[darling(rename = "with")]
-    pub _with: Option<String>,
+    pub with: Option<String>,
     /// Put trait bounds on the implementations.
     #[darling(rename = "bound")]
     pub _bound: Option<String>,
diff --git a/avro_derive/src/lib.rs b/avro_derive/src/lib.rs
index d63ed8f..31869f1 100644
--- a/avro_derive/src/lib.rs
+++ b/avro_derive/src/lib.rs
@@ -127,7 +127,7 @@ fn create_trait_definition(
             }
 
             fn field_default() -> ::std::option::Option<::serde_json::Value> {
-                ::std::option::Option::#field_default_impl
+                #field_default_impl
             }
         }
     }
@@ -153,7 +153,8 @@ fn get_struct_schema_def(
     data_struct: DataStruct,
     ident_span: Span,
 ) -> Result<(TokenStream, TokenStream), Vec<syn::Error>> {
-    let mut record_field_exprs = vec![];
+    let mut errors = Vec::new();
+    let mut record_field_exprs = Vec::new();
     match data_struct.fields {
         Fields::Named(a) => {
             for field in a.named {
@@ -165,7 +166,13 @@ fn get_struct_schema_def(
                 if let Some(raw_name) = name.strip_prefix("r#") {
                     name = raw_name.to_string();
                 }
-                let field_attrs = FieldOptions::new(&field.attrs, 
field.span())?;
+                let field_attrs = match FieldOptions::new(&field.attrs, 
field.span()) {
+                    Ok(field_attrs) => field_attrs,
+                    Err(errs) => {
+                        errors.extend(errs);
+                        continue;
+                    }
+                };
                 let doc = preserve_optional(field_attrs.doc);
                 match (field_attrs.rename, container_attrs.rename_all) {
                     (Some(rename), _) => {
@@ -182,7 +189,13 @@ 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)?;
+                        match get_field_get_record_fields_expr(&field, 
field_attrs.with) {
+                            Ok(get_record_fields) => get_record_fields,
+                            Err(err) => {
+                                errors.push(err);
+                                continue;
+                            }
+                        };
                     record_field_exprs.push(quote! {
                         if let Some(flattened_fields) = #get_record_fields {
                             schema_fields.extend(flattened_fields);
@@ -194,24 +207,21 @@ fn get_struct_schema_def(
                     // Don't add this field as it's been replaced by the child 
record fields
                     continue;
                 }
-                let default_value = match field_attrs.default {
-                    FieldDefault::Disabled => 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.ident.span(),
-                                    format!("Invalid avro default json: 
\n{e}"),
-                                )]
-                            })?;
-                        quote! {
-                            
::std::option::Option::Some(::serde_json::from_str(#default_value).expect("Unreachable!
 This parsed at compile time!"))
-                        }
+                let default_value = match field_attrs.default.to_expr(&field) {
+                    Ok(default_value) => default_value,
+                    Err(err) => {
+                        errors.push(err);
+                        continue;
                     }
                 };
                 let aliases = field_aliases(&field_attrs.alias);
-                let schema_expr = get_field_schema_expr(&field, 
field_attrs.with)?;
+                let schema_expr = match get_field_schema_expr(&field, 
field_attrs.with) {
+                    Ok(schema_expr) => schema_expr,
+                    Err(errs) => {
+                        errors.extend(errs);
+                        continue;
+                    }
+                };
                 record_field_exprs.push(quote! {
                     schema_fields.push(::apache_avro::schema::RecordField {
                         name: #name.to_string(),
@@ -225,19 +235,23 @@ fn get_struct_schema_def(
             }
         }
         Fields::Unnamed(_) => {
-            return Err(vec![syn::Error::new(
+            errors.push(syn::Error::new(
                 ident_span,
                 "AvroSchema derive does not work for tuple structs",
-            )]);
+            ));
         }
         Fields::Unit => {
-            return Err(vec![syn::Error::new(
+            errors.push(syn::Error::new(
                 ident_span,
                 "AvroSchema derive does not work for unit structs",
-            )]);
+            ));
         }
     }
 
+    if !errors.is_empty() {
+        return Err(errors);
+    }
+
     let record_doc = preserve_optional(container_attrs.doc.as_ref());
     let record_aliases = aliases(&container_attrs.aliases);
     let full_schema_name = &container_attrs.name;
@@ -284,30 +298,54 @@ fn get_transparent_struct_schema_def(
 ) -> Result<(TokenStream, TokenStream), Vec<syn::Error>> {
     match fields {
         Fields::Named(fields_named) => {
+            let mut errors = Vec::new();
             let mut found = None;
             for field in fields_named.named {
-                let attrs = FieldOptions::new(&field.attrs, field.span())?;
+                let attrs = match FieldOptions::new(&field.attrs, 
field.span()) {
+                    Ok(attrs) => attrs,
+                    Err(errs) => {
+                        errors.extend(errs);
+                        continue;
+                    }
+                };
                 if attrs.skip {
                     continue;
                 }
+                let span = field.span();
                 if found.replace((field, attrs)).is_some() {
-                    return Err(vec![syn::Error::new(
-                        input_span,
+                    errors.push(syn::Error::new(
+                        span,
                         "AvroSchema: #[serde(transparent)] is only allowed on 
structs with one unskipped field",
-                    )]);
+                    ));
                 }
             }
 
             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 schema_expr = match get_field_schema_expr(&field, 
attrs.with.clone()) {
+                    Ok(schema_expr) => schema_expr,
+                    Err(err) => {
+                        errors.push(err);
+                        TokenStream::default()
+                    }
+                };
+                let fields_expr = match 
get_field_get_record_fields_expr(&field, attrs.with) {
+                    Ok(fields_expr) => fields_expr,
+                    Err(err) => {
+                        errors.push(err);
+                        TokenStream::default()
+                    }
+                };
+                if !errors.is_empty() {
+                    Err(errors)
+                } else {
+                    Ok((schema_expr, fields_expr))
+                }
             } else {
-                Err(vec![syn::Error::new(
+                errors.push(syn::Error::new(
                     input_span,
                     "AvroSchema: #[serde(transparent)] is only allowed on 
structs with one unskipped field",
-                )])
+                ));
+                Err(errors)
             }
         }
         Fields::Unnamed(_) => Err(vec![syn::Error::new(
@@ -321,7 +359,7 @@ fn get_transparent_struct_schema_def(
     }
 }
 
-fn get_field_schema_expr(field: &Field, with: With) -> Result<TokenStream, 
Vec<syn::Error>> {
+fn get_field_schema_expr(field: &Field, with: With) -> Result<TokenStream, 
syn::Error> {
     match with {
         With::Trait => Ok(type_to_schema_expr(&field.ty)?),
         With::Serde(path) => {
@@ -331,24 +369,21 @@ fn get_field_schema_expr(field: &Field, with: With) -> 
Result<TokenStream, Vec<s
             if closure.inputs.is_empty() {
                 Ok(quote! { (#closure)() })
             } else {
-                Err(vec![syn::Error::new(
+                Err(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(
+        With::Expr(_expr) => Err(syn::Error::new(
             field.span(),
             "Invalid expression, expected function or closure",
-        )]),
+        )),
     }
 }
 
-fn get_field_get_record_fields_expr(
-    field: &Field,
-    with: With,
-) -> Result<TokenStream, Vec<syn::Error>> {
+fn get_field_get_record_fields_expr(field: &Field, with: With) -> 
Result<TokenStream, syn::Error> {
     match with {
         With::Trait => Ok(type_to_get_record_fields_expr(&field.ty)?),
         With::Serde(path) => {
@@ -364,86 +399,86 @@ fn get_field_get_record_fields_expr(
                     )
                 })
             } else {
-                Err(vec![syn::Error::new(
+                Err(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(
+        With::Expr(_expr) => Err(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`
-fn type_to_schema_expr(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
+fn type_to_schema_expr(ty: &Type) -> Result<TokenStream, 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(
+        Type::Ptr(_) => Err(syn::Error::new_spanned(
             ty,
             "AvroSchema: derive does not support raw pointers",
-        )]),
-        Type::Tuple(_) => Err(vec![syn::Error::new_spanned(
+        )),
+        Type::Tuple(_) => Err(syn::Error::new_spanned(
             ty,
             "AvroSchema: derive does not support tuples",
-        )]),
-        _ => Err(vec![syn::Error::new_spanned(
+        )),
+        _ => Err(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>> {
+fn type_to_get_record_fields_expr(ty: &Type) -> Result<TokenStream, 
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(
+        Type::Ptr(_) => Err(syn::Error::new_spanned(
             ty,
             "AvroSchema: derive does not support raw pointers",
-        )]),
-        Type::Tuple(_) => Err(vec![syn::Error::new_spanned(
+        )),
+        Type::Tuple(_) => Err(syn::Error::new_spanned(
             ty,
             "AvroSchema: derive does not support tuples",
-        )]),
-        _ => Err(vec![syn::Error::new_spanned(
+        )),
+        _ => Err(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>> {
+fn type_to_field_default_expr(ty: &Type) -> Result<TokenStream, 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(
+        Type::Ptr(_) => Err(syn::Error::new_spanned(
             ty,
             "AvroSchema: derive does not support raw pointers",
-        )]),
-        Type::Tuple(_) => Err(vec![syn::Error::new_spanned(
+        )),
+        Type::Tuple(_) => Err(syn::Error::new_spanned(
             ty,
             "AvroSchema: derive does not support tuples",
-        )]),
-        _ => Err(vec![syn::Error::new_spanned(
+        )),
+        _ => Err(syn::Error::new_spanned(
             ty,
             format!(
                 "AvroSchema: Unexpected type encountered! Please open an issue 
if this kind of type should be supported: {ty:?}"
             ),
-        )]),
+        )),
     }
 }
 
@@ -496,4 +531,20 @@ mod tests {
         
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());
     }
+
+    // #[test]
+    // fn cassss() {
+    //     let stream = quote! {
+    //     #[avro(default = r#"{"_field": true}"#)]
+    //         struct Spam {
+    //             _field: bool,
+    //         }
+    //     };
+    //     let result = syn::parse2::<DeriveInput>(stream).unwrap();
+    //     let output = derive_avro_schema(result).unwrap();
+    //     println!("{}", output.to_string());
+    //     let pretty = prettyplease::unparse(&syn::parse2(output).unwrap());
+    //     println!("{}", pretty);
+    //     panic!();
+    // }
 }

Reply via email to