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 f16fc522b4ca25b6956c3874f05cb4fe98298e7a
Author: default <[email protected]>
AuthorDate: Thu Mar 19 12:47:49 2026 +0000

    temp
---
 avro/src/bigdecimal.rs                        |   45 +-
 avro/src/error.rs                             |   15 +-
 avro/src/schema/mod.rs                        |   64 +
 avro/src/serde/derive.rs                      |  384 +++++-
 avro/src/serde/mod.rs                         |    5 +-
 avro/src/serde/ser_schema/mod.rs              | 1723 ++++++++++---------------
 avro/src/serde/ser_schema/record/mod.rs       |   58 +-
 avro/src/serde/with.rs                        |  309 +++++
 avro/src/writer/mod.rs                        |    2 +-
 avro/tests/serde_human_readable_false.rs      |    2 +-
 avro/tests/serde_human_readable_true.rs       |    4 +-
 avro_derive/tests/derive.proptest-regressions |    8 +
 avro_derive/tests/derive.rs                   |   27 +-
 avro_derive/tests/serde.rs                    |    9 +-
 14 files changed, 1516 insertions(+), 1139 deletions(-)

diff --git a/avro/src/bigdecimal.rs b/avro/src/bigdecimal.rs
index 7a6a379..ee26ee1 100644
--- a/avro/src/bigdecimal.rs
+++ b/avro/src/bigdecimal.rs
@@ -190,6 +190,47 @@ mod tests {
             big_decimal: BigDecimal,
         }
 
