You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by kr...@apache.org on 2026/03/11 14:01:42 UTC

(avro-rs) 07/08: finished?

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

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

commit ecdd9aabe6908dd5bb3e28ca9474c40520a54634
Author: default <ad...@kriskras99.nl>
AuthorDate: Mon Mar 9 13:13:24 2026 +0000

    finished?
---
 avro/src/schema/mod.rs                             |   1 -
 avro/src/serde/deser_schema/enums/union.rs         |   1 -
 avro/src/serde/deser_schema/mod.rs                 |   1 -
 avro/src/serde/deser_schema/record.rs              |   3 -
 avro/src/serde/ser_schema/mod.rs                   |  47 +--
 avro/tests/avro-rs-219.rs                          |   1 -
 avro_derive/src/attributes/mod.rs                  |  68 ++-
 avro_derive/src/enums/bare_union.rs                |  44 +-
 avro_derive/src/enums/mod.rs                       |  23 +-
 avro_derive/src/enums/plain.rs                     |  12 +-
 avro_derive/src/enums/record_internally_tagged.rs  |  35 +-
 avro_derive/src/enums/record_tag_content.rs        |  25 +-
 avro_derive/src/enums/union_of_records.rs          |  16 +-
 avro_derive/src/fields.rs                          | 129 +++---
 avro_derive/src/lib.rs                             | 122 +++---
 avro_derive/src/tuple.rs                           |  20 +-
 avro_derive/src/utils.rs                           |  91 ++++
 avro_derive/tests/derive.rs                        |   2 +
 avro_derive/tests/enum.rs                          | 465 ++++++++++++++++++++-
 .../avro_3709_record_field_attributes.expanded.rs  |   4 +-
 .../expanded/avro_rs_xxx_bare_union.expanded.rs    | 135 ------
 .../tests/expanded/avro_rs_xxx_bare_union.rs       |  15 +-
 .../avro_rs_xxx_internally_tagged.expanded.rs      | 207 ++++-----
 .../expanded/avro_rs_xxx_internally_tagged.rs      |  27 +-
 .../expanded/avro_rs_xxx_tag_content.expanded.rs   | 340 +++++++++------
 .../tests/expanded/avro_rs_xxx_tag_content.rs      |  27 +-
 .../avro_rs_xxx_union_of_records.expanded.rs       | 369 +++++++++-------
 .../tests/expanded/avro_rs_xxx_union_of_records.rs |  26 +-
 avro_derive/tests/expanded/mod.rs                  |   4 +-
 avro_derive/tests/ui/avro_rs_401_tuple.stderr      |   5 -
 ...rs => avro_rs_xxx_bare_union_struct_variant.rs} |   5 +-
 .../avro_rs_xxx_bare_union_struct_variant.stderr   |   5 +
 ...ple.rs => avro_rs_xxx_untagged_newtype_docs.rs} |   8 +-
 .../ui/avro_rs_xxx_untagged_newtype_docs.stderr    |   6 +
 34 files changed, 1398 insertions(+), 891 deletions(-)

diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs
index 4bffaca..b9a75f5 100644
--- a/avro/src/schema/mod.rs
+++ b/avro/src/schema/mod.rs
@@ -4866,7 +4866,6 @@ mod tests {
             _ => panic!("Expected ArraySchema. got: {}", &schema1),
         }
         let canonical_form1 = schema1.canonical_form();
-        println!("Canonical Form 1: {}", &canonical_form1);
         let schema2 = Schema::parse_str(&canonical_form1)?;
         let canonical_form2 = schema2.canonical_form();
 
