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

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

commit 762c23027ff907c35e3875b432a0e532350f8dd9
Author: default <[email protected]>
AuthorDate: Thu Dec 18 14:25:07 2025 +0000

    feat: Use the Serde attributes and check for conflicting attributes
    
    This commit changes the attribute parsing in `#[derive(AvroSchema)]` to 
also use the Serde attributes.
    It will use the Serde attributes and check that they match the Avro 
attributes if provided. It will
    also check that known incompatible Serde attributes result in a compile 
error instead of a runtime panic.
    
    Testing for the compile errors is done using `trybuild`. These tests run 
only on nightly as the output
    can vary between compiler versions (otherwise MSRV and latest stable could 
have different outputs and break the entire CI).
---
 Cargo.lock                                         | 108 ++++
 avro/tests/avro-rs-226.rs                          |  66 ---
 avro_derive/Cargo.toml                             |   4 +-
 avro_derive/src/attributes/avro.rs                 |  40 ++
 avro_derive/src/attributes/mod.rs                  | 202 +++++++
 avro_derive/src/attributes/serde.rs                | 298 ++++++++++
 avro_derive/src/case.rs                            |  44 +-
 avro_derive/src/lib.rs                             |  78 +--
 avro_derive/tests/serde.rs                         | 659 +++++++++++++++++++++
 avro_derive/tests/ui.rs                            |   9 +
 .../tests/ui/avro_rs_226_skip_serializing.rs       |  11 +
 .../tests/ui/avro_rs_226_skip_serializing.stderr   |   6 +
 .../tests/ui/avro_rs_226_skip_serializing_if.rs    |  11 +
 .../ui/avro_rs_226_skip_serializing_if.stderr      |   6 +
 avro_derive/tests/ui/avro_rs_373_remote.rs         |  16 +
 avro_derive/tests/ui/avro_rs_373_remote.stderr     |   9 +
 .../tests/ui/avro_rs_373_rename_all_fields.rs      |  12 +
 .../tests/ui/avro_rs_373_rename_all_fields.stderr  |   5 +
 .../tests/ui/avro_rs_373_skip_de_serializing.rs    |  11 +
 .../ui/avro_rs_373_skip_de_serializing.stderr      |   6 +
 avro_derive/tests/ui/avro_rs_373_tag struct.rs     |  10 +
 avro_derive/tests/ui/avro_rs_373_tag struct.stderr |   9 +
 .../tests/ui/avro_rs_373_tag_content_enum.rs       |  10 +
 .../tests/ui/avro_rs_373_tag_content_enum.stderr   |   9 +
 avro_derive/tests/ui/avro_rs_373_tag_enum.rs       |  10 +
 avro_derive/tests/ui/avro_rs_373_tag_enum.stderr   |   9 +
 avro_derive/tests/ui/avro_rs_373_transparent.rs    |  15 +
 .../tests/ui/avro_rs_373_transparent.stderr        |   8 +
 avro_derive/tests/ui/avro_rs_373_untagged_enum.rs  |  10 +
 .../tests/ui/avro_rs_373_untagged_enum.stderr      |   9 +
 30 files changed, 1559 insertions(+), 141 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 19468a0..1292b3a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -100,9 +100,11 @@ dependencies = [
  "proc-macro2",
  "proptest",
  "quote",
+ "rustversion",
  "serde",
  "serde_json",
  "syn",
+ "trybuild",
 ]
 
 [[package]]
@@ -505,6 +507,12 @@ dependencies = [
  "log",
 ]
 
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
 [[package]]
 name = "find-msvc-tools"
 version = "0.1.4"
@@ -562,6 +570,12 @@ dependencies = [
  "zerocopy",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+
 [[package]]
 name = "heck"
 version = "0.5.0"
@@ -591,6 +605,16 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
 
+[[package]]
+name = "indexmap"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
 [[package]]
 name = "itertools"
 version = "0.13.0"
@@ -1055,6 +1079,15 @@ dependencies = [
  "zmij",
 ]
 
+[[package]]
+name = "serde_spanned"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
+dependencies = [
+ "serde_core",
+]
+
 [[package]]
 name = "sha2"
 version = "0.10.9"
@@ -1113,6 +1146,21 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "target-triple"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b"
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
 [[package]]
 name = "thiserror"
 version = "2.0.17"
@@ -1143,6 +1191,60 @@ dependencies = [
  "serde_json",
 ]
 
+[[package]]
+name = "toml"
+version = "0.9.10+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
+dependencies = [
+ "indexmap",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.7.5+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.0.6+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.0.6+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
+
+[[package]]
+name = "trybuild"
+version = "1.0.114"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "3e17e807bff86d2a06b52bca4276746584a78375055b6e45843925ce2802b335"
+dependencies = [
+ "glob",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "target-triple",
+ "termcolor",
+ "toml",
+]
+
 [[package]]
 name = "typenum"
 version = "1.19.0"
@@ -1416,6 +1518,12 @@ version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
+[[package]]
+name = "winnow"
+version = "0.7.14"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
+
 [[package]]
 name = "wit-bindgen"
 version = "0.46.0"
diff --git a/avro/tests/avro-rs-226.rs b/avro/tests/avro-rs-226.rs
index 15f060b..060fc9d 100644
--- a/avro/tests/avro-rs-226.rs
+++ b/avro/tests/avro-rs-226.rs
@@ -133,69 +133,3 @@ fn 
avro_rs_226_index_out_of_bounds_with_serde_skip_multiple_fields() -> TestResu
         },
     )
 }