+        let schema_str = r#"
+        {
+          "type": "record",
+          "name": "Test",
+          "fields": [
+            {
+              "name": "big_decimal",
+              "type": "string"
+            }
+          ]
+        }
+        "#;
+        let schema = Schema::parse_str(schema_str)?;
+
+        let test = Test::default();
+
+        // write a record
+        let mut writer = Writer::new(&schema, Vec::new())?;
+        writer.append_ser(test.clone())?;
+
+        let wrote_data = writer.into_inner()?;
+
+        // read record
+        let mut reader = Reader::new(&wrote_data[..])?;
+
+        let value = reader.next().unwrap()?;
+
+        assert_eq!(test, from_value::<Test>(&value)?);
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_338_deserialize_serde_way_with_bigdecimal() -> TestResult {
+        #[derive(Clone, PartialEq, Eq, Debug, Default, serde::Deserialize, 
serde::Serialize)]
+        #[serde(rename = "test")]
+        struct Test {
+            #[serde(with = "crate::serde::bigdecimal")]
+            big_decimal: BigDecimal,
+        }
+
         let schema_str = r#"
         {
           "type": "record",
@@ -216,11 +257,11 @@ mod tests {
         let wrote_data = writer.into_inner()?;
 
         // read record
-        let mut reader = Reader::new(&wrote_data[..])?;
+        let mut reader = Reader::new(&wrote_data[..])?.into_deser_iter();
 
         let value = reader.next().unwrap()?;
 
-        assert_eq!(test, from_value::<Test>(&value)?);
+        assert_eq!(test, value);
 
         Ok(())
     }
diff --git a/avro/src/error.rs b/avro/src/error.rs
index 85c7fd8..e4c09cd 100644
--- a/avro/src/error.rs
+++ b/avro/src/error.rs
@@ -559,7 +559,7 @@ pub enum Details {
     #[error("Failed to serialize value into Avro value: {0}")]
     SerializeValue(String),
 
-    #[error("Failed to serialize value of type {value_type} using schema 
{schema:?}: {value}")]
+    #[error("Failed to serialize value of type `{value_type}` using 
Schema::{schema:?}: {value}")]
     SerializeValueWithSchema {
         value_type: &'static str,
         value: String,
@@ -572,15 +572,18 @@ pub enum Details {
         schema: RecordSchema,
     },
 
-    #[error("Failed to serialize field '{field_name}' for record 
{record_schema:?}: {error}")]
+    #[error("Failed to serialize field '{field_name}' of record 
{record_schema:?}: {error}")]
     SerializeRecordFieldWithSchema {
         field_name: String,
-        record_schema: Schema,
-        error: Box<Error>,
+        record_schema: RecordSchema,
+        error: String,
     },
 
-    #[error("Missing default for skipped field '{field_name}' for schema 
{schema:?}")]
-    MissingDefaultForSkippedField { field_name: String, schema: Schema },
+    #[error("Missing default for skipped field '{field_name}' of schema 
{schema:?}")]
+    MissingDefaultForSkippedField {
+        field_name: String,
+        schema: RecordSchema,
+    },
 
     #[error("Failed to deserialize Avro value into value: {0}")]
     DeserializeValue(String),
diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs
index 40476cb..ccaa9f8 100644
--- a/avro/src/schema/mod.rs
+++ b/avro/src/schema/mod.rs
@@ -45,6 +45,7 @@ use serde::{
     ser::{SerializeMap, SerializeSeq},
 };
 use serde_json::{Map, Value as JsonValue};
+use std::borrow::Cow;
 use std::fmt::Formatter;
 use std::{
     collections::{BTreeMap, HashMap, HashSet},
@@ -784,6 +785,69 @@ impl Schema {
         }
         Ok(())
     }
+
+    /// Derive a name for this schema.
+    ///
+    /// The name is a valid schema name and will be unique if the named
+    /// schemas in this schema have unique names.
+    pub(crate) fn unique_normalized_name(&self) -> Cow<'static, str> {
+        match self {
+            Schema::Null => Cow::Borrowed("n"),
+            Schema::Boolean => Cow::Borrowed("B"),
+            Schema::Int => Cow::Borrowed("i"),
+            Schema::Long => Cow::Borrowed("l"),
+            Schema::Float => Cow::Borrowed("f"),
+            Schema::Double => Cow::Borrowed("d"),
+            Schema::Bytes => Cow::Borrowed("b"),
+            Schema::String => Cow::Borrowed("s"),
+            Schema::Array(array) => {
+                Cow::Owned(format!("a_{}", 
array.items.unique_normalized_name()))
+            }
+            Schema::Map(map) => Cow::Owned(format!("m_{}", 
map.types.unique_normalized_name())),
+            Schema::Union(union) => {
+                let mut name = format!("u{}", union.schemas.len());
+                for schema in &union.schemas {
+                    name.push('_');
+                    name.push_str(&schema.unique_normalized_name());
+                }
+                Cow::Owned(name)
+            }
+            Schema::BigDecimal => Cow::Borrowed("bd"),
+            Schema::Date => Cow::Borrowed("D"),
+            Schema::TimeMillis => Cow::Borrowed("t"),
+            Schema::TimeMicros => Cow::Borrowed("tm"),
+            Schema::TimestampMillis => Cow::Borrowed("T"),
+            Schema::TimestampMicros => Cow::Borrowed("TM"),
+            Schema::TimestampNanos => Cow::Borrowed("TN"),
+            Schema::LocalTimestampMillis => Cow::Borrowed("L"),
+            Schema::LocalTimestampMicros => Cow::Borrowed("LM"),
+            Schema::LocalTimestampNanos => Cow::Borrowed("LN"),
+            Schema::Decimal(DecimalSchema {
+                inner: InnerDecimalSchema::Bytes,
+                precision,
+                scale,
+            }) => Cow::Owned(format!("db_{precision}_{scale}")),
+            Schema::Uuid(UuidSchema::Bytes) => Cow::Borrowed("ub"),
+            Schema::Uuid(UuidSchema::String) => Cow::Borrowed("us"),
+            Schema::Record(RecordSchema { name, .. })
+            | Schema::Enum(EnumSchema { name, .. })
+            | Schema::Fixed(FixedSchema { name, .. })
+            | Schema::Decimal(DecimalSchema {
+                inner: InnerDecimalSchema::Fixed(FixedSchema { name, .. }),
+                ..
+            })
+            | Schema::Uuid(UuidSchema::Fixed(FixedSchema { name, .. }))
+            | Schema::Duration(FixedSchema { name, .. })
+            | Schema::Ref { name } => {
+                let name: String = name
+                    .to_string()
+                    .chars()
+                    .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
+                    .collect();
+                Cow::Owned(format!("r{}_{}", name.len(), name))
+            }
+        }
+    }
 }
 
 impl Serialize for Schema {
diff --git a/avro/src/serde/derive.rs b/avro/src/serde/derive.rs
index 33c4f5b..255da4b 100644
--- a/avro/src/serde/derive.rs
+++ b/avro/src/serde/derive.rs
@@ -558,27 +558,6 @@ macro_rules! impl_array_schema (
 
 impl_array_schema!([T] where T: AvroSchemaComponent);
 impl_array_schema!(Vec<T> where T: AvroSchemaComponent);
-// This doesn't work as the macro doesn't allow specifying the N parameter
-// impl_array_schema!([T; N] where T: AvroSchemaComponent);
-
-impl<const N: usize, T> AvroSchemaComponent for [T; N]
-where
-    T: AvroSchemaComponent,
-{
-    fn get_schema_in_ctxt(
-        named_schemas: &mut HashSet<Name>,
-        enclosing_namespace: NamespaceRef,
-    ) -> Schema {
-        Schema::array(T::get_schema_in_ctxt(named_schemas, 
enclosing_namespace)).build()
-    }
-
-    fn get_record_fields_in_ctxt(
-        _: &mut HashSet<Name>,
-        _: NamespaceRef,
-    ) -> Option<Vec<RecordField>> {
-        None
-    }
-}
 
 impl<T> AvroSchemaComponent for HashMap<String, T>
 where
@@ -630,27 +609,35 @@ where
 }
 
 impl AvroSchemaComponent for core::time::Duration {
-    /// The schema is [`Schema::Duration`] with the name `duration`.
+    /// The schema is [`Schema::Record`] with the name `Duration`.
     ///
-    /// This is a lossy conversion as this Avro type does not store the amount 
of nanoseconds.
+    /// It has two fields:
+    /// - `secs` with the schema `Schema::Fixed(name: "u64", size: 8)`
+    /// - `nanos` with the schema `Schema::Long`
     fn get_schema_in_ctxt(
         named_schemas: &mut HashSet<Name>,
         enclosing_namespace: NamespaceRef,
     ) -> Schema {
-        let name = Name::new_with_enclosing_namespace("duration", 
enclosing_namespace)
+        let name = Name::new_with_enclosing_namespace("Duration", 
enclosing_namespace)
             .expect("Name is valid");
         if named_schemas.contains(&name) {
             Schema::Ref { name }
         } else {
-            let schema = Schema::Duration(FixedSchema {
-                name: name.clone(),
-                aliases: None,
-                doc: None,
-                size: 12,
-                attributes: Default::default(),
-            });
-            named_schemas.insert(name);
-            schema
+            named_schemas.insert(name.clone());
+            Schema::record(name)
+                .fields(vec![
+                    // Secs is an u64
+                    RecordField::builder()
+                        .name("secs")
+                        .schema(u64::get_schema_in_ctxt(named_schemas, 
enclosing_namespace))
+                        .build(),
+                    // Nanos is an u32
+                    RecordField::builder()
+                        .name("nanos")
+                        .schema(Schema::Long)
+                        .build(),
+                ])
+                .build()
         }
     }
 
@@ -666,6 +653,8 @@ impl AvroSchemaComponent for uuid::Uuid {
     /// The schema is [`Schema::Uuid`] with the name `uuid`.
     ///
     /// The underlying schema is [`Schema::Fixed`] with a size of 16.
+    ///
+    /// If you're using `human_readable: true` you need to override this 
schema with a `Schema::String`.
     fn get_schema_in_ctxt(
         named_schemas: &mut HashSet<Name>,
         enclosing_namespace: NamespaceRef,
@@ -788,13 +777,197 @@ impl AvroSchemaComponent for i128 {
     }
 }
 
+/// Schema definition for `[T; N]`
+///
+/// Schema is defined as follows:
+/// - 0-sized arrays: [`Schema::Null`]
+/// - 1-sized arrays: `T::get_schema_in_ctxt`
+/// - N-sized arrays: [`Schema::Record`] with a field for every index
+///
+/// If you need or want a [`Schema::Array`], [`Schema::Bytes`], or 
[`Schema::Fixed`] instead,
+/// use [`apache_avro::serde::array`], [`apache_avro::serde::bytes`], or 
[`apache_avro::serde::fixed`] respectively.
+///
+/// [`apache_avro::serde::array`]: crate::serde::array
+/// [`apache_avro::serde::bytes`]: crate::serde::bytes
+/// [`apache_avro::serde::fixed`]: crate::serde::fixed
+impl<const N: usize, T: AvroSchemaComponent> AvroSchemaComponent for [T; N] {
+    fn get_schema_in_ctxt(
+        named_schemas: &mut HashSet<Name>,
+        enclosing_namespace: NamespaceRef,
+    ) -> Schema {
+        if N == 0 {
+            Schema::Null
+        } else if N == 1 {
+            T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
+        } else {
+            let t_schema = T::get_schema_in_ctxt(named_schemas, 
enclosing_namespace);
+            let name = Name::new_with_enclosing_namespace(
+                format!("A{N}_{}", t_schema.unique_normalized_name()),
+                enclosing_namespace,
+            )
+            .expect("Name is valid");
+            if named_schemas.contains(&name) {
+                Schema::Ref { name }
+            } else {
+                named_schemas.insert(name.clone());
+
+                let t_default = T::field_default();
+                // If T is a named schema or contains named schemas, they'll 
now be a reference.
+                let t_ref = T::get_schema_in_ctxt(named_schemas, 
enclosing_namespace);
+                let fields = std::iter::once(
+                    RecordField::builder()
+                        .name("field_0".to_string())
+                        .schema(t_schema)
+                        .maybe_default(t_default.clone())
+                        .build(),
+                )
+                .chain((1..N).map(|n| {
+                    RecordField::builder()
+                        .name(format!("field_{n}"))
+                        .schema(t_ref.clone())
+                        .maybe_default(t_default.clone())
+                        .build()
+                }))
+                .collect();
+
+                Schema::record(name).fields(fields).build()
+            }
+        }
+    }
+
+    fn get_record_fields_in_ctxt(
+        named_schemas: &mut HashSet<Name>,
+        enclosing_namespace: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
+        if N == 0 {
+            None
+        } else if N == 1 {
+            T::get_record_fields_in_ctxt(named_schemas, enclosing_namespace)
+        } else {
+            let t_schema = T::get_schema_in_ctxt(named_schemas, 
enclosing_namespace);
+            let t_default = T::field_default();
+            // If T is a named schema or contains named schemas, they'll now 
be a reference.
+            let t_ref = T::get_schema_in_ctxt(named_schemas, 
enclosing_namespace);
+            let fields = std::iter::once(
+                RecordField::builder()
+                    .name("field_0".to_string())
+                    .schema(t_schema)
+                    .maybe_default(t_default.clone())
+                    .build(),
+            )
+            .chain((1..N).map(|n| {
+                RecordField::builder()
+                    .name(format!("field_{n}"))
+                    .schema(t_ref.clone())
+                    .maybe_default(t_default.clone())
+                    .build()
+            }))
+            .collect();
+            Some(fields)
+        }
+    }
+
+    /// `None` for 0-sized and N-sized arrays, `T::field_default` for 1-sized 
arrays
+    fn field_default() -> Option<serde_json::Value> {
+        if N == 1 { T::field_default() } else { None }
+    }
+}
+
+/// Schema definition for `(T₁, T₂, …, Tₙ)`.
+///
+/// Implemented for tuples of up to 16 elements.
+///
+/// Schema is defined as follows:
+/// - 1-tuple: `T::get_schema_in_ctxt`
+/// - N-tuple: [`Schema::Record`] with a field for every element
+#[cfg_attr(docsrs, doc(fake_variadic))]
+impl<T: AvroSchemaComponent> AvroSchemaComponent for (T,) {
+    fn get_schema_in_ctxt(
+        named_schemas: &mut HashSet<Name>,
+        enclosing_namespace: NamespaceRef,
+    ) -> Schema {
+        T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
+    }
+
+    fn get_record_fields_in_ctxt(
+        named_schemas: &mut HashSet<Name>,
+        enclosing_namespace: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
+        T::get_record_fields_in_ctxt(named_schemas, enclosing_namespace)
+    }
+
+    /// `None` for N-tuples, `T::field_default()` for 1-tuple.
+    fn field_default() -> Option<serde_json::Value> {
+        T::field_default()
+    }
+}
+
+macro_rules! tuple_impls {
+    ($($len:expr => ($($name:ident)+))+) => {
+        $(
+            #[cfg_attr(docsrs, doc(hidden))]
+            impl<$($name: AvroSchemaComponent),+> AvroSchemaComponent for 
($($name),+) {
+                fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, 
enclosing_namespace: NamespaceRef) -> Schema {
+                    let schemas: [Schema; $len] = 
[$($name::get_schema_in_ctxt(named_schemas, enclosing_namespace)),+];
+
+                    let mut name = format!("T{}", $len);
+                    for schema in &schemas {
+                        name.push('_');
+                        name.push_str(&schema.unique_normalized_name());
+                    }
+                    let name = Name::new_with_enclosing_namespace(name, 
enclosing_namespace).expect("Name is valid");
+
+                    if named_schemas.contains(&name) {
+                        Schema::Ref { name }
+                    } else {
+                        named_schemas.insert(name.clone());
+
+                        let defaults: [Option<serde_json::Value>; $len] = 
[$($name::field_default()),+];
+
+                        let fields = 
schemas.into_iter().zip(defaults.into_iter()).enumerate().map(|(n, (schema, 
default))| {
+                            RecordField::builder()
+                                .name(format!("field_{n}"))
+                                .schema(schema)
+                                .maybe_default(default)
+                                .build()
+                        }).collect();
+
+                        Schema::record(name).fields(fields).build()
+                    }
+                }
+            }
+        )+
+    }
+}
+
+tuple_impls! {
+    2 => (T0 T1)
+    3 => (T0 T1 T2)
+    4 => (T0 T1 T2 T3)
+    5 => (T0 T1 T2 T3 T4)
+    6 => (T0 T1 T2 T3 T4 T5)
+    7 => (T0 T1 T2 T3 T4 T5 T6)
+    8 => (T0 T1 T2 T3 T4 T5 T6 T7)
+    9 => (T0 T1 T2 T3 T4 T5 T6 T7 T8)
+    10 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9)
+    11 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10)
+    12 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11)
+    13 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12)
+    14 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13)
+    15 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14)
+    16 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15)
+}
+
 #[cfg(test)]
 mod tests {
+    use crate::reader::datum::GenericDatumReader;
+    use crate::writer::datum::GenericDatumWriter;
     use crate::{
         AvroSchema, Schema,
         schema::{FixedSchema, Name},
     };
     use apache_avro_test_helper::TestResult;
+    use std::io::Cursor;
 
     #[test]
     fn avro_rs_401_str() -> TestResult {
@@ -823,23 +996,12 @@ mod tests {
         Ok(())
     }
 
-    #[test]
-    fn avro_rs_401_array() -> TestResult {
-        let schema = <[u8; 55]>::get_schema();
-        assert_eq!(schema, Schema::array(Schema::Int).build());
-
-        Ok(())
-    }
-
     #[test]
     fn avro_rs_401_option_ref_slice_array() -> TestResult {
-        let schema = <Option<&[[u8; 55]]>>::get_schema();
+        let schema = <Option<&[u8]>>::get_schema();
         assert_eq!(
             schema,
-            Schema::union(vec![
-                Schema::Null,
-                Schema::array(Schema::array(Schema::Int).build()).build()
-            ])?
+            Schema::union(vec![Schema::Null, 
Schema::array(Schema::Int).build()])?
         );
 
         Ok(())
@@ -927,4 +1089,134 @@ mod tests {
     fn avro_rs_489_option_option() {
         <Option<Option<i32>>>::get_schema();
     }
+
+    #[test]
+    fn avro_rs_xxx_std_time_duration() -> TestResult {
+        let schema = Schema::parse_str(
+            r#"{
+            "type": "record",
+            "name": "Duration",
+            "fields": [
+                { "name": "secs", "type": {"type": "fixed", "name": "u64", 
"size": 8} },
+                { "name": "nanos", "type": "long" }
+            ]
+        }"#,
+        )?;
+        let zero = std::time::Duration::ZERO;
+        let max = std::time::Duration::MAX;
+        assert_eq!(schema, std::time::Duration::get_schema());
+
+        let writer = GenericDatumWriter::builder(&schema).build()?;
+        let written_zero = writer.write_ser_to_vec(&zero)?;
+        let written_max = writer.write_ser_to_vec(&max)?;
+
+        let reader = GenericDatumReader::builder(&schema).build()?;
+        let read_zero = reader.read_deser(&mut Cursor::new(written_zero))?;
+        assert_eq!(zero, read_zero);
+        let read_max = reader.read_deser(&mut Cursor::new(written_max))?;
+        assert_eq!(max, read_max);
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_xxx_0_array() -> TestResult {
+        assert_eq!(Schema::Null, <[String; 0]>::get_schema());
+        assert_eq!(Schema::Null, <[(); 0]>::get_schema());
+        assert_eq!(Schema::Null, <[bool; 0]>::get_schema());
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_xxx_1_array() -> TestResult {
+        assert_eq!(Schema::String, <[String; 1]>::get_schema());
+        assert_eq!(Schema::Null, <[(); 1]>::get_schema());
+        assert_eq!(Schema::Boolean, <[bool; 1]>::get_schema());
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_xxx_n_array() -> TestResult {
+        let schema = Schema::parse_str(
+            r#"{
+            "type": "record",
+            "name": "A5_s",
+            "fields": [
+                { "name": "field_0", "type": "string" },
+                { "name": "field_1", "type": "string" },
+                { "name": "field_2", "type": "string" },
+                { "name": "field_3", "type": "string" },
+                { "name": "field_4", "type": "string" }
+            ]
+        }"#,
+        )?;
+
+        assert_eq!(schema, <[String; 5]>::get_schema());
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_xxx_n_array_complex_type() -> TestResult {
+        let schema = Schema::parse_str(
+            r#"{
+            "type": "record",
+            "name": "A2_u2_n_r4_uuid",
+            "fields": [
+                { "name": "field_0", "type": ["null", {"type": "fixed", 
"logicalType": "uuid", "size": 16, "name": "uuid"}], "default": null },
+                { "name": "field_1", "type": ["null", "uuid"], "default": null 
}
+            ]
+        }"#,
+        )?;
+
+        assert_eq!(schema, <[Option<uuid::Uuid>; 2]>::get_schema());
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_xxx_1_tuple() -> TestResult {
+        assert_eq!(Schema::String, <(String,)>::get_schema());
+        assert_eq!(Schema::Null, <((),)>::get_schema());
+        assert_eq!(Schema::Boolean, <(bool,)>::get_schema());
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_xxx_n_tuple() -> TestResult {
+        let schema = Schema::parse_str(
+            r#"{
+            "type": "record",
+            "name": "T5_s_i_l_B_n",
+            "fields": [
+                { "name": "field_0", "type": "string" },
+                { "name": "field_1", "type": "int" },
+                { "name": "field_2", "type": "long" },
+                { "name": "field_3", "type": "boolean" },
+                { "name": "field_4", "type": "null" }
+            ]
+        }"#,
+        )?;
+
+        assert_eq!(schema, <(String, i32, i64, bool, ())>::get_schema());
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rx_xxx_n_tuple_complex_type() -> TestResult {
+        let schema = Schema::parse_str(
+            r#"{
+            "type": "record",
+            "name": "T3_u2_n_r4_uuid_r4_uuid_s",
+            "fields": [
+                { "name": "field_0", "type": ["null", {"type": "fixed", 
"logicalType": "uuid", "size": 16, "name": "uuid"}], "default": null },
+                { "name": "field_1", "type": "uuid" },
+                { "name": "field_2", "type": "string" }
+            ]
+        }"#,
+        )?;
+
+        assert_eq!(
+            schema,
+            <(Option<uuid::Uuid>, uuid::Uuid, String)>::get_schema()
+        );
+        Ok(())
+    }
 }
diff --git a/avro/src/serde/mod.rs b/avro/src/serde/mod.rs
index 256963d..3b1718b 100644
--- a/avro/src/serde/mod.rs
+++ b/avro/src/serde/mod.rs
@@ -118,7 +118,10 @@ mod with;
 pub use de::from_value;
 pub use derive::{AvroSchema, AvroSchemaComponent};
 pub use ser::to_value;
-pub use with::{bytes, bytes_opt, fixed, fixed_opt, slice, slice_opt};
+pub use with::{
+    array, array_opt, bigdecimal, bigdecimal_opt, bytes, bytes_opt, fixed, 
fixed_opt, slice,
+    slice_opt,
+};
 
 #[doc(hidden)]
 pub use derive::get_record_fields_in_ctxt;
diff --git a/avro/src/serde/ser_schema/mod.rs b/avro/src/serde/ser_schema/mod.rs
index 829cec4..c971bff 100644
--- a/avro/src/serde/ser_schema/mod.rs
+++ b/avro/src/serde/ser_schema/mod.rs
@@ -268,7 +268,7 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> Serializer for 
SchemaAwareSerializer<'
     }
 
     fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {
-        self.checked_write_long("i64", i64::from(v))
+        self.checked_write_long("u32", i64::from(v))
     }
 
     fn serialize_u64(mut self, v: u64) -> Result<Self::Ok, Self::Error> {
@@ -334,7 +334,7 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> Serializer for 
SchemaAwareSerializer<'
             Schema::Union(union) => {
                 UnionSerializer::new(self.writer, union, 
self.config).serialize_str(v)
             }
-            _ => Err(self.error("string", "Expected Schema::String ")),
+            _ => Err(self.error("str", "Expected Schema::String | 
Schema::Uuid(String)")),
         }
     }
 
@@ -406,36 +406,36 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> Serializer for 
SchemaAwareSerializer<'
             }
             _ => Err(self.error(
                 "unit struct",
-                format!("Expected Schema::Record(name: {name}, fields: [])"),
+                format!(r#"Expected Schema::Record(name: "{name}", fields: 
[])"#),
             )),
         }
     }
 
     fn serialize_unit_variant(
         self,
-        name: &'static str,
+        _name: &'static str,
         variant_index: u32,
         variant: &'static str,
     ) -> Result<Self::Ok, Self::Error> {
         match self.schema {
             Schema::Enum(enum_schema) => {
                 // Plain enum
-                if name.as_ptr() == SERIALIZING_SCHEMA_DEFAULT.as_ptr() || 
enum_schema.symbols[variant_index as usize] == variant {
+                if variant.as_ptr() == SERIALIZING_SCHEMA_DEFAULT.as_ptr() || 
enum_schema.symbols[variant_index as usize] == variant {
                     zig_i32(variant_index as i32, &mut *self.writer)
                 } else {
-                    Err(self.error("unit variant", format!(r#"Expected symbol 
"{name}" at index {variant_index} in enum"#)))
+                    Err(self.error("unit variant", format!(r#"Expected symbol 
"{variant}" at index {variant_index} in enum"#)))
                 }
             }
             Schema::Union(union) => match 
self.get_resolved_union_variant(union, variant_index)? {
                 // Bare union
                 Schema::Null => zig_i32(variant_index as i32, &mut 
*self.writer),
-                Schema::Record(record) if record.fields.is_empty() && 
record.name.name() == name => {
+                Schema::Record(record) if record.fields.is_empty() && 
record.name.name() == variant => {
                     // Union of records
                     zig_i32(variant_index as i32, &mut *self.writer)
                 }
-                _ => Err(self.error("unit variant", format!("Expected 
Schema::Null | Schema::Record(name: {name}, fields: []) at index 
{variant_index} in the union"))),
+                _ => Err(self.error("unit variant", format!("Expected 
Schema::Null | Schema::Record(name: {variant}, fields: []) at index 
{variant_index} in the union"))),
             }
-            _ => Err(self.error("unit variant", format!("Expected 
Schema::Enum(symbols[{variant_index}] == {name}) | 
Schema::Union(variants[{variant_index}] == Schema::Null | Schema::Record(name: 
{name}, fields: []))"))),
+            _ => Err(self.error("unit variant", format!("Expected 
Schema::Enum(symbols[{variant_index}] == {variant}) | 
Schema::Union(variants[{variant_index}] == Schema::Null | Schema::Record(name: 
{variant}, fields: []))"))),
         }
     }
 
@@ -527,7 +527,10 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> Serializer for 
SchemaAwareSerializer<'
                 None,
             )),
             // This error case can only happen for len > 1
-            _ => Err(self.error("tuple", "Expected Schema::Record(fields.len() 
== {len}")),
+            _ => Err(self.error(
+                "tuple",
+                format!("Expected Schema::Record(fields.len() == {len})"),
+            )),
         }
     }
 
@@ -725,47 +728,94 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> SerializeMap 
for MapOrRecordSerializer
 mod tests {
     use super::*;
     use crate::schema::FixedSchema;
-    use crate::{
-        Days, Duration, Millis, Months, Reader, Writer, decimal::Decimal, 
error::Details,
-        from_value, schema::ResolvedSchema,
-    };
+    use crate::{Days, Duration, Millis, Months, decimal::Decimal, 
schema::ResolvedSchema};
     use apache_avro_test_helper::TestResult;
     use bigdecimal::BigDecimal;
     use num_bigint::{BigInt, Sign};
+    use pretty_assertions::assert_eq;
     use serde::{Deserialize, Serialize};
-    use serde_bytes::{ByteArray, Bytes};
+    use serde_bytes::Bytes;
     use std::{
         collections::{BTreeMap, HashMap},
         marker::PhantomData,
     };
     use uuid::Uuid;
 
+    #[track_caller]
+    fn assert_serialize_err<T: Serialize>(
+        t: T,
+        schema: &Schema,
+        names: &HashMap<Name, &Schema>,
+        expected: &str,
+    ) {
+        let config = Config {
+            names,
+            target_block_size: None,
+            human_readable: false,
+        };
+        let mut buffer = Vec::new();
+        let serializer = SchemaAwareSerializer::new(&mut buffer, schema, 
config).unwrap();
+        let error = t
+            .serialize(serializer)
+            .expect_err("This should not serialize");
+        assert_eq!(error.to_string(), expected);
+    }
+
+    #[track_caller]
+    fn assert_serialize<T: Serialize>(
+        t: T,
+        schema: &Schema,
+        names: &HashMap<Name, &Schema>,
+        expected: &[u8],
+    ) {
+        let config = Config {
+            names,
+            target_block_size: None,
+            human_readable: false,
+        };
+        let mut buffer = Vec::new();
+        let serializer = SchemaAwareSerializer::new(&mut buffer, schema, 
config).unwrap();
+        let bytes_written = t.serialize(serializer).expect("This should 
serialize");
+        assert_eq!(bytes_written, buffer.len());
+        assert_eq!(&buffer, expected);
+    }
+
     #[test]
     fn test_serialize_null() -> TestResult {
         let schema = Schema::Null;
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        ().serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        None::<()>.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        None::<i32>.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        None::<String>.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-        assert!(
-            "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize((), &schema, &names, &[]);
+        assert_serialize_err(
+            None::<()>,
+            &schema,
+            &names,
+            "Failed to serialize value of type `none` using Schema::Null: 
Expected Schema::Union([Schema::Null, _])",
+        );
+        assert_serialize_err(
+            None::<i32>,
+            &schema,
+            &names,
+            "Failed to serialize value of type `none` using Schema::Null: 
Expected Schema::Union([Schema::Null, _])",
         );
-        assert!(
-            Some("")
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize_err(
+            None::<String>,
+            &schema,
+            &names,
+            "Failed to serialize value of type `none` using Schema::Null: 
Expected Schema::Union([Schema::Null, _])",
+        );
+        assert_serialize_err(
+            "",
+            &schema,
+            &names,
+            "Failed to serialize value of type `str` using Schema::Null: 
Expected Schema::String | Schema::Uuid(String)",
+        );
+        assert_serialize_err(
+            Some(""),
+            &schema,
+            &names,
+            "Failed to serialize value of type `some` using Schema::Null: 
Expected Schema::Union([Schema::Null, _])",
         );
-
-        assert_eq!(buffer.as_slice(), Vec::<u8>::new().as_slice());
 
         Ok(())
     }
@@ -773,59 +823,54 @@ mod tests {
     #[test]
     fn test_serialize_bool() -> TestResult {
         let schema = Schema::Boolean;
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        true.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        false.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        assert!(
-            "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize(true, &schema, &names, &[1]);
+        assert_serialize(false, &schema, &names, &[0]);
+        assert_serialize_err(
+            "",
+            &schema,
+            &names,
+            "Failed to serialize value of type `str` using Schema::Boolean: 
Expected Schema::String | Schema::Uuid(String)",
         );
-        assert!(
-            Some("")
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize_err(
+            Some(""),
+            &schema,
+            &names,
+            "Failed to serialize value of type `some` using Schema::Boolean: 
Expected Schema::Union([Schema::Null, _])",
         );
 
-        assert_eq!(buffer.as_slice(), &[1, 0]);
-
         Ok(())
     }
 
     #[test]
     fn test_serialize_int() -> TestResult {
         let schema = Schema::Int;
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        4u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        31u16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        13u32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        7i8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        (-57i16).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        129i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        assert!(
-            "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize(4u8, &schema, &names, &[8]);
+        assert_serialize(31u16, &schema, &names, &[62]);
+        assert_serialize(7i8, &schema, &names, &[14]);
+        assert_serialize(-57i16, &schema, &names, &[113]);
+        assert_serialize(129i32, &schema, &names, &[130, 2]);
+        assert_serialize_err(
+            13u32,
+            &schema,
+            &names,
+            "Failed to serialize value of type `u32` using Schema::Int: 
Expected Schema::Long | Schema::TimeMicros | 
Schema::{,Local}Timestamp{Millis,Micros,Nanos}",
         );
-        assert!(
-            Some("")
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize_err(
+            "",
+            &schema,
+            &names,
+            "Failed to serialize value of type `str` using Schema::Int: 
Expected Schema::String | Schema::Uuid(String)",
+        );
+        assert_serialize_err(
+            Some(""),
+            &schema,
+            &names,
+            "Failed to serialize value of type `some` using Schema::Int: 
Expected Schema::Union([Schema::Null, _])",
         );
-
-        assert_eq!(buffer.as_slice(), &[8, 62, 26, 14, 113, 130, 2]);
 
         Ok(())
     }
@@ -833,35 +878,57 @@ mod tests {
     #[test]
     fn test_serialize_long() -> TestResult {
         let schema = Schema::Long;
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        4u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        31u16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        13u32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        291u64.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        7i8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        (-57i16).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        129i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        (-432i64).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        assert!(
-            "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize(13u32, &schema, &names, &[26]);
+        assert_serialize(-432i64, &schema, &names, &[223, 6]);
+        assert_serialize_err(
+            4u8,
+            &schema,
+            &names,
+            "Failed to serialize value of type `u8` using Schema::Long: 
Expected Schema::Int | Schema::Date | Schema::TimeMillis",
         );
-        assert!(
-            Some("")
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize_err(
+            31u16,
+            &schema,
+            &names,
+            "Failed to serialize value of type `u16` using Schema::Long: 
Expected Schema::Int | Schema::Date | Schema::TimeMillis",
         );
-
-        assert_eq!(
-            buffer.as_slice(),
-            &[8, 62, 26, 198, 4, 14, 113, 130, 2, 223, 6]
+        assert_serialize_err(
+            7i8,
+            &schema,
+            &names,
+            "Failed to serialize value of type `i8` using Schema::Long: 
Expected Schema::Int | Schema::Date | Schema::TimeMillis",
+        );
+        assert_serialize_err(
+            -57i16,
+            &schema,
+            &names,
+            "Failed to serialize value of type `i16` using Schema::Long: 
Expected Schema::Int | Schema::Date | Schema::TimeMillis",
+        );
+        assert_serialize_err(
+            129i32,
+            &schema,
+            &names,
+            "Failed to serialize value of type `i32` using Schema::Long: 
Expected Schema::Int | Schema::Date | Schema::TimeMillis",
+        );
+        assert_serialize_err(
+            24u64,
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `u64` using Schema::Long: 
Expected Schema::Fixed(name: "u64", size: 8)"#,
+        );
+        assert_serialize_err(
+            "",
+            &schema,
+            &names,
+            "Failed to serialize value of type `str` using Schema::Long: 
Expected Schema::String | Schema::Uuid(String)",
+        );
+        assert_serialize_err(
+            Some(""),
+            &schema,
+            &names,
+            "Failed to serialize value of type `some` using Schema::Long: 
Expected Schema::Union([Schema::Null, _])",
         );
 
         Ok(())
@@ -870,55 +937,60 @@ mod tests {
     #[test]
     fn test_serialize_float() -> TestResult {
         let schema = Schema::Float;
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        4.7f32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        (-14.1f64).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        assert!(
-            "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize(4.7f32, &schema, &names, &[102, 102, 150, 64]);
+        assert_serialize_err(
+            -14.1f64,
+            &schema,
+            &names,
+            "Failed to serialize value of type `f64` using Schema::Float: 
Expected Schema::Double",
+        );
+        assert_serialize_err(
+            "",
+            &schema,
+            &names,
+            "Failed to serialize value of type `str` using Schema::Float: 
Expected Schema::String | Schema::Uuid(String)",
         );
-        assert!(
-            Some("")
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize_err(
+            Some(""),
+            &schema,
+            &names,
+            "Failed to serialize value of type `some` using Schema::Float: 
Expected Schema::Union([Schema::Null, _])",
         );
 
-        assert_eq!(buffer.as_slice(), &[102, 102, 150, 64, 154, 153, 97, 193]);
-
         Ok(())
     }
 
     #[test]
     fn test_serialize_double() -> TestResult {
-        let schema = Schema::Float;
-        let mut buffer: Vec<u8> = Vec::new();
+        let schema = Schema::Double;
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        4.7f32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        (-14.1f64).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        assert!(
-            "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize(
+            -14.1f64,
+            &schema,
+            &names,
+            &[51, 51, 51, 51, 51, 51, 44, 192],
         );
-        assert!(
-            Some("")
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize_err(
+            4.7f32,
+            &schema,
+            &names,
+            "Failed to serialize value of type `f32` using Schema::Double: 
Expected Schema::Float",
+        );
+        assert_serialize_err(
+            "",
+            &schema,
+            &names,
+            "Failed to serialize value of type `str` using Schema::Double: 
Expected Schema::String | Schema::Uuid(String)",
+        );
+        assert_serialize_err(
+            Some(""),
+            &schema,
+            &names,
+            "Failed to serialize value of type `some` using Schema::Double: 
Expected Schema::Union([Schema::Null, _])",
         );
-
-        assert_eq!(buffer.as_slice(), &[102, 102, 150, 64, 154, 153, 97, 193]);
 
         Ok(())
     }
@@ -926,34 +998,43 @@ mod tests {
     #[test]
     fn test_serialize_bytes() -> TestResult {
         let schema = Schema::Bytes;
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        "test".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        Bytes::new(&[12, 3, 7, 91, 4]).serialize(SchemaAwareSerializer::new(
-            &mut buffer,
+        assert_serialize(
+            Bytes::new(&[12, 3, 7, 91, 4]),
             &schema,
-            config,
-        )?)?;
-        assert!(
-            ().serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+            &names,
+            &[10, 12, 3, 7, 91, 4],
         );
-        assert!(
-            PhantomData::<String>
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize_err(
+            'a',
+            &schema,
+            &names,
+            "Failed to serialize value of type `char` using Schema::Bytes: 
Expected Schema::String",
         );
-
-        assert_eq!(
-            buffer.as_slice(),
-            &[2, b'a', 8, b't', b'e', b's', b't', 10, 12, 3, 7, 91, 4]
+        assert_serialize_err(
+            "test",
+            &schema,
+            &names,
+            "Failed to serialize value of type `str` using Schema::Bytes: 
Expected Schema::String | Schema::Uuid(String)",
+        );
+        assert_serialize_err(
+            (),
+            &schema,
+            &names,
+            "Failed to serialize value of type `unit` using Schema::Bytes: 
Expected Schema::Null",
+        );
+        assert_serialize_err(
+            PhantomData::<String>,
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `unit struct` using 
Schema::Bytes: Expected Schema::Record(name: "PhantomData", fields: [])"#,
+        );
+        assert_serialize_err(
+            Some(""),
+            &schema,
+            &names,
+            "Failed to serialize value of type `some` using Schema::Bytes: 
Expected Schema::Union([Schema::Null, _])",
         );
 
         Ok(())
@@ -962,34 +1043,39 @@ mod tests {
     #[test]
     fn test_serialize_string() -> TestResult {
         let schema = Schema::String;
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        "test".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        Bytes::new(&[12, 3, 7, 91, 4]).serialize(SchemaAwareSerializer::new(
-            &mut buffer,
+        assert_serialize('a', &schema, &names, &[2, b'a']);
+        assert_serialize("test", &schema, &names, &[8, b't', b'e', b's', 
b't']);
+        assert_serialize(
+            BigDecimal::new(BigInt::new(Sign::Plus, vec![50024]), 2),
             &schema,
-            config,
-        )?)?;
-        assert!(
-            ().serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+            &names,
+            &[12, b'5', b'0', b'0', b'.', b'2', b'4'],
         );
-        assert!(
-            PhantomData::<String>
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize_err(
+            Bytes::new(&[12, 3, 7, 91, 4]),
+            &schema,
+            &names,
+            "Failed to serialize value of type `bytes` using Schema::String: 
Expected Schema::Bytes | Schema::Fixed | Schema::BigDecimal | Schema::Decimal | 
Schema::Uuid(Bytes | Fixed) | Schema::Duration",
         );
-
-        assert_eq!(
-            buffer.as_slice(),
-            &[2, b'a', 8, b't', b'e', b's', b't', 10, 12, 3, 7, 91, 4]
+        assert_serialize_err(
+            (),
+            &schema,
+            &names,
+            "Failed to serialize value of type `unit` using Schema::String: 
Expected Schema::Null",
+        );
+        assert_serialize_err(
+            PhantomData::<String>,
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `unit struct` using 
Schema::String: Expected Schema::Record(name: "PhantomData", fields: [])"#,
+        );
+        assert_serialize_err(
+            Some(""),
+            &schema,
+            &names,
+            "Failed to serialize value of type `some` using Schema::String: 
Expected Schema::Union([Schema::Null, _])",
         );
 
         Ok(())
@@ -1009,55 +1095,55 @@ mod tests {
         )?;
 
         #[derive(Serialize)]
-        #[serde(rename_all = "camelCase")]
+        #[serde(rename_all = "camelCase", rename = "TestRecord")]
         struct GoodTestRecord {
             string_field: String,
             int_field: i32,
         }
 
         #[derive(Serialize)]
-        #[serde(rename_all = "camelCase")]
+        #[serde(rename_all = "camelCase", rename = "TestRecord")]
         struct BadTestRecord {
             foo_string_field: String,
             bar_int_field: i32,
         }
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
         let good_record = GoodTestRecord {
             string_field: String::from("test"),
             int_field: 10,
         };
-        good_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
+        assert_serialize(
+            good_record,
+            &schema,
+            &names,
+            &[8, b't', b'e', b's', b't', 20],
+        );
 
         let bad_record = BadTestRecord {
             foo_string_field: String::from("test"),
             bar_int_field: 10,
         };
-        assert!(
-            bad_record
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize_err(
+            bad_record,
+            &schema,
+            &names,
+            r#"Missing field in record: "fooStringField""#,
         );
-
-        assert!(
-            "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize_err(
+            "",
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `str` using 
Schema::Record(RecordSchema { name: Name { name: "TestRecord", .. }, fields: 
[RecordField { name: "stringField", schema: String, .. }, RecordField { name: 
"intField", schema: Int, .. }], .. }): Expected Schema::String | 
Schema::Uuid(String)"#,
         );
-        assert!(
-            Some("")
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .is_err()
+        assert_serialize_err(
+            Some(""),
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `some` using 
Schema::Record(RecordSchema { name: Name { name: "TestRecord", .. }, fields: 
[RecordField { name: "stringField", schema: String, .. }, RecordField { name: 
"intField", schema: Int, .. }], .. }): Expected Schema::Union([Schema::Null, 
_])"#,
         );
 
-        assert_eq!(buffer.as_slice(), &[8, b't', b'e', b's', b't', 20]);
-
         Ok(())
     }
 
@@ -1071,47 +1157,45 @@ mod tests {
         }"#,
         )?;
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
         #[derive(Serialize)]
         struct EmptyRecord;
-        EmptyRecord.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
+        assert_serialize(EmptyRecord, &schema, &names, &[]);
 
         #[derive(Serialize)]
+        #[serde(rename = "EmptyRecord")]
         struct NonEmptyRecord {
             foo: String,
         }
         let record = NonEmptyRecord {
             foo: "bar".to_string(),
         };
-        match record
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::FieldName(field_name)) if field_name == "foo" => (),
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        match ().serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?).map_err(Error::into_details) {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "none"); // serialize_unit() delegates 
to serialize_none()
-                assert_eq!(value, "None. Cause: Expected: Record. Got: Null");
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        assert_eq!(buffer.len(), 0);
+        assert_serialize_err(record, &schema, &names, r#"Missing field in 
record: "foo""#);
+        assert_serialize_err(
+            (),
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `unit` using 
Schema::Record(RecordSchema { name: Name { name: "EmptyRecord", .. }, fields: 
[], .. }): Expected Schema::Null"#,
+        );
+        assert_serialize_err(
+            "",
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `str` using 
Schema::Record(RecordSchema { name: Name { name: "EmptyRecord", .. }, fields: 
[], .. }): Expected Schema::String | Schema::Uuid(String)"#,
+        );
+        assert_serialize_err(
+            PhantomData::<String>,
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `unit struct` using 
Schema::Record(RecordSchema { name: Name { name: "EmptyRecord", .. }, fields: 
[], .. }): Expected Schema::Record(name: "PhantomData", fields: [])"#,
+        );
+        assert_serialize_err(
+            Some(""),
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `some` using 
Schema::Record(RecordSchema { name: Name { name: "EmptyRecord", .. }, fields: 
[], .. }): Expected Schema::Union([Schema::Null, _])"#,
+        );
 
         Ok(())
     }
@@ -1127,6 +1211,7 @@ mod tests {
         )?;
 
         #[derive(Serialize)]
+        #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
         enum Suit {
             Spades,
             Hearts,
@@ -1134,35 +1219,18 @@ mod tests {
             Clubs,
         }
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
-
-        Suit::Spades.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-        Suit::Hearts.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-        Suit::Diamonds.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-        Suit::Clubs.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        match None::<()>
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "none");
-                assert_eq!(value, "None. Cause: Expected: Enum. Got: Null");
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
 
-        assert_eq!(buffer.as_slice(), &[0, 2, 4, 6]);
+        assert_serialize(Suit::Spades, &schema, &names, &[0]);
+        assert_serialize(Suit::Hearts, &schema, &names, &[2]);
+        assert_serialize(Suit::Diamonds, &schema, &names, &[4]);
+        assert_serialize(Suit::Clubs, &schema, &names, &[6]);
+        assert_serialize_err(
+            None::<()>,
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `none` using 
Schema::Enum(EnumSchema { name: Name { name: "Suit", .. }, symbols: ["SPADES", 
"HEARTS", "DIAMONDS", "CLUBS"], .. }): Expected Schema::Union([Schema::Null, 
_])"#,
+        );
 
         Ok(())
     }
@@ -1176,34 +1244,20 @@ mod tests {
         }"#,
         )?;
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        let arr: Vec<i64> = vec![10, 5, 400];
-        arr.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-
-        match vec![1_f32]
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "f32");
-                assert_eq!(value, "1. Cause: Expected: Long. Got: Float");
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        assert_eq!(buffer.as_slice(), &[6, 20, 10, 160, 6, 0]);
+        assert_serialize(
+            vec![10i64, 5, 400],
+            &schema,
+            &names,
+            &[6, 20, 10, 160, 6, 0],
+        );
+        assert_serialize_err(
+            vec![1_f32],
+            &schema,
+            &names,
+            "Failed to serialize value of type `f32` using Schema::Long: 
Expected Schema::Float",
+        );
 
         Ok(())
     }
@@ -1217,44 +1271,29 @@ mod tests {
         }"#,
         )?;
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
         let mut map: BTreeMap<String, i64> = BTreeMap::new();
         map.insert(String::from("item1"), 10);
         map.insert(String::from("item2"), 400);
 
-        map.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
+        assert_serialize(
+            map,
+            &schema,
+            &names,
+            &[
+                4, 10, b'i', b't', b'e', b'm', b'1', 20, 10, b'i', b't', b'e', 
b'm', b'2', 160, 6,
+                0,
+            ],
+        );
 
         let mut map: BTreeMap<String, &str> = BTreeMap::new();
         map.insert(String::from("item1"), "value1");
-        match map
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "string");
-                assert_eq!(value, "value1. Cause: Expected: Long. Got: 
String");
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        assert_eq!(
-            buffer.as_slice(),
-            &[
-                4, 10, b'i', b't', b'e', b'm', b'1', 20, 10, b'i', b't', b'e', 
b'm', b'2', 160, 6,
-                0
-            ]
+        assert_serialize_err(
+            map,
+            &schema,
+            &names,
+            "Failed to serialize value of type `str` using Schema::Long: 
Expected Schema::String | Schema::Uuid(String)",
         );
 
         Ok(())
@@ -1274,43 +1313,20 @@ mod tests {
             Long(i64),
         }
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        Some(10i64).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        None::<i64>.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        NullableLong::Long(400).serialize(SchemaAwareSerializer::new(
-            &mut buffer,
+        assert_serialize(Some(10i64), &schema, &names, &[2, 20]);
+        assert_serialize(None::<i64>, &schema, &names, &[0]);
+        assert_serialize(NullableLong::Long(400), &schema, &names, &[2, 160, 
6]);
+        assert_serialize(NullableLong::Null, &schema, &names, &[0]);
+        assert_serialize(400i64, &schema, &names, &[2, 160, 6]);
+        assert_serialize((), &schema, &names, &[0]);
+        assert_serialize_err(
+            "invalid",
             &schema,
-            config,
-        )?)?;
-        NullableLong::Null.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-
-        match "invalid"
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "string");
-                assert_eq!(
-                    value,
-                    "invalid. Cause: Expected one of the union variants [Null, 
Long]. Got: String"
-                );
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        assert_eq!(buffer.as_slice(), &[2, 20, 0, 2, 160, 6, 0]);
+            &names,
+            "Failed to serialize value of type `str` using 
Schema::Union(UnionSchema { schemas: [Null, Long] }): Expected Schema::String 
in variants",
+        );
 
         Ok(())
     }
@@ -1330,48 +1346,23 @@ mod tests {
             Str(String),
         }
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
-
-        LongOrString::Null.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-        LongOrString::Long(400).serialize(SchemaAwareSerializer::new(
-            &mut buffer,
+        assert_serialize(LongOrString::Null, &schema, &names, &[0]);
+        assert_serialize(LongOrString::Long(400), &schema, &names, &[2, 160, 
6]);
+        assert_serialize(
+            LongOrString::Str("test".into()),
             &schema,
-            config,
-        )?)?;
-        
LongOrString::Str(String::from("test")).serialize(SchemaAwareSerializer::new(
-            &mut buffer,
+            &names,
+            &[4, 8, b't', b'e', b's', b't'],
+        );
+        assert_serialize((), &schema, &names, &[0]);
+        assert_serialize(400i64, &schema, &names, &[2, 160, 6]);
+        assert_serialize("test", &schema, &names, &[4, 8, b't', b'e', b's', 
b't']);
+        assert_serialize_err(
+            1f64,
             &schema,
-            config,
-        )?)?;
-
-        match 1_f64
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "f64");
-                assert_eq!(
-                    value,
-                    "1. Cause: Cannot find a Double schema in [Null, Long, 
String]"
-                );
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        assert_eq!(
-            buffer.as_slice(),
-            &[0, 2, 160, 6, 4, 8, b't', b'e', b's', b't']
+            &names,
+            "Failed to serialize value of type `f64` using 
Schema::Union(UnionSchema { schemas: [Null, Long, String] }): Expected 
Schema::Double in variants",
         );
 
         Ok(())
@@ -1387,75 +1378,32 @@ mod tests {
         }"#,
         )?;
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        Bytes::new(&[10, 124, 31, 97, 14, 201, 3, 
88]).serialize(SchemaAwareSerializer::new(
-            &mut buffer,
+        assert_serialize(
+            Bytes::new(&[10, 124, 31, 97, 14, 201, 3, 88]),
             &schema,
-            config,
-        )?)?;
-
-        // non-8 size
-        match Bytes::new(&[123])
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "bytes");
-                assert_eq!(
-                    value,
-                    "7b. Cause: Fixed schema size (8) does not match the value 
length (1)"
-                ); // Bytes represents its values as hexadecimals: '7b' is 123
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        // array
-        match [1; 8]
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "tuple"); // TODO: why is this 'tuple' 
?!
-                assert_eq!(value, "tuple (len=8). Cause: Expected: Fixed. Got: 
Array");
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        // slice
-        match &[1, 2, 3, 4, 5, 6, 7, 8]
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(*value_type, "tuple"); // TODO: why is this 'tuple' 
?!
-                assert_eq!(value, "tuple (len=8). Cause: Expected: Fixed. Got: 
Array");
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        assert_eq!(buffer.as_slice(), &[10, 124, 31, 97, 14, 201, 3, 88]);
+            &names,
+            &[10, 124, 31, 97, 14, 201, 3, 88],
+        );
+        assert_serialize_err(
+            Bytes::new(&[123]),
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `bytes` using 
Schema::Fixed(FixedSchema { name: Name { name: "LongVal", .. }, size: 8, .. }): 
Fixed size (8) does not match bytes length (1)"#,
+        );
+        assert_serialize_err(
+            [1u8; 8],
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `tuple` using 
Schema::Fixed(FixedSchema { name: Name { name: "LongVal", .. }, size: 8, .. }): 
Expected Schema::Record(fields.len() == 8)"#,
+        );
+        assert_serialize_err(
+            [1u8, 2, 3, 4, 5, 6, 7, 8].as_slice(),
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `seq` using 
Schema::Fixed(FixedSchema { name: Name { name: "LongVal", .. }, size: 8, .. }): 
Expected Schema::Array"#,
+        );
 
         Ok(())
     }
@@ -1471,31 +1419,16 @@ mod tests {
         }"#,
         )?;
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
         let val = Decimal::from(&[251, 155]);
-        val.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-
-        match ().serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?).map_err(Error::into_details) {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "none");
-                assert_eq!(value, "None. Cause: Expected: Decimal. Got: Null");
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        assert_eq!(buffer.as_slice(), &[4, 251, 155]);
+        assert_serialize(val, &schema, &names, &[4, 251, 155]);
+        assert_serialize_err(
+            (),
+            &schema,
+            &names,
+            "Failed to serialize value of type `unit` using 
Schema::Decimal(DecimalSchema { precision: 16, scale: 2, inner: Bytes }): 
Expected Schema::Null",
+        );
 
         Ok(())
     }
@@ -1513,31 +1446,16 @@ mod tests {
         }"#,
         )?;
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
         let val = Decimal::from(&[0, 0, 0, 0, 0, 0, 251, 155]);
-        val.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-
-        match ().serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?).map_err(Error::into_details) {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "none");
-                assert_eq!(value, "None. Cause: Expected: Decimal. Got: Null");
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        assert_eq!(buffer.as_slice(), &[0, 0, 0, 0, 0, 0, 251, 155]);
+        assert_serialize(val, &schema, &names, &[0, 0, 0, 0, 0, 0, 251, 155]);
+        assert_serialize_err(
+            (),
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `unit` using 
Schema::Decimal(DecimalSchema { precision: 16, scale: 2, inner: 
Fixed(FixedSchema { name: Name { name: "FixedDecimal", .. }, size: 8, 
attributes: {"precision": Number(16), "scale": Number(2)}, .. }) }): Expected 
Schema::Null"#,
+        );
 
         Ok(())
     }
@@ -1551,18 +1469,21 @@ mod tests {
         }"#,
         )?;
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        let val = BigDecimal::new(BigInt::new(Sign::Plus, vec![50024]), 2);
-        val.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
+        #[derive(Serialize)]
+        #[serde(transparent)]
+        struct BigDecimalWrapper {
+            // This is needed because the Serialize implementation of 
BigDecimal serializes to a string.
+            // The with implementation serializes to bytes.
+            #[serde(with = "crate::serde::bigdecimal")]
+            value: BigDecimal,
+        }
 
-        assert_eq!(buffer.as_slice(), &[10, 6, 0, 195, 104, 4]);
+        let val = BigDecimalWrapper {
+            value: BigDecimal::new(BigInt::new(Sign::Plus, vec![50024]), 2),
+        };
+        assert_serialize(val, &schema, &names, &[10, 6, 0, 195, 104, 4]);
 
         Ok(())
     }
@@ -1578,40 +1499,25 @@ mod tests {
         }"#,
         )?;
 
+        // Uuid serialize implementation changes based on this value
         assert!(!crate::util::is_human_readable());
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        "8c28da81-238c-4326-bddd-4e3d00cc5099"
-            .parse::<Uuid>()?
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
+        let uuid = "8c28da81-238c-4326-bddd-4e3d00cc5099".parse::<Uuid>()?;
 
-        match 1_u8
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "u8");
-                assert_eq!(value, "1. Cause: Expected: Uuid. Got: Int");
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        assert_eq!(
-            buffer.as_slice(),
+        assert_serialize(
+            uuid,
+            &schema,
+            &names,
             &[
-                140, 40, 218, 129, 35, 140, 67, 38, 189, 221, 78, 61, 0, 204, 
80, 153
-            ]
+                140, 40, 218, 129, 35, 140, 67, 38, 189, 221, 78, 61, 0, 204, 
80, 153,
+            ],
+        );
+        assert_serialize_err(
+            1u8,
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `u8` using 
Schema::Uuid(Fixed(FixedSchema { name: Name { name: "FixedUuid", .. }, size: 
16, .. })): Expected Schema::Int | Schema::Date | Schema::TimeMillis"#,
         );
 
         Ok(())
@@ -1626,39 +1532,23 @@ mod tests {
         }"#,
         )?;
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
-
-        100_u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        1000_u16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        10000_u32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        1000_i16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        10000_i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-
-        match 10000_f32
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "f32");
-                assert_eq!(value, "10000. Cause: Expected: Date. Got: Float");
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
 
-        assert_eq!(
-            buffer.as_slice(),
-            &[200, 1, 208, 15, 160, 156, 1, 208, 15, 160, 156, 1]
+        assert_serialize(100u8, &schema, &names, &[200, 1]);
+        assert_serialize(1000u16, &schema, &names, &[208, 15]);
+        assert_serialize(1000i16, &schema, &names, &[208, 15]);
+        assert_serialize(10000i32, &schema, &names, &[160, 156, 1]);
+        assert_serialize_err(
+            10000u32,
+            &schema,
+            &names,
+            "Failed to serialize value of type `u32` using Schema::Date: 
Expected Schema::Long | Schema::TimeMicros | 
Schema::{,Local}Timestamp{Millis,Micros,Nanos}",
+        );
+        assert_serialize_err(
+            10000f32,
+            &schema,
+            &names,
+            "Failed to serialize value of type `f32` using Schema::Date: 
Expected Schema::Float",
         );
 
         Ok(())
@@ -1673,39 +1563,23 @@ mod tests {
         }"#,
         )?;
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
-
-        100_u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        1000_u16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        10000_u32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        1000_i16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        10000_i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
 
-        match 10000_f32
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "f32");
-                assert_eq!(value, "10000. Cause: Expected: TimeMillis. Got: 
Float");
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        assert_eq!(
-            buffer.as_slice(),
-            &[200, 1, 208, 15, 160, 156, 1, 208, 15, 160, 156, 1]
+        assert_serialize(100u8, &schema, &names, &[200, 1]);
+        assert_serialize(1000u16, &schema, &names, &[208, 15]);
+        assert_serialize(1000i16, &schema, &names, &[208, 15]);
+        assert_serialize(10000i32, &schema, &names, &[160, 156, 1]);
+        assert_serialize_err(
+            10000u32,
+            &schema,
+            &names,
+            "Failed to serialize value of type `u32` using Schema::TimeMillis: 
Expected Schema::Long | Schema::TimeMicros | 
Schema::{,Local}Timestamp{Millis,Micros,Nanos}",
+        );
+        assert_serialize_err(
+            10000f32,
+            &schema,
+            &names,
+            "Failed to serialize value of type `f32` using Schema::TimeMillis: 
Expected Schema::Float",
         );
 
         Ok(())
@@ -1720,42 +1594,39 @@ mod tests {
         }"#,
         )?;
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
-
-        100_u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        1000_u16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        10000_u32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        1000_i16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        10000_i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        10000_i64.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-
-        match 10000_f32
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "f32");
-                assert_eq!(value, "10000. Cause: Expected: TimeMicros. Got: 
Float");
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
 
-        assert_eq!(
-            buffer.as_slice(),
-            &[
-                200, 1, 208, 15, 160, 156, 1, 208, 15, 160, 156, 1, 160, 156, 1
-            ]
+        assert_serialize(10000u32, &schema, &names, &[160, 156, 1]);
+        assert_serialize(10000i64, &schema, &names, &[160, 156, 1]);
+        assert_serialize_err(
+            100u8,
+            &schema,
+            &names,
+            "Failed to serialize value of type `u8` using Schema::TimeMicros: 
Expected Schema::Int | Schema::Date | Schema::TimeMillis",
+        );
+        assert_serialize_err(
+            1000u16,
+            &schema,
+            &names,
+            "Failed to serialize value of type `u16` using Schema::TimeMicros: 
Expected Schema::Int | Schema::Date | Schema::TimeMillis",
+        );
+        assert_serialize_err(
+            1000i16,
+            &schema,
+            &names,
+            "Failed to serialize value of type `i16` using Schema::TimeMicros: 
Expected Schema::Int | Schema::Date | Schema::TimeMillis",
+        );
+        assert_serialize_err(
+            10000i32,
+            &schema,
+            &names,
+            "Failed to serialize value of type `i32` using Schema::TimeMicros: 
Expected Schema::Int | Schema::Date | Schema::TimeMillis",
+        );
+        assert_serialize_err(
+            10000f32,
+            &schema,
+            &names,
+            "Failed to serialize value of type `f32` using Schema::TimeMicros: 
Expected Schema::Float",
         );
 
         Ok(())
@@ -1763,7 +1634,11 @@ mod tests {
 
     #[test]
     fn test_serialize_timestamp() -> TestResult {
-        for precision in ["millis", "micros", "nanos"] {
+        for (precision, error) in [
+            ("millis", "Millis"),
+            ("micros", "Micros"),
+            ("nanos", "Nanos"),
+        ] {
             let schema = Schema::parse_str(&format!(
                 r#"{{
                 "type": "long",
@@ -1771,50 +1646,49 @@ mod tests {
             }}"#
             ))?;
 
-            let mut buffer: Vec<u8> = Vec::new();
             let names = HashMap::new();
-            let config = Config::<'_, Schema> {
-                names: &names,
-                target_block_size: None,
-                human_readable: false,
-            };
-            100_u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-            1000_u16.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-            10000_u32.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-            1000_i16.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-            10000_i32.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-            10000_i64.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-
-            match 10000_f64
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .map_err(Error::into_details)
-            {
-                Err(Details::SerializeValueWithSchema {
-                    value_type,
-                    value,
-                    schema,
-                }) => {
-                    let mut capital_precision = precision.to_string();
-                    if let Some(c) = capital_precision.chars().next() {
-                        capital_precision.replace_range(..1, 
&c.to_uppercase().to_string());
-                    }
-                    assert_eq!(value_type, "f64");
-                    assert_eq!(
-                        value,
-                        format!(
-                            "10000. Cause: Expected: 
Timestamp{capital_precision}. Got: Double"
-                        )
-                    );
-                    assert_eq!(schema, schema);
-                }
-                unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-            }
 
-            assert_eq!(
-                buffer.as_slice(),
-                &[
-                    200, 1, 208, 15, 160, 156, 1, 208, 15, 160, 156, 1, 160, 
156, 1
-                ]
+            assert_serialize(10000u32, &schema, &names, &[160, 156, 1]);
+            assert_serialize(10000i64, &schema, &names, &[160, 156, 1]);
+            assert_serialize_err(
+                100u8,
+                &schema,
+                &names,
+                &format!(
+                    "Failed to serialize value of type `u8` using 
Schema::Timestamp{error}: Expected Schema::Int | Schema::Date | 
Schema::TimeMillis"
+                ),
+            );
+            assert_serialize_err(
+                1000u16,
+                &schema,
+                &names,
+                &format!(
+                    "Failed to serialize value of type `u16` using 
Schema::Timestamp{error}: Expected Schema::Int | Schema::Date | 
Schema::TimeMillis"
+                ),
+            );
+            assert_serialize_err(
+                1000i16,
+                &schema,
+                &names,
+                &format!(
+                    "Failed to serialize value of type `i16` using 
Schema::Timestamp{error}: Expected Schema::Int | Schema::Date | 
Schema::TimeMillis"
+                ),
+            );
+            assert_serialize_err(
+                10000i32,
+                &schema,
+                &names,
+                &format!(
+                    "Failed to serialize value of type `i32` using 
Schema::Timestamp{error}: Expected Schema::Int | Schema::Date | 
Schema::TimeMillis"
+                ),
+            );
+            assert_serialize_err(
+                10000f32,
+                &schema,
+                &names,
+                &format!(
+                    "Failed to serialize value of type `f32` using 
Schema::Timestamp{error}: Expected Schema::Float"
+                ),
             );
         }
 
@@ -1832,38 +1706,21 @@ mod tests {
         }"#,
         )?;
 
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        let duration_bytes =
-            ByteArray::new(Duration::new(Months::new(3), Days::new(2), 
Millis::new(1200)).into());
-        duration_bytes.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-
-        match [1; 12]
-            .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-            .map_err(Error::into_details)
-        {
-            Err(Details::SerializeValueWithSchema {
-                value_type,
-                value,
-                schema,
-            }) => {
-                assert_eq!(value_type, "tuple"); // TODO: why is this 'tuple' 
?!
-                assert_eq!(
-                    value,
-                    "tuple (len=12). Cause: Expected: Duration. Got: Array"
-                );
-                assert_eq!(schema, schema);
-            }
-            unexpected => panic!("Expected an error. Got: {unexpected:?}"),
-        }
-
-        assert_eq!(buffer.as_slice(), &[3, 0, 0, 0, 2, 0, 0, 0, 176, 4, 0, 0]);
+        let duration = Duration::new(Months::new(3), Days::new(2), 
Millis::new(1200));
+        assert_serialize(
+            duration,
+            &schema,
+            &names,
+            &[3, 0, 0, 0, 2, 0, 0, 0, 176, 4, 0, 0],
+        );
+        assert_serialize_err(
+            [0u8; 12],
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `tuple` using 
Schema::Duration(FixedSchema { name: Name { name: "duration", .. }, size: 12, 
.. }): Expected Schema::Record(fields.len() == 12)"#,
+        );
 
         Ok(())
     }
@@ -1877,7 +1734,6 @@ mod tests {
             "fields": [
                 {"name": "stringField", "type": "string"},
                 {"name": "intField", "type": "int"},
-                {"name": "bigDecimalField", "type": {"type": "bytes", 
"logicalType": "big-decimal"}},
                 {"name": "uuidField", "type": {"name": "uuid", "type": 
"fixed", "size": 16, "logicalType": "uuid"}},
                 {"name": "innerRecord", "type": ["null", "TestRecord"]}
             ]
@@ -1889,44 +1745,34 @@ mod tests {
         struct TestRecord {
             string_field: String,
             int_field: i32,
-            big_decimal_field: BigDecimal,
             uuid_field: Uuid,
             // #[serde(skip_serializing_if = "Option::is_none")] => Never 
ignore None!
             inner_record: Option<Box<TestRecord>>,
         }
 
         assert!(!crate::util::is_human_readable());
-        let mut buffer: Vec<u8> = Vec::new();
         let rs = ResolvedSchema::try_from(&schema)?;
-        let config = Config {
-            names: rs.get_names(),
-            target_block_size: None,
-            human_readable: false,
-        };
 
         let good_record = TestRecord {
             string_field: String::from("test"),
             int_field: 10,
-            big_decimal_field: BigDecimal::new(BigInt::new(Sign::Plus, 
vec![50024]), 2),
             uuid_field: 
"8c28da81-238c-4326-bddd-4e3d00cc5098".parse::<Uuid>()?,
             inner_record: Some(Box::new(TestRecord {
                 string_field: String::from("inner_test"),
                 int_field: 100,
-                big_decimal_field: BigDecimal::new(BigInt::new(Sign::Plus, 
vec![20038]), 2),
                 uuid_field: 
"8c28da81-238c-4326-bddd-4e3d00cc5099".parse::<Uuid>()?,
                 inner_record: None,
             })),
         };
-        good_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-
-        assert_eq!(
-            buffer.as_slice(),
+        assert_serialize(
+            good_record,
+            &schema,
+            rs.get_names(),
             &[
-                8, 116, 101, 115, 116, 20, 10, 6, 0, 195, 104, 4, 140, 40, 
218, 129, 35, 140, 67,
-                38, 189, 221, 78, 61, 0, 204, 80, 152, 2, 20, 105, 110, 110, 
101, 114, 95, 116,
-                101, 115, 116, 200, 1, 8, 4, 78, 70, 4, 140, 40, 218, 129, 35, 
140, 67, 38, 189,
-                221, 78, 61, 0, 204, 80, 153, 0
-            ]
+                8, 116, 101, 115, 116, 20, 140, 40, 218, 129, 35, 140, 67, 38, 
189, 221, 78, 61, 0,
+                204, 80, 152, 2, 20, 105, 110, 110, 101, 114, 95, 116, 101, 
115, 116, 200, 1, 140,
+                40, 218, 129, 35, 140, 67, 38, 189, 221, 78, 61, 0, 204, 80, 
153, 0,
+            ],
         );
 
         Ok(())
@@ -1980,34 +1826,43 @@ mod tests {
             bar: String,
         }
 
-        let mut buffer: Vec<u8> = Vec::new();
         let rs = ResolvedSchema::try_from(&schema)?;
-        let config = Config {
-            names: rs.get_names(),
-            target_block_size: None,
-            human_readable: false,
-        };
 
         let foo_record = TestRecord {
             inner_union: InnerUnion::InnerVariantFoo(InnerRecordFoo {
                 foo: String::from("foo"),
             }),
         };
-        foo_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
+        assert_serialize(
+            foo_record,
+            &schema,
+            rs.get_names(),
+            &[0, 6, b'f', b'o', b'o'],
+        );
         let bar_record = TestRecord {
             inner_union: InnerUnion::InnerVariantBar(InnerRecordBar {
                 bar: String::from("bar"),
             }),
         };
-        bar_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
+        assert_serialize(
+            bar_record,
+            &schema,
+            rs.get_names(),
+            &[2, 6, b'b', b'a', b'r'],
+        );
         let int_record = TestRecord {
             inner_union: InnerUnion::IntField(1),
         };
-        int_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
+        assert_serialize(int_record, &schema, rs.get_names(), &[4, 2]);
         let string_record = TestRecord {
             inner_union: InnerUnion::StringField(String::from("string")),
         };
-        string_record.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
+        assert_serialize(
+            string_record,
+            &schema,
+            rs.get_names(),
+            &[6, 12, b's', b't', b'r', b'i', b'n', b'g'],
+        );
         Ok(())
     }
 
@@ -2042,54 +1897,28 @@ mod tests {
         #[derive(Serialize)]
         #[serde(untagged)]
         enum InnerUnion {
-            InnerVariantFoo(InnerRecordFoo),
-            InnerVariantBar(InnerRecordBar),
             IntField(i32),
-            StringField(String),
-        }
-
-        #[derive(Serialize)]
-        #[serde(rename = "innerRecordFoo")]
-        struct InnerRecordFoo {
-            foo: String,
         }
 
-        #[derive(Serialize)]
-        #[serde(rename = "innerRecordBar")]
-        struct InnerRecordBar {
-            bar: String,
-        }
-
-        let mut buffer: Vec<u8> = Vec::new();
         let rs = ResolvedSchema::try_from(&schema)?;
-        let config = Config {
-            names: rs.get_names(),
-            target_block_size: None,
-            human_readable: false,
-        };
 
+        // Flattening a Option into the underlying union is NOT supported
         let null_record = TestRecord { inner_union: None };
-        null_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
+        assert_serialize_err(
+            null_record,
+            &schema,
+            rs.get_names(),
+            r#"Failed to serialize field 'innerUnion' of record RecordSchema { 
name: Name { name: "TestRecord", .. }, fields: [RecordField { name: 
"innerUnion", schema: Union(UnionSchema { schemas: [Null, Record(RecordSchema { 
name: Name { name: "innerRecordFoo", .. }, fields: [RecordField { name: "foo", 
schema: String, .. }], .. }), Record(RecordSchema { name: Name { name: 
"innerRecordBar", .. }, fields: [RecordField { name: "bar", schema: String, .. 
}], .. }), Int, String] }), .. }], . [...]
+        );
         let foo_record = TestRecord {
-            inner_union: Some(InnerUnion::InnerVariantFoo(InnerRecordFoo {
-                foo: String::from("foo"),
-            })),
+            inner_union: Some(InnerUnion::IntField(42)),
         };
-        foo_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        let bar_record = TestRecord {
-            inner_union: Some(InnerUnion::InnerVariantBar(InnerRecordBar {
-                bar: String::from("bar"),
-            })),
-        };
-        bar_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        let int_record = TestRecord {
-            inner_union: Some(InnerUnion::IntField(1)),
-        };
-        int_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-        let string_record = TestRecord {
-            inner_union: Some(InnerUnion::StringField(String::from("string"))),
-        };
-        string_record.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
+        assert_serialize_err(
+            foo_record,
+            &schema,
+            rs.get_names(),
+            r#"Failed to serialize field 'innerUnion' of record RecordSchema { 
name: Name { name: "TestRecord", .. }, fields: [RecordField { name: 
"innerUnion", schema: Union(UnionSchema { schemas: [Null, Record(RecordSchema { 
name: Name { name: "innerRecordFoo", .. }, fields: [RecordField { name: "foo", 
schema: String, .. }], .. }), Record(RecordSchema { name: Name { name: 
"innerRecordBar", .. }, fields: [RecordField { name: "bar", schema: String, .. 
}], .. }), Int, String] }), .. }], . [...]
+        );
         Ok(())
     }
 
@@ -2099,9 +1928,6 @@ mod tests {
         struct Foo {
             a: String,
             b: String,
-            c: usize,
-            d: f64,
-            e: usize,
         }
         let schema = Schema::parse_str(
             r#"
@@ -2116,65 +1942,34 @@ mod tests {
                 {
                     "name":"a",
                     "type":"string"
-                },
-                {
-                    "name":"d",
-                    "type":"double"
-                },
-                {
-                    "name":"e",
-                    "type":"long"
-                },
-                {
-                    "name":"c",
-                    "type":"long"
                 }
             ]
         }
         "#,
         )?;
 
-        let mut writer = Writer::new(&schema, Vec::new())?;
-        writer.append_ser(Foo {
+        let names = HashMap::new();
+        let foo = Foo {
             a: "Hello".into(),
             b: "World".into(),
-            c: 42,
-            d: std::f64::consts::PI,
-            e: 5,
-        })?;
-        let encoded = writer.into_inner()?;
-        let mut reader = Reader::builder(&encoded[..])
-            .reader_schema(&schema)
-            .build()?;
-        let decoded = from_value::<Foo>(&reader.next().unwrap()?)?;
-        assert_eq!(
-            decoded,
-            Foo {
-                a: "Hello".into(),
-                b: "World".into(),
-                c: 42,
-                d: std::f64::consts::PI,
-                e: 5
-            }
+        };
+        // Serializing fields out of order is NOT supported
+        assert_serialize_err(
+            foo,
+            &schema,
+            &names,
+            r#"Missing default for skipped field 'b' of schema RecordSchema { 
name: Name { name: "Foo", .. }, fields: [RecordField { name: "b", schema: 
String, .. }, RecordField { name: "a", schema: String, .. }], .. }"#,
         );
+
         Ok(())
     }
 
     #[test]
     fn avro_rs_414_serialize_char_as_string() -> TestResult {
         let schema = Schema::String;
-
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
-
-        'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
 
-        assert_eq!(buffer.as_slice(), &[2, b'a']);
+        assert_serialize('a', &schema, &names, &[2, b'a']);
 
         Ok(())
     }
@@ -2182,18 +1977,14 @@ mod tests {
     #[test]
     fn avro_rs_414_serialize_char_as_bytes() -> TestResult {
         let schema = Schema::Bytes;
-
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-
-        assert_eq!(buffer.as_slice(), &[2, b'a']);
+        assert_serialize_err(
+            'a',
+            &schema,
+            &names,
+            "Failed to serialize value of type `char` using Schema::Bytes: 
Expected Schema::String",
+        );
 
         Ok(())
     }
@@ -2207,18 +1998,14 @@ mod tests {
             size: 4,
             attributes: Default::default(),
         });
-
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-
-        assert_eq!(buffer.as_slice(), &[b'a', 0, 0, 0]);
+        assert_serialize_err(
+            'a',
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `char` using 
Schema::Fixed(FixedSchema { name: Name { name: "char", .. }, size: 4, .. }): 
Expected Schema::String"#,
+        );
 
         Ok(())
     }
@@ -2226,120 +2013,9 @@ mod tests {
     #[test]
     fn avro_rs_414_serialize_emoji_char_as_string() -> TestResult {
         let schema = Schema::String;
-
-        let mut buffer: Vec<u8> = Vec::new();
-        let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
-
-        '👹'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-
-        assert_eq!(buffer.as_slice(), &[8, 240, 159, 145, 185]);
-
-        Ok(())
-    }
-
-    #[test]
-    fn avro_rs_414_serialize_emoji_char_as_bytes() -> TestResult {
-        let schema = Schema::Bytes;
-
-        let mut buffer: Vec<u8> = Vec::new();
-        let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
-
-        '👹'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-
-        assert_eq!(buffer.as_slice(), &[8, 240, 159, 145, 185]);
-
-        Ok(())
-    }
-
-    #[test]
-    fn avro_rs_414_serialize_emoji_char_as_fixed() -> TestResult {
-        let schema = Schema::Fixed(FixedSchema {
-            name: Name::new("char")?,
-            aliases: None,
-            doc: None,
-            size: 4,
-            attributes: Default::default(),
-        });
-
-        let mut buffer: Vec<u8> = Vec::new();
-        let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
-
-        '👹'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)?;
-
-        // This is a different byte value than the tests above. This is 
because by creating a String
-        // the unicode value is normalized by Rust
-        assert_eq!(buffer.as_slice(), &[121, 244, 1, 0]);
-
-        Ok(())
-    }
-
-    #[test]
-    fn avro_rs_414_serialize_char_as_fixed_wrong_name() -> TestResult {
-        let schema = Schema::Fixed(FixedSchema {
-            name: Name::new("characters")?,
-            aliases: None,
-            doc: None,
-            size: 4,
-            attributes: Default::default(),
-        });
-
-        let mut buffer: Vec<u8> = Vec::new();
-        let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
-
-        assert!(matches!(
-            'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .unwrap_err()
-                .details(),
-            Details::SerializeValueWithSchema { .. }
-        ));
-
-        Ok(())
-    }
-
-    #[test]
-    fn avro_rs_414_serialize_char_as_fixed_wrong_size() -> TestResult {
-        let schema = Schema::Fixed(FixedSchema {
-            name: Name::new("char")?,
-            aliases: None,
-            doc: None,
-            size: 1,
-            attributes: Default::default(),
-        });
-
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        assert!(matches!(
-            'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .unwrap_err()
-                .details(),
-            Details::SerializeValueWithSchema { .. }
-        ));
+        assert_serialize('👹', &schema, &names, &[8, 240, 159, 145, 185]);
 
         Ok(())
     }
@@ -2353,25 +2029,16 @@ mod tests {
             size: 16,
             attributes: Default::default(),
         });
-
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
-
-        let bytes_written =
-            i128::MAX.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-        assert_eq!(bytes_written, 16);
 
-        assert_eq!(
-            buffer.as_slice(),
+        assert_serialize(
+            i128::MAX,
+            &schema,
+            &names,
             &[
                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF,
-                0xFF, 0x7F
-            ]
+                0xFF, 0x7F,
+            ],
         );
 
         Ok(())
@@ -2386,22 +2053,14 @@ mod tests {
             size: 16,
             attributes: Default::default(),
         });
-
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        assert!(matches!(
-            i128::MAX
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .unwrap_err()
-                .details(),
-            Details::SerializeValueWithSchema { .. }
-        ));
+        assert_serialize_err(
+            i128::MAX,
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `i128` using 
Schema::Fixed(FixedSchema { name: Name { name: "onehundredtwentyeight", .. }, 
size: 16, .. }): Expected Schema::Fixed(name: "i128", size: 16)"#,
+        );
 
         Ok(())
     }
@@ -2415,22 +2074,14 @@ mod tests {
             size: 8,
             attributes: Default::default(),
         });
-
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        assert!(matches!(
-            i128::MAX
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .unwrap_err()
-                .details(),
-            Details::SerializeValueWithSchema { .. }
-        ));
+        assert_serialize_err(
+            i128::MAX,
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `i128` using 
Schema::Fixed(FixedSchema { name: Name { name: "i128", .. }, size: 8, .. }): 
Expected Schema::Fixed(name: "i128", size: 16)"#,
+        );
 
         Ok(())
     }
@@ -2444,25 +2095,16 @@ mod tests {
             size: 16,
             attributes: Default::default(),
         });
-
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        let bytes_written =
-            u128::MAX.serialize(SchemaAwareSerializer::new(&mut buffer, 
&schema, config)?)?;
-        assert_eq!(bytes_written, 16);
-
-        assert_eq!(
-            buffer.as_slice(),
+        assert_serialize(
+            u128::MAX,
+            &schema,
+            &names,
             &[
                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF,
-                0xFF, 0xFF
-            ]
+                0xFF, 0xFF,
+            ],
         );
 
         Ok(())
@@ -2477,22 +2119,14 @@ mod tests {
             size: 16,
             attributes: Default::default(),
         });
-
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        assert!(matches!(
-            u128::MAX
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .unwrap_err()
-                .details(),
-            Details::SerializeValueWithSchema { .. }
-        ));
+        assert_serialize_err(
+            u128::MAX,
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `u128` using 
Schema::Fixed(FixedSchema { name: Name { name: "onehundredtwentyeight", .. }, 
size: 16, .. }): Expected Schema::Fixed(name: "u128", size: 16)"#,
+        );
 
         Ok(())
     }
@@ -2506,22 +2140,14 @@ mod tests {
             size: 8,
             attributes: Default::default(),
         });
-
-        let mut buffer: Vec<u8> = Vec::new();
         let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
 
-        assert!(matches!(
-            u128::MAX
-                .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, 
config)?)
-                .unwrap_err()
-                .details(),
-            Details::SerializeValueWithSchema { .. }
-        ));
+        assert_serialize_err(
+            u128::MAX,
+            &schema,
+            &names,
+            r#"Failed to serialize value of type `u128` using 
Schema::Fixed(FixedSchema { name: Name { name: "u128", .. }, size: 8, .. }): 
Expected Schema::Fixed(name: "u128", size: 16)"#,
+        );
 
         Ok(())
     }
@@ -2533,28 +2159,15 @@ mod tests {
             { "name": "fixed4", "type": "fixed", "size": 4 },
             { "name": "fixed8", "type": "fixed", "size": 8 }
         ]"#,
-        )
-        .unwrap();
-
-        let mut buffer: Vec<u8> = Vec::new();
-        let names = HashMap::new();
-        let config = Config::<'_, Schema> {
-            names: &names,
-            target_block_size: None,
-            human_readable: false,
-        };
-        let bytes_written = crate::serde::fixed::serialize(
-            &[0, 1, 2, 3],
-            SchemaAwareSerializer::new(&mut buffer, &schema, config)?,
         )?;
-        assert_eq!(bytes_written, 4);
-        let bytes_written = crate::serde::fixed::serialize(
-            &[4, 5, 6, 7, 8, 9, 10, 11],
-            SchemaAwareSerializer::new(&mut buffer, &schema, config)?,
-        )?;
-        assert_eq!(bytes_written, 8);
-
-        assert_eq!(buffer, &[0, 0, 1, 2, 3, 2, 4, 5, 6, 7, 8, 9, 10, 11][..]);
+        let names = HashMap::new();
+        assert_serialize(Bytes::new(&[0, 1, 2, 3]), &schema, &names, &[0, 0, 
1, 2, 3]);
+        assert_serialize(
+            Bytes::new(&[4, 5, 6, 7, 8, 9, 10, 11]),
+            &schema,
+            &names,
+            &[2, 4, 5, 6, 7, 8, 9, 10, 11],
+        );
 
         Ok(())
     }
diff --git a/avro/src/serde/ser_schema/record/mod.rs 
b/avro/src/serde/ser_schema/record/mod.rs
index 853867d..0d44828 100644
--- a/avro/src/serde/ser_schema/record/mod.rs
+++ b/avro/src/serde/ser_schema/record/mod.rs
@@ -53,6 +53,37 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> 
RecordSerializer<'s, 'w, W, S> {
         }
     }
 
+    fn field_error(&self, position: usize, error: Error) -> Error {
+        let field = &self.record.fields[position];
+        let error = match error.into_details() {
+            Details::SerializeValueWithSchema {
+                value_type,
+                value,
+                schema: _,
+            } => format!("Failed to serialize value of type `{value_type}`: 
{value}"),
+            Details::SerializeRecordFieldWithSchema {
+                field_name,
+                record_schema,
+                error,
+            } => format!(
+                "Failed to serialize field '{field_name}' of record {}: 
{error}",
+                record_schema.name
+            ),
+            Details::MissingDefaultForSkippedField { field_name, schema } => {
+                format!(
+                    "Missing default for skipped field '{field_name}' for 
record {}",
+                    schema.name
+                )
+            }
+            details => format!("{details:?}"),
+        };
+        Error::new(Details::SerializeRecordFieldWithSchema {
+            field_name: field.name.clone(),
+            record_schema: self.record.clone(),
+            error,
+        })
+    }
+
     fn serialize_next_field<T: ?Sized + Serialize>(
         &mut self,
         position: usize,
@@ -60,14 +91,16 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> 
RecordSerializer<'s, 'w, W, S> {
     ) -> Result<(), Error> {
         // Serialize any skipped fields using their default value
         while self.field_position < position {
-            self.serialize_default(position)?;
+            self.serialize_default(self.field_position)?;
         }
         let field = &self.record.fields[position];
-        self.bytes_written += value.serialize(SchemaAwareSerializer::new(
-            self.writer,
-            &field.schema,
-            self.config,
-        )?)?;
+        self.bytes_written += value
+            .serialize(SchemaAwareSerializer::new(
+                self.writer,
+                &field.schema,
+                self.config,
+            )?)
+            .map_err(|e| self.field_error(self.field_position, e))?;
         self.field_position += 1;
 
         Ok(())
@@ -77,21 +110,14 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> 
RecordSerializer<'s, 'w, W, S> {
         let field = &self.record.fields[position];
         if let Some(default) = &field.default {
             self.serialize_next_field(
-                self.field_position,
+                position,
                 &SchemaAwareRecordFieldDefault::new(default, &field.schema),
             )
-            .map_err(|e| {
-                Details::SerializeRecordFieldWithSchema {
-                    field_name: field.name.clone(),
-                    record_schema: Schema::Record(self.record.clone()),
-                    error: Box::new(e),
-                }
-                .into()
-            })
+            .map_err(|e| self.field_error(position, e))
         } else {
             Err(Details::MissingDefaultForSkippedField {
                 field_name: field.name.clone(),
-                schema: Schema::Record(self.record.clone()),
+                schema: self.record.clone(),
             }
             .into())
         }
diff --git a/avro/src/serde/with.rs b/avro/src/serde/with.rs
index 29744e1..74c0a3a 100644
--- a/avro/src/serde/with.rs
+++ b/avro/src/serde/with.rs
@@ -503,6 +503,315 @@ pub mod slice_opt {
     }
 }
 
+/// (De)serialize [`BigDecimal`] as a [`Schema::BigDecimal`] instead of a 
[`Schema::String`].
+///
+/// This module is intended to be used through the Serde `with` attribute.
+///
+/// Use [`apache_avro::serde::bigdecimal_opt`] for optional big decimals 
values.
+///
+/// When used with different serialization formats, this will write bytes.
+///
+/// See usage with below example:
+/// ```
+/// # use apache_avro::AvroSchema;
+/// # use serde::{Deserialize, Serialize};
+/// #[derive(AvroSchema, Serialize, Deserialize)]
+/// struct StructWithBigDecimal<'a> {
+///     #[avro(with)]
+///     #[serde(with = "apache_avro::serde::bigdecimal")]
+///     decimal: BigDecimal,
+/// }
+/// ```
+///
+/// [`BigDecimal`]: ::bigdecimal::BigDecimal
+/// [`Schema::BigDecimal`]: crate::Schema::BigDecimal
+/// [`Schema::String`]: crate::Schema::String
+/// [`apache_avro::serde::bigdecimal_opt`]: bigdecimal_opt
+pub mod bigdecimal {
+    use std::collections::HashSet;
+
+    use bigdecimal::BigDecimal;
+    use serde::{Deserializer, Serializer, de::Error as _, ser::Error as _};
+
+    use crate::{
+        Schema,
+        bigdecimal::{big_decimal_as_bytes, deserialize_big_decimal},
+        schema::{Name, NamespaceRef, RecordField},
+        serde::with::BytesType,
+    };
+
+    /// Returns [`Schema::BigDecimal`]
+    pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> 
Schema {
+        Schema::BigDecimal
+    }
+
+    /// Returns `None`
+    pub fn get_record_fields_in_ctxt(
+        _: &mut HashSet<Name>,
+        _: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
+        None
+    }
+
+    pub fn serialize<S>(decimal: &BigDecimal, serializer: S) -> Result<S::Ok, 
S::Error>
+    where
+        S: Serializer,
+    {
+        let _guard = super::BytesTypeGuard::set(BytesType::Bytes);
+        let decimal_bytes = 
big_decimal_as_bytes(decimal).map_err(S::Error::custom)?;
+        serde_bytes::serialize(&decimal_bytes, serializer)
+    }
+
+    pub fn deserialize<'de, D>(deserializer: D) -> Result<BigDecimal, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let _bytes_guard = super::BytesTypeGuard::set(BytesType::Bytes);
+        let _guard = super::BorrowedGuard::set(true);
+        // We don't use &'de [u8] here as the deserializer doesn't support that
+        let bytes: Vec<u8> = serde_bytes::deserialize(deserializer)?;
+
+        deserialize_big_decimal(&bytes).map_err(D::Error::custom)
+    }
+}
+
+/// (De)serialize [`Option<BigDecimal>`] as a `Schema::Union(Schema::Null, 
Schema::BigDecimal)` instead of a `Schema::Union(Schema::Null, Schema::String)`.
+///
+/// This module is intended to be used through the Serde `with` attribute.
+///
+/// Use [`apache_avro::serde::bigdecimal`] for non-optional big decimals 
values.
+///
+/// When used with different serialization formats, this will write bytes.
+///
+/// See usage with below example:
+/// ```
+/// # use apache_avro::AvroSchema;
+/// # use serde::{Deserialize, Serialize};
+/// #[derive(AvroSchema, Serialize, Deserialize)]
+/// struct StructWithBigDecimal<'a> {
+///     #[avro(with)]
+///     #[serde(with = "apache_avro::serde::bigdecimal_opt")]
+///     decimal: Option<BigDecimal>,
+/// }
+/// ```
+///
+/// [`Option<BigDecimal>`]: ::bigdecimal::BigDecimal
+/// [`apache_avro::serde::bigdecimal`]: bigdecimal
+pub mod bigdecimal_opt {
+    use std::collections::HashSet;
+
+    use bigdecimal::BigDecimal;
+    use serde::{Deserializer, Serializer, de::Error as _, ser::Error as _};
+
+    use crate::{
+        Schema,
+        bigdecimal::{big_decimal_as_bytes, deserialize_big_decimal},
+        schema::{Name, NamespaceRef, RecordField, UnionSchema},
+        serde::with::BytesType,
+    };
+
+    /// Returns `Schema::Union(Schema::Null, Schema::BigDecimal)`
+    pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> 
Schema {
+        Schema::Union(
+            UnionSchema::new(vec![Schema::Null, Schema::BigDecimal])
+                .expect("This is a valid union"),
+        )
+    }
+
+    /// Returns `None`
+    pub fn get_record_fields_in_ctxt(
+        _: &mut HashSet<Name>,
+        _: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
+        None
+    }
+
+    pub fn serialize<S>(decimal: &Option<BigDecimal>, serializer: S) -> 
Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let _guard = super::BytesTypeGuard::set(BytesType::Bytes);
+        if let Some(decimal) = decimal {
+            let decimal_bytes = 
big_decimal_as_bytes(decimal).map_err(S::Error::custom)?;
+            serde_bytes::serialize(&Some(decimal_bytes), serializer)
+        } else {
+            serde_bytes::serialize(&None::<Vec<u8>>, serializer)
+        }
+    }
+
+    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<BigDecimal>, 
D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let _bytes_guard = super::BytesTypeGuard::set(BytesType::Bytes);
+        let _guard = super::BorrowedGuard::set(true);
+        let bytes: Option<Vec<u8>> = serde_bytes::deserialize(deserializer)?;
+        if let Some(bytes) = bytes {
+            deserialize_big_decimal(&bytes)
+                .map(Some)
+                .map_err(D::Error::custom)
+        } else {
+            Ok(None)
+        }
+    }
+}
+
+/// (De)serialize an Rust array (`[T; N]`) as an Avro [`Schema::Array`].
+///
+/// This module is intended to be used through the Serde `with` attribute.
+///
+/// Use [`apache_avro::serde::array_opt`] for optional array values.
+///
+/// See usage with below example:
+/// ```
+/// # use apache_avro::AvroSchema;
+/// # use serde::{Deserialize, Serialize};
+/// #[derive(AvroSchema, Serialize, Deserialize)]
+/// struct StructWithBytes<'a> {
+///     #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<i32>)]
+///     #[serde(with = "apache_avro::serde::array")]
+///     array: [i32; 10],
+/// }
+/// ```
+///
+/// [`apache_avro::serde::array_opt`]: array_opt
+/// [`Schema::Array`]: crate::schema::Schema::Array
+pub mod array {
+    use crate::{
+        AvroSchemaComponent, Schema,
+        schema::{Name, NamespaceRef, RecordField},
+    };
+    use serde::de::DeserializeOwned;
+    use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as 
_};
+    use std::collections::HashSet;
+
+    /// Returns `Schema::Array(T::get_schema_in_ctxt())`
+    pub fn get_schema_in_ctxt<T: AvroSchemaComponent>(
+        named_schemas: &mut HashSet<Name>,
+        enclosing_namespace: NamespaceRef,
+    ) -> Schema {
+        Schema::array(T::get_schema_in_ctxt(named_schemas, 
enclosing_namespace)).build()
+    }
+
+    /// Returns `None`
+    pub fn get_record_fields_in_ctxt(
+        _: &mut HashSet<Name>,
+        _: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
+        None
+    }
+
+    pub fn serialize<const N: usize, S, T>(value: &[T; N], serializer: S) -> 
Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+        T: Serialize,
+    {
+        value.as_slice().serialize(serializer)
+    }
+
+    pub fn deserialize<'de, const N: usize, D, T>(deserializer: D) -> 
Result<[T; N], D::Error>
+    where
+        D: Deserializer<'de>,
+        T: DeserializeOwned,
+    {
+        let bytes = <Vec<T> as Deserialize>::deserialize(deserializer)?;
+        bytes.try_into().map_err(|v: Vec<T>| {
+            D::Error::custom(format!(
+                "Deserialized array has length {} which does not match array 
length of {N}",
+                v.len()
+            ))
+        })
+    }
+}
+
+/// (De)serialize an optional Rust array (`Option<[T; N]>`) as an Avro 
`Schema::Union([Schema::Null, Schema::Array])`.
+///
+/// This module is intended to be used through the Serde `with` attribute.
+///
+/// Use [`apache_avro::serde::array`] for non-optional array values.
+///
+/// When used with different serialization formats, this is equivalent to 
[`serde_bytes`].
+///
+/// See usage with below example:
+/// ```
+/// # use apache_avro::AvroSchema;
+/// # use serde::{Deserialize, Serialize};
+/// #[derive(AvroSchema, Serialize, Deserialize)]
+/// struct StructWithBytes<'a> {
+///     #[avro(with = 
apache_avro::serde::array_opt::get_schema_in_ctxt::<i32>)]
+///     #[serde(with = "apache_avro::serde::array_opt")]
+///     array: Option<[i32; 10]>,
+/// }
+/// ```
+///
+/// [`apache_avro::serde::array`]: mod@array
+pub mod array_opt {
+    use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as 
_};
+    use std::collections::HashSet;
+
+    use crate::{
+        AvroSchemaComponent, Schema,
+        schema::{Name, NamespaceRef, RecordField, UnionSchema},
+    };
+
+    /// Returns `Schema::Union(Schema::Null, 
Schema::Array(T::get_schema_in_ctxt()))`
+    pub fn get_schema_in_ctxt<T: AvroSchemaComponent>(
+        named_schemas: &mut HashSet<Name>,
+        enclosing_namespace: NamespaceRef,
+    ) -> Schema {
+        Schema::Union(
+            UnionSchema::new(vec![
+                Schema::Null,
+                Schema::array(T::get_schema_in_ctxt(named_schemas, 
enclosing_namespace)).build(),
+            ])
+            .expect("This is a valid union"),
+        )
+    }
+
+    /// Returns `None`
+    pub fn get_record_fields_in_ctxt(
+        _: &mut HashSet<Name>,
+        _: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
+        None
+    }
+
+    pub fn serialize<const N: usize, S, T>(
+        value: &Option<[T; N]>,
+        serializer: S,
+    ) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+        T: Serialize,
+    {
+        if let Some(array) = value {
+            Some(array.as_slice()).serialize(serializer)
+        } else {
+            None::<Vec<T>>.serialize(serializer)
+        }
+    }
+
+    pub fn deserialize<'de, const N: usize, D, T>(
+        deserializer: D,
+    ) -> Result<Option<[T; N]>, D::Error>
+    where
+        D: Deserializer<'de>,
+        T: Deserialize<'de>,
+    {
+        let bytes = <Option<Vec<T>> as 
Deserialize>::deserialize(deserializer)?;
+        if let Some(bytes) = bytes {
+            Ok(Some(bytes.try_into().map_err(|v: Vec<T>| {
+                D::Error::custom(format!(
+                    "Deserialized array has length {} which does not match 
array length of {N}",
+                    v.len()
+                ))
+            })?))
+        } else {
+            Ok(None)
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use crate::{Schema, from_value, to_value, types::Value};
diff --git a/avro/src/writer/mod.rs b/avro/src/writer/mod.rs
index 2d255a1..b11bbde 100644
--- a/avro/src/writer/mod.rs
+++ b/avro/src/writer/mod.rs
@@ -1160,7 +1160,7 @@ mod tests {
             Err(e) => {
                 assert_eq!(
                     e.to_string(),
-                    r#"Failed to serialize field 'time' for record 
Record(RecordSchema { name: Name { name: "Conference", .. }, fields: 
[RecordField { name: "name", schema: String, .. }, RecordField { name: "date", 
aliases: ["time2", "time"], schema: Union(UnionSchema { schemas: [Null, Long] 
}), .. }], .. }): Failed to serialize value of type f64 using schema 
Union(UnionSchema { schemas: [Null, Long] }): 12345678.9. Cause: Cannot find a 
Double schema in [Null, Long]"#
+                    r#"Failed to serialize field 'date' of record RecordSchema 
{ name: Name { name: "Conference", .. }, fields: [RecordField { name: "name", 
schema: String, .. }, RecordField { name: "date", aliases: ["time2", "time"], 
schema: Union(UnionSchema { schemas: [Null, Long] }), .. }], .. }: Failed to 
serialize value of type `f64`: Expected Schema::Double"#
                 );
             }
         }
diff --git a/avro/tests/serde_human_readable_false.rs 
b/avro/tests/serde_human_readable_false.rs
index 2f8be3f..544e09e 100644
--- a/avro/tests/serde_human_readable_false.rs
+++ b/avro/tests/serde_human_readable_false.rs
@@ -79,7 +79,7 @@ fn avro_rs_440_uuid_string() -> TestResult {
     let writer = SpecificSingleObjectWriter::new()?;
     assert_eq!(
         writer.write(uuid, &mut buffer).unwrap_err().to_string(),
-        "Failed to serialize value of type bytes using schema Uuid(String): 
55e840e29b41d4a7164466554400. Cause: Expected a string, but got 16 bytes. Did 
you mean to use `Schema::Uuid(UuidSchema::Fixed)` or 
`utils::serde_set_human_readable(true)`?"
+        "Failed to serialize value of type `bytes` using Schema::Uuid(String): 
Expected Schema::Bytes | Schema::Fixed | Schema::BigDecimal | Schema::Decimal | 
Schema::Uuid(Bytes | Fixed) | Schema::Duration"
     );
 
     Ok(())
diff --git a/avro/tests/serde_human_readable_true.rs 
b/avro/tests/serde_human_readable_true.rs
index d1c71d7..c0612bb 100644
--- a/avro/tests/serde_human_readable_true.rs
+++ b/avro/tests/serde_human_readable_true.rs
@@ -103,7 +103,7 @@ fn avro_rs_440_uuid_bytes() -> TestResult {
     let writer = SpecificSingleObjectWriter::new()?;
     assert_eq!(
         writer.write(uuid, &mut buffer).unwrap_err().to_string(),
-        r#"Failed to serialize value of type string using schema Uuid(Bytes): 
550e8400-e29b-41d4-a716-446655440000. Cause: Expected bytes but got a string. 
Did you mean to use `Schema::Uuid(UuidSchema::String)` or 
`utils::serde_set_human_readable(false)`?"#
+        r#"Failed to serialize value of type `str` using Schema::Uuid(Bytes): 
Expected Schema::String | Schema::Uuid(String)"#
     );
 
     Ok(())
@@ -129,7 +129,7 @@ fn avro_rs_440_uuid_fixed() -> TestResult {
     let writer = SpecificSingleObjectWriter::new()?;
     assert_eq!(
         writer.write(uuid, &mut buffer).unwrap_err().to_string(),
-        r#"Failed to serialize value of type string using schema 
Uuid(Fixed(FixedSchema { name: Name { name: "uuid", .. }, size: 16, .. })): 
550e8400-e29b-41d4-a716-446655440000. Cause: Expected bytes but got a string. 
Did you mean to use `Schema::Uuid(UuidSchema::String)` or 
`utils::serde_set_human_readable(false)`?"#
+        r#"Failed to serialize value of type `str` using 
Schema::Uuid(Fixed(FixedSchema { name: Name { name: "uuid", .. }, size: 16, .. 
})): Expected Schema::String | Schema::Uuid(String)"#
     );
 
     Ok(())
diff --git a/avro_derive/tests/derive.proptest-regressions 
b/avro_derive/tests/derive.proptest-regressions
new file mode 100644
index 0000000..d9ec339
--- /dev/null
+++ b/avro_derive/tests/derive.proptest-regressions
@@ -0,0 +1,8 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc fcee0d6e798e3fea29e4567deeaa845a8868672476574c6f1c6723734e8852b7 # shrinks 
to a = [], b = [0, 0]
+cc 3b35e254022cadeafda5adb27919361b91e7b77a07ed1eea7e0919856aa247d0 # shrinks 
to a = "", b = [], c = {"": 0}
diff --git a/avro_derive/tests/derive.rs b/avro_derive/tests/derive.rs
index ae366e3..66d179e 100644
--- a/avro_derive/tests/derive.rs
+++ b/avro_derive/tests/derive.rs
@@ -33,6 +33,7 @@ use apache_avro::schema::NamespaceRef;
 use pretty_assertions::assert_eq;
 
 /// 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
+#[track_caller]
 fn serde_assert<T>(obj: T)
 where
     T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone + 
PartialEq,
@@ -40,6 +41,7 @@ where
     assert_eq!(obj, serde(obj.clone()));
 }
 
+#[track_caller]
 fn serde<T>(obj: T) -> T
 where
     T: Serialize + DeserializeOwned + AvroSchema,
@@ -47,6 +49,7 @@ where
     de(ser(obj))
 }
 
+#[track_caller]
 fn ser<T>(obj: T) -> Vec<u8>
 where
     T: Serialize + AvroSchema,
@@ -59,6 +62,7 @@ where
     writer.into_inner().unwrap()
 }
 
+#[track_caller]
 fn de<T>(encoded: Vec<u8>) -> T
 where
     T: DeserializeOwned + AvroSchema,
@@ -800,6 +804,8 @@ fn test_cons_generic() {
 
 #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
 struct TestSimpleArray {
+    #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<i32>)]
+    #[serde(with = "apache_avro::serde::array")]
     a: [i32; 4],
 }
 
@@ -828,7 +834,9 @@ fn test_simple_array(a: [i32; 4]) {
 }}
 
 #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
-struct TestComplexArray<T: AvroSchemaComponent> {
+struct TestComplexArray<T: AvroSchemaComponent + Serialize + DeserializeOwned> 
{
+    #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<T>)]
+    #[serde(with = "apache_avro::serde::array")]
     a: [T; 2],
 }
 
@@ -882,6 +890,8 @@ fn test_complex_array() {
 #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
 struct Testu8 {
     a: Vec<u8>,
+    #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<u8>)]
+    #[serde(with = "apache_avro::serde::array")]
     b: [u8; 2],
 }
 
@@ -2063,6 +2073,8 @@ fn avro_rs_401_supported_type_variants() {
         three: &'static i32,
         four: &'a str,
         five: &'a mut f64,
+        #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<u8>)]
+        #[serde(with = "apache_avro::serde::array")]
         six: [u8; 5],
         seven: [u8],
     }
@@ -2390,7 +2402,11 @@ fn avro_rs_476_field_default() {
         _m: char,
         _n: Box<Spam>,
         _o: Vec<bool>,
+        #[serde(with = "apache_avro::serde::array")]
+        #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<u8>)]
         _p: [u8; 5],
+        #[serde(with = "apache_avro::serde::array")]
+        #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<Bar>)]
         _p_alt: [Bar; 5],
         _q: HashMap<String, String>,
         _r: Option<f64>,
@@ -2406,7 +2422,7 @@ fn avro_rs_476_field_default() {
     let schema = Foo::get_schema();
     assert_eq!(
         serde_json::to_string(&schema).unwrap(),
-        
r#"{"type":"record","name":"Foo","fields":[{"name":"_a","type":"boolean"},{"name":"_b","type":"int"},{"name":"_c","type":"int"},{"name":"_d","type":"int"},{"name":"_e","type":"long"},{"name":"_f","type":"int"},{"name":"_g","type":"int"},{"name":"_h","type":"long"},{"name":"_i","type":"float"},{"name":"_j","type":"double"},{"name":"_k","type":"string"},{"name":"_l","type":"string"},{"name":"_m","type":"string"},{"name":"_n","type":{"type":"record","name":"Spam","fields":[{"name":"
 [...]
+        
r#"{"type":"record","name":"Foo","fields":[{"name":"_a","type":"boolean"},{"name":"_b","type":"int"},{"name":"_c","type":"int"},{"name":"_d","type":"int"},{"name":"_e","type":"long"},{"name":"_f","type":"int"},{"name":"_g","type":"int"},{"name":"_h","type":"long"},{"name":"_i","type":"float"},{"name":"_j","type":"double"},{"name":"_k","type":"string"},{"name":"_l","type":"string"},{"name":"_m","type":"string"},{"name":"_n","type":{"type":"record","name":"Spam","fields":[{"name":"
 [...]
     );
 }
 
@@ -2479,8 +2495,11 @@ fn avro_rs_476_field_default_provided() {
         _n: Box<Spam>,
         #[avro(default = "[true, false, true]")]
         _o: Vec<bool>,
-        #[avro(default = "[1,2,3,4,5]")]
+        #[serde(with = "apache_avro::serde::array")]
+        #[avro(default = "[1,2,3,4,5]", with = 
apache_avro::serde::array::get_schema_in_ctxt::<u8>)]
         _p: [u8; 5],
+        #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<Spam>)]
+        #[serde(with = "apache_avro::serde::array")]
         #[avro(
             default = r#"[{"_field": true},{"_field": false},{"_field": 
true},{"_field": false},{"_field": true}]"#
         )]
@@ -2514,7 +2533,7 @@ fn avro_rs_476_field_default_provided() {
     let schema = Foo::get_schema();
     assert_eq!(
         serde_json::to_string(&schema).unwrap(),
-        
r#"{"type":"record","name":"Foo","fields":[{"name":"_a","type":"boolean","default":true},{"name":"_b","type":"int","default":42},{"name":"_c","type":"int","default":42},{"name":"_d","type":"int","default":42},{"name":"_e","type":"long","default":42},{"name":"_f","type":"int","default":42},{"name":"_g","type":"int","default":42},{"name":"_h","type":"long","default":42},{"name":"_i","type":"float","default":42.0},{"name":"_j","type":"double","default":42.0},{"name":"_k","type":"str
 [...]
+        
r#"{"type":"record","name":"Foo","fields":[{"name":"_a","type":"boolean","default":true},{"name":"_b","type":"int","default":42},{"name":"_c","type":"int","default":42},{"name":"_d","type":"int","default":42},{"name":"_e","type":"long","default":42},{"name":"_f","type":"int","default":42},{"name":"_g","type":"int","default":42},{"name":"_h","type":"long","default":42},{"name":"_i","type":"float","default":42.0},{"name":"_j","type":"double","default":42.0},{"name":"_k","type":"str
 [...]
     );
 }
 
diff --git a/avro_derive/tests/serde.rs b/avro_derive/tests/serde.rs
index cf11fa4..448d59f 100644
--- a/avro_derive/tests/serde.rs
+++ b/avro_derive/tests/serde.rs
@@ -20,6 +20,7 @@ use serde::{Deserialize, Serialize, de::DeserializeOwned};
 
 /// Takes in a type that implements the right combination of traits and runs 
it through a Serde
 /// round-trip and asserts the result is the same.
+#[track_caller]
 fn serde_assert<T>(obj: T)
 where
     T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone + 
PartialEq,
@@ -29,15 +30,13 @@ where
 
 /// Takes in a type that implements the right combination of traits and runs 
it through a Serde
 /// round-trip and asserts that the error matches the expected string.
+#[track_caller]
 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}`"
-    );
+    assert_eq!(error, expected);
 }
 
 fn serde<T>(obj: T) -> Result<T, Error>
@@ -313,7 +312,7 @@ mod container_attributes {
                 a: "spam".to_string(),
                 b: 321,
             },
-            "Invalid field name c",
+            r#"Missing field in record: "c""#,
         );
     }
 


Reply via email to