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/01/23 21:31:53 UTC

(avro-rs) branch reorganize/derive_traits created (now 2d858e1)

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

kriskras99 pushed a change to branch reorganize/derive_traits
in repository https://gitbox.apache.org/repos/asf/avro-rs.git


      at 2d858e1  chore: Move `AvroSchema` and `AvroSchemaComponent` to the `serde` module

This branch includes the following new commits:

     new 2d858e1  chore: Move `AvroSchema` and `AvroSchemaComponent` to the `serde` module

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(avro-rs) 01/01: chore: Move `AvroSchema` and `AvroSchemaComponent` to the `serde` module

Posted by kr...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 2d858e16219deb4b06ce0022a6b60f8c97ab51b3
Author: Kriskras99 <gi...@kriskras99.nl>
AuthorDate: Fri Jan 23 22:26:00 2026 +0100

    chore: Move `AvroSchema` and `AvroSchemaComponent` to the `serde` module
---
 .../test_interop_single_object_encoding.rs         |   2 +-
 avro/src/lib.rs                                    |   4 +-
 avro/src/reader.rs                                 |   3 +-
 avro/src/schema/mod.rs                             | 440 +-------------------
 avro/src/serde/derive.rs                           | 450 +++++++++++++++++++++
 avro/src/serde/mod.rs                              |   2 +
 avro/src/writer.rs                                 |   4 +-
 avro_derive/tests/derive.rs                        |   6 +-
 8 files changed, 462 insertions(+), 449 deletions(-)

diff --git a/avro/examples/test_interop_single_object_encoding.rs b/avro/examples/test_interop_single_object_encoding.rs
index 61b1e6f..e65b35e 100644
--- a/avro/examples/test_interop_single_object_encoding.rs
+++ b/avro/examples/test_interop_single_object_encoding.rs
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use apache_avro::{schema::AvroSchema, types::Value};
+use apache_avro::{serde::AvroSchema, types::Value};
 use std::error::Error;
 
 struct InteropMessage;
diff --git a/avro/src/lib.rs b/avro/src/lib.rs
index 785d718..f60c8c2 100644
--- a/avro/src/lib.rs
+++ b/avro/src/lib.rs
@@ -982,8 +982,8 @@ pub use reader::{
     GenericSingleObjectReader, Reader, SpecificSingleObjectReader, from_avro_datum,
     from_avro_datum_reader_schemata, from_avro_datum_schemata, read_marker,
 };