-
-#[test]
-#[should_panic(expected = "Missing default for skipped field 'y' for schema")]
-fn avro_rs_351_no_default_for_serde_skip_serializing_if_should_panic() {
-    #[derive(AvroSchema, Clone, Debug, Deserialize, PartialEq, Serialize)]
-    struct T {
-        x: Option<i8>,
-        #[serde(skip_serializing_if = "Option::is_none")]
-        y: Option<String>,
-        z: Option<i8>,
-    }
-
-    ser_deser::<T>(
-        &T::get_schema(),
-        T {
-            x: None,
-            y: None,
-            z: Some(1),
-        },
-    )
-    .unwrap()
-}
-
-#[test]
-#[should_panic(expected = "Missing default for skipped field 'x' for schema")]
-fn avro_rs_351_no_default_for_serde_skip_should_panic() {
-    #[derive(AvroSchema, Clone, Debug, Deserialize, PartialEq, Serialize)]
-    struct T {
-        #[serde(skip)]
-        x: Option<i8>,
-        y: Option<String>,
-        z: Option<i8>,
-    }
-
-    ser_deser::<T>(
-        &T::get_schema(),
-        T {
-            x: None,
-            y: None,
-            z: Some(1),
-        },
-    )
-    .unwrap()
-}
-
-#[test]
-#[should_panic(expected = "Missing default for skipped field 'z' for schema")]
-fn avro_rs_351_no_default_for_serde_skip_serializing_should_panic() {
-    #[derive(AvroSchema, Clone, Debug, Deserialize, PartialEq, Serialize)]
-    struct T {
-        x: Option<i8>,
-        y: Option<String>,
-        #[serde(skip_serializing)]
-        z: Option<i8>,
-    }
-
-    ser_deser::<T>(
-        &T::get_schema(),
-        T {
-            x: Some(0),
-            y: None,
-            z: None,
-        },
-    )
-    .unwrap()
-}
diff --git a/avro_derive/Cargo.toml b/avro_derive/Cargo.toml
index 9b13bd3..85c26a2 100644
--- a/avro_derive/Cargo.toml
+++ b/avro_derive/Cargo.toml
@@ -40,9 +40,11 @@ syn = { default-features = false, version = "2.0.111", 
features = ["full", "fold
 
 [dev-dependencies]
 apache-avro = { default-features = false, path = "../avro", features = 
["derive"] }
+pretty_assertions = { workspace = true }
 proptest = { default-features = false, version = "1.9.0", features = ["std"] }
+rustversion = "1.0.22"
 serde = { workspace = true }
-pretty_assertions = { workspace = true }
+trybuild = "1.0.114"
 
 [package.metadata.docs.rs]
 rustdoc-args = ["--cfg", "docsrs"]
diff --git a/avro_derive/src/attributes/avro.rs 
b/avro_derive/src/attributes/avro.rs
new file mode 100644
index 0000000..b478ec5
--- /dev/null
+++ b/avro_derive/src/attributes/avro.rs
@@ -0,0 +1,40 @@
+use crate::case::RenameRule;
+
+#[derive(darling::FromAttributes)]
+#[darling(attributes(avro))]
+pub struct FieldAttributes {
+    #[darling(default)]
+    pub doc: Option<String>,
+    #[darling(default)]
+    pub default: Option<String>,
+    #[darling(multiple)]
+    pub alias: Vec<String>,
+    #[darling(default)]
+    pub rename: Option<String>,
+    #[darling(default)]
+    pub skip: bool,
+    #[darling(default)]
+    pub flatten: bool,
+}
+
+#[derive(darling::FromAttributes)]
+#[darling(attributes(avro))]
+pub struct VariantAttributes {
+    #[darling(default)]
+    pub rename: Option<String>,
+}
+
+#[derive(darling::FromAttributes)]
+#[darling(attributes(avro))]
+pub struct ContainerAttributes {
+    #[darling(default)]
+    pub name: Option<String>,
+    #[darling(default)]
+    pub namespace: Option<String>,
+    #[darling(default)]
+    pub doc: Option<String>,
+    #[darling(multiple)]
+    pub alias: Vec<String>,
+    #[darling(default)]
+    pub rename_all: RenameRule,
+}
diff --git a/avro_derive/src/attributes/mod.rs 
b/avro_derive/src/attributes/mod.rs
new file mode 100644
index 0000000..11b1e43
--- /dev/null
+++ b/avro_derive/src/attributes/mod.rs
@@ -0,0 +1,202 @@
+use darling::FromAttributes;
+use proc_macro2::Span;
+use syn::Attribute;
+
+use crate::{case::RenameRule, darling_to_syn};
+
+pub mod avro;
+pub mod serde;
+
+#[derive(Default)]
+pub struct NamedTypeOptions {
+    pub name: Option<String>,
+    pub namespace: Option<String>,
+    pub doc: Option<String>,
+    pub alias: Vec<String>,
+    pub rename_all: RenameRule,
+}
+
+impl NamedTypeOptions {
+    pub fn new(attributes: &[Attribute], span: Span) -> Result<Self, 
Vec<syn::Error>> {
+        let avro =
+            
avro::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+        let serde =
+            
serde::ContainerAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+
+        // Collect errors so user gets all feedback at once
+        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
+        {
+            errors.push(syn::Error::new(
+                span,
+                "AvroSchema derive does not support changing the tagging Serde 
generates (`tag`, `content`, `untagged`, `variant_identifier`, 
`field_identifier`)",
+            ));
+        }
+        if serde.remote.is_some() {
+            errors.push(syn::Error::new(
+                span,
+                "AvroSchema derive does not support the Serde `remote` 
attribute",
+            ));
+        }
+        if serde.transparent {
+            errors.push(syn::Error::new(
+                span,
+                "AvroSchema derive does not support Serde transparent",
+            ))
+        }
+        if serde.rename_all.deserialize != serde.rename_all.serialize {
+            errors.push(syn::Error::new(
+                span,
+                "AvroSchema derive does not support different rename rules for 
serializing and deserializing (`rename_all(serialize = \"..\", deserialize = 
\"..\")`)"
+            ))
+        }
+
+        // Check for conflicts between Serde and Avro
+        if avro.rename_all != RenameRule::None && serde.rename_all.serialize 
!= avro.rename_all {
+            errors.push(syn::Error::new(
+                span,
+                "#[avro(rename_all = \"..\")] must match #[serde(rename_all = 
\"..\")], it's also deprecated. Please use only `#[serde(rename_all = 
\"..\")]`",
+            ))
+        }
+
+        if !errors.is_empty() {
+            return Err(errors);
+        }
+
+        Ok(Self {
+            name: avro.name,
+            namespace: avro.namespace,
+            doc: avro.doc,
+            alias: avro.alias,
+            rename_all: serde.rename_all.serialize,
+        })
+    }
+}
+
+pub struct FieldOptions {
+    pub doc: Option<String>,
+    pub default: Option<String>,
+    pub alias: Vec<String>,
+    pub rename: Option<String>,
+    pub skip: bool,
+    pub flatten: bool,
+}
+
+impl FieldOptions {
+    pub fn new(attributes: &[Attribute], span: Span) -> Result<Self, 
Vec<syn::Error>> {
+        let avro = 
avro::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+        let serde = 
serde::FieldAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+
+        // Collect errors so user gets all feedback at once
+        let mut errors = Vec::new();
+
+        // Check for any Serde attributes that are hard errors
+        if serde.getter.is_some() {
+            errors.push(syn::Error::new(
+                span,
+                "AvroSchema derive does not support the Serde `getter` 
attribute",
+            ));
+        }
+
+        // Check for conflicts between Serde and Avro
+        if avro.skip && !serde.skip {
+            errors.push(syn::Error::new(
+                span,
+                "`#[avro(skip)]` requires `#[serde(skip)]`, it's also 
deprecated. Please use only `#[serde(skip)]`"
+            ));
+        }
+        if avro.flatten && !serde.flatten {
+            errors.push(syn::Error::new(
+                span,
+                "`#[avro(flatten)]` requires `#[serde(flatten)]`, it's also 
deprecated. Please use only `#[serde(flatten)]`"
+            ));
+        }
+        // TODO: rename and alias checking can be relaxed with a more complex 
check, would require the field name
+        if avro.rename.is_some() && serde.rename != avro.rename {
+            errors.push(syn::Error::new(
+                span,
+                "`#[avro(rename = \"..\")]` must match `#[serde(rename = 
\"..\")]`, it's also deprecated. Please use only `#[serde(rename  = \"..\")]`"
+            ));
+        }
+        if serde.alias != avro.alias {
+            errors.push(syn::Error::new(
+                span,
+                "`#[avro(alias = \"..\")]` must match `#[serde(alias = 
\"..\")]`, it's also deprecated. Please use only `#[serde(alias  = \"..\")]`"
+            ));
+        }
+        if serde.skip_serializing && serde.skip_deserializing {
+            errors.push(syn::Error::new(
+                span,
+                "Use `#[serde(skip)]` instead of `#[serde(skip_serializing, 
skip_deserializing)]`",
+            ));
+
+            // Don't want to suggest default for skip_serializing as it's not 
needed for skip
+            return Err(errors);
+        }
+        if ((serde.skip_serializing) || serde.skip_serializing_if.is_some())
+            && avro.default.is_none()
+        {
+            errors.push(syn::Error::new(
+                span,
+                "`#[serde(skip_serializing)]` and 
`#[serde(skip_serializing_if)]` require `#[avro(default = \"..\")]"
+            ));
+        }
+
+        if !errors.is_empty() {
+            return Err(errors);
+        }
+
+        Ok(Self {
+            doc: avro.doc,
+            default: avro.default,
+            alias: serde.alias,
+            rename: serde.rename,
+            skip: serde.skip,
+            flatten: serde.flatten,
+        })
+    }
+}
+
+pub struct VariantOptions {
+    pub rename: Option<String>,
+}
+
+impl VariantOptions {
+    pub fn new(attributes: &[Attribute], span: Span) -> Result<Self, 
Vec<syn::Error>> {
+        let avro = 
avro::VariantAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+        let serde =
+            
serde::VariantAttributes::from_attributes(attributes).map_err(darling_to_syn)?;
+
+        // Collect errors so user gets all feedback at once
+        let mut errors = Vec::new();
+
+        // Check for any Serde attributes that are hard errors
+        if serde.other || serde.untagged.is_some() {
+            errors.push(syn::Error::new(
+                span,
+                "AvroSchema derive does not support changing the tagging Serde 
generates (`other`, `untagged`)",
+            ));
+        }
+
+        if avro.rename.is_some() && serde.rename != avro.rename {
+            errors.push(syn::Error::new(
+                span,
+                "`#[avro(rename = \"..\")]` must match `#[serde(rename = 
\"..\")]`, it's also deprecated. Please use only `#[serde(rename  = \"..\")]`"
+            ));
+        }
+
+        if !errors.is_empty() {
+            return Err(errors);
+        }
+
+        Ok(Self {
+            rename: serde.rename,
+        })
+    }
+}
diff --git a/avro_derive/src/attributes/serde.rs 
b/avro_derive/src/attributes/serde.rs
new file mode 100644
index 0000000..80e69ba
--- /dev/null
+++ b/avro_derive/src/attributes/serde.rs
@@ -0,0 +1,298 @@
+//! Attribute parsing for Serde attributes
+//!
+//! # Serde attributes
+//! This module only parses the minimal amount to be able to read Serde 
attributes. This means
+//! most attributes are decoded as an `Option<String>` as the actual content 
is not relevant.
+//! Attributes which are not needed by the derive are prefixed with a `_` as 
we can't ignore them
+//! (this would be a compile error).
+//!
+//! If Serde adds new attributes they need to be added here too.
+
+use darling::{FromAttributes, FromMeta};
+use syn::Expr;
+
+use crate::case::RenameRule;
+
+// from_expr is used for `rename_all = ".."`
+// FromMeta is used for `rename_all(serialize = "..", ..)`
+#[derive(Debug, Default, FromMeta, PartialEq, Eq)]
+#[darling(from_expr = RenameAll::from_expr)]
+pub struct RenameAll {
+    #[darling(default)]
+    pub serialize: RenameRule,
+    #[darling(default)]
+    pub deserialize: RenameRule,
+}
+
+impl From<RenameRule> for RenameAll {
+    fn from(value: RenameRule) -> Self {
+        Self {
+            serialize: value,
+            deserialize: value,
+        }
+    }
+}
+
+impl RenameAll {
+    fn from_expr(expr: &Expr) -> darling::Result<Self> {
+        let Expr::Lit(lit) = expr else {
+            return Err(darling::Error::custom("Expected a string or a 
tuple!"));
+        };
+        let rule = RenameRule::from_value(&lit.lit)?;
+        Ok(RenameAll::from(rule))
+    }
+}
+
+#[derive(Debug, FromMeta, PartialEq)]
+#[darling(from_expr = |expr| Ok(SerdeDefault::Expr(expr.clone())))]
+pub enum SerdeDefault {
+    #[darling(word, skip)]
+    UseTrait,
+    Expr(Expr),
+}
+
+/// All Serde attributes that a container can have.
+#[derive(Debug, FromAttributes)]
+#[darling(attributes(serde))]
+pub struct ContainerAttributes {
+    #[darling(rename = "rename")]
+    /// Rename this container.
+    pub _rename: Option<String>,
+    /// Rename all the fields (if this is a struct) or variants (if this is an 
enum) according to the given case convention.
+    #[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,
+    /// Error when encountering unknown fields when deserialising.
+    #[darling(default, rename = "deny_unknown_fields")]
+    pub _deny_unknown_fields: bool,
+    /// Add/expect a tag during serialisation.
+    ///
+    /// When used on a struct, this adds an extra field which is not in the 
schema definition.
+    /// When used on an enum, serde transforms it into a struct which does not 
match the schema definition.
+    pub tag: Option<String>,
+    /// Put the content in a field with this name.
+    pub content: Option<String>,
+    /// This makes the enum transparent, (de)serializing based on the variant 
directly.
+    ///
+    /// This does not match the schema definition.
+    #[darling(default)]
+    pub untagged: bool,
+    /// Add a bound to the Serialize/Deserialize trait.
+    #[darling(default, rename = "bound")]
+    pub _bound: Option<String>,
+    /// When deserializing, any missing fields should be filled in from the 
struct's implementation of `Default`.
+    #[darling(rename = "default")]
+    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>,
+    /// 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>,
+    /// Deserialize using the given type and then convert to this type with 
`TryFrom`.
+    #[darling(default, rename = "try_from")]
+    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>,
+    /// Use the Serde API at this path.
+    #[darling(default, rename = "crate")]
+    pub _crate: Option<String>,
+    /// Custom error text.
+    #[darling(default, rename = "expecting")]
+    pub _expecting: Option<String>,
+    /// This does something with tags, which are incompatible.
+    #[darling(default)]
+    pub variant_identifier: bool,
+    /// This does something with tags,  `` which are incompatible.
+    #[darling(default)]
+    pub field_identifier: bool,
+}
+
+/// All Serde attributes that a enum variant can have.
+#[derive(Debug, FromAttributes)]
+#[darling(attributes(serde))]
+pub struct VariantAttributes {
+    /// Rename the variant.
+    #[darling(default)]
+    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,
+    /// Do not serialize or deserialize this variant.
+    #[darling(default, rename = "skip")]
+    pub _skip: bool,
+    /// Do not serialize this variant.
+    #[darling(default, rename = "skip_serializing")]
+    pub _skip_serializing: bool,
+    /// Do not deserialize this variant.
+    #[darling(default, rename = "skip_deserializing")]
+    pub _skip_deserializing: bool,
+    /// Use this function for serializing.
+    #[darling(rename = "serialize_with")]
+    pub _serialize_with: Option<String>,
+    /// Use this function for deserializing.
+    #[darling(rename = "deserialize_with")]
+    pub _deserialize_with: Option<String>,
+    /// Use this module for (de)serializing.
+    #[darling(rename = "with")]
+    pub _with: Option<String>,
+    /// Put trait bounds on the implementations.
+    #[darling(rename = "bound")]
+    pub _bound: Option<String>,
+    /// Put bounds on the lifetimes.
+    #[darling(rename = "borrow")]
+    pub _borrow: Option<SerdeBorrow>,
+    /// This does something with tags, which are incompatible.
+    #[darling(default)]
+    pub other: bool,
+    /// (De)serialize this variant as if it was not part of the enum.
+    pub untagged: Option<String>,
+}
+
+#[derive(Debug, FromMeta, PartialEq)]
+#[darling(from_expr = |expr| Ok(SerdeBorrow::Expr(expr.clone())))]
+pub enum SerdeBorrow {
+    #[darling(word, skip)]
+    Default,
+    Expr(Expr),
+}
+
+/// All Serde attributes that a field can have.
+#[derive(Debug, FromAttributes)]
+#[darling(attributes(serde))]
+pub struct FieldAttributes {
+    /// Rename the field.
+    #[darling(default)]
+    pub rename: Option<String>,
+    /// Aliases for this field, only used during deserialisation.
+    #[darling(multiple)]
+    pub alias: Vec<String>,
+    /// When deserializing, if this field is missing use `Default` or the 
given function.
+    #[darling(rename = "default")]
+    pub _default: Option<SerdeDefault>,
+    #[darling(default)]
+    pub flatten: bool,
+    /// Do not serialize or deserialize this field.
+    #[darling(default)]
+    pub skip: bool,
+    /// Do not serialize this field.
+    #[darling(default)]
+    pub skip_serializing: bool,
+    /// Do not deserialize this field.
+    #[darling(default)]
+    pub skip_deserializing: bool,
+    /// Do not serialize this field if the function returns `false`.
+    pub skip_serializing_if: Option<String>,
+    /// Use this function for serializing.
+    #[darling(rename = "serialize_with")]
+    pub _serialize_with: Option<String>,
+    /// Use this function for deserializing.
+    #[darling(rename = "deserialize_with")]
+    pub _deserialize_with: Option<String>,
+    /// Use this module for (de)serializing.
+    #[darling(rename = "with")]
+    pub _with: Option<String>,
+    /// Put bounds on the lifetimes.
+    #[darling(rename = "borrow")]
+    pub _borrow: Option<SerdeBorrow>,
+    /// Used for remote types.
+    pub getter: Option<String>,
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{
+        RenameRule,
+        attributes::serde::{ContainerAttributes, RenameAll, SerdeDefault},
+    };
+    use darling::FromAttributes;
+    use syn::DeriveInput;
+
+    #[test]
+    fn test_rename_all() {
+        let derive: DeriveInput = syn::parse_quote! {
+            #[serde(rename_all = "lowercase")]
+            struct Config {
+                field: String,
+            }
+        };
+        let input = 
ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+        assert_eq!(input.rename_all, RenameAll::from(RenameRule::LowerCase));
+
+        let derive: DeriveInput = syn::parse_quote! {
+            #[serde(rename_all(serialize = "lowercase"))]
+            struct Config {
+                field: String,
+            }
+        };
+        let input = 
ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+        assert_eq!(
+            input.rename_all,
+            RenameAll {
+                serialize: RenameRule::LowerCase,
+                deserialize: RenameRule::None
+            }
+        );
+
+        let derive: DeriveInput = syn::parse_quote! {
+            #[serde(rename_all(deserialize = "lowercase"))]
+            struct Config {
+                field: String,
+            }
+        };
+        let input = 
ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+        assert_eq!(
+            input.rename_all,
+            RenameAll {
+                serialize: RenameRule::None,
+                deserialize: RenameRule::LowerCase
+            }
+        );
+
+        let derive: DeriveInput = syn::parse_quote! {
+            struct Config {
+                field: String,
+            }
+        };
+        let input = 
ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+        assert_eq!(input.rename_all, RenameAll::from(RenameRule::None));
+    }
+
+    #[test]
+    fn test_default() {
+        let derive: DeriveInput = syn::parse_quote! {
+            #[serde(default)]
+            struct Config {
+                field: String,
+            }
+        };
+        let input = 
ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+        assert_eq!(input._default, Some(SerdeDefault::UseTrait));
+
+        let derive: DeriveInput = syn::parse_quote! {
+            #[serde(default = "some::path")]
+            struct Config {
+                field: String,
+            }
+        };
+        let input = 
ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+        assert!(matches!(input._default, Some(SerdeDefault::Expr(_))));
+
+        let derive: DeriveInput = syn::parse_quote! {
+            struct Config {
+                field: String,
+            }
+        };
+        let input = 
ContainerAttributes::from_attributes(&derive.attrs).unwrap();
+        assert_eq!(input._default, None);
+    }
+}
diff --git a/avro_derive/src/case.rs b/avro_derive/src/case.rs
index 8dbaafd..d0f3c8c 100644
--- a/avro_derive/src/case.rs
+++ b/avro_derive/src/case.rs
@@ -18,13 +18,17 @@
 //! Code to convert the Rust-styled field/variant (e.g. `my_field`, `MyType`) 
to the
 //! case of the source (e.g. `my-field`, `MY_FIELD`).
 //! Code copied from serde 
<https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/case.rs>
+use darling::FromMeta;
+use syn::Lit;
+
 use self::RenameRule::*;
 use std::fmt::{self, Debug, Display};
 
 /// The different possible ways to change case of fields in a struct, or 
variants in an enum.
-#[derive(Copy, Clone, PartialEq)]
+#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
 pub enum RenameRule {
     /// Don't apply a default rename rule.
+    #[default]
     None,
     /// Rename direct children to "lowercase" style.
     LowerCase,
@@ -47,6 +51,15 @@ pub enum RenameRule {
     ScreamingKebabCase,
 }
 
+impl FromMeta for RenameRule {
+    fn from_value(value: &Lit) -> darling::Result<Self> {
+        let Lit::Str(litstr) = value else {
+            panic!("Only expected a string");
+        };
+        Self::from_str(&litstr.value()).map_err(darling::Error::custom)
+    }
+}
+
 static RENAME_RULES: &[(&str, RenameRule)] = &[
     ("lowercase", LowerCase),
     ("UPPERCASE", UpperCase),
@@ -59,15 +72,20 @@ static RENAME_RULES: &[(&str, RenameRule)] = &[
 ];
 
 impl RenameRule {
-    pub fn from_str(rename_all_str: &str) -> Result<Self, ParseError<'_>> {
-        for (name, rule) in RENAME_RULES {
-            if rename_all_str == *name {
-                return Ok(*rule);
-            }
+    pub fn from_str(rename_all_str: &str) -> Result<Self, ParseError> {
+        match rename_all_str {
+            "lowercase" => Ok(Self::LowerCase),
+            "UPPERCASE" => Ok(Self::UpperCase),
+            "PascalCase" => Ok(Self::PascalCase),
+            "camelCase" => Ok(Self::CamelCase),
+            "snake_case" => Ok(Self::SnakeCase),
+            "SCREAMING_SNAKE_CASE" => Ok(Self::ScreamingSnakeCase),
+            "kebab-case" => Ok(Self::KebabCase),
+            "SCREAMING-KEBAB-CASE" => Ok(Self::ScreamingKebabCase),
+            _ => Err(ParseError {
+                unknown: rename_all_str.to_string(),
+            }),
         }
-        Err(ParseError {
-            unknown: rename_all_str,
-        })
     }
 
     /// Apply a renaming rule to an enum variant, returning the version 
expected in the source.
@@ -126,14 +144,14 @@ impl RenameRule {
     }
 }
 
-pub struct ParseError<'a> {
-    unknown: &'a str,
+pub struct ParseError {
+    unknown: String,
 }
 
-impl Display for ParseError<'_> {
+impl Display for ParseError {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str("unknown rename rule `rename_all = ")?;
-        Debug::fmt(self.unknown, f)?;
+        Debug::fmt(&self.unknown, f)?;
         f.write_str("`, expected one of ")?;
         for (i, (name, _rule)) in RENAME_RULES.iter().enumerate() {
             if i > 0 {
diff --git a/avro_derive/src/lib.rs b/avro_derive/src/lib.rs
index 2b225fb..a331bdc 100644
--- a/avro_derive/src/lib.rs
+++ b/avro_derive/src/lib.rs
@@ -15,57 +15,20 @@
 // specific language governing permissions and limitations
 // under the License.
 
+mod attributes;
 mod case;
-use case::RenameRule;
-use darling::FromAttributes;
+
 use proc_macro2::{Span, TokenStream};
 use quote::quote;
-
 use syn::{
     AttrStyle, Attribute, DeriveInput, Ident, Meta, Type, TypePath, 
parse_macro_input,
     spanned::Spanned,
 };
 
-#[derive(darling::FromAttributes)]
-#[darling(attributes(avro))]
-struct FieldOptions {
-    #[darling(default)]
-    doc: Option<String>,
-    #[darling(default)]
-    default: Option<String>,
-    #[darling(multiple)]
-    alias: Vec<String>,
-    #[darling(default)]
-    rename: Option<String>,
-    #[darling(default)]
-    skip: Option<bool>,
-    #[darling(default)]
-    flatten: Option<bool>,
-}
+use crate::attributes::{FieldOptions, NamedTypeOptions, VariantOptions};
+use crate::case::RenameRule;
 
-#[derive(darling::FromAttributes)]
-#[darling(attributes(avro))]
-struct VariantOptions {
-    #[darling(default)]
-    rename: Option<String>,
-}
-
-#[derive(darling::FromAttributes)]
-#[darling(attributes(avro))]
-struct NamedTypeOptions {
-    #[darling(default)]
-    name: Option<String>,
-    #[darling(default)]
-    namespace: Option<String>,
-    #[darling(default)]
-    doc: Option<String>,
-    #[darling(multiple)]
-    alias: Vec<String>,
-    #[darling(default)]
-    rename_all: Option<String>,
-}
-
-#[proc_macro_derive(AvroSchema, attributes(avro))]
+#[proc_macro_derive(AvroSchema, attributes(avro, serde))]
 // Templated from Serde
 pub fn proc_macro_derive_avro_schema(input: proc_macro::TokenStream) -> 
proc_macro::TokenStream {
     let mut input = parse_macro_input!(input as DeriveInput);
@@ -75,10 +38,9 @@ pub fn proc_macro_derive_avro_schema(input: 
proc_macro::TokenStream) -> proc_mac
 }
 
 fn derive_avro_schema(input: &mut DeriveInput) -> Result<TokenStream, 
Vec<syn::Error>> {
-    let named_type_options =
-        
NamedTypeOptions::from_attributes(&input.attrs[..]).map_err(darling_to_syn)?;
+    let named_type_options = NamedTypeOptions::new(&input.attrs, 
input.span())?;
 
-    let rename_all = parse_case(named_type_options.rename_all.as_deref(), 
input.span())?;
+    let rename_all = named_type_options.rename_all;
     let name = named_type_options.name.unwrap_or(input.ident.to_string());
 
     let full_schema_name = vec![named_type_options.namespace, Some(name)]
@@ -153,8 +115,7 @@ fn get_data_struct_schema_def(
                 if let Some(raw_name) = name.strip_prefix("r#") {
                     name = raw_name.to_string();
                 }
-                let field_attrs =
-                    
FieldOptions::from_attributes(&field.attrs).map_err(darling_to_syn)?;
+                let field_attrs = FieldOptions::new(&field.attrs, 
field.span())?;
                 let doc =
                     preserve_optional(field_attrs.doc.or_else(|| 
extract_outer_doc(&field.attrs)));
                 match (field_attrs.rename, rename_all) {
@@ -166,9 +127,9 @@ fn get_data_struct_schema_def(
                     }
                     _ => {}
                 }
-                if Some(true) == field_attrs.skip {
+                if field_attrs.skip {
                     continue;
-                } else if Some(true) == field_attrs.flatten {
+                } 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 flatten_ty = &field.ty;
@@ -271,8 +232,7 @@ fn get_data_enum_schema_def(
         let default = preserve_optional(default_value);
         let mut symbols = Vec::new();
         for variant in &e.variants {
-            let field_attrs =
-                
VariantOptions::from_attributes(&variant.attrs[..]).map_err(darling_to_syn)?;
+            let field_attrs = VariantOptions::new(&variant.attrs, 
variant.span())?;
             let name = match (field_attrs.rename, rename_all) {
                 (Some(rename), _) => rename,
                 (None, rename_all) if !matches!(rename_all, RenameRule::None) 
=> {
@@ -430,16 +390,6 @@ fn darling_to_syn(e: darling::Error) -> Vec<syn::Error> {
     vec![syn::Error::new(token_errors.span(), msg)]
 }
 
-fn parse_case(case: Option<&str>, span: Span) -> Result<RenameRule, 
Vec<syn::Error>> {
-    match case {
-        None => Ok(RenameRule::None),
-        Some(case) => {
-            Ok(RenameRule::from_str(case)
-                .map_err(|e| vec![syn::Error::new(span, e.to_string())])?)
-        }
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -701,6 +651,7 @@ mod tests {
     fn test_avro_3709_record_field_attributes() {
         let test_struct = quote! {
             struct A {
+                #[serde(alias = "a1", alias = "a2", rename = "a3")]
                 #[avro(alias = "a1", alias = "a2", doc = "a doc", default = 
"123", rename = "a3")]
                 a: i32
             }
@@ -720,6 +671,7 @@ mod tests {
 
         let test_enum = quote! {
             enum A {
+                #[serde(rename = "A3")]
                 #[avro(rename = "A3")]
                 Item1,
             }
@@ -741,6 +693,7 @@ mod tests {
     #[test]
     fn test_avro_rs_207_rename_all_attribute() {
         let test_struct = quote! {
+            #[serde(rename_all="SCREAMING_SNAKE_CASE")]
             #[avro(rename_all="SCREAMING_SNAKE_CASE")]
             struct A {
                 item: i32,
@@ -761,6 +714,7 @@ mod tests {
         };
 
         let test_enum = quote! {
+            #[serde(rename_all="SCREAMING_SNAKE_CASE")]
             #[avro(rename_all="SCREAMING_SNAKE_CASE")]
             enum B {
                 Item,
@@ -784,9 +738,11 @@ mod tests {
     #[test]
     fn test_avro_rs_207_rename_attr_has_priority_over_rename_all_attribute() {
         let test_struct = quote! {
+            #[serde(rename_all="SCREAMING_SNAKE_CASE")]
             #[avro(rename_all="SCREAMING_SNAKE_CASE")]
             struct A {
                 item: i32,
+                #[serde(rename="DoubleItem")]
                 #[avro(rename="DoubleItem")]
                 double_item: i32
             }
diff --git a/avro_derive/tests/serde.rs b/avro_derive/tests/serde.rs
new file mode 100644
index 0000000..d4bee13
--- /dev/null
+++ b/avro_derive/tests/serde.rs
@@ -0,0 +1,659 @@
+use apache_avro::{AvroSchema, Error, Reader, Schema, Writer, from_value};
+use serde::{Deserialize, Serialize, de::DeserializeOwned};
+
+/// Takes in a type that implements the right combination of traits and runs 
it through a Serde Cycle and asserts the result is the same
+fn serde_assert<T>(obj: T)
+where
+    T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone + 
PartialEq,
+{
+    assert_eq!(obj, serde(obj.clone()).unwrap());
+}
+
+/// Takes in a type that implements the right combination of traits and runs 
it through a Serde Cycle and asserts that the error matches the expected string
+fn serde_assert_err<T>(obj: T, expected: &str)
+where
+    T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone + 
PartialEq,
+{
+    let error = serde(obj).unwrap_err().to_string();
+    assert!(
+        error.contains(expected),
+        "Error `{error}` does not contain `{expected}`"
+    );
+}
+
+fn serde<T>(obj: T) -> Result<T, Error>
+where
+    T: Serialize + DeserializeOwned + AvroSchema,
+{
+    de(ser(obj)?)
+}
+
+fn ser<T>(obj: T) -> Result<Vec<u8>, Error>
+where
+    T: Serialize + AvroSchema,
+{
+    let schema = T::get_schema();
+    let mut writer = Writer::new(&schema, Vec::new())?;
+    writer.append_ser(obj)?;
+    writer.into_inner()
+}
+
+fn de<T>(encoded: Vec<u8>) -> Result<T, Error>
+where
+    T: DeserializeOwned + AvroSchema,
+{
+    assert!(!encoded.is_empty());
+    let schema = T::get_schema();
+    let mut reader = Reader::with_schema(&schema, &encoded[..])?;
+    if let Some(res) = reader.next() {
+        return res.and_then(|v| from_value::<T>(&v));
+    }
+    unreachable!("Nothing was encoded!")
+}
+
+mod container_attributes {
+    use super::*;
+
+    #[test]
+    fn avro_rs_373_rename() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        #[serde(rename = "Bar")]
+        struct Foo {
+            a: String,
+            b: i32,
+        }
+
+        let schema = r#"
+        {
+            "type":"record",
+            "name":"Foo",
+            "fields": [
+                {
+                    "name":"a",
+                    "type":"string"
+                },
+                {
+                    "name":"b",
+                    "type":"int"
+                }
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo {
+            a: "spam".to_string(),
+            b: 321,
+        });
+    }
+
+    #[test]
+    fn avro_rs_373_nested_rename() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        #[serde(rename = "Bar")]
+        struct Foo {
+            a: String,
+            b: i32,
+        }
+
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        struct Outer {
+            bar: Foo,
+        }
+
+        let schema = r#"
+        {
+            "type":"record",
+            "name":"Outer",
+            "fields": [
+                {
+                    "name":"bar",
+                    "type": {
+                        "type":"record",
+                        "name":"Foo",
+                        "fields": [
+                            {
+                                "name":"a",
+                                "type":"string"
+                            },
+                            {
+                                "name":"b",
+                                "type":"int"
+                            }
+                        ]
+                    }
+                }
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Outer::get_schema());
+
+        serde_assert(Outer {
+            bar: Foo {
+                a: "spam".to_string(),
+                b: 321,
+            },
+        });
+    }
+
+    #[test]
+    fn avro_rs_373_rename_all() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        #[serde(rename_all = "UPPERCASE")]
+        #[avro(rename_all = "UPPERCASE")]
+        struct Foo {
+            a: String,
+            b: i32,
+        }
+
+        let schema = r#"
+        {
+            "type":"record",
+            "name":"Foo",
+            "fields": [
+                {
+                    "name":"A",
+                    "type":"string"
+                },
+                {
+                    "name":"B",
+                    "type":"int"
+                }
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo {
+            a: "spam".to_string(),
+            b: 321,
+        });
+    }
+
+    #[test]
+    fn avro_rs_373_rename_all_no_avro() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        #[serde(rename_all = "UPPERCASE")]
+        struct Foo {
+            a: String,
+            b: i32,
+        }
+
+        let schema = r#"
+        {
+            "type":"record",
+            "name":"Foo",
+            "fields": [
+                {
+                    "name":"A",
+                    "type":"string"
+                },
+                {
+                    "name":"B",
+                    "type":"int"
+                }
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo {
+            a: "spam".to_string(),
+            b: 321,
+        });
+    }
+
+    #[test]
+    fn avro_rs_373_from_into() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        #[serde(from = "FooFromInto", into = "FooFromInto")]
+        struct Foo {
+            a: String,
+            b: i32,
+        }
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        struct FooFromInto {
+            a: String,
+            b: i32,
+        }
+
+        impl From<FooFromInto> for Foo {
+            fn from(value: FooFromInto) -> Self {
+                Self {
+                    a: value.a,
+                    b: value.b,
+                }
+            }
+        }
+
+        impl From<Foo> for FooFromInto {
+            fn from(value: Foo) -> Self {
+                Self {
+                    a: value.a,
+                    b: value.b,
+                }
+            }
+        }
+
+        let schema = r#"
+        {
+            "type":"record",
+            "name":"Foo",
+            "fields": [
+                {
+                    "name":"a",
+                    "type":"string"
+                },
+                {
+                    "name":"b",
+                    "type":"int"
+                }
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo {
+            a: "spam".to_string(),
+            b: 321,
+        });
+    }
+
+    #[test]
+    fn avro_rs_373_from_into_different() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        #[serde(from = "FooFromInto", into = "FooFromInto")]
+        struct Foo {
+            a: String,
+            b: i32,
+        }
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        struct FooFromInto {
+            a: String,
+            b: i32,
+            c: bool,
+        }
+
+        impl From<FooFromInto> for Foo {
+            fn from(value: FooFromInto) -> Self {
+                Self {
+                    a: value.a,
+                    b: value.b,
+                }
+            }
+        }
+
+        impl From<Foo> for FooFromInto {
+            fn from(value: Foo) -> Self {
+                Self {
+                    a: value.a,
+                    b: value.b,
+                    c: true,
+                }
+            }
+        }
+
+        let schema = r#"
+        {
+            "type":"record",
+            "name":"Foo",
+            "fields": [
+                {
+                    "name":"a",
+                    "type":"string"
+                },
+                {
+                    "name":"b",
+                    "type":"int"
+                }
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert_err(
+            Foo {
+                a: "spam".to_string(),
+                b: 321,
+            },
+            "Invalid field name c",
+        );
+    }
+}
+
+mod variant_attributes {
+    use super::*;
+
+    #[test]
+    fn avro_rs_373_rename_no_avro() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        enum Foo {
+            #[serde(rename = "Three")]
+            One,
+            Two,
+        }
+
+        let schema = r#"
+        {
+            "type":"enum",
+            "name":"Foo",
+            "symbols": [
+                "Three", "Two"
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo::One);
+    }
+
+    #[test]
+    fn avro_rs_373_rename() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        enum Foo {
+            #[serde(rename = "Three")]
+            #[avro(rename = "Three")]
+            One,
+            Two,
+        }
+
+        let schema = r#"
+        {
+            "type":"enum",
+            "name":"Foo",
+            "symbols": [
+                "Three", "Two"
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo::One);
+    }
+
+    #[test]
+    fn avro_rs_373_alias() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        enum Foo {
+            #[serde(rename = "Three", alias = "One")]
+            One,
+            Two,
+        }
+
+        let schema = r#"
+        {
+            "type":"enum",
+            "name":"Foo",
+            "symbols": [
+                "Three", "Two"
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo::One);
+    }
+
+    #[test]
+    fn avro_rs_373_skip() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        enum Foo {
+            #[allow(dead_code)]
+            #[serde(skip)]
+            One,
+            Two,
+        }
+
+        let schema = r#"
+        {
+            "type":"enum",
+            "name":"Foo",
+            "symbols": [
+                "One", "Two"
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo::Two);
+    }
+}
+
+mod field_attributes {
+    use super::*;
+
+    #[test]
+    fn avro_rs_373_rename() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        struct Foo {
+            #[serde(rename = "c")]
+            #[avro(rename = "c")]
+            a: String,
+            b: i32,
+        }
+
+        let schema = r#"
+        {
+            "type":"record",
+            "name":"Foo",
+            "fields": [
+                {
+                    "name":"c",
+                    "type":"string"
+                },
+                {
+                    "name":"b",
+                    "type":"int"
+                }
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo {
+            a: "spam".to_string(),
+            b: 321,
+        });
+    }
+
+    #[test]
+    fn avro_rs_373_rename_no_avro() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        struct Foo {
+            #[serde(rename = "c")]
+            a: String,
+            b: i32,
+        }
+
+        let schema = r#"
+        {
+            "type":"record",
+            "name":"Foo",
+            "fields": [
+                {
+                    "name":"c",
+                    "type":"string"
+                },
+                {
+                    "name":"b",
+                    "type":"int"
+                }
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo {
+            a: "spam".to_string(),
+            b: 321,
+        });
+    }
+
+    #[test]
+    fn avro_rs_373_flatten() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        struct Nested {
+            a: bool,
+        }
+
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        struct Foo {
+            #[serde(flatten)]
+            #[avro(flatten)]
+            nested: Nested,
+            b: i32,
+        }
+
+        let schema = r#"
+        {
+            "type":"record",
+            "name":"Foo",
+            "fields": [
+                {
+                    "name":"a",
+                    "type":"boolean"
+                },
+                {
+                    "name":"b",
+                    "type":"int"
+                }
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo {
+            nested: Nested { a: true },
+            b: 321,
+        });
+    }
+
+    #[test]
+    fn avro_rs_373_flatten_no_avro() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        struct Nested {
+            a: bool,
+        }
+
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        struct Foo {
+            #[serde(flatten)]
+            nested: Nested,
+            b: i32,
+        }
+
+        let schema = r#"
+        {
+            "type":"record",
+            "name":"Foo",
+            "fields": [
+                {
+                    "name":"a",
+                    "type":"boolean"
+                },
+                {
+                    "name":"b",
+                    "type":"int"
+                }
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo {
+            nested: Nested { a: true },
+            b: 321,
+        });
+    }
+
+    #[test]
+    fn avro_rs_373_skip() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        struct Foo {
+            #[serde(skip)]
+            #[avro(skip)]
+            a: String,
+            b: i32,
+        }
+
+        let schema = r#"
+        {
+            "type":"record",
+            "name":"Foo",
+            "fields": [
+                {
+                    "name":"b",
+                    "type":"int"
+                }
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo {
+            a: "".to_string(),
+            b: 321,
+        });
+    }
+
+    #[test]
+    fn avro_rs_373_skip_no_avro() {
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        struct Foo {
+            #[serde(skip)]
+            a: String,
+            b: i32,
+        }
+
+        let schema = r#"
+        {
+            "type":"record",
+            "name":"Foo",
+            "fields": [
+                {
+                    "name":"b",
+                    "type":"int"
+                }
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        assert_eq!(schema, Foo::get_schema());
+
+        serde_assert(Foo {
+            a: "".to_string(),
+            b: 321,
+        });
+    }
+}
diff --git a/avro_derive/tests/ui.rs b/avro_derive/tests/ui.rs
new file mode 100644
index 0000000..25a6b21
--- /dev/null
+++ b/avro_derive/tests/ui.rs
@@ -0,0 +1,9 @@
+/// These tests only run on nightly as the output can change per compiler 
version.
+///
+/// See https://github.com/dtolnay/trybuild/issues/84
+#[rustversion::attr(not(nightly), ignore)]
+#[test]
+fn ui() {
+    let t = trybuild::TestCases::new();
+    t.compile_fail("tests/ui/*.rs");
+}
diff --git a/avro_derive/tests/ui/avro_rs_226_skip_serializing.rs 
b/avro_derive/tests/ui/avro_rs_226_skip_serializing.rs
new file mode 100644
index 0000000..a065cdf
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_226_skip_serializing.rs
@@ -0,0 +1,11 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+struct T {
+    x: Option<i8>,
+    y: Option<String>,
+    #[serde(skip_serializing)]
+    z: Option<i8>,
+}
+
+fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_226_skip_serializing.stderr 
b/avro_derive/tests/ui/avro_rs_226_skip_serializing.stderr
new file mode 100644
index 0000000..8f16aef
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_226_skip_serializing.stderr
@@ -0,0 +1,6 @@
+error: `#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` 
require `#[avro(default = "..")]
+ --> tests/ui/avro_rs_226_skip_serializing.rs:7:5
+  |
+7 | /     #[serde(skip_serializing)]
+8 | |     z: Option<i8>,
+  | |_________________^
diff --git a/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.rs 
b/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.rs
new file mode 100644
index 0000000..f9bba75
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.rs
@@ -0,0 +1,11 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+struct T {
+    x: Option<i8>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    y: Option<String>,
+    z: Option<i8>,
+}
+
+fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.stderr 
b/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.stderr
new file mode 100644
index 0000000..ae84038
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_226_skip_serializing_if.stderr
@@ -0,0 +1,6 @@
+error: `#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` 
require `#[avro(default = "..")]
+ --> tests/ui/avro_rs_226_skip_serializing_if.rs:6:5
+  |
+6 | /     #[serde(skip_serializing_if = "Option::is_none")]
+7 | |     y: Option<String>,
+  | |_____________________^
diff --git a/avro_derive/tests/ui/avro_rs_373_remote.rs 
b/avro_derive/tests/ui/avro_rs_373_remote.rs
new file mode 100644
index 0000000..ea4fa2f
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_remote.rs
@@ -0,0 +1,16 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+struct Foo {
+    a: String,
+    b: i32,
+}
+
+#[derive(AvroSchema)]
+#[serde(remote = "Foo")]
+struct FooRemote {
+    a: String,
+    b: i32,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_373_remote.stderr 
b/avro_derive/tests/ui/avro_rs_373_remote.stderr
new file mode 100644
index 0000000..011fa78
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_remote.stderr
@@ -0,0 +1,9 @@
+error: AvroSchema derive does not support the Serde `remote` attribute
+  --> tests/ui/avro_rs_373_remote.rs:10:1
+   |
+10 | / #[serde(remote = "Foo")]
+11 | | struct FooRemote {
+12 | |     a: String,
+13 | |     b: i32,
+14 | | }
+   | |_^
diff --git a/avro_derive/tests/ui/avro_rs_373_rename_all_fields.rs 
b/avro_derive/tests/ui/avro_rs_373_rename_all_fields.rs
new file mode 100644
index 0000000..540f2d3
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_rename_all_fields.rs
@@ -0,0 +1,12 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+#[serde(rename_all_fields = "UPPERCASE")]
+enum Foo {
+    Bar {
+        a: String,
+        b: i32,
+    }
+}
+
+pub fn main() {}
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
new file mode 100644
index 0000000..2d85557
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_rename_all_fields.stderr
@@ -0,0 +1,5 @@
+error: AvroSchema derive does not work for enums with non unit structs
+ --> tests/ui/avro_rs_373_rename_all_fields.rs:5:6
+  |
+5 | enum Foo {
+  |      ^^^
diff --git a/avro_derive/tests/ui/avro_rs_373_skip_de_serializing.rs 
b/avro_derive/tests/ui/avro_rs_373_skip_de_serializing.rs
new file mode 100644
index 0000000..e95003a
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_skip_de_serializing.rs
@@ -0,0 +1,11 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+struct T {
+    x: Option<i8>,
+    y: Option<String>,
+    #[serde(skip_serializing, skip_deserializing)]
+    z: Option<i8>,
+}
+
+fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_373_skip_de_serializing.stderr 
b/avro_derive/tests/ui/avro_rs_373_skip_de_serializing.stderr
new file mode 100644
index 0000000..c8fb898
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_skip_de_serializing.stderr
@@ -0,0 +1,6 @@
+error: Use `#[serde(skip)]` instead of `#[serde(skip_serializing, 
skip_deserializing)]`
+ --> tests/ui/avro_rs_373_skip_de_serializing.rs:7:5
+  |
+7 | /     #[serde(skip_serializing, skip_deserializing)]
+8 | |     z: Option<i8>,
+  | |_________________^
diff --git a/avro_derive/tests/ui/avro_rs_373_tag struct.rs 
b/avro_derive/tests/ui/avro_rs_373_tag struct.rs
new file mode 100644
index 0000000..1859d4f
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_tag struct.rs    
@@ -0,0 +1,10 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+#[serde(tag = "bar")]
+struct Foo {
+    a: String,
+    b: i32,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_373_tag struct.stderr 
b/avro_derive/tests/ui/avro_rs_373_tag struct.stderr
new file mode 100644
index 0000000..b6cbf56
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_tag struct.stderr        
@@ -0,0 +1,9 @@
+error: AvroSchema derive does not support changing the tagging Serde generates 
(`tag`, `content`, `untagged`, `variant_identifier`, `field_identifier`)
+ --> tests/ui/avro_rs_373_tag struct.rs:4:1
+  |
+4 | / #[serde(tag = "bar")]
+5 | | struct Foo {
+6 | |     a: String,
+7 | |     b: i32,
+8 | | }
+  | |_^
diff --git a/avro_derive/tests/ui/avro_rs_373_tag_content_enum.rs 
b/avro_derive/tests/ui/avro_rs_373_tag_content_enum.rs
new file mode 100644
index 0000000..df1cf3e
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_tag_content_enum.rs
@@ -0,0 +1,10 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+#[serde(tag = "bar", content = "spam")]
+enum Foo {
+    One,
+    Two,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_373_tag_content_enum.stderr 
b/avro_derive/tests/ui/avro_rs_373_tag_content_enum.stderr
new file mode 100644
index 0000000..658ff24
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_tag_content_enum.stderr
@@ -0,0 +1,9 @@
+error: AvroSchema derive does not support changing the tagging Serde generates 
(`tag`, `content`, `untagged`, `variant_identifier`, `field_identifier`)
+ --> tests/ui/avro_rs_373_tag_content_enum.rs:4:1
+  |
+4 | / #[serde(tag = "bar", content = "spam")]
+5 | | enum Foo {
+6 | |     One,
+7 | |     Two,
+8 | | }
+  | |_^
diff --git a/avro_derive/tests/ui/avro_rs_373_tag_enum.rs 
b/avro_derive/tests/ui/avro_rs_373_tag_enum.rs
new file mode 100644
index 0000000..d89bd09
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_tag_enum.rs
@@ -0,0 +1,10 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+#[serde(tag = "bar")]
+enum Foo {
+    One,
+    Two,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_373_tag_enum.stderr 
b/avro_derive/tests/ui/avro_rs_373_tag_enum.stderr
new file mode 100644
index 0000000..299cb97
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_tag_enum.stderr
@@ -0,0 +1,9 @@
+error: AvroSchema derive does not support changing the tagging Serde generates 
(`tag`, `content`, `untagged`, `variant_identifier`, `field_identifier`)
+ --> tests/ui/avro_rs_373_tag_enum.rs:4:1
+  |
+4 | / #[serde(tag = "bar")]
+5 | | enum Foo {
+6 | |     One,
+7 | |     Two,
+8 | | }
+  | |_^
diff --git a/avro_derive/tests/ui/avro_rs_373_transparent.rs 
b/avro_derive/tests/ui/avro_rs_373_transparent.rs
new file mode 100644
index 0000000..0df31ef
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_transparent.rs
@@ -0,0 +1,15 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+struct Foo {
+    a: String,
+    b: i32,
+}
+
+#[derive(AvroSchema)]
+#[serde(transparent)]
+struct Bar {
+    foo: Foo,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_373_transparent.stderr 
b/avro_derive/tests/ui/avro_rs_373_transparent.stderr
new file mode 100644
index 0000000..2a17192
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_transparent.stderr
@@ -0,0 +1,8 @@
+error: AvroSchema derive does not support Serde transparent
+  --> tests/ui/avro_rs_373_transparent.rs:10:1
+   |
+10 | / #[serde(transparent)]
+11 | | struct Bar {
+12 | |     foo: Foo,
+13 | | }
+   | |_^
diff --git a/avro_derive/tests/ui/avro_rs_373_untagged_enum.rs 
b/avro_derive/tests/ui/avro_rs_373_untagged_enum.rs
new file mode 100644
index 0000000..29aa6e9
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_untagged_enum.rs
@@ -0,0 +1,10 @@
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+#[serde(untagged)]
+enum Foo {
+    One,
+    Two,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_373_untagged_enum.stderr 
b/avro_derive/tests/ui/avro_rs_373_untagged_enum.stderr
new file mode 100644
index 0000000..b6c613c
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_373_untagged_enum.stderr
@@ -0,0 +1,9 @@
+error: AvroSchema derive does not support changing the tagging Serde generates 
(`tag`, `content`, `untagged`, `variant_identifier`, `field_identifier`)
+ --> tests/ui/avro_rs_373_untagged_enum.rs:4:1
+  |
+4 | / #[serde(untagged)]
+5 | | enum Foo {
+6 | |     One,
+7 | |     Two,
+8 | | }
+  | |_^

Reply via email to