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 6492782 feat!:Make AvroSchemaComponent available without `derive`
feature (#394)
6492782 is described below
commit 6492782d0726265c6164b39d2b162b4bb6fe0049
Author: Martin Grigorov <[email protected]>
AuthorDate: Tue Jan 13 10:56:20 2026 +0200
feat!:Make AvroSchemaComponent available without `derive` feature (#394)
* feat!:Make AvroSchemaComponent available without `derive` feature
Fixes #363
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
* Expose all default impls of AvroSchemaComponent without requiring the
"derive" feature
* Fix docstring link for AvroSchemaComponent
Do not use wildcard import for apache_avro_derive::* types
* Make `Names` alias type public, because it is used as a parameter in
AvroSchemaComponent::get_schema_in_ctxt()
Fix docstring example
* Flatten depply optional types when resolving their AvroSchemaComponent
* Add an IT test for AvroSchemaComponent
* Highlight the link
Co-authored-by: Kriskras99 <[email protected]>
* Panic when deriving a nested Union schema
---------
Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
Co-authored-by: Kriskras99 <[email protected]>
---
avro/src/lib.rs | 4 +-
avro/src/schema.rs | 343 ++++++++++++++++--------------------
avro/tests/avro_schema_component.rs | 33 ++++
avro_derive/src/lib.rs | 16 +-
avro_derive/tests/derive.rs | 2 +-
5 files changed, 200 insertions(+), 198 deletions(-)
diff --git a/avro/src/lib.rs b/avro/src/lib.rs
index 5c744ff..40be7f9 100644
--- a/avro/src/lib.rs
+++ b/avro/src/lib.rs
@@ -984,7 +984,7 @@ pub use reader::{
GenericSingleObjectReader, Reader, SpecificSingleObjectReader,
from_avro_datum,
from_avro_datum_reader_schemata, from_avro_datum_schemata, read_marker,
};
-pub use schema::{AvroSchema, Schema};
+pub use schema::{AvroSchema, AvroSchemaComponent, Schema};
pub use serde::{de::from_value, ser::to_value};
pub use uuid::Uuid;
pub use writer::{
@@ -993,7 +993,7 @@ pub use writer::{
};
#[cfg(feature = "derive")]
-pub use apache_avro_derive::*;
+pub use apache_avro_derive::AvroSchema;
/// A convenience type alias for `Result`s with `Error`s.
pub type AvroResult<T> = Result<T, Error>;
diff --git a/avro/src/schema.rs b/avro/src/schema.rs
index 47fddc4..809606a 100644
--- a/avro/src/schema.rs
+++ b/avro/src/schema.rs
@@ -34,7 +34,7 @@ use serde::{
};
use serde_json::{Map, Value};
use std::{
- borrow::Borrow,
+ borrow::{Borrow, Cow},
collections::{BTreeMap, HashMap, HashSet},
fmt,
fmt::Debug,
@@ -245,7 +245,7 @@ pub type Documentation = Option<String>;
/// Represents the aliases for Named Schema
pub type Aliases = Option<Vec<Alias>>;
/// Represents Schema lookup within a schema env
-pub(crate) type Names = HashMap<Name, Schema>;
+pub type Names = HashMap<Name, Schema>;
/// Represents Schema lookup within a schema
pub type NamesRef<'a> = HashMap<Name, &'a Schema>;
/// Represents the namespace for Named Schema
@@ -2568,215 +2568,184 @@ fn field_ordering_position(field: &str) ->
Option<usize> {
/// Trait for types that serve as an Avro data model. Derive implementation
available
/// through `derive` feature. Do not implement directly!
-/// Implement `apache_avro::schema::derive::AvroSchemaComponent` to get this
trait
+/// Implement [`AvroSchemaComponent`] to get this trait
/// through a blanket implementation.
pub trait AvroSchema {
fn get_schema() -> Schema;
}
-#[cfg(feature = "derive")]
-pub mod derive {
- use super::*;
- use std::borrow::Cow;
+/// 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;
+}
- /// 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(names, 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,
+{
+ fn get_schema() -> Schema {
+ T::get_schema_in_ctxt(&mut HashMap::default(), &None)
}
+}
- impl<T> AvroSchema for T
- where
- T: AvroSchemaComponent,
- {
- 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!(uuid::Uuid, Schema::Uuid(UuidSchema::String));
+
+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))
}
+}
- 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<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),
+ ];
- 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!(uuid::Uuid, Schema::Uuid(UuidSchema::String));
-
- 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))
- }
+ Schema::Union(
+ UnionSchema::new(variants).expect("Option<T> must produce a valid
(non-nested) union"),
+ )
}
+}
- impl<T> AvroSchemaComponent for Option<T>
- where
- T: AvroSchemaComponent,
- {
- fn get_schema_in_ctxt(
- named_schemas: &mut Names,
- enclosing_namespace: &Namespace,
- ) -> Schema {
- let inner_schema = T::get_schema_in_ctxt(named_schemas,
enclosing_namespace);
- Schema::Union(UnionSchema {
- schemas: vec![Schema::Null, inner_schema.clone()],
- variant_index: [Schema::Null, inner_schema]
- .iter()
- .enumerate()
- .map(|(idx, s)| (SchemaKind::from(s), idx))
- .collect(),
- })
- }
- }
-
- 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 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 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 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 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<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 {
- 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,
- aliases: None,
- doc: None,
- size: 12,
- default: None,
- attributes: Default::default(),
- }))
- .clone()
- }
+impl AvroSchemaComponent for core::time::Duration {
+ 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,
+ aliases: None,
+ doc: None,
+ size: 12,
+ default: None,
+ attributes: Default::default(),
+ }))
+ .clone()
}
}
diff --git a/avro/tests/avro_schema_component.rs
b/avro/tests/avro_schema_component.rs
new file mode 100644
index 0000000..2efacb1
--- /dev/null
+++ b/avro/tests/avro_schema_component.rs
@@ -0,0 +1,33 @@
+// 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::{AvroSchemaComponent, Schema};
+use std::collections::HashMap;
+
+#[test]
+fn avro_rs_394_avro_schema_component_without_derive_feature() {
+ let schema = i32::get_schema_in_ctxt(&mut HashMap::default(), &None);
+ assert!(matches!(schema, Schema::Int));
+}
+
+#[test]
+#[should_panic(expected = "Option<T> must produce a valid (non-nested) union")]
+fn avro_rs_394_avro_schema_component_nested_options() {
+ type VeryOptional = Option<Option<i32>>;
+
+ let _schema = VeryOptional::get_schema_in_ctxt(&mut HashMap::default(),
&None);
+}
diff --git a/avro_derive/src/lib.rs b/avro_derive/src/lib.rs
index 8723593..c3587ed 100644
--- a/avro_derive/src/lib.rs
+++ b/avro_derive/src/lib.rs
@@ -84,7 +84,7 @@ fn derive_avro_schema(input: &mut DeriveInput) ->
Result<TokenStream, Vec<syn::E
let (impl_generics, ty_generics, where_clause) =
input.generics.split_for_impl();
Ok(quote! {
#[automatically_derived]
- impl #impl_generics apache_avro::schema::derive::AvroSchemaComponent
for #ident #ty_generics #where_clause {
+ 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;
@@ -338,10 +338,10 @@ fn is_default_attr(attr: &Attribute) -> bool {
}
/// Generates the schema def expression for fully qualified type paths using
the associated function
-/// - `A -> <A as
apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt()`
-/// - `A<T> -> <A<T> as
apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt()`
+/// - `A -> <A as apache_avro::AvroSchemaComponent>::get_schema_in_ctxt()`
+/// - `A<T> -> <A<T> as
apache_avro::AvroSchemaComponent>::get_schema_in_ctxt()`
fn type_path_schema_expr(p: &TypePath) -> TokenStream {
- quote! {<#p as
apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}
+ quote! {<#p as
apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}
}
/// Stolen from serde
@@ -496,7 +496,7 @@ mod tests {
assert!(derived.is_ok());
assert_eq!(derived.unwrap().to_string(), quote! {
#[automatically_derived]
- impl apache_avro::schema::derive::AvroSchemaComponent for
Basic {
+ impl apache_avro::AvroSchemaComponent for Basic {
fn get_schema_in_ctxt(
named_schemas: &mut std::collections::HashMap<
apache_avro::schema::Name,
@@ -640,9 +640,9 @@ mod tests {
#[test]
fn test_trait_cast() {
-
assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{i32}).unwrap()).to_string(),
quote!{<i32 as
apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}.to_string());
-
assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{Vec<T>}).unwrap()).to_string(),
quote!{<Vec<T> as
apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}.to_string());
-
assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{AnyType}).unwrap()).to_string(),
quote!{<AnyType as
apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}.to_string());
+
assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{i32}).unwrap()).to_string(),
quote!{<i32 as
apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}.to_string());
+
assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{Vec<T>}).unwrap()).to_string(),
quote!{<Vec<T> as
apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}.to_string());
+
assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{AnyType}).unwrap()).to_string(),
quote!{<AnyType as
apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas,
enclosing_namespace)}.to_string());
}
#[test]
diff --git a/avro_derive/tests/derive.rs b/avro_derive/tests/derive.rs
index 72c6db0..cf3be7e 100644
--- a/avro_derive/tests/derive.rs
+++ b/avro_derive/tests/derive.rs
@@ -17,7 +17,7 @@
use apache_avro::{
Reader, Schema, Writer, from_value,
- schema::{AvroSchema, derive::AvroSchemaComponent},
+ schema::{AvroSchema, AvroSchemaComponent},
};
use apache_avro_derive::*;
use proptest::prelude::*;