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 31b231d feat!: Use `HashSet` instead of `HashMap` for
`AvroSchemaComponent` (#471)
31b231d is described below
commit 31b231d9c7273eebc8efd8975128286b983c4720
Author: Kriskras99 <[email protected]>
AuthorDate: Thu Feb 19 13:13:05 2026 +0100
feat!: Use `HashSet` instead of `HashMap` for `AvroSchemaComponent` (#471)
---
avro/src/serde/derive.rs | 156 +++++++++++++++++++++---------------
avro/src/serde/with.rs | 53 ++++++------
avro/tests/avro_schema_component.rs | 6 +-
avro/tests/get_record_fields.rs | 8 +-
avro_derive/src/lib.rs | 37 ++++-----
avro_derive/tests/derive.rs | 20 +++--
6 files changed, 153 insertions(+), 127 deletions(-)
diff --git a/avro/src/serde/derive.rs b/avro/src/serde/derive.rs
index c631db9..066fe00 100644
--- a/avro/src/serde/derive.rs
+++ b/avro/src/serde/derive.rs
@@ -17,10 +17,10 @@
use crate::Schema;
use crate::schema::{
- FixedSchema, Name, Names, Namespace, RecordField, RecordSchema,
UnionSchema, UuidSchema,
+ FixedSchema, Name, Namespace, RecordField, RecordSchema, UnionSchema,
UuidSchema,
};
use std::borrow::Cow;
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
/// Trait for types that serve as an Avro data model.
///
@@ -175,15 +175,15 @@ use std::collections::HashMap;
///
/// 1. In combination with `#[serde(with = "path::to::module)]`
///
-/// To get the schema, it will call the functions `fn
get_schema_in_ctxt(&mut Names, &Namespace) -> Schema`
-/// and `fn get_record_fields_in_ctxt(usize, &mut Names, &Namespace) ->
Option<Vec<RecordField>>` in the module provided
+/// To get the schema, it will call the functions `fn
get_schema_in_ctxt(&mut HashSet<Name>, &Namespace) -> Schema`
+/// and `fn get_record_fields_in_ctxt(usize, &mut HashSet<Name>,
&Namespace) -> Option<Vec<RecordField>>` in the module provided
/// to the Serde attribute. See [`AvroSchemaComponent`] for details on how
to implement those
/// functions.
///
/// 2. By providing a function directly, `#[avro(with = some_fn)]`.
///
/// To get the schema, it will call the function provided. It must have the
signature
-/// `fn(&mut Names, &Namespace) -> Schema`. When this is used for a
`transparent` struct, the
+/// `fn(&mut HashSet<Name>, &Namespace) -> Schema`. When this is used for a
`transparent` struct, the
/// default implementation of
[`AvroSchemaComponent::get_record_fields_in_ctxt`] will be used.
/// This is only recommended for primitive types, as the default
implementation cannot be efficiently
/// implemented for complex types.
@@ -208,15 +208,16 @@ pub trait AvroSchema {
///
/// For example, you have a custom integer type:
/// ```
-/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Names,
Namespace, RecordField}};
+/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name,
Namespace, RecordField}};
+/// # use std::collections::HashSet;
/// // Make sure to implement `Serialize` and `Deserialize` to use the right
serialization methods
/// pub struct U24([u8; 3]);
/// impl AvroSchemaComponent for U24 {
-/// fn get_schema_in_ctxt(_: &mut Names, _: &Namespace) -> Schema {
+/// fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema {
/// Schema::Int
/// }
///
-/// fn get_record_fields_in_ctxt(_: usize, _: &mut Names, _: &Namespace)
-> Option<Vec<RecordField>> {
+/// fn get_record_fields_in_ctxt(_: usize, _: &mut HashSet<Name>, _:
&Namespace) -> Option<Vec<RecordField>> {
/// None // A Schema::Int is not a Schema::Record so there are no
fields to return
/// }
///}
@@ -227,17 +228,18 @@ pub trait AvroSchema {
/// To construct a schema for a type is "transparent", such as for smart
pointers, simply
/// pass through the arguments to the inner type:
/// ```
-/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Names,
Namespace, RecordField}};
+/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name,
Namespace, RecordField}};
/// # use serde::{Serialize, Deserialize};
+/// # use std::collections::HashSet;
/// #[derive(Serialize, Deserialize)]
/// #[serde(transparent)] // This attribute is important for all passthrough
implementations!
/// pub struct Transparent<T>(T);
/// impl<T: AvroSchemaComponent> AvroSchemaComponent for Transparent<T> {
-/// fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace:
&Namespace) -> Schema {
+/// fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>,
enclosing_namespace: &Namespace) -> Schema {
/// T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
/// }
///
-/// fn get_record_fields_in_ctxt(first_field_position: usize,
named_schemas: &mut Names, enclosing_namespace: &Namespace) ->
Option<Vec<RecordField>> {
+/// fn get_record_fields_in_ctxt(first_field_position: usize,
named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) ->
Option<Vec<RecordField>> {
/// T::get_record_fields_in_ctxt(first_field_position, named_schemas,
enclosing_namespace)
/// }
///}
@@ -256,9 +258,9 @@ pub trait AvroSchema {
/// - Even if your schema is not a record, still implement the function
and just return `None`
///
/// ```
-/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name,
Names, Namespace, RecordField, RecordSchema}};
+/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name,
Namespace, RecordField, RecordSchema}};
/// # use serde::{Serialize, Deserialize};
-/// # use std::time::Duration;
+/// # use std::{time::Duration, collections::HashSet};
/// pub struct Foo {
/// one: String,
/// two: i32,
@@ -266,26 +268,25 @@ pub trait AvroSchema {
/// }
///
/// impl AvroSchemaComponent for Foo {
-/// fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace:
&Namespace) -> Schema {
+/// fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>,
enclosing_namespace: &Namespace) -> Schema {
/// // Create the fully qualified name for your type given the
enclosing namespace
/// let name =
Name::new("Foo").unwrap().fully_qualified_name(enclosing_namespace);
-/// if named_schemas.contains_key(&name) {
+/// if named_schemas.contains(&name) {
/// Schema::Ref { name }
/// } else {
/// let enclosing_namespace = &name.namespace;
-/// // This is needed because otherwise recursive types will
recurse forever and cause a stack overflow
-/// named_schemas.insert(name.clone(), Schema::Ref { name:
name.clone() });
+/// // Do this before you start creating the schema, as otherwise
recursive types will cause infinite recursion.
+/// named_schemas.insert(name.clone());
/// let schema = Schema::Record(RecordSchema::builder()
/// .name(name.clone())
/// .fields(Self::get_record_fields_in_ctxt(0, named_schemas,
enclosing_namespace).expect("Impossible!"))
/// .build()
/// );
-/// named_schemas.insert(name, schema.clone());
/// schema
/// }
/// }
///
-/// fn get_record_fields_in_ctxt(first_field_position: usize,
named_schemas: &mut Names, enclosing_namespace: &Namespace) ->
Option<Vec<RecordField>> {
+/// fn get_record_fields_in_ctxt(first_field_position: usize,
named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) ->
Option<Vec<RecordField>> {
/// Some(vec![
/// RecordField::builder()
/// .name("one")
@@ -308,7 +309,10 @@ pub trait AvroSchema {
/// ```
pub trait AvroSchemaComponent {
/// Get the schema for this component
- fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace:
&Namespace) -> Schema;
+ fn get_schema_in_ctxt(
+ named_schemas: &mut HashSet<Name>,
+ enclosing_namespace: &Namespace,
+ ) -> Schema;
/// Get the fields of this schema if it is a record.
///
@@ -318,7 +322,7 @@ pub trait AvroSchemaComponent {
/// implement this function when manually implementing this trait.
fn get_record_fields_in_ctxt(
first_field_position: usize,
- named_schemas: &mut Names,
+ named_schemas: &mut HashSet<Name>,
enclosing_namespace: &Namespace,
) -> Option<Vec<RecordField>> {
get_record_fields_in_ctxt(
@@ -335,22 +339,23 @@ pub trait AvroSchemaComponent {
/// This is public so the derive macro can use it for `#[avro(with = ||)]` and
`#[avro(with = path)]`
pub fn get_record_fields_in_ctxt(
first_field_position: usize,
- named_schemas: &mut Names,
+ named_schemas: &mut HashSet<Name>,
enclosing_namespace: &Namespace,
- schema_fn: fn(named_schemas: &mut Names, enclosing_namespace: &Namespace)
-> Schema,
+ schema_fn: fn(named_schemas: &mut HashSet<Name>, enclosing_namespace:
&Namespace) -> Schema,
) -> Option<Vec<RecordField>> {
let mut record = match schema_fn(named_schemas, enclosing_namespace) {
Schema::Record(record) => record,
Schema::Ref { name } => {
// This schema already exists in `named_schemas` so temporarily
remove it so we can
// get the actual schema.
- let temp = named_schemas
- .remove(&name)
- .unwrap_or_else(|| panic!("Name '{name}' should exist in
`named_schemas` otherwise Ref is invalid: {named_schemas:?}"));
+ assert!(
+ named_schemas.remove(&name),
+ "Name '{name}' should exist in `named_schemas` otherwise Ref
is invalid: {named_schemas:?}"
+ );
// Get the schema
let schema = schema_fn(named_schemas, enclosing_namespace);
// Reinsert the old value
- named_schemas.insert(name, temp);
+ named_schemas.insert(name);
// Now check if we actually got a record and return the fields if
that is the case
let Schema::Record(record) = schema else {
@@ -446,7 +451,7 @@ pub fn get_record_fields_in_ctxt(
};
// The schema is used, so reinsert it
- named_schemas.insert(name.clone(), Schema::Ref { name });
+ named_schemas.insert(name.clone());
break;
}
@@ -468,18 +473,18 @@ where
T: AvroSchemaComponent + ?Sized,
{
fn get_schema() -> Schema {
- T::get_schema_in_ctxt(&mut HashMap::default(), &None)
+ T::get_schema_in_ctxt(&mut HashSet::default(), &None)
}
}
macro_rules! impl_schema (
($type:ty, $variant_constructor:expr) => (
impl AvroSchemaComponent for $type {
- fn get_schema_in_ctxt(_: &mut Names, _: &Namespace) -> Schema {
+ fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) ->
Schema {
$variant_constructor
}
- fn get_record_fields_in_ctxt(_: usize, _: &mut Names, _:
&Namespace) -> Option<Vec<RecordField>> {
+ fn get_record_fields_in_ctxt(_: usize, _: &mut HashSet<Name>, _:
&Namespace) -> Option<Vec<RecordField>> {
None
}
}
@@ -503,11 +508,11 @@ impl_schema!(char, Schema::String);
macro_rules! impl_passthrough_schema (
($type:ty where T: AvroSchemaComponent + ?Sized $(+ $bound:tt)*) => (
impl<T: AvroSchemaComponent $(+ $bound)* + ?Sized> AvroSchemaComponent
for $type {
- fn get_schema_in_ctxt(named_schemas: &mut Names,
enclosing_namespace: &Namespace) -> Schema {
+ fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>,
enclosing_namespace: &Namespace) -> Schema {
T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
}
- fn get_record_fields_in_ctxt(first_field_position: usize,
named_schemas: &mut Names, enclosing_namespace: &Namespace) ->
Option<Vec<RecordField>> {
+ fn get_record_fields_in_ctxt(first_field_position: usize,
named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) ->
Option<Vec<RecordField>> {
T::get_record_fields_in_ctxt(first_field_position,
named_schemas, enclosing_namespace)
}
}
@@ -523,11 +528,11 @@ impl_passthrough_schema!(std::sync::Mutex<T> where T:
AvroSchemaComponent + ?Siz
macro_rules! impl_array_schema (
($type:ty where T: AvroSchemaComponent) => (
impl<T: AvroSchemaComponent> AvroSchemaComponent for $type {
- fn get_schema_in_ctxt(named_schemas: &mut Names,
enclosing_namespace: &Namespace) -> Schema {
+ fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>,
enclosing_namespace: &Namespace) -> Schema {
Schema::array(T::get_schema_in_ctxt(named_schemas,
enclosing_namespace))
}
- fn get_record_fields_in_ctxt(_: usize, _: &mut Names, _:
&Namespace) -> Option<Vec<RecordField>> {
+ fn get_record_fields_in_ctxt(_: usize, _: &mut HashSet<Name>, _:
&Namespace) -> Option<Vec<RecordField>> {
None
}
}
@@ -543,13 +548,16 @@ 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 {
+ fn get_schema_in_ctxt(
+ named_schemas: &mut HashSet<Name>,
+ enclosing_namespace: &Namespace,
+ ) -> Schema {
Schema::array(T::get_schema_in_ctxt(named_schemas,
enclosing_namespace))
}
fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
@@ -560,13 +568,16 @@ impl<T> AvroSchemaComponent for HashMap<String, T>
where
T: AvroSchemaComponent,
{
- fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace:
&Namespace) -> Schema {
+ fn get_schema_in_ctxt(
+ named_schemas: &mut HashSet<Name>,
+ enclosing_namespace: &Namespace,
+ ) -> Schema {
Schema::map(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
}
fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
@@ -577,7 +588,10 @@ impl<T> AvroSchemaComponent for Option<T>
where
T: AvroSchemaComponent,
{
- fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace:
&Namespace) -> Schema {
+ fn get_schema_in_ctxt(
+ named_schemas: &mut HashSet<Name>,
+ enclosing_namespace: &Namespace,
+ ) -> Schema {
let variants = vec![
Schema::Null,
T::get_schema_in_ctxt(named_schemas, enclosing_namespace),
@@ -590,7 +604,7 @@ where
fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
@@ -601,12 +615,14 @@ 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 {
+ fn get_schema_in_ctxt(
+ named_schemas: &mut HashSet<Name>,
+ enclosing_namespace: &Namespace,
+ ) -> Schema {
let name = Name::new("duration")
.expect("Name is valid")
.fully_qualified_name(enclosing_namespace);
- if named_schemas.contains_key(&name) {
+ if named_schemas.contains(&name) {
Schema::Ref { name }
} else {
let schema = Schema::Duration(FixedSchema {
@@ -616,14 +632,14 @@ impl AvroSchemaComponent for core::time::Duration {
size: 12,
attributes: Default::default(),
});
- named_schemas.insert(name, schema.clone());
+ named_schemas.insert(name);
schema
}
}
fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
@@ -634,12 +650,14 @@ 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 {
+ fn get_schema_in_ctxt(
+ named_schemas: &mut HashSet<Name>,
+ enclosing_namespace: &Namespace,
+ ) -> Schema {
let name = Name::new("uuid")
.expect("Name is valid")
.fully_qualified_name(enclosing_namespace);
- if named_schemas.contains_key(&name) {
+ if named_schemas.contains(&name) {
Schema::Ref { name }
} else {
let schema = Schema::Uuid(UuidSchema::Fixed(FixedSchema {
@@ -649,14 +667,14 @@ impl AvroSchemaComponent for uuid::Uuid {
size: 16,
attributes: Default::default(),
}));
- named_schemas.insert(name, schema.clone());
+ named_schemas.insert(name);
schema
}
}
fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
@@ -665,12 +683,14 @@ impl AvroSchemaComponent for uuid::Uuid {
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 {
+ fn get_schema_in_ctxt(
+ named_schemas: &mut HashSet<Name>,
+ enclosing_namespace: &Namespace,
+ ) -> Schema {
let name = Name::new("u64")
.expect("Name is valid")
.fully_qualified_name(enclosing_namespace);
- if named_schemas.contains_key(&name) {
+ if named_schemas.contains(&name) {
Schema::Ref { name }
} else {
let schema = Schema::Fixed(FixedSchema {
@@ -680,14 +700,14 @@ impl AvroSchemaComponent for u64 {
size: 8,
attributes: Default::default(),
});
- named_schemas.insert(name, schema.clone());
+ named_schemas.insert(name);
schema
}
}
fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
@@ -696,12 +716,14 @@ impl AvroSchemaComponent for u64 {
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 {
+ fn get_schema_in_ctxt(
+ named_schemas: &mut HashSet<Name>,
+ enclosing_namespace: &Namespace,
+ ) -> Schema {
let name = Name::new("u128")
.expect("Name is valid")
.fully_qualified_name(enclosing_namespace);
- if named_schemas.contains_key(&name) {
+ if named_schemas.contains(&name) {
Schema::Ref { name }
} else {
let schema = Schema::Fixed(FixedSchema {
@@ -711,14 +733,14 @@ impl AvroSchemaComponent for u128 {
size: 16,
attributes: Default::default(),
});
- named_schemas.insert(name, schema.clone());
+ named_schemas.insert(name);
schema
}
}
fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
@@ -727,12 +749,14 @@ impl AvroSchemaComponent for u128 {
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 {
+ fn get_schema_in_ctxt(
+ named_schemas: &mut HashSet<Name>,
+ enclosing_namespace: &Namespace,
+ ) -> Schema {
let name = Name::new("i128")
.expect("Name is valid")
.fully_qualified_name(enclosing_namespace);
- if named_schemas.contains_key(&name) {
+ if named_schemas.contains(&name) {
Schema::Ref { name }
} else {
let schema = Schema::Fixed(FixedSchema {
@@ -742,14 +766,14 @@ impl AvroSchemaComponent for i128 {
size: 16,
attributes: Default::default(),
});
- named_schemas.insert(name, schema.clone());
+ named_schemas.insert(name);
schema
}
}
fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
diff --git a/avro/src/serde/with.rs b/avro/src/serde/with.rs
index bbcd7de..d5da690 100644
--- a/avro/src/serde/with.rs
+++ b/avro/src/serde/with.rs
@@ -92,22 +92,24 @@ impl Drop for BorrowedGuard {
///
/// [`apache_avro::serde::bytes_opt`]: bytes_opt
pub mod bytes {
+ use std::collections::HashSet;
+
use serde::{Deserializer, Serializer};
use crate::{
Schema,
- schema::{Names, Namespace, RecordField},
+ schema::{Name, Namespace, RecordField},
};
/// Returns [`Schema::Bytes`]
- pub fn get_schema_in_ctxt(_: &mut Names, _: &Namespace) -> Schema {
+ pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema {
Schema::Bytes
}
/// Returns `None`
pub fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
@@ -154,15 +156,15 @@ pub mod bytes {
/// [`apache_avro::serde::bytes`]: bytes
pub mod bytes_opt {
use serde::{Deserializer, Serializer};
- use std::borrow::Borrow;
+ use std::{borrow::Borrow, collections::HashSet};
use crate::{
Schema,
- schema::{Names, Namespace, RecordField, UnionSchema},
+ schema::{Name, Namespace, RecordField, UnionSchema},
};
/// Returns `Schema::Union(Schema::Null, Schema::Bytes)`
- pub fn get_schema_in_ctxt(_: &mut Names, _: &Namespace) -> Schema {
+ pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema {
Schema::Union(
UnionSchema::new(vec![Schema::Null, Schema::Bytes]).expect("This
is a valid union"),
)
@@ -171,7 +173,7 @@ pub mod bytes_opt {
/// Returns `None`
pub fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
@@ -218,28 +220,29 @@ pub mod bytes_opt {
///
/// [`apache_avro::serde::fixed_opt`]: fixed_opt
pub mod fixed {
+ use std::collections::HashSet;
+
use super::BytesType;
use serde::{Deserializer, Serializer};
use crate::{
Schema,
- schema::{FixedSchema, Name, Names, Namespace, RecordField},
+ schema::{FixedSchema, Name, Namespace, RecordField},
};
/// 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,
+ named_schemas: &mut HashSet<Name>,
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) {
+ if named_schemas.contains(&name) {
Schema::Ref { name }
} else {
let schema =
Schema::Fixed(FixedSchema::builder().name(name.clone()).size(N).build());
- named_schemas.insert(name, schema.clone());
+ named_schemas.insert(name);
schema
}
}
@@ -247,7 +250,7 @@ pub mod fixed {
/// Returns `None`
pub fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
@@ -296,16 +299,16 @@ pub mod fixed {
pub mod fixed_opt {
use super::BytesType;
use serde::{Deserializer, Serializer};
- use std::borrow::Borrow;
+ use std::{borrow::Borrow, collections::HashSet};
use crate::{
Schema,
- schema::{Names, Namespace, RecordField, UnionSchema},
+ schema::{Name, Namespace, RecordField, 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,
+ named_schemas: &mut HashSet<Name>,
enclosing_namespace: &Namespace,
) -> Schema {
Schema::Union(
@@ -320,7 +323,7 @@ pub mod fixed_opt {
/// Returns `None`
pub fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
@@ -370,22 +373,24 @@ pub mod fixed_opt {
/// [`Value::Fixed`]: crate::types::Value::Fixed
/// [`apache_avro::serde::slice_opt`]: slice_opt
pub mod slice {
+ use std::collections::HashSet;
+
use serde::{Deserializer, Serializer};
use crate::{
Schema,
- schema::{Names, Namespace, RecordField},
+ schema::{Name, Namespace, RecordField},
};
/// Returns [`Schema::Bytes`]
- pub fn get_schema_in_ctxt(_: &mut Names, _: &Namespace) -> Schema {
+ pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema {
Schema::Bytes
}
/// Returns `None`
pub fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
@@ -435,15 +440,15 @@ pub mod slice {
/// [`apache_avro::serde::slice`]: mod@slice
pub mod slice_opt {
use serde::{Deserializer, Serializer};
- use std::borrow::Borrow;
+ use std::{borrow::Borrow, collections::HashSet};
use crate::{
Schema,
- schema::{Names, Namespace, RecordField, UnionSchema},
+ schema::{Name, Namespace, RecordField, UnionSchema},
};
/// Returns `Schema::Union(Schema::Null, Schema::Bytes)`
- pub fn get_schema_in_ctxt(_: &mut Names, _: &Namespace) -> Schema {
+ pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema {
Schema::Union(
UnionSchema::new(vec![Schema::Null, Schema::Bytes]).expect("This
is a valid union"),
)
@@ -452,7 +457,7 @@ pub mod slice_opt {
/// Returns `None`
pub fn get_record_fields_in_ctxt(
_: usize,
- _: &mut Names,
+ _: &mut HashSet<Name>,
_: &Namespace,
) -> Option<Vec<RecordField>> {
None
diff --git a/avro/tests/avro_schema_component.rs
b/avro/tests/avro_schema_component.rs
index 2efacb1..596f35f 100644
--- a/avro/tests/avro_schema_component.rs
+++ b/avro/tests/avro_schema_component.rs
@@ -16,11 +16,11 @@
// under the License.
use apache_avro::{AvroSchemaComponent, Schema};
-use std::collections::HashMap;
+use std::collections::HashSet;
#[test]
fn avro_rs_394_avro_schema_component_without_derive_feature() {
- let schema = i32::get_schema_in_ctxt(&mut HashMap::default(), &None);
+ let schema = i32::get_schema_in_ctxt(&mut HashSet::default(), &None);
assert!(matches!(schema, Schema::Int));
}
@@ -29,5 +29,5 @@ fn avro_rs_394_avro_schema_component_without_derive_feature()
{
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);
+ let _schema = VeryOptional::get_schema_in_ctxt(&mut HashSet::default(),
&None);
}
diff --git a/avro/tests/get_record_fields.rs b/avro/tests/get_record_fields.rs
index 9b729fe..3417f26 100644
--- a/avro/tests/get_record_fields.rs
+++ b/avro/tests/get_record_fields.rs
@@ -19,7 +19,7 @@ use apache_avro::{
Schema,
serde::{AvroSchemaComponent, get_record_fields_in_ctxt},
};
-use std::collections::HashMap;
+use std::collections::HashSet;
use apache_avro_test_helper::TestResult;
@@ -31,7 +31,7 @@ fn avro_rs_448_default_get_record_fields_no_recursion() ->
TestResult {
_b: String,
}
- let mut named_schemas = HashMap::new();
+ let mut named_schemas = HashSet::new();
let fields =
get_record_fields_in_ctxt(0, &mut named_schemas, &None,
Foo::get_schema_in_ctxt).unwrap();
@@ -72,7 +72,7 @@ fn avro_rs_448_default_get_record_fields_recursion() ->
TestResult {
_b: Option<Box<Foo>>,
}
- let mut named_schemas = HashMap::new();
+ let mut named_schemas = HashSet::new();
let fields =
get_record_fields_in_ctxt(0, &mut named_schemas, &None,
Foo::get_schema_in_ctxt).unwrap();
@@ -110,7 +110,7 @@ fn avro_rs_448_default_get_record_fields_position() ->
TestResult {
_b: String,
}
- let mut named_schemas = HashMap::new();
+ let mut named_schemas = HashSet::new();
let fields =
get_record_fields_in_ctxt(10, &mut named_schemas, &None,
Foo::get_schema_in_ctxt).unwrap();
diff --git a/avro_derive/src/lib.rs b/avro_derive/src/lib.rs
index 64e8fc9..df14b97 100644
--- a/avro_derive/src/lib.rs
+++ b/avro_derive/src/lib.rs
@@ -113,11 +113,11 @@ fn create_trait_definition(
quote! {
#[automatically_derived]
impl #impl_generics ::apache_avro::AvroSchemaComponent for #ident
#ty_generics #where_clause {
- fn get_schema_in_ctxt(named_schemas: &mut
::apache_avro::schema::Names, enclosing_namespace:
&::std::option::Option<::std::string::String>) -> ::apache_avro::schema::Schema
{
+ fn get_schema_in_ctxt(named_schemas: &mut
::std::collections::HashSet<::apache_avro::schema::Name>, enclosing_namespace:
&::std::option::Option<::std::string::String>) -> ::apache_avro::schema::Schema
{
#get_schema_impl
}
- fn get_record_fields_in_ctxt(mut field_position: usize,
named_schemas: &mut ::apache_avro::schema::Names, enclosing_namespace:
&::std::option::Option<::std::string::String>) ->
::std::option::Option<::std::vec::Vec<::apache_avro::schema::RecordField>> {
+ fn get_record_fields_in_ctxt(mut field_position: usize,
named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
enclosing_namespace: &::std::option::Option<::std::string::String>) ->
::std::option::Option<::std::vec::Vec<::apache_avro::schema::RecordField>> {
#get_record_fields_impl
}
}
@@ -128,16 +128,12 @@ fn create_trait_definition(
fn handle_named_schemas(full_schema_name: String, schema_def: TokenStream) ->
TokenStream {
quote! {
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) {
+ if named_schemas.contains(&name) {
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()});
- let schema = #schema_def;
- named_schemas.insert(name, schema.clone());
- schema
+ named_schemas.insert(name.clone());
+ #schema_def
}
}
}
@@ -632,18 +628,17 @@ mod tests {
#[automatically_derived]
impl ::apache_avro::AvroSchemaComponent for Basic {
fn get_schema_in_ctxt(
- named_schemas: &mut ::apache_avro::schema::Names,
+ named_schemas: &mut
::std::collections::HashSet<::apache_avro::schema::Name>,
enclosing_namespace:
&::std::option::Option<::std::string::String>
) -> ::apache_avro::schema::Schema {
let name = apache_avro::schema::Name::new("Basic")
.expect(concat!("Unable to parse schema name
", "Basic"))
.fully_qualified_name(enclosing_namespace);
- if named_schemas.contains_key(&name) {
+ if named_schemas.contains(&name) {
apache_avro::schema::Schema::Ref { name }
} else {
let enclosing_namespace = &name.namespace;
- named_schemas.insert(name.clone(),
apache_avro::schema::Schema::Ref{name: name.clone()});
- let schema =
+ named_schemas.insert(name.clone());
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")[..]
@@ -658,15 +653,13 @@ mod tests {
],
default: Some("A".into()),
attributes: Default::default(),
- });
- named_schemas.insert(name, schema.clone());
- schema
+ })
}
}
fn get_record_fields_in_ctxt(
mut field_position: usize,
- named_schemas: &mut ::apache_avro::schema::Names,
+ named_schemas: &mut
::std::collections::HashSet<::apache_avro::schema::Name>,
enclosing_namespace:
&::std::option::Option<::std::string::String>
) -> ::std::option::Option
<::std::vec::Vec<::apache_avro::schema::RecordField>> {
None
@@ -797,7 +790,7 @@ mod tests {
match syn::parse2::<DeriveInput>(test_struct) {
Ok(input) => {
let schema_res = derive_avro_schema(input);
- let expected_token_stream = r#"# [automatically_derived] impl
:: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt
(named_schemas : & mut :: apache_avro :: schema :: Names , enclosing_namespace
: & :: std :: option :: Option < :: std :: string :: 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_namespace) ; if n [...]
+ let expected_token_stream = r#"# [automatically_derived] impl
:: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt
(named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro ::
schema :: Name > , enclosing_namespace : & :: std :: option :: Option < :: std
:: string :: String >) -> :: apache_avro :: schema :: Schema { let name =
apache_avro :: schema :: Name :: new ("A") . expect (concat ! ("Unable to parse
schema name " , "A")) . fully_qualifi [...]
let schema_token_stream = schema_res.unwrap().to_string();
assert_eq!(schema_token_stream, expected_token_stream);
}
@@ -816,7 +809,7 @@ mod tests {
match syn::parse2::<DeriveInput>(test_enum) {
Ok(input) => {
let schema_res = derive_avro_schema(input);
- let expected_token_stream = r#"# [automatically_derived] impl
:: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt
(named_schemas : & mut :: apache_avro :: schema :: Names , enclosing_namespace
: & :: std :: option :: Option < :: std :: string :: 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_namespace) ; if n [...]
+ let expected_token_stream = r#"# [automatically_derived] impl
:: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt
(named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro ::
schema :: Name > , enclosing_namespace : & :: std :: option :: Option < :: std
:: string :: String >) -> :: apache_avro :: schema :: Schema { let name =
apache_avro :: schema :: Name :: new ("A") . expect (concat ! ("Unable to parse
schema name " , "A")) . fully_qualifi [...]
let schema_token_stream = schema_res.unwrap().to_string();
assert_eq!(schema_token_stream, expected_token_stream);
}
@@ -839,7 +832,7 @@ mod tests {
match syn::parse2::<DeriveInput>(test_struct) {
Ok(input) => {
let schema_res = derive_avro_schema(input);
- let expected_token_stream = r#"# [automatically_derived] impl
:: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt
(named_schemas : & mut :: apache_avro :: schema :: Names , enclosing_namespace
: & :: std :: option :: Option < :: std :: string :: 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_namespace) ; if n [...]
+ let expected_token_stream = r#"# [automatically_derived] impl
:: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt
(named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro ::
schema :: Name > , enclosing_namespace : & :: std :: option :: Option < :: std
:: string :: String >) -> :: apache_avro :: schema :: Schema { let name =
apache_avro :: schema :: Name :: new ("A") . expect (concat ! ("Unable to parse
schema name " , "A")) . fully_qualifi [...]
let schema_token_stream = schema_res.unwrap().to_string();
assert_eq!(schema_token_stream, expected_token_stream);
}
@@ -859,7 +852,7 @@ mod tests {
match syn::parse2::<DeriveInput>(test_enum) {
Ok(input) => {
let schema_res = derive_avro_schema(input);
- let expected_token_stream = r#"# [automatically_derived] impl
:: apache_avro :: AvroSchemaComponent for B { fn get_schema_in_ctxt
(named_schemas : & mut :: apache_avro :: schema :: Names , enclosing_namespace
: & :: std :: option :: Option < :: std :: string :: 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_namespace) ; if n [...]
+ let expected_token_stream = r#"# [automatically_derived] impl
:: apache_avro :: AvroSchemaComponent for B { fn get_schema_in_ctxt
(named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro ::
schema :: Name > , enclosing_namespace : & :: std :: option :: Option < :: std
:: string :: String >) -> :: apache_avro :: schema :: Schema { let name =
apache_avro :: schema :: Name :: new ("B") . expect (concat ! ("Unable to parse
schema name " , "B")) . fully_qualifi [...]
let schema_token_stream = schema_res.unwrap().to_string();
assert_eq!(schema_token_stream, expected_token_stream);
}
@@ -883,7 +876,7 @@ mod tests {
match syn::parse2::<DeriveInput>(test_struct) {
Ok(input) => {
let schema_res = derive_avro_schema(input);
- let expected_token_stream = r#"# [automatically_derived] impl
:: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt
(named_schemas : & mut :: apache_avro :: schema :: Names , enclosing_namespace
: & :: std :: option :: Option < :: std :: string :: 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_namespace) ; if n [...]
+ let expected_token_stream = r#"# [automatically_derived] impl
:: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt
(named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro ::
schema :: Name > , enclosing_namespace : & :: std :: option :: Option < :: std
:: string :: String >) -> :: apache_avro :: schema :: Schema { let name =
apache_avro :: schema :: Name :: new ("A") . expect (concat ! ("Unable to parse
schema name " , "A")) . fully_qualifi [...]
let schema_token_stream = schema_res.unwrap().to_string();
assert_eq!(schema_token_stream, expected_token_stream);
}
diff --git a/avro_derive/tests/derive.rs b/avro_derive/tests/derive.rs
index e52ba7b..631e825 100644
--- a/avro_derive/tests/derive.rs
+++ b/avro_derive/tests/derive.rs
@@ -17,11 +17,15 @@
use apache_avro::{
AvroSchema, AvroSchemaComponent, Reader, Schema, Writer, from_value,
- schema::{Alias, EnumSchema, FixedSchema, Name, Names, Namespace,
RecordSchema},
+ schema::{Alias, EnumSchema, FixedSchema, Name, Namespace, RecordSchema},
};
use proptest::prelude::*;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
-use std::{borrow::Cow, collections::HashMap, sync::Mutex};
+use std::{
+ borrow::Cow,
+ collections::{HashMap, HashSet},
+ sync::Mutex,
+};
use pretty_assertions::assert_eq;
@@ -1850,14 +1854,14 @@ fn avro_rs_397_with() {
)
.unwrap();
- fn long_schema(_named_schemas: &mut Names, _enclosing_namespace:
&Namespace) -> Schema {
+ fn long_schema(_named_schemas: &mut HashSet<Name>, _enclosing_namespace:
&Namespace) -> Schema {
Schema::Long
}
mod module {
use super::*;
pub fn get_schema_in_ctxt(
- _named_schemas: &mut Names,
+ _named_schemas: &mut HashSet<Name>,
_enclosing_namespace: &Namespace,
) -> Schema {
Schema::Bytes
@@ -1902,7 +1906,7 @@ fn avro_rs_397_with_generic() {
.unwrap();
fn generic<const N: usize>(
- _named_schemas: &mut Names,
+ _named_schemas: &mut HashSet<Name>,
_enclosing_namespace: &Namespace,
) -> Schema {
Schema::Fixed(FixedSchema {
@@ -1993,7 +1997,7 @@ fn avro_rs_397_derive_with_expr_lambda() {
#[test]
fn avro_rs_398_transparent_with_skip() {
- fn long_schema(_named_schemas: &mut Names, _enclosing_namespace:
&Namespace) -> Schema {
+ fn long_schema(_named_schemas: &mut HashSet<Name>, _enclosing_namespace:
&Namespace) -> Schema {
Schema::Long
}
@@ -2293,7 +2297,7 @@ fn avro_rs_448_transparent_with() {
pub _a: i32,
}
- let mut named_schemas = HashMap::new();
+ let mut named_schemas = HashSet::new();
assert_eq!(
TestStruct::get_record_fields_in_ctxt(0, &mut named_schemas, &None),
None
@@ -2319,7 +2323,7 @@ fn avro_rs_448_transparent_with_2() {
pub _a: Foo,
}
- let mut named_schemas = HashMap::new();
+ let mut named_schemas = HashSet::new();
let fields = TestStruct::get_record_fields_in_ctxt(0, &mut named_schemas,
&None).unwrap();
assert!(
named_schemas.is_empty(),