diff --git a/avro/src/serde/deser_schema/enums/union.rs b/avro/src/serde/deser_schema/enums/union.rs
index 8712128..aa1c13b 100644
--- a/avro/src/serde/deser_schema/enums/union.rs
+++ b/avro/src/serde/deser_schema/enums/union.rs
@@ -100,7 +100,6 @@ impl<'de, 's, 'r, R: Read, S: Borrow<Schema>> VariantAccess<'de>
     {
         if let Schema::Record(record) = self.schema
             && record.fields.len() == 1
-            && record.fields[0].name == "field_0"
             && record
                 .attributes
                 .get("org.apache.avro.rust.union_of_records")
diff --git a/avro/src/serde/deser_schema/mod.rs b/avro/src/serde/deser_schema/mod.rs
index 8fb8bb5..cf9d0d7 100644
--- a/avro/src/serde/deser_schema/mod.rs
+++ b/avro/src/serde/deser_schema/mod.rs
@@ -576,7 +576,6 @@ impl<'de, 's, 'r, R: Read, S: Borrow<Schema>> Deserializer<'de>
     where
         V: serde::de::Visitor<'de>,
     {
-        println!("deserialize_tuple(len: {len}): {self:?}");
         match self.schema {
             Schema::Null if len == 0 => visitor.visit_seq(UnitTupleDeserializer),
             schema if len == 1 => {
diff --git a/avro/src/serde/deser_schema/record.rs b/avro/src/serde/deser_schema/record.rs
index f2771f3..b2114dc 100644
--- a/avro/src/serde/deser_schema/record.rs
+++ b/avro/src/serde/deser_schema/record.rs
@@ -65,7 +65,6 @@ impl<'de, 's, 'r, R: Read, S: Borrow<Schema>> MapAccess<'de> for RecordDeseriali
     where
         K: serde::de::DeserializeSeed<'de>,
     {
-        println!("next_key_seed: {self:?}");
         let State::Key(index) = self.current_field else {
             panic!("`next_key_seed` and `next_value_seed` where called in the wrong error")
         };
@@ -85,14 +84,12 @@ impl<'de, 's, 'r, R: Read, S: Borrow<Schema>> MapAccess<'de> for RecordDeseriali
     where
         V: serde::de::DeserializeSeed<'de>,
     {
-        println!("next_value_seed: {self:?}");
         let State::Value(index) = self.current_field else {
             panic!("`next_key_seed` and `next_value_seed` where called in the wrong error")
         };
         let schema = &self.schema.fields[index].schema;
         let v = if let Schema::Record(record) = schema
             && record.fields.len() == 1
-            && record.fields[0].name == "field_0"
             && record
                 .attributes
                 .get("org.apache.avro.rust.union_of_records")
diff --git a/avro/src/serde/ser_schema/mod.rs b/avro/src/serde/ser_schema/mod.rs
index 870aa14..0387097 100644
--- a/avro/src/serde/ser_schema/mod.rs
+++ b/avro/src/serde/ser_schema/mod.rs
@@ -217,7 +217,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     type SerializeStructVariant = RecordSerializer<'s, 'w, W>;
 
     fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_bool({v}): {self:?}");
         match self.schema {
             Schema::Boolean => {
                 self.writer
@@ -233,27 +232,22 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_i8({v}): {self:?}");
         self.serialize_int("i8", i32::from(v))
     }
 
     fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_i16({v}): {self:?}");
         self.serialize_int("i16", i32::from(v))
     }
 
     fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_i32({v}): {self:?}");
         self.serialize_int("i32", v)
     }
 
     fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_i64({v}): {self:?}");
         self.serialize_long("i64", v)
     }
 
     fn serialize_i128(mut self, v: i128) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_i128({v}): {self:?}");
         match self.schema {
             Schema::Fixed(fixed) if fixed.name.name() == "i128" && fixed.size == 16 => {
                 let bytes = v.to_le_bytes();
@@ -267,22 +261,18 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_u8({v}): {self:?}");
         self.serialize_int("u8", i32::from(v))
     }
 
     fn serialize_u16(self, v: u16) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_u16({v}): {self:?}");
         self.serialize_int("u16", i32::from(v))
     }
 
     fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_u32({v}): {self:?}");
         self.serialize_long("u32", i64::from(v))
     }
 
     fn serialize_u64(mut self, v: u64) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_u64({v}): {self:?}");
         match self.schema {
             Schema::Fixed(fixed) if fixed.name.name() == "u64" && fixed.size == 8 => {
                 let bytes = v.to_le_bytes();
@@ -296,7 +286,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_u128(mut self, v: u128) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_u128({v}): {self:?}");
         match self.schema {
             Schema::Fixed(fixed) if fixed.name.name() == "u128" && fixed.size == 16 => {
                 let bytes = v.to_le_bytes();
@@ -310,7 +299,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_f32(mut self, v: f32) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_f32({v}): {self:?}");
         match self.schema {
             Schema::Float => {
                 let bytes = v.to_le_bytes();
@@ -324,7 +312,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_f64(mut self, v: f64) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_f64({v}): {self:?}");
         match self.schema {
             Schema::Double => {
                 let bytes = v.to_le_bytes();
@@ -338,7 +325,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_char(mut self, v: char) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_char({v}): {self:?}");
         match self.schema {
             Schema::String => {
                 let bytes = v.to_string().into_bytes();
@@ -352,7 +338,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_str(mut self, v: &str) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_str({v}): {self:?}");
         match self.schema {
             Schema::String | Schema::Uuid(UuidSchema::String) => {
                 self.write_bytes_with_len(v.as_bytes())
@@ -365,7 +350,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_bytes(mut self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_bytes({v:?}): {self:?}");
         match (SER_BYTES_TYPE.get(), self.schema) {
             (BytesType::Unset | BytesType::Bytes, Schema::Bytes | Schema::BigDecimal | Schema::Decimal(DecimalSchema { inner: InnerDecimalSchema::Bytes, .. })) => {
                 self.write_bytes_with_len(v)
@@ -385,7 +369,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_none: {self:?}");
         if let Schema::Union(union) = self.schema
             && union.variants().len() == 2
             && let Some(index) = union.index().get(&SchemaKind::Null).copied()
@@ -403,7 +386,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     where
         T: ?Sized + Serialize,
     {
-        println!("serialize_some: {self:?}");
         if let Schema::Union(union) = self.schema
             && union.variants().len() == 2
             && let Some(index) = union.index().get(&SchemaKind::Null).copied()
@@ -423,7 +405,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_unit: {self:?}");
         match self.schema {
             Schema::Null => Ok(0),
             Schema::Union(union) => {
@@ -434,7 +415,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok, Self::Error> {
-        println!("serialize_unit_struct(name: {name}): {self:?}");
         match self.schema {
             Schema::Record(record) if record.name.name() == name && record.fields.is_empty() => {
                 Ok(0)
@@ -454,9 +434,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
         variant_index: u32,
         variant: &'static str,
     ) -> Result<Self::Ok, Self::Error> {
-        println!(
-            "serialize_unit_variant(name: {name}, index: {variant_index}, variant: {variant}): {self:?}"
-        );
         match self.schema {
             Schema::Enum(enum_schema) => {
                 if name.as_ptr() == SERIALIZING_SCHEMA_DEFAULT.as_ptr() {
@@ -504,7 +481,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     where
         T: ?Sized + Serialize,
     {
-        println!("serialize_newtype_struct(name: {name}): {self:?}");
         match self.schema {
             Schema::Record(record) if record.name.name() == name && record.fields.len() == 1 => {
                 let schema = &record.fields[0].schema;
@@ -530,15 +506,11 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     where
         T: ?Sized + Serialize,
     {
-        println!(
-            "serialize_newtype_variant(name: {_name}, index: {variant_index}, variant: {variant}): {self:?}"
-        );
         match self.schema {
             Schema::Union(union) => {
                 if let Some(Schema::Record(record)) = union.variants().get(variant_index as usize)
                     && record.name.name() == variant
                     && record.fields.len() == 1
-                    && record.fields[0].name == "field_0"
                     && record
                         .attributes
                         .get("org.apache.avro.rust.union_of_records")
@@ -562,7 +534,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
-        println!("serialize_seq(len: {len:?}): {self:?}");
         match self.schema {
             Schema::Array(array) => {
                 ArraySerializer::new(self.writer, array, self.config, len, None)
@@ -575,7 +546,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
-        println!("serialize_tuple(len: {len}): {self:?}");
         match self.schema {
             Schema::Union(union) => {
                 UnionAwareSerializer::new(self.writer, union, self.config).serialize_tuple(len)
@@ -601,7 +571,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
         name: &'static str,
         len: usize,
     ) -> Result<Self::SerializeTupleStruct, Self::Error> {
-        println!("serialize_tuple_struct(name: {name}, len: {len}): {self:?}");
         match self.schema {
             Schema::Record(record) if record.name.name() == name && record.fields.len() == len => {
                 Ok(ManyTupleSerializer::new(
@@ -627,9 +596,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
         variant: &'static str,
         len: usize,
     ) -> Result<Self::SerializeTupleVariant, Self::Error> {
-        println!(
-            "serialize_tuple_variant(name: {_name}, index: {variant_index}, variant: {variant}, len: {len}): {self:?}"
-        );
         match self.schema {
             Schema::Union(union) => {
                 if let Some(Schema::Record(record)) = union.variants().get(variant_index as usize)
@@ -673,7 +639,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
     }
 
     fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
-        println!("serialize_map(len: {len:?}): {self:?}");
         match self.schema {
             Schema::Map(map) => Ok(MapOrRecordSerializer::Map(MapSerializer::new(
                 self.writer,
@@ -703,10 +668,8 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
         name: &'static str,
         len: usize,
     ) -> Result<Self::SerializeStruct, Self::Error> {
-        println!("serialize_struct(name: {name}, len: {len}): {self:?}");
         match self.schema {
-            // For unit variants with tag,content `len` will be 1 but we expect 2.
-            Schema::Record(record) if record.name.name() == name => Ok(RecordSerializer::new(
+            Schema::Record(record) => Ok(RecordSerializer::new(
                 self.writer,
                 record,
                 self.config,
@@ -714,10 +677,7 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
             )),
             Schema::Union(union) => UnionAwareSerializer::new(self.writer, union, self.config)
                 .serialize_struct(name, len),
-            _ => Err(self.error(
-                "struct",
-                format!("Expected Schema::Record(name: {name}, fields.len() == {len})"),
-            )),
+            _ => Err(self.error("struct", "Expected Schema::Record")),
         }
     }
 
@@ -728,9 +688,6 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> {
         variant: &'static str,
         len: usize,
     ) -> Result<Self::SerializeStructVariant, Self::Error> {
-        println!(
-            "serialize_struct_variant(name: {_name}, index: {variant_index}, variant: {variant}, len: {len}): {self:?}"
-        );
         match self.schema {
             Schema::Union(union) => {
                 if let Some(Schema::Record(record)) = union.variants().get(variant_index as usize)
diff --git a/avro/tests/avro-rs-219.rs b/avro/tests/avro-rs-219.rs
index 34d503f..5e96a3e 100644
--- a/avro/tests/avro-rs-219.rs
+++ b/avro/tests/avro-rs-219.rs
@@ -38,6 +38,5 @@ fn avro_rs_219_failing_deserialization_due_to_bigdecimal_dependency() {
     }"#;
 
     let result = serde_json::from_str::<S3>(test);
-    println!("result : {result:#?}");
     result.unwrap();
 }
diff --git a/avro_derive/src/attributes/mod.rs b/avro_derive/src/attributes/mod.rs
index ab66911..396e6fa 100644
--- a/avro_derive/src/attributes/mod.rs
+++ b/avro_derive/src/attributes/mod.rs
@@ -15,11 +15,11 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use crate::case::RenameRule;
+use crate::{TypedTokenStream, case::RenameRule};
 use darling::{FromAttributes, FromMeta};
-use proc_macro2::{Span, TokenStream};
+use proc_macro2::Span;
 use quote::quote;
-use syn::{AttrStyle, Attribute, Expr, Ident, Path, Type, spanned::Spanned};
+use syn::{AttrStyle, Attribute, Expr, Ident, Path, spanned::Spanned};
 
 mod avro;
 mod serde;
@@ -39,7 +39,6 @@ pub enum EnumRepr {
     RecordInternallyTagged { tag: String },
 }
 
-#[derive(Default)]
 pub struct NamedTypeOptions {
     pub name: String,
     pub doc: Option<String>,
@@ -47,7 +46,7 @@ pub struct NamedTypeOptions {
     pub rename_all: RenameRule,
     pub rename_all_fields: RenameRule,
     pub transparent: bool,
-    pub default: TokenStream,
+    pub default: TypedTokenStream<Option<serde_json::Value>>,
     pub repr: Option<EnumRepr>,
     pub with: Option<Path>,
 }
@@ -230,18 +229,18 @@ impl NamedTypeOptions {
         };
 
         let default = match avro.default {
-            None => quote! { ::std::option::Option::None },
+            None => TypedTokenStream::none(),
             Some(default_value) => {
                 if let Err(err) = serde_json::from_str::<serde_json::Value>(&default_value[..]) {
                     errors.push(syn::Error::new(
                         ident.span(),
                         format!("Invalid Avro `default` JSON: \n{err}"),
                     ));
-                    quote! { ::std::option::Option::None }
+                    TypedTokenStream::none()
                 } else {
-                    quote! {
+                    TypedTokenStream::<Option<serde_json::Value>>::new(quote! {
                         ::std::option::Option::Some(serde_json::from_str(#default_value).expect("Unreachable! This was checked at compile time"))
-                    }
+                    })
                 }
             }
         };
@@ -377,6 +376,7 @@ impl With {
         }
     }
 }
+
 /// How to get the default value for a value.
 #[derive(Debug, PartialEq, Default)]
 pub enum FieldDefault {
@@ -500,6 +500,56 @@ impl FieldOptions {
             with,
         })
     }
+
+    /// Parse and check the attributes for the anonymous field of newtype variants.
+    ///
+    /// This function is not used by `union_of_records`.
+    pub fn new_for_newtype(attributes: &[Attribute], span: Span) -> Result<Self, Vec<syn::Error>> {
+        let options = Self::new(attributes, span)?;
+        let mut errors = Vec::new();
+        if options.doc.is_some() {
+            errors.push(syn::Error::new(
+                span,
+                r#"AvroSchema: `#[avro(doc = "..")]` only works on newtype variants when the enum uses `#[avro(repr = "union_of_records")`"#
+            ));
+        }
+        if matches!(options.default, FieldDefault::Value(_)) {
+            errors.push(syn::Error::new(
+                span,
+                r#"AvroSchema: `#[avro(default = "..")]` only works on newtype variants when the enum uses `#[avro(repr = "union_of_records")`"#
+            ));
+        }
+        if !options.alias.is_empty() {
+            errors.push(syn::Error::new(
+                span,
+                r#"AvroSchema: `#[avro(alias = "..")]` only works on newtype variants when the enum uses `#[avro(repr = "union_of_records")`"#
+            ));
+        }
+        if options.rename.is_some() {
+            errors.push(syn::Error::new(
+                span,
+                r#"AvroSchema: `#[avro(alias = "..")]` only works on newtype variants when the enum uses `#[avro(repr = "union_of_records")`"#
+            ));
+        }
+        if options.skip {
+            errors.push(syn::Error::new(
+                span,
+                r#"AvroSchema: `#[avro(skip)]` is not supported for newtype variants"#,
+            ));
+        }
+        if options.flatten {
+            errors.push(syn::Error::new(
+                span,
+                r#"AvroSchema: `#[avro(flatten)]` is not supported for newtype variants"#,
+            ));
+        }
+
+        if !errors.is_empty() {
+            return Err(errors);
+        }
+
+        Ok(options)
+    }
 }
 
 fn extract_rustdoc(attributes: &[Attribute]) -> Option<String> {
diff --git a/avro_derive/src/enums/bare_union.rs b/avro_derive/src/enums/bare_union.rs
index 7752b81..6439c81 100644
--- a/avro_derive/src/enums/bare_union.rs
+++ b/avro_derive/src/enums/bare_union.rs
@@ -1,7 +1,8 @@
-use crate::attributes::{FieldOptions, NamedTypeOptions, VariantOptions};
+use crate::attributes::{NamedTypeOptions, VariantOptions};
 use crate::tuple::tuple_struct_variant_to_record_schema;
-use crate::{fields, named_to_record_fields};
-use proc_macro2::{Span, TokenStream};
+use crate::utils::{Schema, TypedTokenStream};
+use crate::{FieldOptions, fields};
+use proc_macro2::Span;
 use quote::quote;
 use syn::spanned::Spanned;
 use syn::{DataEnum, Fields};
@@ -10,7 +11,7 @@ pub fn get_data_enum_schema_def(
     container_attrs: &NamedTypeOptions,
     data_enum: DataEnum,
     ident_span: Span,
-) -> Result<TokenStream, Vec<syn::Error>> {
+) -> Result<TypedTokenStream<Schema>, Vec<syn::Error>> {
     let mut variant_expr = Vec::new();
     let mut have_null = false;
     for variant in data_enum.variants {
@@ -22,22 +23,12 @@ pub fn get_data_enum_schema_def(
         });
         match variant.fields {
             Fields::Named(named) => {
-                let fields = named_to_record_fields(
-                    named,
-                    variant_attrs
-                        .rename_all
-                        .or(container_attrs.rename_all_fields),
-                )?;
-
-                let schema_expr = quote! {
-                    ::apache_avro::schema::Schema::Record(
-                        ::apache_avro::schema::RecordSchema::builder()
-                            .name(::apache_avro::schema::Name::new_with_enclosing_namespace(#name, enclosing_namespace).expect(&format!("Unable to parse variant record name for schema {}", #name)[..]))
-                            .fields(#fields)
-                            .build()
-                    )
-                };
-                variant_expr.push(schema_expr);
+                // TODO: Support struct variants for untagged enums. All fields of all variants need to be in one record with
+                //       defaults for every field and the record named as the enum.
+                return Err(vec![syn::Error::new(
+                    named.span(),
+                    "AvroSchema: Struct variants are not supported for untagged structs",
+                )]);
             }
             Fields::Unnamed(unnamed) => {
                 if unnamed.unnamed.is_empty() {
@@ -47,8 +38,9 @@ pub fn get_data_enum_schema_def(
                     )]);
                 } else if unnamed.unnamed.len() == 1 {
                     let only_one = unnamed.unnamed.iter().next().expect("There is one");
-                    let field_attrs = FieldOptions::new(&only_one.attrs, only_one.span())?;
-                    let schema_expr = fields::to_schema(&only_one, field_attrs.with)?;
+                    let field_attrs =
+                        FieldOptions::new_for_newtype(&only_one.attrs, only_one.span())?;
+                    let schema_expr = fields::to_schema(only_one, field_attrs.with)?;
                     variant_expr.push(schema_expr);
                 } else if unnamed.unnamed.len() > 1 {
                     let schema_expr = tuple_struct_variant_to_record_schema(unnamed, &name, &[])?;
@@ -63,16 +55,18 @@ pub fn get_data_enum_schema_def(
                         "More than one variant maps to Schema::Null, this is not supported for bare unions",
                     )]);
                 }
-                variant_expr.push(quote! { ::apache_avro::schema::Schema::Null });
+                variant_expr.push(TypedTokenStream::<Schema>::new(
+                    quote! { ::apache_avro::schema::Schema::Null },
+                ));
                 have_null = true;
             }
         }
     }
-    Ok(quote! {
+    Ok(TypedTokenStream::new(quote! {{
         let mut builder = ::apache_avro::schema::UnionSchema::builder();
 
         #(builder.variant(#variant_expr).expect("Duplicate Schema found");)*
 
         ::apache_avro::schema::Schema::Union(builder.build())
-    })
+    }}))
 }
diff --git a/avro_derive/src/enums/mod.rs b/avro_derive/src/enums/mod.rs
index e9c2418..d83033d 100644
--- a/avro_derive/src/enums/mod.rs
+++ b/avro_derive/src/enums/mod.rs
@@ -21,8 +21,12 @@ mod record_internally_tagged;
 mod record_tag_content;
 mod union_of_records;
 
-use crate::attributes::{EnumRepr, NamedTypeOptions};
-use proc_macro2::{Ident, Span, TokenStream};
+use crate::{
+    attributes::{EnumRepr, NamedTypeOptions},
+    utils::{Schema, TypedTokenStream},
+};
+use proc_macro2::{Ident, Span};
+use quote::quote;
 use syn::{Attribute, DataEnum, Fields, Meta};
 
 /// Generate a schema definition for a enum.
@@ -30,7 +34,7 @@ pub fn get_data_enum_schema_def(
     container_attrs: &NamedTypeOptions,
     data_enum: DataEnum,
     ident_span: Span,
-) -> Result<TokenStream, Vec<syn::Error>> {
+) -> Result<TypedTokenStream<Schema>, Vec<syn::Error>> {
     match &container_attrs.repr {
         None => {
             if data_enum.variants.iter().all(|v| Fields::Unit == v.fields) {
@@ -58,15 +62,22 @@ pub fn get_data_enum_schema_def(
 fn default_enum_variant(
     data_enum: &DataEnum,
     error_span: Span,
-) -> Result<Option<String>, Vec<syn::Error>> {
+) -> Result<TypedTokenStream<Option<String>>, Vec<syn::Error>> {
     match data_enum
         .variants
         .iter()
         .filter(|v| v.attrs.iter().any(is_default_attr))
         .collect::<Vec<_>>()
     {
-        variants if variants.is_empty() => Ok(None),
-        single if single.len() == 1 => Ok(Some(single[0].ident.to_string())),
+        variants if variants.is_empty() => Ok(TypedTokenStream::new(
+            quote! { ::std::option::Option::None },
+        )),
+        single if single.len() == 1 => {
+            let ident = single[0].ident.to_string();
+            Ok(TypedTokenStream::new(
+                quote! { ::std::option::Option::Some(#ident.to_string()) },
+            ))
+        }
         multiple => Err(vec![syn::Error::new(
             error_span,
             format!(
diff --git a/avro_derive/src/enums/plain.rs b/avro_derive/src/enums/plain.rs
index 648ebd4..29bbe90 100644
--- a/avro_derive/src/enums/plain.rs
+++ b/avro_derive/src/enums/plain.rs
@@ -18,8 +18,9 @@
 use crate::attributes::{NamedTypeOptions, VariantOptions};
 use crate::case::RenameRule;
 use crate::enums::default_enum_variant;
+use crate::utils::{Schema, TypedTokenStream};
 use crate::{aliases, preserve_optional};
-use proc_macro2::{Span, TokenStream};
+use proc_macro2::Span;
 use quote::quote;
 use syn::spanned::Spanned;
 use syn::{DataEnum, Fields};
@@ -28,12 +29,11 @@ pub fn schema_def(
     container_attrs: &NamedTypeOptions,
     data_enum: DataEnum,
     ident_span: Span,
-) -> Result<TokenStream, Vec<syn::Error>> {
+) -> Result<TypedTokenStream<Schema>, Vec<syn::Error>> {
     let doc = preserve_optional(container_attrs.doc.as_ref());
     let enum_aliases = aliases(&container_attrs.aliases);
     if data_enum.variants.iter().all(|v| Fields::Unit == v.fields) {
-        let default_value = default_enum_variant(&data_enum, ident_span)?;
-        let default = preserve_optional(default_value);
+        let default = default_enum_variant(&data_enum, ident_span)?;
         let mut symbols = Vec::new();
         for variant in &data_enum.variants {
             let field_attrs = VariantOptions::new(&variant.attrs, variant.span())?;
@@ -46,7 +46,7 @@ pub fn schema_def(
             };
             symbols.push(name);
         }
-        Ok(quote! {
+        Ok(TypedTokenStream::new(quote! {
             ::apache_avro::schema::Schema::Enum(::apache_avro::schema::EnumSchema {
                 name,
                 aliases: #enum_aliases,
@@ -55,7 +55,7 @@ pub fn schema_def(
                 default: #default,
                 attributes: ::std::collections::BTreeMap::new(),
             })
-        })
+        }))
     } else {
         Err(vec![syn::Error::new(
             ident_span,
diff --git a/avro_derive/src/enums/record_internally_tagged.rs b/avro_derive/src/enums/record_internally_tagged.rs
index e9173cd..4387e42 100644
--- a/avro_derive/src/enums/record_internally_tagged.rs
+++ b/avro_derive/src/enums/record_internally_tagged.rs
@@ -1,6 +1,6 @@
-use crate::attributes::{FieldOptions, NamedTypeOptions, VariantOptions};
-use crate::{aliases, fields, named_to_record_fields, preserve_optional};
-use proc_macro2::TokenStream;
+use crate::attributes::{NamedTypeOptions, VariantOptions};
+use crate::utils::{Schema, TypedTokenStream};
+use crate::{FieldOptions, aliases, fields, named_to_record_fields, preserve_optional};
 use quote::quote;
 use syn::spanned::Spanned;
 use syn::{DataEnum, Fields};
@@ -9,18 +9,12 @@ pub fn get_data_enum_schema_def(
     container_attrs: &NamedTypeOptions,
     data_enum: DataEnum,
     tag: &str,
-) -> Result<TokenStream, Vec<syn::Error>> {
+) -> Result<TypedTokenStream<Schema>, Vec<syn::Error>> {
     let doc = preserve_optional(container_attrs.doc.as_ref());
     let enum_aliases = aliases(&container_attrs.aliases);
-    let mut symbols = Vec::new();
     let mut field_additions = Vec::new();
     for variant in data_enum.variants {
         let variant_attrs = VariantOptions::new(&variant.attrs, variant.span())?;
-        let name = variant_attrs.rename.unwrap_or_else(|| {
-            container_attrs
-                .rename_all
-                .apply_to_variant(&variant.ident.to_string())
-        });
         match variant.fields {
             Fields::Named(named) => {
                 let named_fields = named_to_record_fields(
@@ -36,10 +30,15 @@ pub fn get_data_enum_schema_def(
             Fields::Unnamed(unnamed) => {
                 if unnamed.unnamed.len() == 1 {
                     let only_one = unnamed.unnamed.iter().next().expect("There is one");
-                    let field_attrs = FieldOptions::new(&only_one.attrs, only_one.span())?;
-                    let schema_expr = fields::to_schema(&only_one, field_attrs.with)?;
+                    let field_attrs =
+                        FieldOptions::new_for_newtype(&only_one.attrs, only_one.span())?;
+                    let schema_expr = fields::to_record_fields(only_one, field_attrs.with)?;
                     field_additions.push(quote! {
-                        fields.push(#schema_expr);
+                        if let Some(record_fields) = #schema_expr {
+                            fields.extend(record_fields);
+                        } else {
+                            panic!("Newtype variant type must implement `get_record_fields` for internally tagged enums");
+                        }
                     });
                 } else if unnamed.unnamed.len() > 1 {
                     return Err(vec![syn::Error::new(
@@ -50,18 +49,14 @@ pub fn get_data_enum_schema_def(
             }
             Fields::Unit => {}
         }
-        symbols.push(name);
     }
-    Ok(quote! {
+    Ok(TypedTokenStream::new(quote! {{
         let mut builder = ::apache_avro::schema::UnionSchema::builder();
 
-        let tag_name = ::apache_avro::schema::Name::new_with_enclosing_namespace(#tag, enclosing_namespace).expect(&format!("Unable to parse name for schema tag {}", #tag)[..]);
-        let tag_schema = ::apache_avro::schema::Schema::r#enum(tag_name, vec![#(#symbols.to_owned()),*]).build();
-
         let mut fields = ::std::vec::Vec::new();
         fields.push(::apache_avro::schema::RecordField::builder()
             .name(#tag)
-            .schema(tag_schema)
+            .schema(::apache_avro::schema::Schema::String)
             .build()
         );
         #(
@@ -74,5 +69,5 @@ pub fn get_data_enum_schema_def(
             .fields(fields)
             .build()
         )
-    })
+    }}))
 }
diff --git a/avro_derive/src/enums/record_tag_content.rs b/avro_derive/src/enums/record_tag_content.rs
index a1aebf5..81ac0a7 100644
--- a/avro_derive/src/enums/record_tag_content.rs
+++ b/avro_derive/src/enums/record_tag_content.rs
@@ -1,7 +1,7 @@
 use crate::attributes::{FieldOptions, NamedTypeOptions, VariantOptions};
 use crate::tuple::tuple_struct_variant_to_record_schema;
+use crate::utils::{Schema, TypedTokenStream};
 use crate::{aliases, fields, named_to_record_fields, preserve_optional};
-use proc_macro2::TokenStream;
 use quote::quote;
 use syn::spanned::Spanned;
 use syn::{DataEnum, Fields};
@@ -11,7 +11,7 @@ pub fn get_data_enum_schema_def(
     data_enum: DataEnum,
     tag: &str,
     content: &str,
-) -> Result<TokenStream, Vec<syn::Error>> {
+) -> Result<TypedTokenStream<Schema>, Vec<syn::Error>> {
     let doc = preserve_optional(container_attrs.doc.as_ref());
     let enum_aliases = aliases(&container_attrs.aliases);
     let mut symbols = Vec::new();
@@ -32,26 +32,27 @@ pub fn get_data_enum_schema_def(
                         .or(container_attrs.rename_all_fields),
                 )?;
 
-                let schema_expr = quote! {
+                let schema_expr = TypedTokenStream::<Schema>::new(quote! {
                     ::apache_avro::schema::Schema::Record(
                         ::apache_avro::schema::RecordSchema::builder()
                             .name(::apache_avro::schema::Name::new_with_enclosing_namespace(#name, enclosing_namespace).expect(&format!("Unable to parse variant record name for schema {}", #name)[..]))
                             .fields(#fields)
                             .build()
                     )
-                };
+                });
                 schema_definitions.push(schema_expr);
             }
             Fields::Unnamed(unnamed) => {
                 if unnamed.unnamed.is_empty() {
-                    let schema_expr = quote! {
+                    let schema_expr = TypedTokenStream::<Schema>::new(quote! {
                         ::apache_avro::schema::Schema::Null
-                    };
+                    });
                     schema_definitions.push(schema_expr);
                 } else if unnamed.unnamed.len() == 1 {
                     let only_one = unnamed.unnamed.iter().next().expect("There is one");
-                    let field_attrs = FieldOptions::new(&only_one.attrs, only_one.span())?;
-                    let schema_expr = fields::to_schema(&only_one, field_attrs.with)?;
+                    let field_attrs =
+                        FieldOptions::new_for_newtype(&only_one.attrs, only_one.span())?;
+                    let schema_expr = fields::to_schema(only_one, field_attrs.with)?;
                     schema_definitions.push(schema_expr);
                 } else if unnamed.unnamed.len() > 1 {
                     let schema_expr = tuple_struct_variant_to_record_schema(unnamed, &name, &[])?;
@@ -59,11 +60,13 @@ pub fn get_data_enum_schema_def(
                     schema_definitions.push(schema_expr);
                 }
             }
-            Fields::Unit => schema_definitions.push(quote! { ::apache_avro::schema::Schema::Null }),
+            Fields::Unit => schema_definitions.push(TypedTokenStream::<Schema>::new(
+                quote! { ::apache_avro::schema::Schema::Null },
+            )),
         }
         symbols.push(name);
     }
-    Ok(quote! {
+    Ok(TypedTokenStream::new(quote! {{
         let mut builder = ::apache_avro::schema::UnionSchema::builder();
 
         #(
@@ -96,5 +99,5 @@ pub fn get_data_enum_schema_def(
             .fields(fields)
             .build()
         )
-    })
+    }}))
 }
diff --git a/avro_derive/src/enums/union_of_records.rs b/avro_derive/src/enums/union_of_records.rs
index c0f8902..d7cfd59 100644
--- a/avro_derive/src/enums/union_of_records.rs
+++ b/avro_derive/src/enums/union_of_records.rs
@@ -1,7 +1,7 @@
 use crate::attributes::{NamedTypeOptions, VariantOptions};
 use crate::named_to_record_fields;
 use crate::tuple::tuple_struct_variant_to_record_schema;
-use proc_macro2::TokenStream;
+use crate::utils::{Schema, TypedTokenStream};
 use quote::quote;
 use syn::spanned::Spanned;
 use syn::{DataEnum, Fields};
@@ -9,7 +9,7 @@ use syn::{DataEnum, Fields};
 pub fn get_data_enum_schema_def(
     container_attrs: &NamedTypeOptions,
     data_enum: DataEnum,
-) -> Result<TokenStream, Vec<syn::Error>> {
+) -> Result<TypedTokenStream<Schema>, Vec<syn::Error>> {
     let mut variant_expr = Vec::new();
     for variant in data_enum.variants {
         let variant_attrs = VariantOptions::new(&variant.attrs, variant.span())?;
@@ -27,14 +27,14 @@ pub fn get_data_enum_schema_def(
                         .or(container_attrs.rename_all_fields),
                 )?;
 
-                let schema_expr = quote! {
+                let schema_expr = TypedTokenStream::<Schema>::new(quote! {
                     ::apache_avro::schema::Schema::Record(
                         ::apache_avro::schema::RecordSchema::builder()
                             .name(::apache_avro::schema::Name::new_with_enclosing_namespace(#name, enclosing_namespace).expect(&format!("Unable to parse variant record name for schema {}", #name)[..]))
                             .fields(#fields)
                             .build()
                     )
-                };
+                });
                 variant_expr.push(schema_expr);
             }
             Fields::Unnamed(unnamed) => {
@@ -51,22 +51,22 @@ pub fn get_data_enum_schema_def(
                 variant_expr.push(schema_expr);
             }
             Fields::Unit => {
-                let schema_expr = quote! {
+                let schema_expr = TypedTokenStream::<Schema>::new(quote! {
                     ::apache_avro::schema::Schema::Record(
                         ::apache_avro::schema::RecordSchema::builder()
                             .name(::apache_avro::schema::Name::new_with_enclosing_namespace(#name, enclosing_namespace).expect(&format!("Unable to parse variant record name for schema {}", #name)[..]))
                             .build()
                     )
-                };
+                });
                 variant_expr.push(schema_expr);
             }
         }
     }
-    Ok(quote! {
+    Ok(TypedTokenStream::new(quote! {{
         let mut builder = ::apache_avro::schema::UnionSchema::builder();
 
         #(builder.variant(#variant_expr).expect("Duplicate Schema found");)*
 
         ::apache_avro::schema::Schema::Union(builder.build())
-    })
+    }}))
 }
diff --git a/avro_derive/src/fields.rs b/avro_derive/src/fields.rs
index af5b3aa..8e4702d 100644
--- a/avro_derive/src/fields.rs
+++ b/avro_derive/src/fields.rs
@@ -1,18 +1,19 @@
+use crate::RecordField;
 use crate::attributes::{FieldDefault, With};
-use proc_macro2::TokenStream;
+use crate::utils::{Schema, TypedTokenStream};
 use quote::{ToTokens, quote};
 use syn::spanned::Spanned;
 use syn::{Expr, ExprLit, Field, Lit, Type, TypeArray, TypeTuple};
 
-pub fn to_schema(field: &Field, with: With) -> Result<TokenStream, Vec<syn::Error>> {
+pub fn to_schema(field: &Field, with: With) -> Result<TypedTokenStream<Schema>, Vec<syn::Error>> {
     match with {
         With::Trait => Ok(type_to_schema_expr(&field.ty)?),
-        With::Serde(path) => {
-            Ok(quote! { #path::get_schema_in_ctxt(named_schemas, enclosing_namespace) })
-        }
+        With::Serde(path) => Ok(TypedTokenStream::<Schema>::new(
+            quote! { #path::get_schema_in_ctxt(named_schemas, enclosing_namespace) },
+        )),
         With::Expr(Expr::Closure(closure)) => {
             if closure.inputs.is_empty() {
-                Ok(quote! { (#closure)() })
+                Ok(TypedTokenStream::<Schema>::new(quote! { (#closure)() }))
             } else {
                 Err(vec![syn::Error::new(
                     field.span(),
@@ -20,7 +21,9 @@ pub fn to_schema(field: &Field, with: With) -> Result<TokenStream, Vec<syn::Erro
                 )])
             }
         }
-        With::Expr(Expr::Path(path)) => Ok(quote! { #path(named_schemas, enclosing_namespace) }),
+        With::Expr(Expr::Path(path)) => Ok(TypedTokenStream::<Schema>::new(
+            quote! { #path(named_schemas, enclosing_namespace) },
+        )),
         With::Expr(_expr) => Err(vec![syn::Error::new(
             field.span(),
             "Invalid expression, expected function or closure",
@@ -37,21 +40,24 @@ pub fn to_schema(field: &Field, with: With) -> Result<TokenStream, Vec<syn::Erro
 /// - `enclosing_namespace`: `Option<&str>`
 /// ## Returns
 /// An `Expr` that resolves to an instance of `Option<Vec<RecordField>>`.
-pub fn to_record_fields(field: &Field, with: With) -> Result<TokenStream, Vec<syn::Error>> {
+pub fn to_record_fields(
+    field: &Field,
+    with: With,
+) -> Result<TypedTokenStream<Option<Vec<RecordField>>>, Vec<syn::Error>> {
     match with {
         With::Trait => Ok(type_to_record_fields(&field.ty)?),
-        With::Serde(path) => {
-            Ok(quote! { #path::get_record_fields_in_ctxt(named_schemas, enclosing_namespace) })
-        }
+        With::Serde(path) => Ok(TypedTokenStream::<Option<Vec<RecordField>>>::new(
+            quote! { #path::get_record_fields_in_ctxt(named_schemas, enclosing_namespace) },
+        )),
         With::Expr(Expr::Closure(closure)) => {
             if closure.inputs.is_empty() {
-                Ok(quote! {
+                Ok(TypedTokenStream::<Option<Vec<RecordField>>>::new(quote! {
                     ::apache_avro::serde::get_record_fields_in_ctxt(
                         named_schemas,
                         enclosing_namespace,
                         |_, _| (#closure)(),
                     )
-                })
+                }))
             } else {
                 Err(vec![syn::Error::new(
                     field.span(),
@@ -59,9 +65,11 @@ pub fn to_record_fields(field: &Field, with: With) -> Result<TokenStream, Vec<sy
                 )])
             }
         }
-        With::Expr(Expr::Path(path)) => Ok(quote! {
-            ::apache_avro::serde::get_record_fields_in_ctxt(named_schemas, enclosing_namespace, #path)
-        }),
+        With::Expr(Expr::Path(path)) => {
+            Ok(TypedTokenStream::<Option<Vec<RecordField>>>::new(quote! {
+                ::apache_avro::serde::get_record_fields_in_ctxt(named_schemas, enclosing_namespace, #path)
+            }))
+        }
         With::Expr(_expr) => Err(vec![syn::Error::new(
             field.span(),
             "Invalid expression, expected function or closure",
@@ -69,9 +77,12 @@ pub fn to_record_fields(field: &Field, with: With) -> Result<TokenStream, Vec<sy
     }
 }
 
-pub fn to_default(field: &Field, default: FieldDefault) -> Result<TokenStream, Vec<syn::Error>> {
+pub fn to_default(
+    field: &Field,
+    default: FieldDefault,
+) -> Result<TypedTokenStream<Option<serde_json::Value>>, Vec<syn::Error>> {
     match default {
-        FieldDefault::Disabled => Ok(quote! { ::std::option::Option::None }),
+        FieldDefault::Disabled => Ok(TypedTokenStream::none()),
         FieldDefault::Trait => type_to_field_default(&field.ty),
         FieldDefault::Value(default_value) => {
             let _: serde_json::Value = serde_json::from_str(&default_value[..]).map_err(|e| {
@@ -80,9 +91,9 @@ pub fn to_default(field: &Field, default: FieldDefault) -> Result<TokenStream, V
                     format!("Invalid avro default json: \n{e}"),
                 )]
             })?;
-            Ok(quote! {
+            Ok(TypedTokenStream::<Option<serde_json::Value>>::new(quote! {
                 ::std::option::Option::Some(::serde_json::from_str(#default_value).expect("Unreachable! Checked at compile time"))
-            })
+            }))
         }
     }
 }
@@ -96,11 +107,11 @@ pub fn to_default(field: &Field, default: FieldDefault) -> Result<TokenStream, V
 /// - `enclosing_namespace`: `Option<&str>`
 /// ## Returns
 /// An `Expr` that resolves to an instance of `Schema`.
-fn type_to_schema_expr(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
+fn type_to_schema_expr(ty: &Type) -> Result<TypedTokenStream<Schema>, Vec<syn::Error>> {
     match ty {
-        Type::Slice(_) | Type::Path(_) | Type::Reference(_) => Ok(
+        Type::Slice(_) | Type::Path(_) | Type::Reference(_) => Ok(TypedTokenStream::<Schema>::new(
             quote! {<#ty as :: apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)},
-        ),
+        )),
         Type::Tuple(tuple) => tuple_to_schema(tuple),
         Type::Array(array) => array_to_schema(array),
         Type::Ptr(_) => Err(vec![syn::Error::new_spanned(
@@ -130,11 +141,13 @@ fn type_to_schema_expr(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
 /// - `enclosing_namespace`: `Option<&str>`
 /// ## Returns
 /// An `Expr` that resolves to an instance of `Schema`.
-fn tuple_to_schema(tuple: &TypeTuple) -> Result<TokenStream, Vec<syn::Error>> {
+fn tuple_to_schema(tuple: &TypeTuple) -> Result<TypedTokenStream<Schema>, Vec<syn::Error>> {
     if tuple.elems.is_empty() {
-        Ok(quote! {::apache_avro::schema::Schema::Null})
+        Ok(TypedTokenStream::<Schema>::new(
+            quote! {::apache_avro::schema::Schema::Null},
+        ))
     } else if tuple.elems.len() == 1 {
-        type_to_schema_expr(&tuple.elems.iter().next().unwrap())
+        type_to_schema_expr(tuple.elems.iter().next().unwrap())
     } else {
         let mut fields = Vec::with_capacity(tuple.elems.len());
 
@@ -161,7 +174,7 @@ fn tuple_to_schema(tuple: &TypeTuple) -> Result<TokenStream, Vec<syn::Error>> {
 
         let name = format!("tuple_{}{tuple_as_valid_name}", tuple.elems.len());
 
-        Ok(quote! {
+        Ok(TypedTokenStream::<Schema>::new(quote! {
             ::apache_avro::schema::Schema::Record(::apache_avro::schema::RecordSchema::builder()
                 .name(::apache_avro::schema::Name::new_with_enclosing_namespace(#name, enclosing_namespace).expect(&format!("Unable to parse variant record name for schema {}", #name)[..]))
                 .fields(vec![
@@ -174,7 +187,7 @@ fn tuple_to_schema(tuple: &TypeTuple) -> Result<TokenStream, Vec<syn::Error>> {
                 )
                 .build()
             )
-        })
+        }))
     }
 }
 
@@ -192,7 +205,7 @@ fn tuple_to_schema(tuple: &TypeTuple) -> Result<TokenStream, Vec<syn::Error>> {
 /// - `enclosing_namespace`: `Option<&str>`
 /// ## Returns
 /// An `Expr` that resolves to an instance of `Schema`.
-fn array_to_schema(array: &TypeArray) -> Result<TokenStream, Vec<syn::Error>> {
+fn array_to_schema(array: &TypeArray) -> Result<TypedTokenStream<Schema>, Vec<syn::Error>> {
     let Expr::Lit(ExprLit {
         lit: Lit::Int(lit), ..
     }) = &array.len
@@ -206,7 +219,9 @@ fn array_to_schema(array: &TypeArray) -> Result<TokenStream, Vec<syn::Error>> {
     let len: usize = lit.base10_parse().map_err(|e| vec![e])?;
 
     if len == 0 {
-        Ok(quote! {::apache_avro::schema::Schema::Null})
+        Ok(TypedTokenStream::<Schema>::new(
+            quote! {::apache_avro::schema::Schema::Null},
+        ))
     } else if len == 1 {
         type_to_schema_expr(&array.elem)
     } else {
@@ -233,7 +248,7 @@ fn array_to_schema(array: &TypeArray) -> Result<TokenStream, Vec<syn::Error>> {
 
         let name = format!("array_{len}_{array_elem_as_valid_name}");
 
-        Ok(quote! {
+        Ok(TypedTokenStream::<Schema>::new(quote! {
             ::apache_avro::schema::Schema::Record(::apache_avro::schema::RecordSchema::builder()
                 .name(::apache_avro::schema::Name::new_with_enclosing_namespace(#name, enclosing_namespace).expect(&format!("Unable to parse variant record name for schema {}", #name)[..]))
                 .fields(vec![
@@ -246,15 +261,19 @@ fn array_to_schema(array: &TypeArray) -> Result<TokenStream, Vec<syn::Error>> {
                 )
                 .build()
             )
-        })
+        }))
     }
 }
 
-fn type_to_record_fields(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
+fn type_to_record_fields(
+    ty: &Type,
+) -> Result<TypedTokenStream<Option<Vec<RecordField>>>, Vec<syn::Error>> {
     match ty {
-        Type::Slice(_) | Type::Path(_) | Type::Reference(_) => Ok(
-            quote! {<#ty as :: apache_avro::AvroSchemaComponent>::get_record_fields_in_ctxt(named_schemas, enclosing_namespace)},
-        ),
+        Type::Slice(_) | Type::Path(_) | Type::Reference(_) => {
+            Ok(TypedTokenStream::<Option<Vec<RecordField>>>::new(
+                quote! {<#ty as :: apache_avro::AvroSchemaComponent>::get_record_fields_in_ctxt(named_schemas, enclosing_namespace)},
+            ))
+        }
         Type::Array(array) => array_to_record_fields(array),
         Type::Tuple(tuple) => tuple_to_record_fields(tuple),
         Type::Ptr(_) => Err(vec![syn::Error::new_spanned(
@@ -284,11 +303,13 @@ fn type_to_record_fields(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
 /// - `enclosing_namespace`: `Option<&str>`
 /// ## Returns
 /// An `Expr` that resolves to an instance of `Schema`.
-fn tuple_to_record_fields(tuple: &TypeTuple) -> Result<TokenStream, Vec<syn::Error>> {
+fn tuple_to_record_fields(
+    tuple: &TypeTuple,
+) -> Result<TypedTokenStream<Option<Vec<RecordField>>>, Vec<syn::Error>> {
     if tuple.elems.is_empty() {
-        Ok(quote! {::std::option::Option::None})
+        Ok(TypedTokenStream::none())
     } else if tuple.elems.len() == 1 {
-        type_to_record_fields(&tuple.elems.iter().next().unwrap())
+        type_to_record_fields(tuple.elems.iter().next().unwrap())
     } else {
         let mut fields = Vec::with_capacity(tuple.elems.len());
 
@@ -303,9 +324,9 @@ fn tuple_to_record_fields(tuple: &TypeTuple) -> Result<TokenStream, Vec<syn::Err
             });
         }
 
-        Ok(quote! {
+        Ok(TypedTokenStream::new(quote! {
             ::std::option::Option::Some(vec![#(#fields, )*])
-        })
+        }))
     }
 }
 /// Create a schema definition for an array.
@@ -322,10 +343,12 @@ fn tuple_to_record_fields(tuple: &TypeTuple) -> Result<TokenStream, Vec<syn::Err
 /// - `enclosing_namespace`: `Option<&str>`
 /// ## Returns
 /// An `Expr` that resolves to an instance of `Schema`.
-fn array_to_record_fields(array: &TypeArray) -> Result<TokenStream, Vec<syn::Error>> {
+fn array_to_record_fields(
+    array: &TypeArray,
+) -> Result<TypedTokenStream<Option<Vec<RecordField>>>, Vec<syn::Error>> {
     let Expr::Lit(ExprLit {
-                      lit: Lit::Int(lit), ..
-                  }) = &array.len
+        lit: Lit::Int(lit), ..
+    }) = &array.len
     else {
         return Err(vec![syn::Error::new(
             array.span(),
@@ -336,7 +359,9 @@ fn array_to_record_fields(array: &TypeArray) -> Result<TokenStream, Vec<syn::Err
     let len: usize = lit.base10_parse().map_err(|e| vec![e])?;
 
     if len == 0 {
-        Ok(quote! {::std::option::Option::None})
+        Ok(TypedTokenStream::<Option<_>>::new(
+            quote! {::std::option::Option::None},
+        ))
     } else if len == 1 {
         type_to_record_fields(&array.elem)
     } else {
@@ -351,22 +376,26 @@ fn array_to_record_fields(array: &TypeArray) -> Result<TokenStream, Vec<syn::Err
             }
         });
 
-        Ok(quote! {
+        Ok(TypedTokenStream::<Option<Vec<RecordField>>>::new(quote! {
             ::std::option::Option::Some(vec![#(#fields, )*])
-        })
+        }))
     }
 }
 
-fn type_to_field_default(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
+fn type_to_field_default(
+    ty: &Type,
+) -> Result<TypedTokenStream<Option<serde_json::Value>>, Vec<syn::Error>> {
     match ty {
         Type::Slice(_) | Type::Path(_) | Type::Reference(_) => {
-            Ok(quote! {<#ty as :: apache_avro::AvroSchemaComponent>::field_default()})
+            Ok(TypedTokenStream::<Option<serde_json::Value>>::new(
+                quote! {<#ty as :: apache_avro::AvroSchemaComponent>::field_default()},
+            ))
         }
         Type::Ptr(_) => Err(vec![syn::Error::new_spanned(
             ty,
             "AvroSchema: derive does not support raw pointers",
         )]),
-        Type::Tuple(_) | Type::Array(_) => Ok(quote! { ::std::option::Option::None }),
+        Type::Tuple(_) | Type::Array(_) => Ok(TypedTokenStream::none()),
         _ => Err(vec![syn::Error::new_spanned(
             ty,
             format!(
diff --git a/avro_derive/src/lib.rs b/avro_derive/src/lib.rs
index ab3e8c2..57fb3a0 100644
--- a/avro_derive/src/lib.rs
+++ b/avro_derive/src/lib.rs
@@ -34,11 +34,12 @@ mod case;
 mod enums;
 mod fields;
 mod tuple;
+mod utils;
 
-use proc_macro2::{Span, TokenStream};
+use proc_macro2::Span;
 use quote::quote;
 use syn::{
-    DataStruct, DeriveInput, Fields, FieldsNamed, Generics, Ident, Type, parse_macro_input,
+    DataStruct, DeriveInput, Fields, FieldsNamed, Generics, Ident, parse_macro_input,
     spanned::Spanned,
 };
 
@@ -46,6 +47,10 @@ use crate::{
     attributes::{FieldOptions, NamedTypeOptions},
     case::RenameRule,
     tuple::unnamed_to_record_fields,
+    utils::{
+        RecordField, Schema, TypedTokenStream, aliases, doc_into_tokenstream, field_aliases,
+        preserve_optional, to_compile_errors,
+    },
 };
 
 #[proc_macro_derive(AvroSchema, attributes(avro, serde))]
@@ -57,7 +62,7 @@ pub fn proc_macro_derive_avro_schema(input: proc_macro::TokenStream) -> proc_mac
         .into()
 }
 
-fn derive_avro_schema(input: DeriveInput) -> Result<TokenStream, Vec<syn::Error>> {
+fn derive_avro_schema(input: DeriveInput) -> Result<proc_macro2::TokenStream, Vec<syn::Error>> {
     // It would be nice to parse the attributes before the `match`, but we first need to validate that `input` is not a union.
     // Otherwise a user could get errors related to the attributes and after fixing those get an error because the attributes were on a union.
     let input_span = input.span();
@@ -68,9 +73,15 @@ fn derive_avro_schema(input: DeriveInput) -> Result<TokenStream, Vec<syn::Error>
         Ok(create_trait_definition(
             input.ident,
             &input.generics,
-            quote! { <#path as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace) },
-            quote! { <#path as ::apache_avro::AvroSchemaComponent>::get_record_fields_in_ctxt(named_schemas, enclosing_namespace) },
-            quote! { <#path as ::apache_avro::AvroSchemaComponent>::field_default() },
+            TypedTokenStream::<Schema>::new(
+                quote! { <#path as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace) },
+            ),
+            TypedTokenStream::<Option<Vec<RecordField>>>::new(
+                quote! { <#path as ::apache_avro::AvroSchemaComponent>::get_record_fields_in_ctxt(named_schemas, enclosing_namespace) },
+            ),
+            TypedTokenStream::<Option<serde_json::Value>>::new(
+                quote! { <#path as ::apache_avro::AvroSchemaComponent>::field_default() },
+            ),
         ))
     } else {
         match input.data {
@@ -116,7 +127,7 @@ fn derive_avro_schema(input: DeriveInput) -> Result<TokenStream, Vec<syn::Error>
                     input.ident,
                     &input.generics,
                     inner,
-                    quote! { None },
+                    TypedTokenStream::none(),
                     named_type_options.default,
                 ))
             }
@@ -132,10 +143,10 @@ fn derive_avro_schema(input: DeriveInput) -> Result<TokenStream, Vec<syn::Error>
 fn create_trait_definition(
     ident: Ident,
     generics: &Generics,
-    get_schema_impl: TokenStream,
-    get_record_fields_impl: TokenStream,
-    field_default_impl: TokenStream,
-) -> TokenStream {
+    get_schema_impl: TypedTokenStream<Schema>,
+    get_record_fields_impl: TypedTokenStream<Option<Vec<RecordField>>>,
+    field_default_impl: TypedTokenStream<Option<serde_json::Value>>,
+) -> proc_macro2::TokenStream {
     let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
     quote! {
         #[automatically_derived]
@@ -156,8 +167,11 @@ fn create_trait_definition(
 }
 
 /// Generate the code to check `named_schemas` if this schema already exist
-fn handle_named_schemas(full_schema_name: String, schema_def: TokenStream) -> TokenStream {
-    quote! {
+fn handle_named_schemas(
+    full_schema_name: String,
+    schema_def: TypedTokenStream<Schema>,
+) -> TypedTokenStream<Schema> {
+    TypedTokenStream::<Schema>::new(quote! {{
         let name = ::apache_avro::schema::Name::new_with_enclosing_namespace(#full_schema_name, enclosing_namespace).expect(concat!("Unable to parse schema name ", #full_schema_name));
         if named_schemas.contains(&name) {
             ::apache_avro::schema::Schema::Ref{name}
@@ -166,30 +180,36 @@ fn handle_named_schemas(full_schema_name: String, schema_def: TokenStream) -> To
             named_schemas.insert(name.clone());
             #schema_def
         }
-    }
+    }})
 }
 
 /// Generate a schema definition for a struct.
 fn get_struct_schema_def(
     container_attrs: &NamedTypeOptions,
     data_struct: DataStruct,
-) -> Result<(TokenStream, TokenStream), Vec<syn::Error>> {
+) -> Result<
+    (
+        TypedTokenStream<Schema>,
+        TypedTokenStream<Option<Vec<RecordField>>>,
+    ),
+    Vec<syn::Error>,
+> {
     let record_fields = match data_struct.fields {
         Fields::Named(a) => named_to_record_fields(a, container_attrs.rename_all)?,
         Fields::Unnamed(unnamed) => unnamed_to_record_fields(unnamed)?,
-        Fields::Unit => quote! { std::vec::Vec::<::apache_avro::schema::RecordField>::new() },
+        Fields::Unit => TypedTokenStream::<Vec<RecordField>>::new(
+            quote! { std::vec::Vec::<::apache_avro::schema::RecordField>::new() },
+        ),
     };
 
     let record_doc = preserve_optional(container_attrs.doc.as_ref());
     let record_aliases = aliases(&container_attrs.aliases);
-    let full_schema_name = &container_attrs.name;
 
-    let schema_def = quote! {
+    let schema_def = TypedTokenStream::<Schema>::new(quote! {
         {
             let schema_fields = #record_fields;
             let schema_field_set: ::std::collections::HashSet<_> = schema_fields.iter().map(|rf| &rf.name).collect();
             assert_eq!(schema_fields.len(), schema_field_set.len(), "Duplicate field names found: {schema_fields:?}");
-            let name = ::apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse struct name for schema {}", #full_schema_name)[..]);
             let lookup: ::std::collections::BTreeMap<String, usize> = schema_fields
                 .iter()
                 .enumerate()
@@ -204,10 +224,10 @@ fn get_struct_schema_def(
                 attributes: ::std::collections::BTreeMap::new(),
             })
         }
-    };
-    let record_fields = quote! {
+    });
+    let record_fields = TypedTokenStream::<Option<Vec<RecordField>>>::new(quote! {
         ::std::option::Option::Some(#record_fields)
-    };
+    });
 
     Ok((schema_def, record_fields))
 }
@@ -216,7 +236,13 @@ fn get_struct_schema_def(
 fn get_transparent_struct_schema_def(
     fields: Fields,
     input_span: Span,
-) -> Result<(TokenStream, TokenStream), Vec<syn::Error>> {
+) -> Result<
+    (
+        TypedTokenStream<Schema>,
+        TypedTokenStream<Option<Vec<RecordField>>>,
+    ),
+    Vec<syn::Error>,
+> {
     match fields {
         Fields::Named(fields_named) => {
             let mut found = None;
@@ -283,7 +309,7 @@ fn get_transparent_struct_schema_def(
 fn named_to_record_fields(
     named: FieldsNamed,
     rename_all: RenameRule,
-) -> Result<TokenStream, Vec<syn::Error>> {
+) -> Result<TypedTokenStream<Vec<RecordField>>, Vec<syn::Error>> {
     let mut fields = Vec::with_capacity(named.named.len());
     for field in named.named {
         let field_attrs = FieldOptions::new(&field.attrs, field.span())?;
@@ -338,7 +364,7 @@ fn named_to_record_fields(
 
     // When fields are flattened there might be more fields, but this is good for the regular case
     let minimum_length = fields.len();
-    Ok(quote! {
+    Ok(TypedTokenStream::new(quote! {
         {
             let mut fields = ::std::vec::Vec::with_capacity(#minimum_length);
             #(
@@ -346,49 +372,5 @@ fn named_to_record_fields(
             )*
             fields
         }
-    })
-}
-
-/// Stolen from serde
-fn to_compile_errors(errors: Vec<syn::Error>) -> TokenStream {
-    let compile_errors = errors.iter().map(syn::Error::to_compile_error);
-    quote!(#(#compile_errors)*)
-}
-
-fn preserve_optional(op: Option<impl quote::ToTokens>) -> TokenStream {
-    match op {
-        Some(tt) => quote! {::std::option::Option::Some(#tt.into())},
-        None => quote! {::std::option::Option::None},
-    }
-}
-
-fn doc_into_tokenstream(doc: Option<String>) -> TokenStream {
-    match doc {
-        Some(doc) => quote! {::std::option::Option::Some(#doc.to_string())},
-        None => quote! {::std::option::Option::None},
-    }
-}
-
-fn aliases(op: &[impl quote::ToTokens]) -> TokenStream {
-    let items: Vec<TokenStream> = op
-        .iter()
-        .map(|tt| quote! {#tt.try_into().expect("Alias is invalid")})
-        .collect();
-    if items.is_empty() {
-        quote! {::std::option::Option::None}
-    } else {
-        quote! {::std::option::Option::Some(vec![#(#items),*])}
-    }
-}
-
-fn field_aliases(op: &[impl quote::ToTokens]) -> TokenStream {
-    let items: Vec<TokenStream> = op
-        .iter()
-        .map(|tt| quote! {#tt.try_into().expect("Alias is invalid")})
-        .collect();
-    if items.is_empty() {
-        quote! {::std::vec::Vec::new()}
-    } else {
-        quote! {vec![#(#items),*]}
-    }
+    }))
 }
diff --git a/avro_derive/src/tuple.rs b/avro_derive/src/tuple.rs
index 8253332..1f669d1 100644
--- a/avro_derive/src/tuple.rs
+++ b/avro_derive/src/tuple.rs
@@ -1,8 +1,10 @@
-use proc_macro2::TokenStream;
 use quote::quote;
 use syn::{FieldsUnnamed, spanned::Spanned};
 
-use crate::{FieldOptions, doc_into_tokenstream, field_aliases, fields};
+use crate::{
+    FieldOptions, RecordField, Schema, TypedTokenStream, fields,
+    utils::{doc_into_tokenstream, field_aliases},
+};
 
 /// Create a `Schema::Record` from this tuple definition.
 ///
@@ -22,10 +24,10 @@ pub fn tuple_struct_variant_to_record_schema(
     unnamed: FieldsUnnamed,
     name: &str,
     extra_attributes: &[&str],
-) -> Result<TokenStream, Vec<syn::Error>> {
+) -> Result<TypedTokenStream<Schema>, Vec<syn::Error>> {
     let fields = unnamed_to_record_fields(unnamed)?;
 
-    Ok(quote! {
+    Ok(TypedTokenStream::<Schema>::new(quote! {
         ::apache_avro::schema::Schema::Record(::apache_avro::schema::RecordSchema::builder()
             .name(::apache_avro::schema::Name::new_with_enclosing_namespace(#name, enclosing_namespace).expect(&format!("Unable to parse variant record name for schema {}", #name)[..]))
             .fields(#fields)
@@ -37,7 +39,7 @@ pub fn tuple_struct_variant_to_record_schema(
             )
             .build()
         )
-    })
+    }))
 }
 
 /// Create a vector of `RecordField`s named `field_{field_index}`.
@@ -49,7 +51,9 @@ pub fn tuple_struct_variant_to_record_schema(
 /// - `enclosing_namespace`: `Option<&str>`
 /// ## Returns
 /// An `Expr` that resolves to an instance of `Vec<RecordField>`.
-pub fn unnamed_to_record_fields(unnamed: FieldsUnnamed) -> Result<TokenStream, Vec<syn::Error>> {
+pub fn unnamed_to_record_fields(
+    unnamed: FieldsUnnamed,
+) -> Result<TypedTokenStream<Vec<RecordField>>, Vec<syn::Error>> {
     let mut fields = Vec::with_capacity(unnamed.unnamed.len());
     for (index, field) in unnamed.unnamed.into_iter().enumerate() {
         let field_attrs = FieldOptions::new(&field.attrs, field.span())?;
@@ -78,9 +82,9 @@ pub fn unnamed_to_record_fields(unnamed: FieldsUnnamed) -> Result<TokenStream, V
                 .build()
         });
     }
-    Ok(quote! {
+    Ok(TypedTokenStream::new(quote! {
         vec![
             #(#fields, )*
         ]
-    })
+    }))
 }
diff --git a/avro_derive/src/utils.rs b/avro_derive/src/utils.rs
new file mode 100644
index 0000000..ecfa339
--- /dev/null
+++ b/avro_derive/src/utils.rs
@@ -0,0 +1,91 @@
+use std::{fmt::Display, marker::PhantomData};
+
+use quote::{ToTokens, quote};
+
+/// A [`TokenStream`] that is a expression that evaluates to `Output`
+///
+/// This token stream can define and expect any variables names.
+pub struct TypedTokenStream<Output> {
+    output: PhantomData<Output>,
+    stream: proc_macro2::TokenStream,
+}
+
+/// Stand-in for `apache_avro::Schema` for [`TypedTokenStream`]
+pub struct Schema;
+
+/// Stand-in for `apache_avro::schema::RecordField` for [`TypedTokenStream`]
+pub struct RecordField;
+
+impl<Output> TypedTokenStream<Output> {
+    pub fn new(stream: proc_macro2::TokenStream) -> Self {
+        Self {
+            output: PhantomData,
+            stream,
+        }
+    }
+}
+
+impl<Output> TypedTokenStream<Option<Output>> {
+    pub fn none() -> Self {
+        Self {
+            output: PhantomData,
+            stream: quote! { ::std::option::Option::None },
+        }
+    }
+}
+
+impl<Output> ToTokens for TypedTokenStream<Output> {
+    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+        self.stream.to_tokens(tokens);
+    }
+}
+
+impl<Output> Display for TypedTokenStream<Output> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.stream.fmt(f)
+    }
+}
+
+/// Stolen from serde
+pub fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
+    let compile_errors = errors.iter().map(syn::Error::to_compile_error);
+    quote!(#(#compile_errors)*)
+}
+
+pub fn preserve_optional(op: Option<&String>) -> TypedTokenStream<Option<String>> {
+    match op {
+        Some(tt) => TypedTokenStream::new(quote! {::std::option::Option::Some(#tt.into())}),
+        None => TypedTokenStream::new(quote! {::std::option::Option::None}),
+    }
+}
+
+pub fn doc_into_tokenstream(doc: Option<String>) -> TypedTokenStream<Option<String>> {
+    match doc {
+        Some(doc) => TypedTokenStream::new(quote! {::std::option::Option::Some(#doc.to_string())}),
+        None => TypedTokenStream::new(quote! {::std::option::Option::None}),
+    }
+}
+
+pub fn aliases(op: &[String]) -> TypedTokenStream<Option<Vec<String>>> {
+    let items: Vec<proc_macro2::TokenStream> = op
+        .iter()
+        .map(|tt| quote! {#tt.try_into().expect("Alias is invalid")})
+        .collect();
+    if items.is_empty() {
+        TypedTokenStream::new(quote! {::std::option::Option::None})
+    } else {
+        TypedTokenStream::new(quote! {::std::option::Option::Some(vec![#(#items),*])})
+    }
+}
+
+pub fn field_aliases(op: &[impl quote::ToTokens]) -> TypedTokenStream<Vec<String>> {
+    let items: Vec<proc_macro2::TokenStream> = op
+        .iter()
+        .map(|tt| quote! {#tt.try_into().expect("Alias is invalid")})
+        .collect();
+    if items.is_empty() {
+        TypedTokenStream::new(quote! {::std::vec::Vec::new()})
+    } else {
+        TypedTokenStream::new(quote! {vec![#(#items),*]})
+    }
+}
diff --git a/avro_derive/tests/derive.rs b/avro_derive/tests/derive.rs
index 268e175..74428b6 100644
--- a/avro_derive/tests/derive.rs
+++ b/avro_derive/tests/derive.rs
@@ -2102,6 +2102,8 @@ fn avro_rs_401_supported_type_variants() {
         three: &'static i32,
         four: &'a str,
         five: &'a mut f64,
+        #[avro(with)]
+        #[serde(with = "apache_avro::serde::array")]
         six: [u8; 5],
         seven: [u8],
     }
diff --git a/avro_derive/tests/enum.rs b/avro_derive/tests/enum.rs
index ff79088..e4e272c 100644
--- a/avro_derive/tests/enum.rs
+++ b/avro_derive/tests/enum.rs
@@ -16,6 +16,7 @@
 // under the License.
 
 use apache_avro::{AvroSchema, Error, Reader, Schema, Writer};
+use pretty_assertions::assert_eq;
 use serde::{Deserialize, Serialize, de::DeserializeOwned};
 
 /// Takes in a type that implements the right combination of traits and runs it through a Serde
@@ -76,11 +77,23 @@ fn avro_rs_xxx_enum_repr_default() {
     enum Foo {
         A,
         B,
+        #[serde(rename = "D")]
         C,
     }
 
-    assert!(matches!(Foo::get_schema(), Schema::Enum(_)));
+    let schema = Schema::parse_str(
+        r#"{
+        "type": "enum",
+        "name": "Foo",
+        "symbols": ["A", "B", "D"]
+    }"#,
+    )
+    .unwrap();
+
+    assert_eq!(Foo::get_schema(), schema);
     serde_assert(Foo::A);
+    serde_assert(Foo::B);
+    serde_assert(Foo::C);
 }
 
 #[test]
@@ -90,45 +103,186 @@ fn avro_rs_xxx_enum_repr_enum() {
     enum Foo {
         A,
         B,
+        #[serde(rename = "D")]
         C,
     }
 
-    assert!(matches!(Foo::get_schema(), Schema::Enum(_)));
+    let schema = Schema::parse_str(
+        r#"{
+        "type": "enum",
+        "name": "Foo",
+        "symbols": ["A", "B", "D"]
+    }"#,
+    )
+    .unwrap();
+
+    assert_eq!(Foo::get_schema(), schema);
     serde_assert(Foo::A);
+    serde_assert(Foo::B);
+    serde_assert(Foo::C);
 }
 
 #[test]
-fn avro_rs_xxx_enum_repr_discriminator_value_plain() {
+fn avro_rs_xxx_enum_repr_record_tag_content_plain() {
     #[derive(AvroSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
     #[avro(repr = "record_tag_content")]
     #[serde(tag = "type", content = "value")]
     enum Foo {
         A,
         B,
+        #[serde(rename = "D")]
         C,
     }
 
-    assert!(matches!(Foo::get_schema(), Schema::Record(_)));
+    let schema = Schema::parse_str(
+        r#"{
+        "type": "record",
+        "name": "Foo",
+        "fields": [
+            {
+                "name": "type",
+                "type": {
+                    "type": "enum",
+                    "name": "type",
+                    "symbols": ["A", "B", "D"]
+                }
+            },
+            {
+                "name": "value",
+                "type": [
+                    "null"
+                ]
+            }
+        ]
+    }"#,
+    )
+    .unwrap();
+
+    assert_eq!(Foo::get_schema(), schema);
     serde_assert(Foo::A);
+    serde_assert(Foo::B);
+    serde_assert(Foo::C);
 }
 
 #[test]
-fn avro_rs_xxx_enum_repr_discriminator_value_tuple() {
+fn avro_rs_xxx_enum_repr_record_tag_content_tuple() {
     #[derive(AvroSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
     #[avro(repr = "record_tag_content")]
     #[serde(tag = "type", content = "value")]
     enum Foo {
         A(),
         B(String),
-        C(String, bool),
+        #[serde(rename = "D")]
+        C(
+            String,
+            #[serde(rename = "is_it_true", alias = "is_it_false")] bool,
+        ),
     }
 
-    assert!(matches!(Foo::get_schema(), Schema::Record(_)));
+    let schema = Schema::parse_str(
+        r#"{
+        "type": "record",
+        "name": "Foo",
+        "fields": [
+            {
+                "name": "type",
+                "type": {
+                    "type": "enum",
+                    "name": "type",
+                    "symbols": ["A", "B", "D"]
+                }
+            },
+            {
+                "name": "value",
+                "type": [
+                    "null",
+                    "string",
+                    {
+                        "type": "record",
+                        "name": "D",
+                        "default": "null",
+                        "fields": [
+                            { "name": "field_0", "type": "string" },
+                            { "name": "is_it_true", "aliases": ["is_it_false"], "type": "boolean" }
+                        ],
+                        "org.apache.avro.rust.tuple": true
+                    }
+                ]
+            }
+        ]
+    }"#,
+    )
+    .unwrap();
+
+    assert_eq!(Foo::get_schema(), schema);
     serde_assert(Foo::A());
     serde_assert(Foo::B("Something".to_string()));
     serde_assert(Foo::C("Something".to_string(), true));
 }
 
+#[test]
+fn avro_rs_xxx_enum_repr_record_tag_content_struct() {
+    #[derive(AvroSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
+    #[avro(repr = "record_tag_content")]
+    #[serde(tag = "type", content = "value")]
+    enum Foo {
+        A {},
+        B {
+            spam: String,
+        },
+        #[serde(rename = "D")]
+        C {
+            bar: String,
+            #[serde(rename = "is_it_true", alias = "is_it_false")]
+            other: bool,
+        },
+    }
+
+    let schema = Schema::parse_str(
+        r#"{
+        "type": "record",
+        "name": "Foo",
+        "fields": [
+            {
+                "name": "type",
+                "type": {
+                    "type": "enum",
+                    "name": "type",
+                    "symbols": ["A", "B", "D"]
+                }
+            },
+            {
+                "name": "value",
+                "type": [
+                    {"type": "record", "name": "A", "fields": []},
+                    {"type": "record", "name": "B", "fields": [{"name": "spam", "type": "string"}]},
+                    {
+                        "type": "record",
+                        "name": "D",
+                        "default": "null",
+                        "fields": [
+                            { "name": "bar", "type": "string" },
+                            { "name": "is_it_true", "aliases": ["is_it_false"], "type": "boolean" }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }"#,
+    )
+    .unwrap();
+
+    assert_eq!(Foo::get_schema(), schema);
+    serde_assert(Foo::A {});
+    serde_assert(Foo::B {
+        spam: "Something".to_string(),
+    });
+    serde_assert(Foo::C {
+        bar: "Something".to_string(),
+        other: true,
+    });
+}
+
 #[test]
 fn avro_rs_xxx_enum_repr_bare_union_plain() {
     #[derive(AvroSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
@@ -138,7 +292,9 @@ fn avro_rs_xxx_enum_repr_bare_union_plain() {
         A,
     }
 
-    assert!(matches!(Foo::get_schema(), Schema::Union(_)));
+    let schema = Schema::parse_str(r#"["null"]"#).unwrap();
+
+    assert_eq!(Foo::get_schema(), schema);
     serde_assert(Foo::A);
 }
 
@@ -149,10 +305,299 @@ fn avro_rs_xxx_enum_repr_bare_union_tuple() {
     #[serde(untagged)]
     enum Foo {
         B(String),
-        C(String, bool),
+        #[serde(rename = "D")]
+        C(
+            String,
+            #[serde(rename = "is_it_true", alias = "is_it_false")] bool,
+        ),
     }
 
-    assert!(matches!(Foo::get_schema(), Schema::Union(_)));
+    let schema = Schema::parse_str(
+        r#"[
+        "string",
+        {
+            "type": "record",
+            "name": "D",
+            "default": "null",
+            "fields": [
+                { "name": "field_0", "type": "string" },
+                { "name": "is_it_true", "aliases": ["is_it_false"], "type": "boolean" }
+            ],
+            "org.apache.avro.rust.tuple": true
+        }
+    ]"#,
+    )
+    .unwrap();
+
+    assert_eq!(Foo::get_schema(), schema);
     serde_assert(Foo::B("Something".to_string()));
     serde_assert(Foo::C("Something".to_string(), true));
 }
+
+#[test]
+fn avro_rs_xxx_enum_repr_record_internally_tagged_plain() {
+    #[derive(AvroSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
+    #[avro(repr = "record_internally_tagged")]
+    #[serde(tag = "type")]
+    enum Foo {
+        A,
+        B,
+        #[serde(rename = "D")]
+        C,
+    }
+
+    let schema = Schema::parse_str(
+        r#"{
+        "type": "record",
+        "name": "Foo",
+        "fields": [
+            {
+                "name": "type",
+                "type": "string"
+            }
+        ]
+    }"#,
+    )
+    .unwrap();
+
+    assert_eq!(Foo::get_schema(), schema);
+    serde_assert(Foo::A);
+    serde_assert(Foo::B);
+    serde_assert(Foo::C);
+}
+
+#[test]
+#[should_panic(
+    expected = "Newtype variant type must implement `get_record_fields` for internally tagged enums"
+)]
+fn avro_rs_xxx_enum_repr_record_internally_tagged_tuple_no_get_record_fields() {
+    #[derive(AvroSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
+    #[avro(repr = "record_internally_tagged")]
+    #[serde(tag = "type")]
+    enum Foo {
+        B(String),
+    }
+
+    let _ = Foo::get_schema();
+}
+
+#[test]
+fn avro_rs_xxx_enum_repr_record_internally_tagged_tuple() {
+    #[derive(AvroSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
+    #[avro(repr = "record_internally_tagged")]
+    #[serde(tag = "type")]
+    enum Foo {
+        B(Bar),
+    }
+
+    #[derive(AvroSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
+    struct Bar {
+        spam: String,
+    }
+
+    let schema = Schema::parse_str(
+        r#"{
+        "type": "record",
+        "name": "Foo",
+        "fields": [
+            {
+                "name": "type",
+                "type": "string"
+            },
+            {
+                "name": "spam",
+                "type": "string"
+            }
+        ]
+    }"#,
+    )
+    .unwrap();
+
+    assert_eq!(Foo::get_schema(), schema);
+    serde_assert(Foo::B(Bar {
+        spam: "Something".to_string(),
+    }));
+}
+
+#[test]
+fn avro_rs_xxx_enum_repr_record_internally_tagged_struct() {
+    #[derive(AvroSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
+    #[avro(repr = "record_internally_tagged")]
+    #[serde(tag = "type")]
+    enum Foo {
+        A {},
+        B {
+            #[avro(default = r#""spam""#)]
+            spam: String,
+        },
+        #[serde(rename = "D")]
+        C {
+            #[avro(default = r#""bar""#)]
+            bar: String,
+            #[serde(rename = "is_it_true", alias = "is_it_false")]
+            #[avro(default = "true")]
+            other: bool,
+        },
+    }
+
+    let schema = Schema::parse_str(
+        r#"{
+        "type": "record",
+        "name": "Foo",
+        "fields": [
+            {
+                "name": "type",
+                "type": "string"
+            },
+            {
+                "name": "spam",
+                "type": "string"
+            },
+            {
+                "name": "bar",
+                "type": "string"
+            },
+            {
+                "name": "is_it_true",
+                "aliases": ["is_it_false"],
+                "type": "boolean"
+            }
+        ]
+    }"#,
+    )
+    .unwrap();
+
+    assert_eq!(Foo::get_schema(), schema);
+    serde_assert(Foo::A {});
+    serde_assert(Foo::B {
+        spam: "Something".to_string(),
+    });
+    serde_assert(Foo::C {
+        bar: "Something".to_string(),
+        other: true,
+    });
+}
+
+#[test]
+#[should_panic(expected = "Missing default for skipped field 'spam' for schema")]
+fn avro_rs_xxx_enum_repr_record_internally_tagged_struct_no_defaults() {
+    #[derive(AvroSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
+    #[avro(repr = "record_internally_tagged")]
+    #[serde(tag = "type")]
+    enum Foo {
+        A {},
+        B {
+            spam: String,
+        },
+        #[serde(rename = "D")]
+        C {
+            bar: String,
+            #[serde(rename = "is_it_true", alias = "is_it_false")]
+            other: bool,
+        },
+    }
+
+    serde_assert(Foo::A {});
+}
+
+#[test]
+fn avro_rs_xxx_enum_repr_union_of_records_plain() {
+    #[derive(AvroSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
+    #[avro(repr = "union_of_records")]
+    enum Foo {
+        A,
+        B,
+        #[serde(rename = "D")]
+        C,
+    }
+
+    let schema = Schema::parse_str(
+        r#"[
+        {"type": "record", "name": "A", "fields": []},
+        {"type": "record", "name": "B", "fields": []},
+        {"type": "record", "name": "D", "fields": []}
+    ]"#,
+    )
+    .unwrap();
+
+    assert_eq!(Foo::get_schema(), schema);
+    serde_assert(Foo::A);
+    serde_assert(Foo::B);
+    serde_assert(Foo::C);
+}
+
+#[test]
+fn avro_rs_xxx_enum_repr_union_of_records_tuple() {
+    #[derive(AvroSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
+    #[avro(repr = "union_of_records")]
+    enum Foo {
+        A(),
+        B(String),
+        #[serde(rename = "D")]
+        C(
+            String,
+            #[serde(rename = "is_it_true", alias = "is_it_false")] bool,
+        ),
+    }
+
+    let schema = Schema::parse_str(
+        r#"[
+        {"type": "record", "name": "A", "fields": [], "org.apache.avro.rust.tuple": true},
+        {"type": "record", "name": "B", "fields": [{
+            "name": "field_0", "type": "string"
+        }], "org.apache.avro.rust.tuple": true, "org.apache.avro.rust.union_of_records": true },
+        {"type": "record", "name": "D", "fields": [
+            {"name": "field_0", "type": "string"},
+            {"name": "is_it_true", "aliases": ["is_it_false"], "type": "boolean"}
+        ], "org.apache.avro.rust.tuple": true}
+    ]"#,
+    )
+    .unwrap();
+
+    assert_eq!(Foo::get_schema(), schema);
+    serde_assert(Foo::A());
+    serde_assert(Foo::B("Something".to_string()));
+    serde_assert(Foo::C("Something".to_string(), true));
+}
+
+#[test]
+fn avro_rs_xxx_enum_repr_union_of_records_struct() {
+    #[derive(AvroSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
+    #[avro(repr = "union_of_records")]
+    enum Foo {
+        A {},
+        B {
+            spam: String,
+        },
+        #[serde(rename = "D")]
+        C {
+            bar: String,
+            #[serde(rename = "is_it_true", alias = "is_it_false")]
+            other: bool,
+        },
+    }
+
+    let schema = Schema::parse_str(
+        r#"[
+        {"type": "record", "name": "A", "fields": []},
+        {"type": "record", "name": "B", "fields": [{
+            "name": "spam", "type": "string"
+        }], "org.apache.avro.rust.union_of_records": true },
+        {"type": "record", "name": "D", "fields": [
+            {"name": "bar", "type": "string"},
+            {"name": "is_it_true", "aliases": ["is_it_false"], "type": "boolean"}
+        ]}
+    ]"#,
+    )
+    .unwrap();
+
+    assert_eq!(Foo::get_schema(), schema);
+    serde_assert(Foo::A {});
+    serde_assert(Foo::B {
+        spam: "Something".to_string(),
+    });
+    serde_assert(Foo::C {
+        bar: "Something".to_string(),
+        other: true,
+    });
+}
diff --git a/avro_derive/tests/expanded/avro_3709_record_field_attributes.expanded.rs b/avro_derive/tests/expanded/avro_3709_record_field_attributes.expanded.rs
index 78f8992..7b5abf2 100644
--- a/avro_derive/tests/expanded/avro_3709_record_field_attributes.expanded.rs
+++ b/avro_derive/tests/expanded/avro_3709_record_field_attributes.expanded.rs
@@ -32,7 +32,7 @@ impl ::apache_avro::AvroSchemaComponent for A {
                                 .name("a3".to_string())
                                 .doc(::std::option::Option::Some("a doc".to_string()))
                                 .maybe_default(
-                                    Some(
+                                    ::std::option::Option::Some(
                                         ::serde_json::from_str("123")
                                             .expect(
                                                 ::alloc::__export::must_use({
@@ -124,7 +124,7 @@ impl ::apache_avro::AvroSchemaComponent for A {
                         .name("a3".to_string())
                         .doc(::std::option::Option::Some("a doc".to_string()))
                         .maybe_default(
-                            Some(
+                            ::std::option::Option::Some(
                                 ::serde_json::from_str("123")
                                     .expect(
                                         ::alloc::__export::must_use({
diff --git a/avro_derive/tests/expanded/avro_rs_xxx_bare_union.expanded.rs b/avro_derive/tests/expanded/avro_rs_xxx_bare_union.expanded.rs
index e37481e..99ec83e 100644
--- a/avro_derive/tests/expanded/avro_rs_xxx_bare_union.expanded.rs
+++ b/avro_derive/tests/expanded/avro_rs_xxx_bare_union.expanded.rs
@@ -4,9 +4,6 @@ enum Abc {
     A,
     B(bool),
     C(#[avro(doc = "This is an int")] i32, i64),
-    D {},
-    E { is_it_true: bool },
-    F { #[avro(doc = "This is X")] x: f64, y: f32 },
 }
 #[automatically_derived]
 impl ::apache_avro::AvroSchemaComponent for Abc {
@@ -109,138 +106,6 @@ impl ::apache_avro::AvroSchemaComponent for Abc {
                     ),
                 )
                 .expect("Duplicate Schema found");
-            builder
-                .variant(
-                    ::apache_avro::schema::Schema::Record(
-                        ::apache_avro::schema::RecordSchema::builder()
-                            .name(
-                                ::apache_avro::schema::Name::new_with_enclosing_namespace(
-                                        "D",
-                                        enclosing_namespace,
-                                    )
-                                    .expect(
-                                        &::alloc::__export::must_use({
-                                            ::alloc::fmt::format(
-                                                format_args!(
-                                                    "Unable to parse variant record name for schema {0}", "D",
-                                                ),
-                                            )
-                                        })[..],
-                                    ),
-                            )
-                            .fields({
-                                let mut fields = ::std::vec::Vec::with_capacity(0usize);
-                                fields
-                            })
-                            .build(),
-                    ),
-                )
-                .expect("Duplicate Schema found");
-            builder
-                .variant(
-                    ::apache_avro::schema::Schema::Record(
-                        ::apache_avro::schema::RecordSchema::builder()
-                            .name(
-                                ::apache_avro::schema::Name::new_with_enclosing_namespace(
-                                        "E",
-                                        enclosing_namespace,
-                                    )
-                                    .expect(
-                                        &::alloc::__export::must_use({
-                                            ::alloc::fmt::format(
-                                                format_args!(
-                                                    "Unable to parse variant record name for schema {0}", "E",
-                                                ),
-                                            )
-                                        })[..],
-                                    ),
-                            )
-                            .fields({
-                                let mut fields = ::std::vec::Vec::with_capacity(1usize);
-                                fields
-                                    .push(
-                                        ::apache_avro::schema::RecordField::builder()
-                                            .name("is_it_true".to_string())
-                                            .doc(::std::option::Option::None)
-                                            .maybe_default(
-                                                <bool as ::apache_avro::AvroSchemaComponent>::field_default(),
-                                            )
-                                            .aliases(::std::vec::Vec::new())
-                                            .schema(
-                                                <bool as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-                                                    named_schemas,
-                                                    enclosing_namespace,
-                                                ),
-                                            )
-                                            .build(),
-                                    );
-                                fields
-                            })
-                            .build(),
-                    ),
-                )
-                .expect("Duplicate Schema found");
-            builder
-                .variant(
-                    ::apache_avro::schema::Schema::Record(
-                        ::apache_avro::schema::RecordSchema::builder()
-                            .name(
-                                ::apache_avro::schema::Name::new_with_enclosing_namespace(
-                                        "F",
-                                        enclosing_namespace,
-                                    )
-                                    .expect(
-                                        &::alloc::__export::must_use({
-                                            ::alloc::fmt::format(
-                                                format_args!(
-                                                    "Unable to parse variant record name for schema {0}", "F",
-                                                ),
-                                            )
-                                        })[..],
-                                    ),
-                            )
-                            .fields({
-                                let mut fields = ::std::vec::Vec::with_capacity(2usize);
-                                fields
-                                    .push(
-                                        ::apache_avro::schema::RecordField::builder()
-                                            .name("x".to_string())
-                                            .doc(::std::option::Option::Some("This is X".to_string()))
-                                            .maybe_default(
-                                                <f64 as ::apache_avro::AvroSchemaComponent>::field_default(),
-                                            )
-                                            .aliases(::std::vec::Vec::new())
-                                            .schema(
-                                                <f64 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-                                                    named_schemas,
-                                                    enclosing_namespace,
-                                                ),
-                                            )
-                                            .build(),
-                                    );
-                                fields
-                                    .push(
-                                        ::apache_avro::schema::RecordField::builder()
-                                            .name("y".to_string())
-                                            .doc(::std::option::Option::None)
-                                            .maybe_default(
-                                                <f32 as ::apache_avro::AvroSchemaComponent>::field_default(),
-                                            )
-                                            .aliases(::std::vec::Vec::new())
-                                            .schema(
-                                                <f32 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-                                                    named_schemas,
-                                                    enclosing_namespace,
-                                                ),
-                                            )
-                                            .build(),
-                                    );
-                                fields
-                            })
-                            .build(),
-                    ),
-                )
-                .expect("Duplicate Schema found");
             ::apache_avro::schema::Schema::Union(builder.build())
         }
     }
diff --git a/avro_derive/tests/expanded/avro_rs_xxx_bare_union.rs b/avro_derive/tests/expanded/avro_rs_xxx_bare_union.rs
index 34a3c51..163c1a4 100644
--- a/avro_derive/tests/expanded/avro_rs_xxx_bare_union.rs
+++ b/avro_derive/tests/expanded/avro_rs_xxx_bare_union.rs
@@ -22,18 +22,5 @@ use apache_avro::AvroSchema;
 enum Abc {
     A,
     B(bool),
-    C(
-        #[avro(doc = "This is an int")]
-        i32, 
-        i64
-    ),
-    D {},
-    E {
-        is_it_true: bool,
-    },
-    F {
-        #[avro(doc = "This is X")]
-        x: f64,
-        y: f32,
-    }
+    C(#[avro(doc = "This is an int")] i32, i64),
 }
diff --git a/avro_derive/tests/expanded/avro_rs_xxx_internally_tagged.expanded.rs b/avro_derive/tests/expanded/avro_rs_xxx_internally_tagged.expanded.rs
index d75f321..4cf9142 100644
--- a/avro_derive/tests/expanded/avro_rs_xxx_internally_tagged.expanded.rs
+++ b/avro_derive/tests/expanded/avro_rs_xxx_internally_tagged.expanded.rs
@@ -1,19 +1,24 @@
 use apache_avro::AvroSchema;
-struct A {
-    a: i32,
-    b: String,
+#[avro(repr = "record_internally_tagged")]
+#[serde(tag = "type")]
+enum Abc {
+    A,
+    B(bool),
+    D {},
+    E { is_it_true: bool },
+    F { #[avro(doc = "This is X")] x: f64, y: f32 },
 }
 #[automatically_derived]
-impl ::apache_avro::AvroSchemaComponent for A {
+impl ::apache_avro::AvroSchemaComponent for Abc {
     fn get_schema_in_ctxt(
         named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
         enclosing_namespace: ::apache_avro::schema::NamespaceRef,
     ) -> ::apache_avro::schema::Schema {
         let name = ::apache_avro::schema::Name::new_with_enclosing_namespace(
-                "A",
+                "Abc",
                 enclosing_namespace,
             )
-            .expect("Unable to parse schema name A");
+            .expect("Unable to parse schema name Abc");
         if named_schemas.contains(&name) {
             ::apache_avro::schema::Schema::Ref {
                 name,
@@ -21,20 +26,48 @@ impl ::apache_avro::AvroSchemaComponent for A {
         } else {
             let enclosing_namespace = name.namespace();
             named_schemas.insert(name.clone());
-            {
-                let schema_fields = {
-                    let mut fields = ::std::vec::Vec::with_capacity(2usize);
+            let mut builder = ::apache_avro::schema::UnionSchema::builder();
+            let mut fields = ::std::vec::Vec::new();
+            fields
+                .push(
+                    ::apache_avro::schema::RecordField::builder()
+                        .name("type")
+                        .schema(::apache_avro::schema::Schema::String)
+                        .build(),
+                );
+            if let Some(record_fields) = <bool as ::apache_avro::AvroSchemaComponent>::get_record_fields_in_ctxt(
+                named_schemas,
+                enclosing_namespace,
+            ) {
+                fields.extend(record_fields);
+            } else {
+                {
+                    ::core::panicking::panic_fmt(
+                        format_args!(
+                            "Newtype variant type must implement `get_record_fields` for internally tagged enums",
+                        ),
+                    );
+                };
+            }
+            fields
+                .extend({
+                    let mut fields = ::std::vec::Vec::with_capacity(0usize);
+                    fields
+                });
+            fields
+                .extend({
+                    let mut fields = ::std::vec::Vec::with_capacity(1usize);
                     fields
                         .push(
                             ::apache_avro::schema::RecordField::builder()
-                                .name("a".to_string())
+                                .name("is_it_true".to_string())
                                 .doc(::std::option::Option::None)
                                 .maybe_default(
-                                    <i32 as ::apache_avro::AvroSchemaComponent>::field_default(),
+                                    <bool as ::apache_avro::AvroSchemaComponent>::field_default(),
                                 )
                                 .aliases(::std::vec::Vec::new())
                                 .schema(
-                                    <i32 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
+                                    <bool as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
                                         named_schemas,
                                         enclosing_namespace,
                                     ),
@@ -42,16 +75,21 @@ impl ::apache_avro::AvroSchemaComponent for A {
                                 .build(),
                         );
                     fields
+                });
+            fields
+                .extend({
+                    let mut fields = ::std::vec::Vec::with_capacity(2usize);
+                    fields
                         .push(
                             ::apache_avro::schema::RecordField::builder()
-                                .name("b".to_string())
-                                .doc(::std::option::Option::None)
+                                .name("x".to_string())
+                                .doc(::std::option::Option::Some("This is X".to_string()))
                                 .maybe_default(
-                                    <String as ::apache_avro::AvroSchemaComponent>::field_default(),
+                                    <f64 as ::apache_avro::AvroSchemaComponent>::field_default(),
                                 )
                                 .aliases(::std::vec::Vec::new())
                                 .schema(
-                                    <String as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
+                                    <f64 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
                                         named_schemas,
                                         enclosing_namespace,
                                     ),
@@ -59,124 +97,39 @@ impl ::apache_avro::AvroSchemaComponent for A {
                                 .build(),
                         );
                     fields
-                };
-                let schema_field_set: ::std::collections::HashSet<_> = schema_fields
-                    .iter()
-                    .map(|rf| &rf.name)
-                    .collect();
-                match (&schema_fields.len(), &schema_field_set.len()) {
-                    (left_val, right_val) => {
-                        if !(*left_val == *right_val) {
-                            let kind = ::core::panicking::AssertKind::Eq;
-                            ::core::panicking::assert_failed(
-                                kind,
-                                &*left_val,
-                                &*right_val,
-                                ::core::option::Option::Some(
-                                    format_args!(
-                                        "Duplicate field names found: {0:?}", schema_fields,
+                        .push(
+                            ::apache_avro::schema::RecordField::builder()
+                                .name("y".to_string())
+                                .doc(::std::option::Option::None)
+                                .maybe_default(
+                                    <f32 as ::apache_avro::AvroSchemaComponent>::field_default(),
+                                )
+                                .aliases(::std::vec::Vec::new())
+                                .schema(
+                                    <f32 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
+                                        named_schemas,
+                                        enclosing_namespace,
                                     ),
-                                ),
-                            );
-                        }
-                    }
-                };
-                let name = ::apache_avro::schema::Name::new("A")
-                    .expect(
-                        &::alloc::__export::must_use({
-                            ::alloc::fmt::format(
-                                format_args!(
-                                    "Unable to parse struct name for schema {0}", "A",
-                                ),
-                            )
-                        })[..],
-                    );
-                let lookup: ::std::collections::BTreeMap<String, usize> = schema_fields
-                    .iter()
-                    .enumerate()
-                    .map(|(position, field)| (field.name.to_owned(), position))
-                    .collect();
-                ::apache_avro::schema::Schema::Record(::apache_avro::schema::RecordSchema {
-                    name,
-                    aliases: ::std::option::Option::None,
-                    doc: ::std::option::Option::None,
-                    fields: schema_fields,
-                    lookup,
-                    attributes: ::std::collections::BTreeMap::new(),
-                })
-            }
+                                )
+                                .build(),
+                        );
+                    fields
+                });
+            ::apache_avro::schema::Schema::Record(
+                ::apache_avro::schema::RecordSchema::builder()
+                    .name(name)
+                    .maybe_aliases(::std::option::Option::None)
+                    .doc(::std::option::Option::None)
+                    .fields(fields)
+                    .build(),
+            )
         }
     }
     fn get_record_fields_in_ctxt(
         named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
         enclosing_namespace: ::apache_avro::schema::NamespaceRef,
     ) -> ::std::option::Option<::std::vec::Vec<::apache_avro::schema::RecordField>> {
-        ::std::option::Option::Some({
-            let mut fields = ::std::vec::Vec::with_capacity(2usize);
-            fields
-                .push(
-                    ::apache_avro::schema::RecordField::builder()
-                        .name("a".to_string())
-                        .doc(::std::option::Option::None)
-                        .maybe_default(
-                            <i32 as ::apache_avro::AvroSchemaComponent>::field_default(),
-                        )
-                        .aliases(::std::vec::Vec::new())
-                        .schema(
-                            <i32 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-                                named_schemas,
-                                enclosing_namespace,
-                            ),
-                        )
-                        .build(),
-                );
-            fields
-                .push(
-                    ::apache_avro::schema::RecordField::builder()
-                        .name("b".to_string())
-                        .doc(::std::option::Option::None)
-                        .maybe_default(
-                            <String as ::apache_avro::AvroSchemaComponent>::field_default(),
-                        )
-                        .aliases(::std::vec::Vec::new())
-                        .schema(
-                            <String as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-                                named_schemas,
-                                enclosing_namespace,
-                            ),
-                        )
-                        .build(),
-                );
-            fields
-        })
-    }
-    fn field_default() -> ::std::option::Option<::serde_json::Value> {
-        ::std::option::Option::None
-    }
-}
-#[serde(transparent)]
-struct B {
-    a: A,
-}
-#[automatically_derived]
-impl ::apache_avro::AvroSchemaComponent for B {
-    fn get_schema_in_ctxt(
-        named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
-        enclosing_namespace: ::apache_avro::schema::NamespaceRef,
-    ) -> ::apache_avro::schema::Schema {
-        <A as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-            named_schemas,
-            enclosing_namespace,
-        )
-    }
-    fn get_record_fields_in_ctxt(
-        named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
-        enclosing_namespace: ::apache_avro::schema::NamespaceRef,
-    ) -> ::std::option::Option<::std::vec::Vec<::apache_avro::schema::RecordField>> {
-        <A as ::apache_avro::AvroSchemaComponent>::get_record_fields_in_ctxt(
-            named_schemas,
-            enclosing_namespace,
-        )
+        None
     }
     fn field_default() -> ::std::option::Option<::serde_json::Value> {
         ::std::option::Option::None
diff --git a/avro_derive/tests/expanded/avro_rs_xxx_internally_tagged.rs b/avro_derive/tests/expanded/avro_rs_xxx_internally_tagged.rs
index b23413b..85b18fd 100644
--- a/avro_derive/tests/expanded/avro_rs_xxx_internally_tagged.rs
+++ b/avro_derive/tests/expanded/avro_rs_xxx_internally_tagged.rs
@@ -18,17 +18,18 @@
 use apache_avro::AvroSchema;
 
 #[derive(AvroSchema)]
-struct A {
-    a: i32,
-    b: String,
+#[avro(repr = "record_internally_tagged")]
+#[serde(tag = "type")]
+enum Abc {
+    A,
+    B(bool),
+    D {},
+    E {
+        is_it_true: bool,
+    },
+    F {
+        #[avro(doc = "This is X")]
+        x: f64,
+        y: f32,
+    },
 }
-
-#[derive(AvroSchema)]
-#[serde(transparent)]
-struct B {
-    a: A,
-}
-
-// #[derive(AvroSchema)]
-// #[serde(transparent)]
-// struct C(A);
diff --git a/avro_derive/tests/expanded/avro_rs_xxx_tag_content.expanded.rs b/avro_derive/tests/expanded/avro_rs_xxx_tag_content.expanded.rs
index d75f321..49dc6d1 100644
--- a/avro_derive/tests/expanded/avro_rs_xxx_tag_content.expanded.rs
+++ b/avro_derive/tests/expanded/avro_rs_xxx_tag_content.expanded.rs
@@ -1,19 +1,24 @@
 use apache_avro::AvroSchema;
-struct A {
-    a: i32,
-    b: String,
+#[avro(repr = "record_tag_content")]
+#[serde(tag = "type", content = "value")]
+enum Abc {
+    A,
+    B(bool),
+    D {},
+    E { is_it_true: bool },
+    F { #[avro(doc = "This is X")] x: f64, y: f32 },
 }
 #[automatically_derived]
-impl ::apache_avro::AvroSchemaComponent for A {
+impl ::apache_avro::AvroSchemaComponent for Abc {
     fn get_schema_in_ctxt(
         named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
         enclosing_namespace: ::apache_avro::schema::NamespaceRef,
     ) -> ::apache_avro::schema::Schema {
         let name = ::apache_avro::schema::Name::new_with_enclosing_namespace(
-                "A",
+                "Abc",
                 enclosing_namespace,
             )
-            .expect("Unable to parse schema name A");
+            .expect("Unable to parse schema name Abc");
         if named_schemas.contains(&name) {
             ::apache_avro::schema::Schema::Ref {
                 name,
@@ -21,162 +26,219 @@ impl ::apache_avro::AvroSchemaComponent for A {
         } else {
             let enclosing_namespace = name.namespace();
             named_schemas.insert(name.clone());
-            {
-                let schema_fields = {
-                    let mut fields = ::std::vec::Vec::with_capacity(2usize);
-                    fields
-                        .push(
-                            ::apache_avro::schema::RecordField::builder()
-                                .name("a".to_string())
-                                .doc(::std::option::Option::None)
-                                .maybe_default(
-                                    <i32 as ::apache_avro::AvroSchemaComponent>::field_default(),
-                                )
-                                .aliases(::std::vec::Vec::new())
-                                .schema(
-                                    <i32 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-                                        named_schemas,
+            let mut builder = ::apache_avro::schema::UnionSchema::builder();
+            builder
+                .variant_ignore_duplicates(::apache_avro::schema::Schema::Null)
+                .expect("Unions cannot have duplicates");
+            builder
+                .variant_ignore_duplicates(
+                    <bool as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
+                        named_schemas,
+                        enclosing_namespace,
+                    ),
+                )
+                .expect("Unions cannot have duplicates");
+            builder
+                .variant_ignore_duplicates(
+                    ::apache_avro::schema::Schema::Record(
+                        ::apache_avro::schema::RecordSchema::builder()
+                            .name(
+                                ::apache_avro::schema::Name::new_with_enclosing_namespace(
+                                        "D",
                                         enclosing_namespace,
+                                    )
+                                    .expect(
+                                        &::alloc::__export::must_use({
+                                            ::alloc::fmt::format(
+                                                format_args!(
+                                                    "Unable to parse variant record name for schema {0}", "D",
+                                                ),
+                                            )
+                                        })[..],
                                     ),
-                                )
-                                .build(),
-                        );
-                    fields
-                        .push(
-                            ::apache_avro::schema::RecordField::builder()
-                                .name("b".to_string())
-                                .doc(::std::option::Option::None)
-                                .maybe_default(
-                                    <String as ::apache_avro::AvroSchemaComponent>::field_default(),
-                                )
-                                .aliases(::std::vec::Vec::new())
-                                .schema(
-                                    <String as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-                                        named_schemas,
+                            )
+                            .fields({
+                                let mut fields = ::std::vec::Vec::with_capacity(0usize);
+                                fields
+                            })
+                            .build(),
+                    ),
+                )
+                .expect("Unions cannot have duplicates");
+            builder
+                .variant_ignore_duplicates(
+                    ::apache_avro::schema::Schema::Record(
+                        ::apache_avro::schema::RecordSchema::builder()
+                            .name(
+                                ::apache_avro::schema::Name::new_with_enclosing_namespace(
+                                        "E",
                                         enclosing_namespace,
+                                    )
+                                    .expect(
+                                        &::alloc::__export::must_use({
+                                            ::alloc::fmt::format(
+                                                format_args!(
+                                                    "Unable to parse variant record name for schema {0}", "E",
+                                                ),
+                                            )
+                                        })[..],
                                     ),
-                                )
-                                .build(),
-                        );
-                    fields
-                };
-                let schema_field_set: ::std::collections::HashSet<_> = schema_fields
-                    .iter()
-                    .map(|rf| &rf.name)
-                    .collect();
-                match (&schema_fields.len(), &schema_field_set.len()) {
-                    (left_val, right_val) => {
-                        if !(*left_val == *right_val) {
-                            let kind = ::core::panicking::AssertKind::Eq;
-                            ::core::panicking::assert_failed(
-                                kind,
-                                &*left_val,
-                                &*right_val,
-                                ::core::option::Option::Some(
-                                    format_args!(
-                                        "Duplicate field names found: {0:?}", schema_fields,
+                            )
+                            .fields({
+                                let mut fields = ::std::vec::Vec::with_capacity(1usize);
+                                fields
+                                    .push(
+                                        ::apache_avro::schema::RecordField::builder()
+                                            .name("is_it_true".to_string())
+                                            .doc(::std::option::Option::None)
+                                            .maybe_default(
+                                                <bool as ::apache_avro::AvroSchemaComponent>::field_default(),
+                                            )
+                                            .aliases(::std::vec::Vec::new())
+                                            .schema(
+                                                <bool as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
+                                                    named_schemas,
+                                                    enclosing_namespace,
+                                                ),
+                                            )
+                                            .build(),
+                                    );
+                                fields
+                            })
+                            .build(),
+                    ),
+                )
+                .expect("Unions cannot have duplicates");
+            builder
+                .variant_ignore_duplicates(
+                    ::apache_avro::schema::Schema::Record(
+                        ::apache_avro::schema::RecordSchema::builder()
+                            .name(
+                                ::apache_avro::schema::Name::new_with_enclosing_namespace(
+                                        "F",
+                                        enclosing_namespace,
+                                    )
+                                    .expect(
+                                        &::alloc::__export::must_use({
+                                            ::alloc::fmt::format(
+                                                format_args!(
+                                                    "Unable to parse variant record name for schema {0}", "F",
+                                                ),
+                                            )
+                                        })[..],
                                     ),
-                                ),
-                            );
-                        }
-                    }
-                };
-                let name = ::apache_avro::schema::Name::new("A")
-                    .expect(
-                        &::alloc::__export::must_use({
-                            ::alloc::fmt::format(
-                                format_args!(
-                                    "Unable to parse struct name for schema {0}", "A",
-                                ),
                             )
-                        })[..],
-                    );
-                let lookup: ::std::collections::BTreeMap<String, usize> = schema_fields
-                    .iter()
-                    .enumerate()
-                    .map(|(position, field)| (field.name.to_owned(), position))
-                    .collect();
-                ::apache_avro::schema::Schema::Record(::apache_avro::schema::RecordSchema {
-                    name,
-                    aliases: ::std::option::Option::None,
-                    doc: ::std::option::Option::None,
-                    fields: schema_fields,
-                    lookup,
-                    attributes: ::std::collections::BTreeMap::new(),
-                })
-            }
-        }
-    }
-    fn get_record_fields_in_ctxt(
-        named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
-        enclosing_namespace: ::apache_avro::schema::NamespaceRef,
-    ) -> ::std::option::Option<::std::vec::Vec<::apache_avro::schema::RecordField>> {
-        ::std::option::Option::Some({
-            let mut fields = ::std::vec::Vec::with_capacity(2usize);
+                            .fields({
+                                let mut fields = ::std::vec::Vec::with_capacity(2usize);
+                                fields
+                                    .push(
+                                        ::apache_avro::schema::RecordField::builder()
+                                            .name("x".to_string())
+                                            .doc(::std::option::Option::Some("This is X".to_string()))
+                                            .maybe_default(
+                                                <f64 as ::apache_avro::AvroSchemaComponent>::field_default(),
+                                            )
+                                            .aliases(::std::vec::Vec::new())
+                                            .schema(
+                                                <f64 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
+                                                    named_schemas,
+                                                    enclosing_namespace,
+                                                ),
+                                            )
+                                            .build(),
+                                    );
+                                fields
+                                    .push(
+                                        ::apache_avro::schema::RecordField::builder()
+                                            .name("y".to_string())
+                                            .doc(::std::option::Option::None)
+                                            .maybe_default(
+                                                <f32 as ::apache_avro::AvroSchemaComponent>::field_default(),
+                                            )
+                                            .aliases(::std::vec::Vec::new())
+                                            .schema(
+                                                <f32 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
+                                                    named_schemas,
+                                                    enclosing_namespace,
+                                                ),
+                                            )
+                                            .build(),
+                                    );
+                                fields
+                            })
+                            .build(),
+                    ),
+                )
+                .expect("Unions cannot have duplicates");
+            let content_contains_null = builder
+                .contains(&::apache_avro::schema::Schema::Null);
+            let content_schema = ::apache_avro::schema::Schema::Union(builder.build());
+            let tag_name = ::apache_avro::schema::Name::new_with_enclosing_namespace(
+                    "type",
+                    enclosing_namespace,
+                )
+                .expect(
+                    &::alloc::__export::must_use({
+                        ::alloc::fmt::format(
+                            format_args!(
+                                "Unable to parse name for schema tag {0}", "type",
+                            ),
+                        )
+                    })[..],
+                );
+            let tag_schema = ::apache_avro::schema::Schema::r#enum(
+                    tag_name,
+                    ::alloc::boxed::box_assume_init_into_vec_unsafe(
+                        ::alloc::intrinsics::write_box_via_move(
+                            ::alloc::boxed::Box::new_uninit(),
+                            [
+                                "A".to_owned(),
+                                "B".to_owned(),
+                                "D".to_owned(),
+                                "E".to_owned(),
+                                "F".to_owned(),
+                            ],
+                        ),
+                    ),
+                )
+                .build();
+            let mut fields = ::std::vec::Vec::with_capacity(2);
             fields
                 .push(
                     ::apache_avro::schema::RecordField::builder()
-                        .name("a".to_string())
-                        .doc(::std::option::Option::None)
-                        .maybe_default(
-                            <i32 as ::apache_avro::AvroSchemaComponent>::field_default(),
-                        )
-                        .aliases(::std::vec::Vec::new())
-                        .schema(
-                            <i32 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-                                named_schemas,
-                                enclosing_namespace,
-                            ),
-                        )
+                        .name("type")
+                        .schema(tag_schema)
                         .build(),
                 );
             fields
                 .push(
                     ::apache_avro::schema::RecordField::builder()
-                        .name("b".to_string())
-                        .doc(::std::option::Option::None)
+                        .name("value")
+                        .schema(content_schema)
                         .maybe_default(
-                            <String as ::apache_avro::AvroSchemaComponent>::field_default(),
-                        )
-                        .aliases(::std::vec::Vec::new())
-                        .schema(
-                            <String as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-                                named_schemas,
-                                enclosing_namespace,
-                            ),
+                            if content_contains_null {
+                                Some(::serde_json::Value::Null)
+                            } else {
+                                None
+                            },
                         )
                         .build(),
                 );
-            fields
-        })
-    }
-    fn field_default() -> ::std::option::Option<::serde_json::Value> {
-        ::std::option::Option::None
-    }
-}
-#[serde(transparent)]
-struct B {
-    a: A,
-}
-#[automatically_derived]
-impl ::apache_avro::AvroSchemaComponent for B {
-    fn get_schema_in_ctxt(
-        named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
-        enclosing_namespace: ::apache_avro::schema::NamespaceRef,
-    ) -> ::apache_avro::schema::Schema {
-        <A as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-            named_schemas,
-            enclosing_namespace,
-        )
+            ::apache_avro::schema::Schema::Record(
+                ::apache_avro::schema::RecordSchema::builder()
+                    .name(name)
+                    .maybe_aliases(::std::option::Option::None)
+                    .doc(::std::option::Option::None)
+                    .fields(fields)
+                    .build(),
+            )
+        }
     }
     fn get_record_fields_in_ctxt(
         named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
         enclosing_namespace: ::apache_avro::schema::NamespaceRef,
     ) -> ::std::option::Option<::std::vec::Vec<::apache_avro::schema::RecordField>> {
-        <A as ::apache_avro::AvroSchemaComponent>::get_record_fields_in_ctxt(
-            named_schemas,
-            enclosing_namespace,
-        )
+        None
     }
     fn field_default() -> ::std::option::Option<::serde_json::Value> {
         ::std::option::Option::None
diff --git a/avro_derive/tests/expanded/avro_rs_xxx_tag_content.rs b/avro_derive/tests/expanded/avro_rs_xxx_tag_content.rs
index b23413b..f6fb040 100644
--- a/avro_derive/tests/expanded/avro_rs_xxx_tag_content.rs
+++ b/avro_derive/tests/expanded/avro_rs_xxx_tag_content.rs
@@ -18,17 +18,18 @@
 use apache_avro::AvroSchema;
 
 #[derive(AvroSchema)]
-struct A {
-    a: i32,
-    b: String,
+#[avro(repr = "record_tag_content")]
+#[serde(tag = "type", content = "value")]
+enum Abc {
+    A,
+    B(bool),
+    D {},
+    E {
+        is_it_true: bool,
+    },
+    F {
+        #[avro(doc = "This is X")]
+        x: f64,
+        y: f32,
+    },
 }
-
-#[derive(AvroSchema)]
-#[serde(transparent)]
-struct B {
-    a: A,
-}
-
-// #[derive(AvroSchema)]
-// #[serde(transparent)]
-// struct C(A);
diff --git a/avro_derive/tests/expanded/avro_rs_xxx_union_of_records.expanded.rs b/avro_derive/tests/expanded/avro_rs_xxx_union_of_records.expanded.rs
index d75f321..ccad318 100644
--- a/avro_derive/tests/expanded/avro_rs_xxx_union_of_records.expanded.rs
+++ b/avro_derive/tests/expanded/avro_rs_xxx_union_of_records.expanded.rs
@@ -1,19 +1,23 @@
 use apache_avro::AvroSchema;
-struct A {
-    a: i32,
-    b: String,
+#[avro(repr = "union_of_records")]
+enum Abc {
+    A,
+    B(bool),
+    D {},
+    E { is_it_true: bool },
+    F { #[avro(doc = "This is X")] x: f64, y: f32 },
 }
 #[automatically_derived]
-impl ::apache_avro::AvroSchemaComponent for A {
+impl ::apache_avro::AvroSchemaComponent for Abc {
     fn get_schema_in_ctxt(
         named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
         enclosing_namespace: ::apache_avro::schema::NamespaceRef,
     ) -> ::apache_avro::schema::Schema {
         let name = ::apache_avro::schema::Name::new_with_enclosing_namespace(
-                "A",
+                "Abc",
                 enclosing_namespace,
             )
-            .expect("Unable to parse schema name A");
+            .expect("Unable to parse schema name Abc");
         if named_schemas.contains(&name) {
             ::apache_avro::schema::Schema::Ref {
                 name,
@@ -21,162 +25,229 @@ impl ::apache_avro::AvroSchemaComponent for A {
         } else {
             let enclosing_namespace = name.namespace();
             named_schemas.insert(name.clone());
-            {
-                let schema_fields = {
-                    let mut fields = ::std::vec::Vec::with_capacity(2usize);
-                    fields
-                        .push(
-                            ::apache_avro::schema::RecordField::builder()
-                                .name("a".to_string())
-                                .doc(::std::option::Option::None)
-                                .maybe_default(
-                                    <i32 as ::apache_avro::AvroSchemaComponent>::field_default(),
-                                )
-                                .aliases(::std::vec::Vec::new())
-                                .schema(
-                                    <i32 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-                                        named_schemas,
+            let mut builder = ::apache_avro::schema::UnionSchema::builder();
+            builder
+                .variant(
+                    ::apache_avro::schema::Schema::Record(
+                        ::apache_avro::schema::RecordSchema::builder()
+                            .name(
+                                ::apache_avro::schema::Name::new_with_enclosing_namespace(
+                                        "A",
                                         enclosing_namespace,
+                                    )
+                                    .expect(
+                                        &::alloc::__export::must_use({
+                                            ::alloc::fmt::format(
+                                                format_args!(
+                                                    "Unable to parse variant record name for schema {0}", "A",
+                                                ),
+                                            )
+                                        })[..],
                                     ),
-                                )
-                                .build(),
-                        );
-                    fields
-                        .push(
-                            ::apache_avro::schema::RecordField::builder()
-                                .name("b".to_string())
-                                .doc(::std::option::Option::None)
-                                .maybe_default(
-                                    <String as ::apache_avro::AvroSchemaComponent>::field_default(),
-                                )
-                                .aliases(::std::vec::Vec::new())
-                                .schema(
-                                    <String as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-                                        named_schemas,
+                            )
+                            .build(),
+                    ),
+                )
+                .expect("Duplicate Schema found");
+            builder
+                .variant(
+                    ::apache_avro::schema::Schema::Record(
+                        ::apache_avro::schema::RecordSchema::builder()
+                            .name(
+                                ::apache_avro::schema::Name::new_with_enclosing_namespace(
+                                        "B",
                                         enclosing_namespace,
+                                    )
+                                    .expect(
+                                        &::alloc::__export::must_use({
+                                            ::alloc::fmt::format(
+                                                format_args!(
+                                                    "Unable to parse variant record name for schema {0}", "B",
+                                                ),
+                                            )
+                                        })[..],
                                     ),
-                                )
-                                .build(),
-                        );
-                    fields
-                };
-                let schema_field_set: ::std::collections::HashSet<_> = schema_fields
-                    .iter()
-                    .map(|rf| &rf.name)
-                    .collect();
-                match (&schema_fields.len(), &schema_field_set.len()) {
-                    (left_val, right_val) => {
-                        if !(*left_val == *right_val) {
-                            let kind = ::core::panicking::AssertKind::Eq;
-                            ::core::panicking::assert_failed(
-                                kind,
-                                &*left_val,
-                                &*right_val,
-                                ::core::option::Option::Some(
-                                    format_args!(
-                                        "Duplicate field names found: {0:?}", schema_fields,
+                            )
+                            .fields(
+                                ::alloc::boxed::box_assume_init_into_vec_unsafe(
+                                    ::alloc::intrinsics::write_box_via_move(
+                                        ::alloc::boxed::Box::new_uninit(),
+                                        [
+                                            ::apache_avro::schema::RecordField::builder()
+                                                .name("field_0".to_string())
+                                                .doc(::std::option::Option::None)
+                                                .maybe_default(
+                                                    <bool as ::apache_avro::AvroSchemaComponent>::field_default(),
+                                                )
+                                                .aliases(::std::vec::Vec::new())
+                                                .schema(
+                                                    <bool as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
+                                                        named_schemas,
+                                                        enclosing_namespace,
+                                                    ),
+                                                )
+                                                .build(),
+                                        ],
                                     ),
                                 ),
-                            );
-                        }
-                    }
-                };
-                let name = ::apache_avro::schema::Name::new("A")
-                    .expect(
-                        &::alloc::__export::must_use({
-                            ::alloc::fmt::format(
-                                format_args!(
-                                    "Unable to parse struct name for schema {0}", "A",
-                                ),
                             )
-                        })[..],
-                    );
-                let lookup: ::std::collections::BTreeMap<String, usize> = schema_fields
-                    .iter()
-                    .enumerate()
-                    .map(|(position, field)| (field.name.to_owned(), position))
-                    .collect();
-                ::apache_avro::schema::Schema::Record(::apache_avro::schema::RecordSchema {
-                    name,
-                    aliases: ::std::option::Option::None,
-                    doc: ::std::option::Option::None,
-                    fields: schema_fields,
-                    lookup,
-                    attributes: ::std::collections::BTreeMap::new(),
-                })
-            }
+                            .attributes(
+                                [
+                                    (
+                                        "org.apache.avro.rust.tuple".to_string(),
+                                        ::serde_json::value::Value::Bool(true),
+                                    ),
+                                    (
+                                        "org.apache.avro.rust.union_of_records".to_string(),
+                                        ::serde_json::value::Value::Bool(true),
+                                    ),
+                                ]
+                                    .into(),
+                            )
+                            .build(),
+                    ),
+                )
+                .expect("Duplicate Schema found");
+            builder
+                .variant(
+                    ::apache_avro::schema::Schema::Record(
+                        ::apache_avro::schema::RecordSchema::builder()
+                            .name(
+                                ::apache_avro::schema::Name::new_with_enclosing_namespace(
+                                        "D",
+                                        enclosing_namespace,
+                                    )
+                                    .expect(
+                                        &::alloc::__export::must_use({
+                                            ::alloc::fmt::format(
+                                                format_args!(
+                                                    "Unable to parse variant record name for schema {0}", "D",
+                                                ),
+                                            )
+                                        })[..],
+                                    ),
+                            )
+                            .fields({
+                                let mut fields = ::std::vec::Vec::with_capacity(0usize);
+                                fields
+                            })
+                            .build(),
+                    ),
+                )
+                .expect("Duplicate Schema found");
+            builder
+                .variant(
+                    ::apache_avro::schema::Schema::Record(
+                        ::apache_avro::schema::RecordSchema::builder()
+                            .name(
+                                ::apache_avro::schema::Name::new_with_enclosing_namespace(
+                                        "E",
+                                        enclosing_namespace,
+                                    )
+                                    .expect(
+                                        &::alloc::__export::must_use({
+                                            ::alloc::fmt::format(
+                                                format_args!(
+                                                    "Unable to parse variant record name for schema {0}", "E",
+                                                ),
+                                            )
+                                        })[..],
+                                    ),
+                            )
+                            .fields({
+                                let mut fields = ::std::vec::Vec::with_capacity(1usize);
+                                fields
+                                    .push(
+                                        ::apache_avro::schema::RecordField::builder()
+                                            .name("is_it_true".to_string())
+                                            .doc(::std::option::Option::None)
+                                            .maybe_default(
+                                                <bool as ::apache_avro::AvroSchemaComponent>::field_default(),
+                                            )
+                                            .aliases(::std::vec::Vec::new())
+                                            .schema(
+                                                <bool as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
+                                                    named_schemas,
+                                                    enclosing_namespace,
+                                                ),
+                                            )
+                                            .build(),
+                                    );
+                                fields
+                            })
+                            .build(),
+                    ),
+                )
+                .expect("Duplicate Schema found");
+            builder
+                .variant(
+                    ::apache_avro::schema::Schema::Record(
+                        ::apache_avro::schema::RecordSchema::builder()
+                            .name(
+                                ::apache_avro::schema::Name::new_with_enclosing_namespace(
+                                        "F",
+                                        enclosing_namespace,
+                                    )
+                                    .expect(
+                                        &::alloc::__export::must_use({
+                                            ::alloc::fmt::format(
+                                                format_args!(
+                                                    "Unable to parse variant record name for schema {0}", "F",
+                                                ),
+                                            )
+                                        })[..],
+                                    ),
+                            )
+                            .fields({
+                                let mut fields = ::std::vec::Vec::with_capacity(2usize);
+                                fields
+                                    .push(
+                                        ::apache_avro::schema::RecordField::builder()
+                                            .name("x".to_string())
+                                            .doc(::std::option::Option::Some("This is X".to_string()))
+                                            .maybe_default(
+                                                <f64 as ::apache_avro::AvroSchemaComponent>::field_default(),
+                                            )
+                                            .aliases(::std::vec::Vec::new())
+                                            .schema(
+                                                <f64 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
+                                                    named_schemas,
+                                                    enclosing_namespace,
+                                                ),
+                                            )
+                                            .build(),
+                                    );
+                                fields
+                                    .push(
+                                        ::apache_avro::schema::RecordField::builder()
+                                            .name("y".to_string())
+                                            .doc(::std::option::Option::None)
+                                            .maybe_default(
+                                                <f32 as ::apache_avro::AvroSchemaComponent>::field_default(),
+                                            )
+                                            .aliases(::std::vec::Vec::new())
+                                            .schema(
+                                                <f32 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
+                                                    named_schemas,
+                                                    enclosing_namespace,
+                                                ),
+                                            )
+                                            .build(),
+                                    );
+                                fields
+                            })
+                            .build(),
+                    ),
+                )
+                .expect("Duplicate Schema found");
+            ::apache_avro::schema::Schema::Union(builder.build())
         }
     }
     fn get_record_fields_in_ctxt(
         named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
         enclosing_namespace: ::apache_avro::schema::NamespaceRef,
     ) -> ::std::option::Option<::std::vec::Vec<::apache_avro::schema::RecordField>> {
-        ::std::option::Option::Some({
-            let mut fields = ::std::vec::Vec::with_capacity(2usize);
-            fields
-                .push(
-                    ::apache_avro::schema::RecordField::builder()
-                        .name("a".to_string())
-                        .doc(::std::option::Option::None)
-                        .maybe_default(
-                            <i32 as ::apache_avro::AvroSchemaComponent>::field_default(),
-                        )
-                        .aliases(::std::vec::Vec::new())
-                        .schema(
-                            <i32 as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-                                named_schemas,
-                                enclosing_namespace,
-                            ),
-                        )
-                        .build(),
-                );
-            fields
-                .push(
-                    ::apache_avro::schema::RecordField::builder()
-                        .name("b".to_string())
-                        .doc(::std::option::Option::None)
-                        .maybe_default(
-                            <String as ::apache_avro::AvroSchemaComponent>::field_default(),
-                        )
-                        .aliases(::std::vec::Vec::new())
-                        .schema(
-                            <String as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-                                named_schemas,
-                                enclosing_namespace,
-                            ),
-                        )
-                        .build(),
-                );
-            fields
-        })
-    }
-    fn field_default() -> ::std::option::Option<::serde_json::Value> {
-        ::std::option::Option::None
-    }
-}
-#[serde(transparent)]
-struct B {
-    a: A,
-}
-#[automatically_derived]
-impl ::apache_avro::AvroSchemaComponent for B {
-    fn get_schema_in_ctxt(
-        named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
-        enclosing_namespace: ::apache_avro::schema::NamespaceRef,
-    ) -> ::apache_avro::schema::Schema {
-        <A as ::apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(
-            named_schemas,
-            enclosing_namespace,
-        )
-    }
-    fn get_record_fields_in_ctxt(
-        named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
-        enclosing_namespace: ::apache_avro::schema::NamespaceRef,
-    ) -> ::std::option::Option<::std::vec::Vec<::apache_avro::schema::RecordField>> {
-        <A as ::apache_avro::AvroSchemaComponent>::get_record_fields_in_ctxt(
-            named_schemas,
-            enclosing_namespace,
-        )
+        None
     }
     fn field_default() -> ::std::option::Option<::serde_json::Value> {
         ::std::option::Option::None
diff --git a/avro_derive/tests/expanded/avro_rs_xxx_union_of_records.rs b/avro_derive/tests/expanded/avro_rs_xxx_union_of_records.rs
index b23413b..a46e709 100644
--- a/avro_derive/tests/expanded/avro_rs_xxx_union_of_records.rs
+++ b/avro_derive/tests/expanded/avro_rs_xxx_union_of_records.rs
@@ -18,17 +18,17 @@
 use apache_avro::AvroSchema;
 
 #[derive(AvroSchema)]
-struct A {
-    a: i32,
-    b: String,
+#[avro(repr = "union_of_records")]
+enum Abc {
+    A,
+    B(bool),
+    D {},
+    E {
+        is_it_true: bool,
+    },
+    F {
+        #[avro(doc = "This is X")]
+        x: f64,
+        y: f32,
+    },
 }
-
-#[derive(AvroSchema)]
-#[serde(transparent)]
-struct B {
-    a: A,
-}
-
-// #[derive(AvroSchema)]
-// #[serde(transparent)]
-// struct C(A);
diff --git a/avro_derive/tests/expanded/mod.rs b/avro_derive/tests/expanded/mod.rs
index 80b8b23..ed2c338 100644
--- a/avro_derive/tests/expanded/mod.rs
+++ b/avro_derive/tests/expanded/mod.rs
@@ -27,9 +27,9 @@ mod avro_rs_501_basic;
 mod avro_rs_501_namespace;
 mod avro_rs_501_reference;
 mod avro_rs_501_struct_with_optional;
+mod avro_rs_xxx_bare_union;
+mod avro_rs_xxx_internally_tagged;
 mod avro_rs_xxx_serde_from_into;
 mod avro_rs_xxx_serde_transparent;
-// mod avro_rs_xxx_bare_union;
-mod avro_rs_xxx_internally_tagged;
 mod avro_rs_xxx_tag_content;
 mod avro_rs_xxx_union_of_records;
diff --git a/avro_derive/tests/ui/avro_rs_401_tuple.stderr b/avro_derive/tests/ui/avro_rs_401_tuple.stderr
deleted file mode 100644
index ffc0378..0000000
--- a/avro_derive/tests/ui/avro_rs_401_tuple.stderr
+++ /dev/null
@@ -1,5 +0,0 @@
-error: AvroSchema: derive does not support tuples
-  --> tests/ui/avro_rs_401_tuple.rs:22:8
-   |
-22 |     a: (i32, String),
-   |        ^^^^^^^^^^^^^
diff --git a/avro_derive/tests/ui/avro_rs_401_tuple.rs b/avro_derive/tests/ui/avro_rs_xxx_bare_union_struct_variant.rs
similarity index 96%
copy from avro_derive/tests/ui/avro_rs_401_tuple.rs
copy to avro_derive/tests/ui/avro_rs_xxx_bare_union_struct_variant.rs
index cf00214..0dc8b0a 100644
--- a/avro_derive/tests/ui/avro_rs_401_tuple.rs
+++ b/avro_derive/tests/ui/avro_rs_xxx_bare_union_struct_variant.rs
@@ -18,8 +18,9 @@
 use apache_avro::AvroSchema;
 
 #[derive(AvroSchema)]
-struct Foo {
-    a: (i32, String),
+#[serde(untagged)]
+enum D {
+    A{}
 }
 
 pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_xxx_bare_union_struct_variant.stderr b/avro_derive/tests/ui/avro_rs_xxx_bare_union_struct_variant.stderr
new file mode 100644
index 0000000..592afa8
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_bare_union_struct_variant.stderr
@@ -0,0 +1,5 @@
+error: AvroSchema: Struct variants are not supported for untagged structs
+  --> tests/ui/avro_rs_xxx_bare_union_struct_variant.rs:23:6
+   |
+23 |     A{}
+   |      ^^
diff --git a/avro_derive/tests/ui/avro_rs_401_tuple.rs b/avro_derive/tests/ui/avro_rs_xxx_untagged_newtype_docs.rs
similarity index 89%
rename from avro_derive/tests/ui/avro_rs_401_tuple.rs
rename to avro_derive/tests/ui/avro_rs_xxx_untagged_newtype_docs.rs
index cf00214..e9da9c5 100644
--- a/avro_derive/tests/ui/avro_rs_401_tuple.rs
+++ b/avro_derive/tests/ui/avro_rs_xxx_untagged_newtype_docs.rs
@@ -18,8 +18,12 @@
 use apache_avro::AvroSchema;
 
 #[derive(AvroSchema)]
-struct Foo {
-    a: (i32, String),
+#[serde(untagged)]
+enum D {
+    A(
+        #[avro(doc = "This is a string")]
+        String
+    )
 }
 
 pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_xxx_untagged_newtype_docs.stderr b/avro_derive/tests/ui/avro_rs_xxx_untagged_newtype_docs.stderr
new file mode 100644
index 0000000..48ff5c4
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_xxx_untagged_newtype_docs.stderr
@@ -0,0 +1,6 @@
+error: AvroSchema: `#[avro(doc = "..")]` only works on newtype variants when the enum uses `#[avro(repr = "union_of_records")`
+  --> tests/ui/avro_rs_xxx_untagged_newtype_docs.rs:24:9
+   |
+24 | /         #[avro(doc = "This is a string")]
+25 | |         String
+   | |______________^