-pub use schema::{AvroSchema, AvroSchemaComponent, Schema};
-pub use serde::{from_value, to_value};
+pub use schema::Schema;
+pub use serde::{AvroSchema, AvroSchemaComponent, from_value, to_value};
 pub use uuid::Uuid;
 pub use writer::{
     GenericSingleObjectWriter, SpecificSingleObjectWriter, Writer, WriterBuilder, to_avro_datum,
diff --git a/avro/src/reader.rs b/avro/src/reader.rs
index 23af5db..967edb5 100644
--- a/avro/src/reader.rs
+++ b/avro/src/reader.rs
@@ -23,9 +23,10 @@ use crate::{
     from_value,
     headers::{HeaderBuilder, RabinFingerprintHeader},
     schema::{
-        AvroSchema, Names, ResolvedOwnedSchema, ResolvedSchema, Schema, resolve_names,
+        Names, ResolvedOwnedSchema, ResolvedSchema, Schema, resolve_names,
         resolve_names_with_schemata,
     },
+    serde::AvroSchema,
     types::Value,
     util,
 };
diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs
index f77854e..3e6e4b1 100644
--- a/avro/src/schema/mod.rs
+++ b/avro/src/schema/mod.rs
@@ -28,7 +28,7 @@ use serde::{
 };
 use serde_json::{Map, Value};
 use std::{
-    borrow::{Borrow, Cow},
+    borrow::Borrow,
     collections::{BTreeMap, HashMap, HashSet},
     fmt,
     fmt::Debug,
@@ -1262,333 +1262,6 @@ fn field_ordering_position(field: &str) -> Option<usize> {
         .map(|pos| pos + 1)
 }
 
-/// Trait for types that serve as an Avro data model. Derive implementation available
-/// through `derive` feature. Do not implement directly!
-/// Implement [`AvroSchemaComponent`] to get this trait
-/// through a blanket implementation.
-pub trait AvroSchema {
-    fn get_schema() -> Schema;
-}
-
-/// Trait for types that serve as fully defined components inside an Avro data model. Derive
-/// implementation available through `derive` feature. This is what is implemented by
-/// the `derive(AvroSchema)` macro.
-///
-/// # Implementation guide
-///
-/// ### Simple implementation
-/// To construct a non named simple schema, it is possible to ignore the input argument making the
-/// general form implementation look like
-/// ```ignore
-/// impl AvroSchemaComponent for AType {
-///     fn get_schema_in_ctxt(_: &mut Names, _: &Namespace) -> Schema {
-///        Schema::?
-///    }
-///}
-/// ```
-///
-/// ### Passthrough implementation
-///
-/// To construct a schema for a Type that acts as in "inner" type, such as for smart pointers, simply
-/// pass through the arguments to the inner type
-/// ```ignore
-/// impl AvroSchemaComponent for PassthroughType {
-///     fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-///        InnerType::get_schema_in_ctxt(named_schemas, enclosing_namespace)
-///    }
-///}
-/// ```
-///
-/// ### Complex implementation
-///
-/// To implement this for Named schema there is a general form needed to avoid creating invalid
-/// schemas or infinite loops.
-/// ```ignore
-/// impl AvroSchemaComponent for ComplexType {
-///     fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-///         // Create the fully qualified name for your type given the enclosing namespace
-///         let name =  apache_avro::schema::Name::new("MyName")
-///             .expect("Unable to parse schema name")
-///             .fully_qualified_name(enclosing_namespace);
-///         let enclosing_namespace = &name.namespace;
-///         // Check, if your name is already defined, and if so, return a ref to that name
-///         if named_schemas.contains_key(&name) {
-///             apache_avro::schema::Schema::Ref{name: name.clone()}
-///         } else {
-///             named_schemas.insert(name.clone(), apache_avro::schema::Schema::Ref{name: name.clone()});
-///             // YOUR SCHEMA DEFINITION HERE with the name equivalent to "MyName".
-///             // For non-simple sub types delegate to their implementation of AvroSchemaComponent
-///         }
-///    }
-///}
-/// ```
-pub trait AvroSchemaComponent {
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema;
-}
-
-impl<T> AvroSchema for T
-where
-    T: AvroSchemaComponent + ?Sized,
-{
-    fn get_schema() -> Schema {
-        T::get_schema_in_ctxt(&mut HashMap::default(), &None)
-    }
-}
-
-macro_rules! impl_schema (
-    ($type:ty, $variant_constructor:expr) => (
-        impl AvroSchemaComponent for $type {
-            fn get_schema_in_ctxt(_: &mut Names, _: &Namespace) -> Schema {
-                $variant_constructor
-            }
-        }
-    );
-);
-
-impl_schema!(bool, Schema::Boolean);
-impl_schema!(i8, Schema::Int);
-impl_schema!(i16, Schema::Int);
-impl_schema!(i32, Schema::Int);
-impl_schema!(i64, Schema::Long);
-impl_schema!(u8, Schema::Int);
-impl_schema!(u16, Schema::Int);
-impl_schema!(u32, Schema::Long);
-impl_schema!(f32, Schema::Float);
-impl_schema!(f64, Schema::Double);
-impl_schema!(String, Schema::String);
-impl_schema!(str, Schema::String);
-impl_schema!(char, Schema::String);
-
-impl<T> AvroSchemaComponent for &T
-where
-    T: AvroSchemaComponent + ?Sized,
-{
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
-    }
-}
-
-impl<T> AvroSchemaComponent for &mut T
-where
-    T: AvroSchemaComponent + ?Sized,
-{
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
-    }
-}
-
-impl<T> AvroSchemaComponent for [T]
-where
-    T: AvroSchemaComponent,
-{
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
-    }
-}
-
-impl<const N: usize, T> AvroSchemaComponent for [T; N]
-where
-    T: AvroSchemaComponent,
-{
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
-    }
-}
-
-impl<T> AvroSchemaComponent for Vec<T>
-where
-    T: AvroSchemaComponent,
-{
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
-    }
-}
-
-impl<T> AvroSchemaComponent for Option<T>
-where
-    T: AvroSchemaComponent,
-{
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        let variants = vec![
-            Schema::Null,
-            T::get_schema_in_ctxt(named_schemas, enclosing_namespace),
-        ];
-
-        Schema::Union(
-            UnionSchema::new(variants).expect("Option<T> must produce a valid (non-nested) union"),
-        )
-    }
-}
-
-impl<T> AvroSchemaComponent for Map<String, T>
-where
-    T: AvroSchemaComponent,
-{
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        Schema::map(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
-    }
-}
-
-impl<T> AvroSchemaComponent for HashMap<String, T>
-where
-    T: AvroSchemaComponent,
-{
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        Schema::map(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
-    }
-}
-
-impl<T> AvroSchemaComponent for Box<T>
-where
-    T: AvroSchemaComponent,
-{
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
-    }
-}
-
-impl<T> AvroSchemaComponent for std::sync::Mutex<T>
-where
-    T: AvroSchemaComponent,
-{
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
-    }
-}
-
-impl<T> AvroSchemaComponent for Cow<'_, T>
-where
-    T: AvroSchemaComponent + Clone,
-{
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
-    }
-}
-
-impl AvroSchemaComponent for core::time::Duration {
-    /// The schema is [`Schema::Duration`] with the name `duration`.
-    ///
-    /// This is a lossy conversion as this Avro type does not store the amount of nanoseconds.
-    #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        let name = Name::new("duration")
-            .expect("Name is valid")
-            .fully_qualified_name(enclosing_namespace);
-        if named_schemas.contains_key(&name) {
-            Schema::Ref { name }
-        } else {
-            let schema = Schema::Duration(FixedSchema {
-                name: name.clone(),
-                aliases: None,
-                doc: None,
-                size: 12,
-                default: None,
-                attributes: Default::default(),
-            });
-            named_schemas.insert(name, schema.clone());
-            schema
-        }
-    }
-}
-
-impl AvroSchemaComponent for uuid::Uuid {
-    /// The schema is [`Schema::Uuid`] with the name `uuid`.
-    ///
-    /// The underlying schema is [`Schema::Fixed`] with a size of 16.
-    #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        let name = Name::new("uuid")
-            .expect("Name is valid")
-            .fully_qualified_name(enclosing_namespace);
-        if named_schemas.contains_key(&name) {
-            Schema::Ref { name }
-        } else {
-            let schema = Schema::Uuid(UuidSchema::Fixed(FixedSchema {
-                name: name.clone(),
-                aliases: None,
-                doc: None,
-                size: 16,
-                default: None,
-                attributes: Default::default(),
-            }));
-            named_schemas.insert(name, schema.clone());
-            schema
-        }
-    }
-}
-
-impl AvroSchemaComponent for u64 {
-    /// The schema is [`Schema::Fixed`] of size 8 with the name `u64`.
-    #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        let name = Name::new("u64")
-            .expect("Name is valid")
-            .fully_qualified_name(enclosing_namespace);
-        if named_schemas.contains_key(&name) {
-            Schema::Ref { name }
-        } else {
-            let schema = Schema::Fixed(FixedSchema {
-                name: name.clone(),
-                aliases: None,
-                doc: None,
-                size: 8,
-                default: None,
-                attributes: Default::default(),
-            });
-            named_schemas.insert(name, schema.clone());
-            schema
-        }
-    }
-}
-
-impl AvroSchemaComponent for u128 {
-    /// The schema is [`Schema::Fixed`] of size 16 with the name `u128`.
-    #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        let name = Name::new("u128")
-            .expect("Name is valid")
-            .fully_qualified_name(enclosing_namespace);
-        if named_schemas.contains_key(&name) {
-            Schema::Ref { name }
-        } else {
-            let schema = Schema::Fixed(FixedSchema {
-                name: name.clone(),
-                aliases: None,
-                doc: None,
-                size: 16,
-                default: None,
-                attributes: Default::default(),
-            });
-            named_schemas.insert(name, schema.clone());
-            schema
-        }
-    }
-}
-
-impl AvroSchemaComponent for i128 {
-    /// The schema is [`Schema::Fixed`] of size 16 with the name `i128`.
-    #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
-    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
-        let name = Name::new("i128")
-            .expect("Name is valid")
-            .fully_qualified_name(enclosing_namespace);
-        if named_schemas.contains_key(&name) {
-            Schema::Ref { name }
-        } else {
-            let schema = Schema::Fixed(FixedSchema {
-                name: name.clone(),
-                aliases: None,
-                doc: None,
-                size: 16,
-                default: None,
-                attributes: Default::default(),
-            });
-            named_schemas.insert(name, schema.clone());
-            schema
-        }
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -6359,117 +6032,6 @@ mod tests {
         Ok(())
     }
 
-    #[test]
-    fn avro_rs_401_str() -> TestResult {
-        let schema = str::get_schema();
-        assert_eq!(schema, Schema::String);
-
-        Ok(())
-    }
-
-    #[test]
-    fn avro_rs_401_references() -> TestResult {
-        let schema_ref = <&str>::get_schema();
-        let schema_ref_mut = <&mut str>::get_schema();
-
-        assert_eq!(schema_ref, Schema::String);
-        assert_eq!(schema_ref_mut, Schema::String);
-
-        Ok(())
-    }
-
-    #[test]
-    fn avro_rs_401_slice() -> TestResult {
-        let schema = <[u8]>::get_schema();
-        assert_eq!(schema, Schema::array(Schema::Int));
-
-        Ok(())
-    }
-
-    #[test]
-    fn avro_rs_401_array() -> TestResult {
-        let schema = <[u8; 55]>::get_schema();
-        assert_eq!(schema, Schema::array(Schema::Int));
-
-        Ok(())
-    }
-
-    #[test]
-    fn avro_rs_401_option_ref_slice_array() -> TestResult {
-        let schema = <Option<&[[u8; 55]]>>::get_schema();
-        assert_eq!(
-            schema,
-            Schema::union(vec![
-                Schema::Null,
-                Schema::array(Schema::array(Schema::Int))
-            ])?
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn avro_rs_414_char() -> TestResult {
-        let schema = char::get_schema();
-        assert_eq!(schema, Schema::String);
-
-        Ok(())
-    }
-
-    #[test]
-    fn avro_rs_414_u64() -> TestResult {
-        let schema = u64::get_schema();
-        assert_eq!(
-            schema,
-            Schema::Fixed(FixedSchema {
-                name: Name::new("u64")?,
-                aliases: None,
-                doc: None,
-                size: 8,
-                default: None,
-                attributes: Default::default(),
-            })
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn avro_rs_414_i128() -> TestResult {
-        let schema = i128::get_schema();
-        assert_eq!(
-            schema,
-            Schema::Fixed(FixedSchema {
-                name: Name::new("i128")?,
-                aliases: None,
-                doc: None,
-                size: 16,
-                default: None,
-                attributes: Default::default(),
-            })
-        );
-
-        Ok(())
-    }
-
-    #[test]
-    fn avro_rs_414_u128() -> TestResult {
-        let schema = u128::get_schema();
-        assert_eq!(
-            schema,
-            Schema::Fixed(FixedSchema {
-                name: Name::new("u128")?,
-                aliases: None,
-                doc: None,
-                size: 16,
-                default: None,
-                attributes: Default::default(),
-            })
-        );
-
-        Ok(())
-    }
-
     #[test]
     fn avro_rs_420_independent_canonical_form() -> TestResult {
         let (record, schemata) = Schema::parse_str_with_list(
diff --git a/avro/src/serde/derive.rs b/avro/src/serde/derive.rs
new file mode 100644
index 0000000..e6e4e16
--- /dev/null
+++ b/avro/src/serde/derive.rs
@@ -0,0 +1,450 @@
+use crate::Schema;
+use crate::schema::{FixedSchema, Name, Names, Namespace, UnionSchema, UuidSchema};
+use serde_json::Map;
+use std::borrow::Cow;
+use std::collections::HashMap;
+
+/// Trait for types that serve as an Avro data model. Derive implementation available
+/// through `derive` feature. Do not implement directly!
+/// Implement [`AvroSchemaComponent`] to get this trait
+/// through a blanket implementation.
+pub trait AvroSchema {
+    fn get_schema() -> Schema;
+}
+
+/// Trait for types that serve as fully defined components inside an Avro data model. Derive
+/// implementation available through `derive` feature. This is what is implemented by
+/// the `derive(AvroSchema)` macro.
+///
+/// # Implementation guide
+///
+/// ### Simple implementation
+/// To construct a non named simple schema, it is possible to ignore the input argument making the
+/// general form implementation look like
+/// ```ignore
+/// impl AvroSchemaComponent for AType {
+///     fn get_schema_in_ctxt(_: &mut Names, _: &Namespace) -> Schema {
+///        Schema::?
+///    }
+///}
+/// ```
+///
+/// ### Passthrough implementation
+///
+/// To construct a schema for a Type that acts as in "inner" type, such as for smart pointers, simply
+/// pass through the arguments to the inner type
+/// ```ignore
+/// impl AvroSchemaComponent for PassthroughType {
+///     fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+///        InnerType::get_schema_in_ctxt(named_schemas, enclosing_namespace)
+///    }
+///}
+/// ```
+///
+/// ### Complex implementation
+///
+/// To implement this for Named schema there is a general form needed to avoid creating invalid
+/// schemas or infinite loops.
+/// ```ignore
+/// impl AvroSchemaComponent for ComplexType {
+///     fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+///         // Create the fully qualified name for your type given the enclosing namespace
+///         let name =  apache_avro::schema::Name::new("MyName")
+///             .expect("Unable to parse schema name")
+///             .fully_qualified_name(enclosing_namespace);
+///         let enclosing_namespace = &name.namespace;
+///         // Check, if your name is already defined, and if so, return a ref to that name
+///         if named_schemas.contains_key(&name) {
+///             apache_avro::schema::Schema::Ref{name: name.clone()}
+///         } else {
+///             named_schemas.insert(name.clone(), apache_avro::schema::Schema::Ref{name: name.clone()});
+///             // YOUR SCHEMA DEFINITION HERE with the name equivalent to "MyName".
+///             // For non-simple sub types delegate to their implementation of AvroSchemaComponent
+///         }
+///    }
+///}
+/// ```
+pub trait AvroSchemaComponent {
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema;
+}
+
+impl<T> AvroSchema for T
+where
+    T: AvroSchemaComponent + ?Sized,
+{
+    fn get_schema() -> Schema {
+        T::get_schema_in_ctxt(&mut HashMap::default(), &None)
+    }
+}
+
+macro_rules! impl_schema (
+    ($type:ty, $variant_constructor:expr) => (
+        impl AvroSchemaComponent for $type {
+            fn get_schema_in_ctxt(_: &mut Names, _: &Namespace) -> Schema {
+                $variant_constructor
+            }
+        }
+    );
+);
+
+impl_schema!(bool, Schema::Boolean);
+impl_schema!(i8, Schema::Int);
+impl_schema!(i16, Schema::Int);
+impl_schema!(i32, Schema::Int);
+impl_schema!(i64, Schema::Long);
+impl_schema!(u8, Schema::Int);
+impl_schema!(u16, Schema::Int);
+impl_schema!(u32, Schema::Long);
+impl_schema!(f32, Schema::Float);
+impl_schema!(f64, Schema::Double);
+impl_schema!(String, Schema::String);
+impl_schema!(str, Schema::String);
+impl_schema!(char, Schema::String);
+
+impl<T> AvroSchemaComponent for &T
+where
+    T: AvroSchemaComponent + ?Sized,
+{
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
+    }
+}
+
+impl<T> AvroSchemaComponent for &mut T
+where
+    T: AvroSchemaComponent + ?Sized,
+{
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
+    }
+}
+
+impl<T> AvroSchemaComponent for [T]
+where
+    T: AvroSchemaComponent,
+{
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
+    }
+}
+
+impl<const N: usize, T> AvroSchemaComponent for [T; N]
+where
+    T: AvroSchemaComponent,
+{
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
+    }
+}
+
+impl<T> AvroSchemaComponent for Vec<T>
+where
+    T: AvroSchemaComponent,
+{
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
+    }
+}
+
+impl<T> AvroSchemaComponent for Option<T>
+where
+    T: AvroSchemaComponent,
+{
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        let variants = vec![
+            Schema::Null,
+            T::get_schema_in_ctxt(named_schemas, enclosing_namespace),
+        ];
+
+        Schema::Union(
+            UnionSchema::new(variants).expect("Option<T> must produce a valid (non-nested) union"),
+        )
+    }
+}
+
+impl<T> AvroSchemaComponent for Map<String, T>
+where
+    T: AvroSchemaComponent,
+{
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        Schema::map(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
+    }
+}
+
+impl<T> AvroSchemaComponent for HashMap<String, T>
+where
+    T: AvroSchemaComponent,
+{
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        Schema::map(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
+    }
+}
+
+impl<T> AvroSchemaComponent for Box<T>
+where
+    T: AvroSchemaComponent,
+{
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
+    }
+}
+
+impl<T> AvroSchemaComponent for std::sync::Mutex<T>
+where
+    T: AvroSchemaComponent,
+{
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
+    }
+}
+
+impl<T> AvroSchemaComponent for Cow<'_, T>
+where
+    T: AvroSchemaComponent + Clone,
+{
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
+    }
+}
+
+impl AvroSchemaComponent for core::time::Duration {
+    /// The schema is [`Schema::Duration`] with the name `duration`.
+    ///
+    /// This is a lossy conversion as this Avro type does not store the amount of nanoseconds.
+    #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        let name = Name::new("duration")
+            .expect("Name is valid")
+            .fully_qualified_name(enclosing_namespace);
+        if named_schemas.contains_key(&name) {
+            Schema::Ref { name }
+        } else {
+            let schema = Schema::Duration(FixedSchema {
+                name: name.clone(),
+                aliases: None,
+                doc: None,
+                size: 12,
+                default: None,
+                attributes: Default::default(),
+            });
+            named_schemas.insert(name, schema.clone());
+            schema
+        }
+    }
+}
+
+impl AvroSchemaComponent for uuid::Uuid {
+    /// The schema is [`Schema::Uuid`] with the name `uuid`.
+    ///
+    /// The underlying schema is [`Schema::Fixed`] with a size of 16.
+    #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        let name = Name::new("uuid")
+            .expect("Name is valid")
+            .fully_qualified_name(enclosing_namespace);
+        if named_schemas.contains_key(&name) {
+            Schema::Ref { name }
+        } else {
+            let schema = Schema::Uuid(UuidSchema::Fixed(FixedSchema {
+                name: name.clone(),
+                aliases: None,
+                doc: None,
+                size: 16,
+                default: None,
+                attributes: Default::default(),
+            }));
+            named_schemas.insert(name, schema.clone());
+            schema
+        }
+    }
+}
+
+impl AvroSchemaComponent for u64 {
+    /// The schema is [`Schema::Fixed`] of size 8 with the name `u64`.
+    #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        let name = Name::new("u64")
+            .expect("Name is valid")
+            .fully_qualified_name(enclosing_namespace);
+        if named_schemas.contains_key(&name) {
+            Schema::Ref { name }
+        } else {
+            let schema = Schema::Fixed(FixedSchema {
+                name: name.clone(),
+                aliases: None,
+                doc: None,
+                size: 8,
+                default: None,
+                attributes: Default::default(),
+            });
+            named_schemas.insert(name, schema.clone());
+            schema
+        }
+    }
+}
+
+impl AvroSchemaComponent for u128 {
+    /// The schema is [`Schema::Fixed`] of size 16 with the name `u128`.
+    #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        let name = Name::new("u128")
+            .expect("Name is valid")
+            .fully_qualified_name(enclosing_namespace);
+        if named_schemas.contains_key(&name) {
+            Schema::Ref { name }
+        } else {
+            let schema = Schema::Fixed(FixedSchema {
+                name: name.clone(),
+                aliases: None,
+                doc: None,
+                size: 16,
+                default: None,
+                attributes: Default::default(),
+            });
+            named_schemas.insert(name, schema.clone());
+            schema
+        }
+    }
+}
+
+impl AvroSchemaComponent for i128 {
+    /// The schema is [`Schema::Fixed`] of size 16 with the name `i128`.
+    #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
+    fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
+        let name = Name::new("i128")
+            .expect("Name is valid")
+            .fully_qualified_name(enclosing_namespace);
+        if named_schemas.contains_key(&name) {
+            Schema::Ref { name }
+        } else {
+            let schema = Schema::Fixed(FixedSchema {
+                name: name.clone(),
+                aliases: None,
+                doc: None,
+                size: 16,
+                default: None,
+                attributes: Default::default(),
+            });
+            named_schemas.insert(name, schema.clone());
+            schema
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::schema::{FixedSchema, Name};
+    use crate::{AvroSchema, Schema};
+    use apache_avro_test_helper::TestResult;
+
+    #[test]
+    fn avro_rs_401_str() -> TestResult {
+        let schema = str::get_schema();
+        assert_eq!(schema, Schema::String);
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_401_references() -> TestResult {
+        let schema_ref = <&str>::get_schema();
+        let schema_ref_mut = <&mut str>::get_schema();
+
+        assert_eq!(schema_ref, Schema::String);
+        assert_eq!(schema_ref_mut, Schema::String);
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_401_slice() -> TestResult {
+        let schema = <[u8]>::get_schema();
+        assert_eq!(schema, Schema::array(Schema::Int));
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_401_array() -> TestResult {
+        let schema = <[u8; 55]>::get_schema();
+        assert_eq!(schema, Schema::array(Schema::Int));
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_401_option_ref_slice_array() -> TestResult {
+        let schema = <Option<&[[u8; 55]]>>::get_schema();
+        assert_eq!(
+            schema,
+            Schema::union(vec![
+                Schema::Null,
+                Schema::array(Schema::array(Schema::Int))
+            ])?
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_414_char() -> TestResult {
+        let schema = char::get_schema();
+        assert_eq!(schema, Schema::String);
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_414_u64() -> TestResult {
+        let schema = u64::get_schema();
+        assert_eq!(
+            schema,
+            Schema::Fixed(FixedSchema {
+                name: Name::new("u64")?,
+                aliases: None,
+                doc: None,
+                size: 8,
+                default: None,
+                attributes: Default::default(),
+            })
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_414_i128() -> TestResult {
+        let schema = i128::get_schema();
+        assert_eq!(
+            schema,
+            Schema::Fixed(FixedSchema {
+                name: Name::new("i128")?,
+                aliases: None,
+                doc: None,
+                size: 16,
+                default: None,
+                attributes: Default::default(),
+            })
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_414_u128() -> TestResult {
+        let schema = u128::get_schema();
+        assert_eq!(
+            schema,
+            Schema::Fixed(FixedSchema {
+                name: Name::new("u128")?,
+                aliases: None,
+                doc: None,
+                size: 16,
+                default: None,
+                attributes: Default::default(),
+            })
+        );
+
+        Ok(())
+    }
+}
diff --git a/avro/src/serde/mod.rs b/avro/src/serde/mod.rs
index 01852a8..9c1dea4 100644
--- a/avro/src/serde/mod.rs
+++ b/avro/src/serde/mod.rs
@@ -16,11 +16,13 @@
 // under the License.
 
 mod de;
+mod derive;
 mod ser;
 pub(crate) mod ser_schema;
 mod util;
 mod with;
 
 pub use de::from_value;
+pub use derive::{AvroSchema, AvroSchemaComponent};
 pub use ser::to_value;
 pub use with::{bytes, bytes_opt, fixed, fixed_opt, slice, slice_opt};
diff --git a/avro/src/writer.rs b/avro/src/writer.rs
index 5573e25..4ec219f 100644
--- a/avro/src/writer.rs
+++ b/avro/src/writer.rs
@@ -21,8 +21,8 @@ use crate::{
     encode::{encode, encode_internal, encode_to_vec},
     error::Details,
     headers::{HeaderBuilder, RabinFingerprintHeader},
-    schema::{AvroSchema, Name, ResolvedOwnedSchema, ResolvedSchema, Schema},
-    serde::ser_schema::SchemaAwareWriteSerializer,
+    schema::{Name, ResolvedOwnedSchema, ResolvedSchema, Schema},
+    serde::{AvroSchema, ser_schema::SchemaAwareWriteSerializer},
     types::Value,
 };
 use serde::Serialize;
diff --git a/avro_derive/tests/derive.rs b/avro_derive/tests/derive.rs
index a9012ed..40c1a6a 100644
--- a/avro_derive/tests/derive.rs
+++ b/avro_derive/tests/derive.rs
@@ -17,10 +17,8 @@
 
 use apache_avro::{
     Reader, Schema, Writer, from_value,
-    schema::{
-        Alias, AvroSchema, AvroSchemaComponent, EnumSchema, FixedSchema, Name, Names, Namespace,
-        RecordSchema,
-    },
+    schema::{Alias, EnumSchema, FixedSchema, Name, Names, Namespace, RecordSchema},
+    serde::{AvroSchema, AvroSchemaComponent},
 };
 use apache_avro_derive::*;
 use proptest::prelude::*;