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

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


The following commit(s) were added to refs/heads/main by this push:
     new 283c4b1  feat(derive): allow overriding the schema for a field and 
change the schema for `uuid::Uuid` (#397)
283c4b1 is described below

commit 283c4b168fa6f4fd83adbc7a7257bf353b9ed20a
Author: Kriskras99 <[email protected]>
AuthorDate: Fri Jan 16 16:37:33 2026 +0100

    feat(derive): allow overriding the schema for a field and change the schema 
for `uuid::Uuid` (#397)
---
 Cargo.lock                                         |   1 +
 Cargo.toml                                         |   3 +-
 avro/Cargo.toml                                    |   2 +-
 avro/src/bytes.rs                                  | 123 ++++++++++++--
 avro/src/schema.rs                                 |  65 ++++++--
 avro_derive/Cargo.toml                             |   1 +
 avro_derive/src/attributes/avro.rs                 |  27 ++++
 avro_derive/src/attributes/mod.rs                  |  60 ++++++-
 avro_derive/src/attributes/serde.rs                |   3 +-
 avro_derive/src/lib.rs                             | 116 +++++++++-----
 avro_derive/tests/derive.rs                        | 178 ++++++++++++++++++++-
 avro_derive/tests/serde.rs                         |  70 ++++++++
 .../ui/avro_rs_397_with_closure_parameters.rs      |  27 ++++
 .../ui/avro_rs_397_with_closure_parameters.stderr  |  14 ++
 .../tests/ui/avro_rs_397_with_expr_string.rs       |  27 ++++
 .../tests/ui/avro_rs_397_with_expr_string.stderr   |   6 +
 avro_derive/tests/ui/avro_rs_397_with_expr_type.rs |  27 ++++
 .../tests/ui/avro_rs_397_with_expr_type.stderr     |   8 +
 .../ui/avro_rs_397_with_word_without_serde.rs      |  27 ++++
 .../ui/avro_rs_397_with_word_without_serde.stderr  |   6 +
 20 files changed, 709 insertions(+), 82 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index b219c04..80daff1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -105,6 +105,7 @@ dependencies = [
  "serde_json",
  "syn",
  "trybuild",
+ "uuid",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 0d8acc6..f5a1231 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -41,10 +41,11 @@ documentation = "https://docs.rs/apache-avro";
 # dependencies used by more than one members
 [workspace.dependencies]
 log = { default-features = false, version = "0.4.29" }
+pretty_assertions = { default-features = false, version = "1.4.1", features = 
["std"] }
 serde = { default-features = false, version = "1.0.228", features = ["std", 
"derive"] }
 serde_bytes = { default-features = false, version = "0.11.19", features = 
["std"] }
 serde_json = { default-features = false, version = "1.0.149", features = 
["std"] }
-pretty_assertions = { default-features = false, version = "1.4.1", features = 
["std"] }
+uuid = { default-features = false, version = "1.19.0", features = ["serde", 
"std"] }
 
 [profile.release.package.hello-wasm]
 # Tell `rustc` to optimize for small code size.
diff --git a/avro/Cargo.toml b/avro/Cargo.toml
index 7133ef7..8c50fd3 100644
--- a/avro/Cargo.toml
+++ b/avro/Cargo.toml
@@ -70,7 +70,7 @@ snap = { default-features = false, version = "1.1.0", 
optional = true }
 strum = { default-features = false, version = "0.27.2" }
 strum_macros = { default-features = false, version = "0.27.2" }
 thiserror = { default-features = false, version = "2.0.17" }
-uuid = { default-features = false, version = "1.19.0", features = ["serde", 
"std"] }
+uuid = { workspace = true }
 liblzma = { default-features = false, version = "0.4.5", optional = true }
 zstd = { default-features = false, version = "0.13.3", optional = true }
 
diff --git a/avro/src/bytes.rs b/avro/src/bytes.rs
index 8ac98b5..bac4e3f 100644
--- a/avro/src/bytes.rs
+++ b/avro/src/bytes.rs
@@ -44,14 +44,16 @@ pub(crate) enum BytesType {
 ///
 /// See usage with below example:
 /// ```rust
-/// use apache_avro::{serde_avro_bytes, serde_avro_fixed};
+/// use apache_avro::{AvroSchema, serde_avro_bytes, serde_avro_fixed};
 /// use serde::{Deserialize, Serialize};
 ///
-/// #[derive(Serialize, Deserialize)]
+/// #[derive(AvroSchema, Serialize, Deserialize)]
 /// struct StructWithBytes {
+///     #[avro(with)]
 ///     #[serde(with = "serde_avro_bytes")]
 ///     vec_field: Vec<u8>,
 ///
+///     #[avro(with = serde_avro_fixed::get_schema_in_ctxt::<6>)]
 ///     #[serde(with = "serde_avro_fixed")]
 ///     fixed_field: [u8; 6],
 /// }
@@ -59,6 +61,16 @@ pub(crate) enum BytesType {
 pub mod serde_avro_bytes {
     use serde::{Deserializer, Serializer};
 
+    use crate::{
+        Schema,
+        schema::{Names, Namespace},
+    };
+
+    /// Returns [`Schema::Bytes`]
+    pub fn get_schema_in_ctxt(_names: &mut Names, _enclosing_namespace: 
&Namespace) -> Schema {
+        Schema::Bytes
+    }
+
     pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
@@ -81,14 +93,16 @@ pub mod serde_avro_bytes {
 ///
 /// See usage with below example:
 /// ```rust
-/// use apache_avro::{serde_avro_bytes_opt, serde_avro_fixed_opt};
+/// use apache_avro::{AvroSchema, serde_avro_bytes_opt, serde_avro_fixed_opt};
 /// use serde::{Deserialize, Serialize};
 ///
-/// #[derive(Serialize, Deserialize)]
+/// #[derive(AvroSchema, Serialize, Deserialize)]
 /// struct StructWithBytes {
+///     #[avro(with)]
 ///     #[serde(with = "serde_avro_bytes_opt")]
 ///     vec_field: Option<Vec<u8>>,
 ///
+///     #[avro(with = serde_avro_fixed_opt::get_schema_in_ctxt::<6>)]
 ///     #[serde(with = "serde_avro_fixed_opt")]
 ///     fixed_field: Option<[u8; 6]>,
 /// }
@@ -97,6 +111,18 @@ pub mod serde_avro_bytes_opt {
     use serde::{Deserializer, Serializer};
     use std::borrow::Borrow;
 
+    use crate::{
+        Schema,
+        schema::{Names, Namespace, UnionSchema},
+    };
+
+    /// Returns `Schema::Union(Schema::Null, Schema::Bytes)`
+    pub fn get_schema_in_ctxt(_names: &mut Names, _enclosing_namespace: 
&Namespace) -> Schema {
+        Schema::Union(
+            UnionSchema::new(vec![Schema::Null, Schema::Bytes]).expect("This 
is a valid union"),
+        )
+    }
+
     pub fn serialize<S, B>(bytes: &Option<B>, serializer: S) -> Result<S::Ok, 
S::Error>
     where
         S: Serializer,
@@ -120,14 +146,16 @@ pub mod serde_avro_bytes_opt {
 ///
 /// See usage with below example:
 /// ```rust
-/// use apache_avro::{serde_avro_bytes, serde_avro_fixed};
+/// use apache_avro::{AvroSchema, serde_avro_bytes, serde_avro_fixed};
 /// use serde::{Deserialize, Serialize};
 ///
-/// #[derive(Serialize, Deserialize)]
+/// #[derive(AvroSchema, Serialize, Deserialize)]
 /// struct StructWithBytes {
+///     #[avro(with)]
 ///     #[serde(with = "serde_avro_bytes")]
 ///     vec_field: Vec<u8>,
 ///
+///     #[avro(with = serde_avro_fixed::get_schema_in_ctxt::<6>)]
 ///     #[serde(with = "serde_avro_fixed")]
 ///     fixed_field: [u8; 6],
 /// }
@@ -136,6 +164,29 @@ pub mod serde_avro_fixed {
     use super::{BytesType, SER_BYTES_TYPE};
     use serde::{Deserializer, Serializer};
 
+    use crate::{
+        Schema,
+        schema::{FixedSchema, Name, Names, Namespace},
+    };
+
+    /// Returns `Schema::Fixed(N)` named `serde_avro_fixed_{N}`
+    #[expect(clippy::map_entry, reason = "We don't use the value from the 
map")]
+    pub fn get_schema_in_ctxt<const N: usize>(
+        named_schemas: &mut Names,
+        enclosing_namespace: &Namespace,
+    ) -> Schema {
+        let name = Name::new(&format!("serde_avro_fixed_{N}"))
+            .expect("Name is valid")
+            .fully_qualified_name(enclosing_namespace);
+        if named_schemas.contains_key(&name) {
+            Schema::Ref { name }
+        } else {
+            let schema = 
Schema::Fixed(FixedSchema::builder().name(name.clone()).size(N).build());
+            named_schemas.insert(name, schema.clone());
+            schema
+        }
+    }
+
     pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
@@ -161,14 +212,16 @@ pub mod serde_avro_fixed {
 ///
 /// See usage with below example:
 /// ```rust
-/// use apache_avro::{serde_avro_bytes_opt, serde_avro_fixed_opt};
+/// use apache_avro::{AvroSchema, serde_avro_bytes_opt, serde_avro_fixed_opt};
 /// use serde::{Deserialize, Serialize};
 ///
-/// #[derive(Serialize, Deserialize)]
+/// #[derive(AvroSchema, Serialize, Deserialize)]
 /// struct StructWithBytes {
+///     #[avro(with)]
 ///     #[serde(with = "serde_avro_bytes_opt")]
 ///     vec_field: Option<Vec<u8>>,
 ///
+///     #[avro(with = serde_avro_fixed_opt::get_schema_in_ctxt::<6>)]
 ///     #[serde(with = "serde_avro_fixed_opt")]
 ///     fixed_field: Option<[u8; 6]>,
 /// }
@@ -178,6 +231,28 @@ pub mod serde_avro_fixed_opt {
     use serde::{Deserializer, Serializer};
     use std::borrow::Borrow;
 
+    use crate::{
+        Schema,
+        schema::{Names, Namespace, UnionSchema},
+    };
+
+    /// Returns `Schema::Union(Schema::Null, Schema::Fixed(N))` where the 
fixed schema is named `serde_avro_fixed_{N}`
+    pub fn get_schema_in_ctxt<const N: usize>(
+        named_schemas: &mut Names,
+        enclosing_namespace: &Namespace,
+    ) -> Schema {
+        Schema::Union(
+            UnionSchema::new(vec![
+                Schema::Null,
+                super::serde_avro_fixed::get_schema_in_ctxt::<N>(
+                    named_schemas,
+                    enclosing_namespace,
+                ),
+            ])
+            .expect("This is a valid union"),
+        )
+    }
+
     pub fn serialize<S, B>(bytes: &Option<B>, serializer: S) -> Result<S::Ok, 
S::Error>
     where
         S: Serializer,
@@ -209,11 +284,12 @@ pub mod serde_avro_fixed_opt {
 ///
 /// See usage with below example:
 /// ```rust
-/// use apache_avro::serde_avro_slice;
+/// use apache_avro::{AvroSchema, serde_avro_slice};
 /// use serde::{Deserialize, Serialize};
 ///
-/// #[derive(Serialize, Deserialize)]
+/// #[derive(AvroSchema, Serialize, Deserialize)]
 /// struct StructWithBytes<'a> {
+///     #[avro(with)]
 ///     #[serde(with = "serde_avro_slice")]
 ///     slice_field: &'a [u8],
 /// }
@@ -222,6 +298,16 @@ pub mod serde_avro_slice {
     use super::DE_BYTES_BORROWED;
     use serde::{Deserializer, Serializer};
 
+    use crate::{
+        Schema,
+        schema::{Names, Namespace},
+    };
+
+    /// Returns [`Schema::Bytes`]
+    pub fn get_schema_in_ctxt(_names: &mut Names, _enclosing_namespace: 
&Namespace) -> Schema {
+        Schema::Bytes
+    }
+
     pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
@@ -252,11 +338,12 @@ pub mod serde_avro_slice {
 ///
 /// See usage with below example:
 /// ```rust
-/// use apache_avro::serde_avro_slice_opt;
+/// use apache_avro::{AvroSchema, serde_avro_slice_opt};
 /// use serde::{Deserialize, Serialize};
 ///
-/// #[derive(Serialize, Deserialize)]
+/// #[derive(AvroSchema, Serialize, Deserialize)]
 /// struct StructWithBytes<'a> {
+///     #[avro(with)]
 ///     #[serde(with = "serde_avro_slice_opt")]
 ///     slice_field: Option<&'a [u8]>,
 /// }
@@ -266,6 +353,18 @@ pub mod serde_avro_slice_opt {
     use serde::{Deserializer, Serializer};
     use std::borrow::Borrow;
 
+    use crate::{
+        Schema,
+        schema::{Names, Namespace, UnionSchema},
+    };
+
+    /// Returns `Schema::Union(Schema::Null, Schema::Bytes)`
+    pub fn get_schema_in_ctxt(_names: &mut Names, _enclosing_namespace: 
&Namespace) -> Schema {
+        Schema::Union(
+            UnionSchema::new(vec![Schema::Null, Schema::Bytes]).expect("This 
is a valid union"),
+        )
+    }
+
     pub fn serialize<S, B>(bytes: &Option<B>, serializer: S) -> Result<S::Ok, 
S::Error>
     where
         S: Serializer,
diff --git a/avro/src/schema.rs b/avro/src/schema.rs
index ce75832..e56eab8 100644
--- a/avro/src/schema.rs
+++ b/avro/src/schema.rs
@@ -592,7 +592,8 @@ pub(crate) fn resolve_names(
         | Schema::Decimal(DecimalSchema {
             inner: InnerDecimalSchema::Fixed(FixedSchema { name, .. }),
             ..
-        }) => {
+        })
+        | Schema::Duration(FixedSchema { name, .. }) => {
             let fully_qualified_name = 
name.fully_qualified_name(enclosing_namespace);
             if names
                 .insert(fully_qualified_name.clone(), schema.clone())
@@ -2000,13 +2001,13 @@ impl Parser {
 
         let symbols: Vec<String> = symbols_opt
             .and_then(|v| v.as_array())
-            .ok_or(Details::GetEnumSymbolsField)
+            .ok_or_else(|| Error::from(Details::GetEnumSymbolsField))
             .and_then(|symbols| {
                 symbols
                     .iter()
                     .map(|symbol| symbol.as_str().map(|s| s.to_string()))
                     .collect::<Option<_>>()
-                    .ok_or(Details::GetEnumSymbols)
+                    .ok_or_else(|| Error::from(Details::GetEnumSymbols))
             })?;
 
         let mut existing_symbols: HashSet<&String> = 
HashSet::with_capacity(symbols.len());
@@ -2611,6 +2612,7 @@ pub trait AvroSchema {
 ///    }
 ///}
 /// ```
+///
 /// ### Passthrough implementation
 ///
 /// To construct a schema for a Type that acts as in "inner" type, such as for 
smart pointers, simply
@@ -2622,7 +2624,9 @@ pub trait AvroSchema {
 ///    }
 ///}
 /// ```
-///### Complex implementation
+///
+/// ### Complex implementation
+///
 /// To implement this for Named schema there is a general form needed to avoid 
creating invalid
 /// schemas or infinite loops.
 /// ```ignore
@@ -2679,7 +2683,6 @@ impl_schema!(f32, Schema::Float);
 impl_schema!(f64, Schema::Double);
 impl_schema!(String, Schema::String);
 impl_schema!(str, Schema::String);
-impl_schema!(uuid::Uuid, Schema::Uuid(UuidSchema::String));
 
 impl<T> AvroSchemaComponent for &T
 where
@@ -2788,22 +2791,54 @@ where
 }
 
 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 {
-            name: "duration".to_string(),
-            namespace: enclosing_namespace.clone(),
-        };
-        named_schemas
-            .entry(name.clone())
-            .or_insert(Schema::Duration(FixedSchema {
-                name,
+        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(),
-            }))
-            .clone()
+            });
+            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
+        }
     }
 }
 
diff --git a/avro_derive/Cargo.toml b/avro_derive/Cargo.toml
index 6c01ed7..4f6f835 100644
--- a/avro_derive/Cargo.toml
+++ b/avro_derive/Cargo.toml
@@ -37,6 +37,7 @@ proc-macro2 = { default-features = false, version = "1.0.105" 
}
 quote = { default-features = false, version = "1.0.43" }
 serde_json = { workspace = true }
 syn = { default-features = false, version = "2.0.114", features = ["full", 
"fold"] }
+uuid = { workspace = true }
 
 [dev-dependencies]
 apache-avro = { default-features = false, path = "../avro", features = 
["derive"] }
diff --git a/avro_derive/src/attributes/avro.rs 
b/avro_derive/src/attributes/avro.rs
index ceeafe5..ea171b5 100644
--- a/avro_derive/src/attributes/avro.rs
+++ b/avro_derive/src/attributes/avro.rs
@@ -22,7 +22,9 @@
 //! a user can use. These add extra metadata to the generated schema.
 
 use crate::case::RenameRule;
+use darling::FromMeta;
 use proc_macro2::Span;
+use syn::Expr;
 
 /// All the Avro attributes a container can have.
 #[derive(darling::FromAttributes)]
@@ -97,6 +99,21 @@ impl VariantAttributes {
     }
 }
 
+/// How to get the schema for a field.
+#[derive(Debug, FromMeta, PartialEq, Default)]
+#[darling(from_expr = |expr| Ok(With::Expr(expr.clone())))]
+pub enum With {
+    /// Use `<T as AvroSchemaComponent>::get_schema_in_ctxt`.
+    #[default]
+    #[darling(skip)]
+    Trait,
+    /// Use `module::get_schema_in_ctxt` where the module is defined by 
Serde's `with` attribute.
+    #[darling(word, skip)]
+    Serde,
+    /// Call the function in this expression.
+    Expr(Expr),
+}
+
 /// All the Avro attributes a field can have.
 #[derive(darling::FromAttributes)]
 #[darling(attributes(avro))]
@@ -137,6 +154,16 @@ pub struct FieldAttributes {
     /// [`serde::FieldAttributes::flatten`]: 
crate::attributes::serde::FieldAttributes::flatten
     #[darling(default)]
     pub flatten: bool,
+    /// How to get the schema for a field.
+    ///
+    /// By default uses `<T as AvroSchemaComponent>::get_schema_in_ctxt`.
+    ///
+    /// When it's provided without an argument (`#[avro(with)]`), it will use 
the function `get_schema_in_ctxt` defined
+    /// in the same module as the `#[serde(with = "some_module")]` attribute.
+    ///
+    /// When it's provided with an argument (`#[avro(with = some_fn)]`), it 
will use that function.
+    #[darling(default)]
+    pub with: With,
 }
 
 impl FieldAttributes {
diff --git a/avro_derive/src/attributes/mod.rs 
b/avro_derive/src/attributes/mod.rs
index 6f26a62..5657179 100644
--- a/avro_derive/src/attributes/mod.rs
+++ b/avro_derive/src/attributes/mod.rs
@@ -16,12 +16,12 @@
 // under the License.
 
 use crate::case::RenameRule;
-use darling::FromAttributes;
+use darling::{FromAttributes, FromMeta};
 use proc_macro2::Span;
-use syn::{Attribute, spanned::Spanned};
+use syn::{Attribute, Expr, Path, spanned::Spanned};
 
-pub mod avro;
-pub mod serde;
+mod avro;
+mod serde;
 
 #[derive(Default)]
 pub struct NamedTypeOptions {
@@ -128,6 +128,7 @@ impl VariantOptions {
             ));
         }
 
+        // Check for conflicts between Serde and Avro
         if avro.rename.is_some() && serde.rename != avro.rename {
             errors.push(syn::Error::new(
                 span,
@@ -145,6 +146,47 @@ impl VariantOptions {
     }
 }
 
+/// How to get the schema for this field or variant.
+#[derive(Debug, PartialEq, Default)]
+pub enum With {
+    /// Use `<T as AvroSchemaComponent>::get_schema_in_ctxt`.
+    #[default]
+    Trait,
+    /// Use `module::get_schema_in_ctxt` where the module is defined by 
Serde's `with` attribute.
+    Serde(Path),
+    /// Call the function in this expression.
+    Expr(Expr),
+}
+
+impl With {
+    fn from_avro_and_serde(
+        avro: &avro::With,
+        serde: &Option<String>,
+        span: Span,
+    ) -> Result<Self, syn::Error> {
+        match &avro {
+            avro::With::Trait => Ok(Self::Trait),
+            avro::With::Serde => {
+                if let Some(serde) = serde {
+                    let path = Path::from_string(serde).map_err(|err| {
+                        syn::Error::new(
+                            span,
+                            format!("Expected a path for `#[serde(with = 
\"..\")]`: {err:?}"),
+                        )
+                    })?;
+                    Ok(Self::Serde(path))
+                } else {
+                    Err(syn::Error::new(
+                        span,
+                        "`#[avro(with)]` requires `#[serde(with = 
\"some_module\")]` or provide a function to call `#[avro(with = some_fn)]`",
+                    ))
+                }
+            }
+            avro::With::Expr(expr) => Ok(Self::Expr(expr.clone())),
+        }
+    }
+}
+
 pub struct FieldOptions {
     pub doc: Option<String>,
     pub default: Option<String>,
@@ -152,6 +194,7 @@ pub struct FieldOptions {
     pub rename: Option<String>,
     pub skip: bool,
     pub flatten: bool,
+    pub with: With,
 }
 
 impl FieldOptions {
@@ -213,6 +256,14 @@ impl FieldOptions {
                 "`#[serde(skip_serializing)]` and 
`#[serde(skip_serializing_if)]` require `#[avro(default = \"..\")]`"
             ));
         }
+        let with = match With::from_avro_and_serde(&avro.with, &serde.with, 
span) {
+            Ok(with) => with,
+            Err(error) => {
+                errors.push(error);
+                // This won't actually be used, but it does simplify the code
+                With::Trait
+            }
+        };
 
         if !errors.is_empty() {
             return Err(errors);
@@ -225,6 +276,7 @@ impl FieldOptions {
             rename: serde.rename,
             skip: serde.skip || (serde.skip_serializing && 
serde.skip_deserializing),
             flatten: serde.flatten,
+            with,
         })
     }
 }
diff --git a/avro_derive/src/attributes/serde.rs 
b/avro_derive/src/attributes/serde.rs
index 9f23f83..b1ca6fa 100644
--- a/avro_derive/src/attributes/serde.rs
+++ b/avro_derive/src/attributes/serde.rs
@@ -238,8 +238,7 @@ pub struct FieldAttributes {
     #[darling(rename = "deserialize_with")]
     pub _deserialize_with: Option<String>,
     /// Use this module for (de)serializing.
-    #[darling(rename = "with")]
-    pub _with: Option<String>,
+    pub with: Option<String>,
     /// Put bounds on the lifetimes.
     #[darling(rename = "borrow")]
     pub _borrow: Option<SerdeBorrow>,
diff --git a/avro_derive/src/lib.rs b/avro_derive/src/lib.rs
index 9d9490f..b976756 100644
--- a/avro_derive/src/lib.rs
+++ b/avro_derive/src/lib.rs
@@ -23,11 +23,11 @@ mod case;
 use proc_macro2::{Span, TokenStream};
 use quote::quote;
 use syn::{
-    AttrStyle, Attribute, DeriveInput, Ident, Meta, Type, parse_macro_input, 
spanned::Spanned,
+    AttrStyle, Attribute, DeriveInput, Expr, Ident, Meta, Type, 
parse_macro_input, spanned::Spanned,
 };
 
 use crate::{
-    attributes::{FieldOptions, NamedTypeOptions, VariantOptions},
+    attributes::{FieldOptions, NamedTypeOptions, VariantOptions, With},
     case::RenameRule,
 };
 
@@ -85,13 +85,17 @@ fn derive_avro_schema(input: &mut DeriveInput) -> 
Result<TokenStream, Vec<syn::E
         #[automatically_derived]
         impl #impl_generics apache_avro::AvroSchemaComponent for #ident 
#ty_generics #where_clause {
             fn get_schema_in_ctxt(named_schemas: &mut 
std::collections::HashMap<apache_avro::schema::Name, 
apache_avro::schema::Schema>, enclosing_namespace: &Option<String>) -> 
apache_avro::schema::Schema {
-                let name =  
apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to 
parse schema name {}", 
#full_schema_name)[..]).fully_qualified_name(enclosing_namespace);
-                let enclosing_namespace = &name.namespace;
+                let name = 
apache_avro::schema::Name::new(#full_schema_name).expect(concat!("Unable to 
parse schema name ", 
#full_schema_name)).fully_qualified_name(enclosing_namespace);
                 if named_schemas.contains_key(&name) {
-                    apache_avro::schema::Schema::Ref{name: name.clone()}
+                    apache_avro::schema::Schema::Ref{name}
                 } else {
+                    let enclosing_namespace = &name.namespace;
+                    // This is needed because otherwise recursive types will 
recurse forever and cause a stack overflow
+                    // TODO: Breaking change to AvroSchemaComponent, have 
named_schemas be a set instead
                     named_schemas.insert(name.clone(), 
apache_avro::schema::Schema::Ref{name: name.clone()});
-                    #schema_def
+                    let schema = #schema_def;
+                    named_schemas.insert(name, schema.clone());
+                    schema
                 }
             }
         }
@@ -166,7 +170,31 @@ fn get_data_struct_schema_def(
                     None => quote! { None },
                 };
                 let aliases = preserve_vec(field_attrs.alias);
-                let schema_expr = type_to_schema_expr(&field.ty)?;
+                let schema_expr = match field_attrs.with {
+                    With::Trait => type_to_schema_expr(&field.ty)?,
+                    With::Serde(path) => {
+                        quote! { #path::get_schema_in_ctxt(named_schemas, 
enclosing_namespace) }
+                    }
+                    With::Expr(Expr::Closure(closure)) => {
+                        if closure.inputs.is_empty() {
+                            quote! { (#closure)() }
+                        } else {
+                            return Err(vec![syn::Error::new(
+                                field.span(),
+                                "Expected closure with 0 parameters",
+                            )]);
+                        }
+                    }
+                    With::Expr(Expr::Path(path)) => {
+                        quote! { #path(named_schemas, enclosing_namespace) }
+                    }
+                    With::Expr(_expr) => {
+                        return Err(vec![syn::Error::new(
+                            field.span(),
+                            "Invalid expression, expected function or closure",
+                        )]);
+                    }
+                };
                 record_field_exprs.push(quote! {
                     schema_fields.push(::apache_avro::schema::RecordField {
                         name: #name.to_string(),
@@ -200,23 +228,25 @@ fn get_data_struct_schema_def(
     // the most common case where there is no flatten.
     let minimum_fields = record_field_exprs.len();
     Ok(quote! {
-        let mut schema_fields = Vec::with_capacity(#minimum_fields);
-        #(#record_field_exprs)*
-        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()
-            .map(|field| (field.name.to_owned(), field.position))
-            .collect();
-        apache_avro::schema::Schema::Record(apache_avro::schema::RecordSchema {
-            name,
-            aliases: #record_aliases,
-            doc: #record_doc,
-            fields: schema_fields,
-            lookup,
-            attributes: Default::default(),
-        })
+        {
+            let mut schema_fields = Vec::with_capacity(#minimum_fields);
+            #(#record_field_exprs)*
+            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()
+                .map(|field| (field.name.to_owned(), field.position))
+                .collect();
+            
apache_avro::schema::Schema::Record(apache_avro::schema::RecordSchema {
+                name,
+                aliases: #record_aliases,
+                doc: #record_doc,
+                fields: schema_fields,
+                lookup,
+                attributes: Default::default(),
+            })
+        }
     })
 }
 
@@ -362,6 +392,8 @@ fn preserve_vec(op: Vec<impl quote::ToTokens>) -> 
TokenStream {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use pretty_assertions::assert_eq;
+
     #[test]
     fn basic_case() {
         let test_struct = quote! {
@@ -476,16 +508,14 @@ mod tests {
                             enclosing_namespace: &Option<String>
                         ) -> apache_avro::schema::Schema {
                             let name = apache_avro::schema::Name::new("Basic")
-                                .expect(&format!("Unable to parse schema name 
{}", "Basic")[..])
+                                .expect(concat!("Unable to parse schema name 
", "Basic"))
                                 .fully_qualified_name(enclosing_namespace);
-                            let enclosing_namespace = &name.namespace;
                             if named_schemas.contains_key(&name) {
-                                apache_avro::schema::Schema::Ref { name: 
name.clone() }
+                                apache_avro::schema::Schema::Ref { name }
                             } else {
-                                named_schemas.insert(
-                                    name.clone(),
-                                    apache_avro::schema::Schema::Ref { name: 
name.clone() }
-                                );
+                                let enclosing_namespace = &name.namespace;
+                                named_schemas.insert(name.clone(), 
apache_avro::schema::Schema::Ref{name: name.clone()});
+                                let schema =
                                 
apache_avro::schema::Schema::Enum(apache_avro::schema::EnumSchema {
                                     name: 
apache_avro::schema::Name::new("Basic").expect(
                                         &format!("Unable to parse enum name 
for schema {}", "Basic")[..]
@@ -500,7 +530,9 @@ mod tests {
                                     ],
                                     default: Some("A".into()),
                                     attributes: Default::default(),
-                                })
+                                });
+                                named_schemas.insert(name, schema.clone());
+                                schema
                             }
                         }
                     }
@@ -629,9 +661,9 @@ mod tests {
         match syn::parse2::<DeriveInput>(test_struct) {
             Ok(mut input) => {
                 let schema_res = derive_avro_schema(&mut input);
-                let expected_token_stream = r#"let mut schema_fields = Vec :: 
with_capacity (1usize) ; schema_fields . push (:: apache_avro :: schema :: 
RecordField { name : "a3" . to_string () , doc : Some ("a doc" . into ()) , 
default : Some (serde_json :: from_str ("123") . expect (format ! ("Invalid 
JSON: {:?}" , "123") . as_str ())) , aliases : Some (vec ! ["a1" . into () , 
"a2" . into ()]) , schema : < i32 as apache_avro :: AvroSchemaComponent > :: 
get_schema_in_ctxt (named_schemas [...]
+                let expected_token_stream = r#"# [automatically_derived] impl 
apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas 
: & mut std :: collections :: HashMap < apache_avro :: schema :: Name , 
apache_avro :: schema :: Schema > , enclosing_namespace : & Option < String >) 
-> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name 
:: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) . 
fully_qualified_name (enclosing [...]
                 let schema_token_stream = schema_res.unwrap().to_string();
-                assert!(schema_token_stream.contains(expected_token_stream));
+                assert_eq!(schema_token_stream, expected_token_stream);
             }
             Err(error) => panic!(
                 "Failed to parse as derive input when it should be able to. 
Error: {error:?}"
@@ -648,9 +680,9 @@ mod tests {
         match syn::parse2::<DeriveInput>(test_enum) {
             Ok(mut input) => {
                 let schema_res = derive_avro_schema(&mut input);
-                let expected_token_stream = r#"let name = apache_avro :: 
schema :: Name :: new ("A") . expect (& format ! ("Unable to parse schema name 
{}" , "A") [..]) . fully_qualified_name (enclosing_namespace) ; let 
enclosing_namespace = & name . namespace ; 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 () }) ;  [...]
+                let expected_token_stream = r#"# [automatically_derived] impl 
apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas 
: & mut std :: collections :: HashMap < apache_avro :: schema :: Name , 
apache_avro :: schema :: Schema > , enclosing_namespace : & Option < String >) 
-> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name 
:: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) . 
fully_qualified_name (enclosing [...]
                 let schema_token_stream = schema_res.unwrap().to_string();
-                assert!(schema_token_stream.contains(expected_token_stream));
+                assert_eq!(schema_token_stream, expected_token_stream);
             }
             Err(error) => panic!(
                 "Failed to parse as derive input when it should be able to. 
Error: {error:?}"
@@ -671,9 +703,9 @@ mod tests {
         match syn::parse2::<DeriveInput>(test_struct) {
             Ok(mut input) => {
                 let schema_res = derive_avro_schema(&mut input);
-                let expected_token_stream = r#"let name = apache_avro :: 
schema :: Name :: new ("A") . expect (& format ! ("Unable to parse schema name 
{}" , "A") [..]) . fully_qualified_name (enclosing_namespace) ; let 
enclosing_namespace = & name . namespace ; 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 () }) ;  [...]
+                let expected_token_stream = r#"# [automatically_derived] impl 
apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas 
: & mut std :: collections :: HashMap < apache_avro :: schema :: Name , 
apache_avro :: schema :: Schema > , enclosing_namespace : & Option < String >) 
-> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name 
:: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) . 
fully_qualified_name (enclosing [...]
                 let schema_token_stream = schema_res.unwrap().to_string();
-                assert!(schema_token_stream.contains(expected_token_stream));
+                assert_eq!(schema_token_stream, expected_token_stream);
             }
             Err(error) => panic!(
                 "Failed to parse as derive input when it should be able to. 
Error: {error:?}"
@@ -691,9 +723,9 @@ mod tests {
         match syn::parse2::<DeriveInput>(test_enum) {
             Ok(mut input) => {
                 let schema_res = derive_avro_schema(&mut input);
-                let expected_token_stream = r#"let name = apache_avro :: 
schema :: Name :: new ("B") . expect (& format ! ("Unable to parse schema name 
{}" , "B") [..]) . fully_qualified_name (enclosing_namespace) ; let 
enclosing_namespace = & name . namespace ; 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 () }) ;  [...]
+                let expected_token_stream = r#"# [automatically_derived] impl 
apache_avro :: AvroSchemaComponent for B { fn get_schema_in_ctxt (named_schemas 
: & mut std :: collections :: HashMap < apache_avro :: schema :: Name , 
apache_avro :: schema :: Schema > , enclosing_namespace : & Option < String >) 
-> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name 
:: new ("B") . expect (concat ! ("Unable to parse schema name " , "B")) . 
fully_qualified_name (enclosing [...]
                 let schema_token_stream = schema_res.unwrap().to_string();
-                assert!(schema_token_stream.contains(expected_token_stream));
+                assert_eq!(schema_token_stream, expected_token_stream);
             }
             Err(error) => panic!(
                 "Failed to parse as derive input when it should be able to. 
Error: {error:?}"
@@ -715,9 +747,9 @@ mod tests {
         match syn::parse2::<DeriveInput>(test_struct) {
             Ok(mut input) => {
                 let schema_res = derive_avro_schema(&mut input);
-                let expected_token_stream = r#"let name = apache_avro :: 
schema :: Name :: new ("A") . expect (& format ! ("Unable to parse schema name 
{}" , "A") [..]) . fully_qualified_name (enclosing_namespace) ; let 
enclosing_namespace = & name . namespace ; 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 () }) ;  [...]
+                let expected_token_stream = r#"# [automatically_derived] impl 
apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas 
: & mut std :: collections :: HashMap < apache_avro :: schema :: Name , 
apache_avro :: schema :: Schema > , enclosing_namespace : & Option < String >) 
-> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name 
:: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) . 
fully_qualified_name (enclosing [...]
                 let schema_token_stream = schema_res.unwrap().to_string();
-                assert!(schema_token_stream.contains(expected_token_stream));
+                assert_eq!(schema_token_stream, expected_token_stream);
             }
             Err(error) => panic!(
                 "Failed to parse as derive input when it should be able to. 
Error: {error:?}"
diff --git a/avro_derive/tests/derive.rs b/avro_derive/tests/derive.rs
index 0d4df09..758774e 100644
--- a/avro_derive/tests/derive.rs
+++ b/avro_derive/tests/derive.rs
@@ -17,15 +17,15 @@
 
 use apache_avro::{
     Reader, Schema, Writer, from_value,
-    schema::{AvroSchema, AvroSchemaComponent},
+    schema::{
+        Alias, AvroSchema, AvroSchemaComponent, EnumSchema, FixedSchema, Name, 
Names, Namespace,
+        RecordSchema,
+    },
 };
 use apache_avro_derive::*;
 use proptest::prelude::*;
 use serde::{Deserialize, Serialize, de::DeserializeOwned};
-use std::collections::HashMap;
-
-use apache_avro::schema::{Alias, EnumSchema, RecordSchema};
-use std::{borrow::Cow, sync::Mutex};
+use std::{borrow::Cow, collections::HashMap, sync::Mutex};
 
 use pretty_assertions::assert_eq;
 
@@ -1828,6 +1828,174 @@ fn avro_rs_247_serde_flatten_support_with_skip() {
     });
 }
 
+#[test]
+fn avro_rs_397_with() {
+    let schema = Schema::parse_str(
+        r#"
+    {
+        "type":"record",
+        "name":"Foo",
+        "fields": [
+            {
+                "name":"a",
+                "type":"bytes"
+            },
+            {
+                "name":"b",
+                "type":"long"
+            },
+            {
+                "name":"c",
+                "type":"bytes"
+            }
+        ]
+    }
+    "#,
+    )
+    .unwrap();
+
+    fn long_schema(_named_schemas: &mut Names, _enclosing_namespace: 
&Namespace) -> Schema {
+        Schema::Long
+    }
+
+    mod module {
+        use super::*;
+        pub fn get_schema_in_ctxt(
+            _named_schemas: &mut Names,
+            _enclosing_namespace: &Namespace,
+        ) -> Schema {
+            Schema::Bytes
+        }
+    }
+
+    #[allow(dead_code)]
+    #[derive(AvroSchema)]
+    struct Foo {
+        #[avro(with)]
+        #[serde(with = "module")]
+        a: String,
+        #[avro(with = long_schema)]
+        b: i32,
+        #[avro(with = module::get_schema_in_ctxt)]
+        c: String,
+    }
+
+    assert_eq!(schema, Foo::get_schema());
+}
+
+#[test]
+fn avro_rs_397_with_generic() {
+    let schema = Schema::parse_str(
+        r#"
+    {
+        "type":"record",
+        "name":"Foo",
+        "fields": [
+            {
+                "name":"a",
+                "type": {
+                    "type": "fixed",
+                    "size": 15,
+                    "name": "fixed_15"
+                }
+            }
+        ]
+    }
+    "#,
+    )
+    .unwrap();
+
+    fn generic<const N: usize>(
+        _named_schemas: &mut Names,
+        _enclosing_namespace: &Namespace,
+    ) -> Schema {
+        Schema::Fixed(FixedSchema {
+            name: Name::new(&format!("fixed_{N}")).unwrap(),
+            aliases: None,
+            doc: None,
+            size: N,
+            default: None,
+            attributes: Default::default(),
+        })
+    }
+
+    #[allow(dead_code)]
+    #[derive(AvroSchema)]
+    struct Foo {
+        #[avro(with = generic::<15>)]
+        a: [u8; 15],
+    }
+
+    assert_eq!(schema, Foo::get_schema());
+}
+
+#[test]
+fn avro_rs_397_uuid() {
+    let schema = Schema::parse_str(
+        r#"
+    {
+        "type":"record",
+        "name":"Foo",
+        "fields": [
+            {
+                "name":"baz",
+                "type":{
+                    "type":"fixed",
+                    "logicalType":"uuid",
+                    "name":"uuid",
+                    "size":16
+                }
+            }
+        ]
+    }
+    "#,
+    )
+    .unwrap();
+
+    #[derive(AvroSchema, Debug, Clone, PartialEq, Serialize, Deserialize)]
+    struct Foo {
+        #[serde(rename = "baz")]
+        bar: uuid::Uuid,
+    }
+
+    assert_eq!(schema, Foo::get_schema());
+    serde_assert(Foo {
+        bar: uuid::Uuid::nil(),
+    });
+}
+
+#[test]
+fn avro_rs_397_derive_with_expr_lambda() {
+    let schema = r#"
+  {
+    "type":"record",
+    "name":"Foo",
+    "fields": [
+      {
+        "name": "_a",
+        "type": "bytes"
+      },
+      {
+        "name": "_b",
+        "type": "int"
+      }
+    ]
+  }"#;
+
+    let expected_schema = Schema::parse_str(schema).unwrap();
+
+    #[derive(AvroSchema)]
+    struct Foo {
+        #[avro(with = || Schema::Bytes)]
+        _a: String,
+        _b: i32,
+    }
+
+    let derived_schema = Foo::get_schema();
+
+    assert_eq!(expected_schema, derived_schema);
+}
+
 #[test]
 fn avro_rs_401_do_not_match_typename() {
     #[expect(nonstandard_style, reason = "It needs to be exactly this")]
diff --git a/avro_derive/tests/serde.rs b/avro_derive/tests/serde.rs
index f42e97b..5283b5e 100644
--- a/avro_derive/tests/serde.rs
+++ b/avro_derive/tests/serde.rs
@@ -504,4 +504,74 @@ mod field_attributes {
             b: 321,
         });
     }
+
+    #[test]
+    fn avro_rs_397_avroschema_with_bytes() {
+        use apache_avro::{
+            serde_avro_bytes, serde_avro_bytes_opt, serde_avro_fixed, 
serde_avro_fixed_opt,
+            serde_avro_slice, serde_avro_slice_opt,
+        };
+
+        #[expect(dead_code, reason = "We only care about the schema")]
+        #[derive(AvroSchema)]
+        struct TestStructWithBytes<'a> {
+            #[avro(with)]
+            #[serde(with = "serde_avro_bytes")]
+            vec_field: Vec<u8>,
+            #[avro(with)]
+            #[serde(with = "serde_avro_bytes_opt")]
+            vec_field_opt: Option<Vec<u8>>,
+
+            #[avro(with = serde_avro_fixed::get_schema_in_ctxt::<6>)]
+            #[serde(with = "serde_avro_fixed")]
+            fixed_field: [u8; 6],
+            #[avro(with = serde_avro_fixed_opt::get_schema_in_ctxt::<7>)]
+            #[serde(with = "serde_avro_fixed_opt")]
+            fixed_field_opt: Option<[u8; 7]>,
+
+            #[avro(with)]
+            #[serde(with = "serde_avro_slice")]
+            slice_field: &'a [u8],
+            #[avro(with)]
+            #[serde(with = "serde_avro_slice_opt")]
+            slice_field_opt: Option<&'a [u8]>,
+        }
+
+        let schema = Schema::parse_str(
+            r#"
+            {
+              "type": "record",
+              "name": "TestStructWithBytes",
+              "fields": [ {
+                "name": "vec_field",
+                "type": "bytes"
+              }, {
+                "name": "vec_field_opt",
+                "type": ["null", "bytes"]
+              }, {
+                "name": "fixed_field",
+                "type": {
+                  "name": "serde_avro_fixed_6",
+                  "type": "fixed",
+                  "size": 6
+                }
+              }, {
+                "name": "fixed_field_opt",
+                "type": ["null", {
+                  "name": "serde_avro_fixed_7",
+                  "type": "fixed",
+                  "size": 7
+                } ]
+              }, {
+                "name": "slice_field",
+                "type": "bytes"
+              }, {
+                "name": "slice_field_opt",
+                "type": ["null", "bytes"]
+              } ]
+            }"#,
+        )
+        .unwrap();
+        assert_eq!(schema, TestStructWithBytes::get_schema())
+    }
 }
diff --git a/avro_derive/tests/ui/avro_rs_397_with_closure_parameters.rs 
b/avro_derive/tests/ui/avro_rs_397_with_closure_parameters.rs
new file mode 100644
index 0000000..16c82c4
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_397_with_closure_parameters.rs
@@ -0,0 +1,27 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use apache_avro::{AvroSchema, Schema};
+
+#[derive(AvroSchema)]
+struct Foo {
+    #[avro(with = |_named_schemas, _enclosing_namespace| Schema::Bytes)]
+    a: String,
+    b: i32,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_397_with_closure_parameters.stderr 
b/avro_derive/tests/ui/avro_rs_397_with_closure_parameters.stderr
new file mode 100644
index 0000000..f8b7570
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_397_with_closure_parameters.stderr
@@ -0,0 +1,14 @@
+error: Expected closure with 0 parameters
+  --> tests/ui/avro_rs_397_with_closure_parameters.rs:22:5
+   |
+22 | /     #[avro(with = |_named_schemas, _enclosing_namespace| Schema::Bytes)]
+23 | |     a: String,
+   | |_____________^
+
+warning: unused import: `Schema`
+  --> tests/ui/avro_rs_397_with_closure_parameters.rs:18:31
+   |
+18 | use apache_avro::{AvroSchema, Schema};
+   |                               ^^^^^^
+   |
+   = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
diff --git a/avro_derive/tests/ui/avro_rs_397_with_expr_string.rs 
b/avro_derive/tests/ui/avro_rs_397_with_expr_string.rs
new file mode 100644
index 0000000..dd24c69
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_397_with_expr_string.rs
@@ -0,0 +1,27 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+struct Foo {
+    #[avro(with = "Schema::Bytes")]
+    a: String,
+    b: i32,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_397_with_expr_string.stderr 
b/avro_derive/tests/ui/avro_rs_397_with_expr_string.stderr
new file mode 100644
index 0000000..9107607
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_397_with_expr_string.stderr
@@ -0,0 +1,6 @@
+error: Invalid expression, expected function or closure
+  --> tests/ui/avro_rs_397_with_expr_string.rs:22:5
+   |
+22 | /     #[avro(with = "Schema::Bytes")]
+23 | |     a: String,
+   | |_____________^
diff --git a/avro_derive/tests/ui/avro_rs_397_with_expr_type.rs 
b/avro_derive/tests/ui/avro_rs_397_with_expr_type.rs
new file mode 100644
index 0000000..93829a4
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_397_with_expr_type.rs
@@ -0,0 +1,27 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use apache_avro::{AvroSchema, Schema};
+
+#[derive(AvroSchema)]
+struct Foo {
+    #[avro(with = Schema::Bytes)]
+    a: String,
+    b: i32,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_397_with_expr_type.stderr 
b/avro_derive/tests/ui/avro_rs_397_with_expr_type.stderr
new file mode 100644
index 0000000..5147c91
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_397_with_expr_type.stderr
@@ -0,0 +1,8 @@
+error[E0618]: expected function, found `Schema`
+  --> tests/ui/avro_rs_397_with_expr_type.rs:22:19
+   |
+20 | #[derive(AvroSchema)]
+   |          ---------- call expression requires function
+21 | struct Foo {
+22 |     #[avro(with = Schema::Bytes)]
+   |                   ^^^^^^^^^^^^^
diff --git a/avro_derive/tests/ui/avro_rs_397_with_word_without_serde.rs 
b/avro_derive/tests/ui/avro_rs_397_with_word_without_serde.rs
new file mode 100644
index 0000000..eb44682
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_397_with_word_without_serde.rs
@@ -0,0 +1,27 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use apache_avro::AvroSchema;
+
+#[derive(AvroSchema)]
+struct Foo {
+    #[avro(with)]
+    a: String,
+    b: i32,
+}
+
+pub fn main() {}
diff --git a/avro_derive/tests/ui/avro_rs_397_with_word_without_serde.stderr 
b/avro_derive/tests/ui/avro_rs_397_with_word_without_serde.stderr
new file mode 100644
index 0000000..febd45e
--- /dev/null
+++ b/avro_derive/tests/ui/avro_rs_397_with_word_without_serde.stderr
@@ -0,0 +1,6 @@
+error: `#[avro(with)]` requires `#[serde(with = "some_module")]` or provide a 
function to call `#[avro(with = some_fn)]`
+  --> tests/ui/avro_rs_397_with_word_without_serde.rs:22:5
+   |
+22 | /     #[avro(with)]
+23 | |     a: String,
+   | |_____________^

Reply via email to