This is an automated email from the ASF dual-hosted git repository. kriskras99 pushed a commit to branch feat/full_enum_support in repository https://gitbox.apache.org/repos/asf/avro-rs.git
commit b61fc57a3f47ef29d1f3e3f0e70743cc98a43d79 Author: default <[email protected]> AuthorDate: Tue Mar 17 16:05:18 2026 +0000 wip: Update tests for `SchemaAwareSerializer` --- avro/src/serde/mod.rs | 5 +- avro/src/serde/with.rs | 309 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 313 insertions(+), 1 deletion(-) diff --git a/avro/src/serde/mod.rs b/avro/src/serde/mod.rs index 256963d..3b1718b 100644 --- a/avro/src/serde/mod.rs +++ b/avro/src/serde/mod.rs @@ -118,7 +118,10 @@ mod with; pub use de::from_value; pub use derive::{AvroSchema, AvroSchemaComponent}; pub use ser::to_value; -pub use with::{bytes, bytes_opt, fixed, fixed_opt, slice, slice_opt}; +pub use with::{ + array, array_opt, bigdecimal, bigdecimal_opt, bytes, bytes_opt, fixed, fixed_opt, slice, + slice_opt, +}; #[doc(hidden)] pub use derive::get_record_fields_in_ctxt; diff --git a/avro/src/serde/with.rs b/avro/src/serde/with.rs index 29744e1..74c0a3a 100644 --- a/avro/src/serde/with.rs +++ b/avro/src/serde/with.rs @@ -503,6 +503,315 @@ pub mod slice_opt { } } +/// (De)serialize [`BigDecimal`] as a [`Schema::BigDecimal`] instead of a [`Schema::String`]. +/// +/// This module is intended to be used through the Serde `with` attribute. +/// +/// Use [`apache_avro::serde::bigdecimal_opt`] for optional big decimals values. +/// +/// When used with different serialization formats, this will write bytes. +/// +/// See usage with below example: +/// ``` +/// # use apache_avro::AvroSchema; +/// # use serde::{Deserialize, Serialize}; +/// #[derive(AvroSchema, Serialize, Deserialize)] +/// struct StructWithBigDecimal<'a> { +/// #[avro(with)] +/// #[serde(with = "apache_avro::serde::bigdecimal")] +/// decimal: BigDecimal, +/// } +/// ``` +/// +/// [`BigDecimal`]: ::bigdecimal::BigDecimal +/// [`Schema::BigDecimal`]: crate::Schema::BigDecimal +/// [`Schema::String`]: crate::Schema::String +/// [`apache_avro::serde::bigdecimal_opt`]: bigdecimal_opt +pub mod bigdecimal { + use std::collections::HashSet; + + use bigdecimal::BigDecimal; + use serde::{Deserializer, Serializer, de::Error as _, ser::Error as _}; + + use crate::{ + Schema, + bigdecimal::{big_decimal_as_bytes, deserialize_big_decimal}, + schema::{Name, NamespaceRef, RecordField}, + serde::with::BytesType, + }; + + /// Returns [`Schema::BigDecimal`] + pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Schema { + Schema::BigDecimal + } + + /// Returns `None` + pub fn get_record_fields_in_ctxt( + _: &mut HashSet<Name>, + _: NamespaceRef, + ) -> Option<Vec<RecordField>> { + None + } + + pub fn serialize<S>(decimal: &BigDecimal, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let _guard = super::BytesTypeGuard::set(BytesType::Bytes); + let decimal_bytes = big_decimal_as_bytes(decimal).map_err(S::Error::custom)?; + serde_bytes::serialize(&decimal_bytes, serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result<BigDecimal, D::Error> + where + D: Deserializer<'de>, + { + let _bytes_guard = super::BytesTypeGuard::set(BytesType::Bytes); + let _guard = super::BorrowedGuard::set(true); + // We don't use &'de [u8] here as the deserializer doesn't support that + let bytes: Vec<u8> = serde_bytes::deserialize(deserializer)?; + + deserialize_big_decimal(&bytes).map_err(D::Error::custom) + } +} + +/// (De)serialize [`Option<BigDecimal>`] as a `Schema::Union(Schema::Null, Schema::BigDecimal)` instead of a `Schema::Union(Schema::Null, Schema::String)`. +/// +/// This module is intended to be used through the Serde `with` attribute. +/// +/// Use [`apache_avro::serde::bigdecimal`] for non-optional big decimals values. +/// +/// When used with different serialization formats, this will write bytes. +/// +/// See usage with below example: +/// ``` +/// # use apache_avro::AvroSchema; +/// # use serde::{Deserialize, Serialize}; +/// #[derive(AvroSchema, Serialize, Deserialize)] +/// struct StructWithBigDecimal<'a> { +/// #[avro(with)] +/// #[serde(with = "apache_avro::serde::bigdecimal_opt")] +/// decimal: Option<BigDecimal>, +/// } +/// ``` +/// +/// [`Option<BigDecimal>`]: ::bigdecimal::BigDecimal +/// [`apache_avro::serde::bigdecimal`]: bigdecimal +pub mod bigdecimal_opt { + use std::collections::HashSet; + + use bigdecimal::BigDecimal; + use serde::{Deserializer, Serializer, de::Error as _, ser::Error as _}; + + use crate::{ + Schema, + bigdecimal::{big_decimal_as_bytes, deserialize_big_decimal}, + schema::{Name, NamespaceRef, RecordField, UnionSchema}, + serde::with::BytesType, + }; + + /// Returns `Schema::Union(Schema::Null, Schema::BigDecimal)` + pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Schema { + Schema::Union( + UnionSchema::new(vec![Schema::Null, Schema::BigDecimal]) + .expect("This is a valid union"), + ) + } + + /// Returns `None` + pub fn get_record_fields_in_ctxt( + _: &mut HashSet<Name>, + _: NamespaceRef, + ) -> Option<Vec<RecordField>> { + None + } + + pub fn serialize<S>(decimal: &Option<BigDecimal>, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let _guard = super::BytesTypeGuard::set(BytesType::Bytes); + if let Some(decimal) = decimal { + let decimal_bytes = big_decimal_as_bytes(decimal).map_err(S::Error::custom)?; + serde_bytes::serialize(&Some(decimal_bytes), serializer) + } else { + serde_bytes::serialize(&None::<Vec<u8>>, serializer) + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<BigDecimal>, D::Error> + where + D: Deserializer<'de>, + { + let _bytes_guard = super::BytesTypeGuard::set(BytesType::Bytes); + let _guard = super::BorrowedGuard::set(true); + let bytes: Option<Vec<u8>> = serde_bytes::deserialize(deserializer)?; + if let Some(bytes) = bytes { + deserialize_big_decimal(&bytes) + .map(Some) + .map_err(D::Error::custom) + } else { + Ok(None) + } + } +} + +/// (De)serialize an Rust array (`[T; N]`) as an Avro [`Schema::Array`]. +/// +/// This module is intended to be used through the Serde `with` attribute. +/// +/// Use [`apache_avro::serde::array_opt`] for optional array values. +/// +/// See usage with below example: +/// ``` +/// # use apache_avro::AvroSchema; +/// # use serde::{Deserialize, Serialize}; +/// #[derive(AvroSchema, Serialize, Deserialize)] +/// struct StructWithBytes<'a> { +/// #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<i32>)] +/// #[serde(with = "apache_avro::serde::array")] +/// array: [i32; 10], +/// } +/// ``` +/// +/// [`apache_avro::serde::array_opt`]: array_opt +/// [`Schema::Array`]: crate::schema::Schema::Array +pub mod array { + use crate::{ + AvroSchemaComponent, Schema, + schema::{Name, NamespaceRef, RecordField}, + }; + use serde::de::DeserializeOwned; + use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _}; + use std::collections::HashSet; + + /// Returns `Schema::Array(T::get_schema_in_ctxt())` + pub fn get_schema_in_ctxt<T: AvroSchemaComponent>( + named_schemas: &mut HashSet<Name>, + enclosing_namespace: NamespaceRef, + ) -> Schema { + Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace)).build() + } + + /// Returns `None` + pub fn get_record_fields_in_ctxt( + _: &mut HashSet<Name>, + _: NamespaceRef, + ) -> Option<Vec<RecordField>> { + None + } + + pub fn serialize<const N: usize, S, T>(value: &[T; N], serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + T: Serialize, + { + value.as_slice().serialize(serializer) + } + + pub fn deserialize<'de, const N: usize, D, T>(deserializer: D) -> Result<[T; N], D::Error> + where + D: Deserializer<'de>, + T: DeserializeOwned, + { + let bytes = <Vec<T> as Deserialize>::deserialize(deserializer)?; + bytes.try_into().map_err(|v: Vec<T>| { + D::Error::custom(format!( + "Deserialized array has length {} which does not match array length of {N}", + v.len() + )) + }) + } +} + +/// (De)serialize an optional Rust array (`Option<[T; N]>`) as an Avro `Schema::Union([Schema::Null, Schema::Array])`. +/// +/// This module is intended to be used through the Serde `with` attribute. +/// +/// Use [`apache_avro::serde::array`] for non-optional array values. +/// +/// When used with different serialization formats, this is equivalent to [`serde_bytes`]. +/// +/// See usage with below example: +/// ``` +/// # use apache_avro::AvroSchema; +/// # use serde::{Deserialize, Serialize}; +/// #[derive(AvroSchema, Serialize, Deserialize)] +/// struct StructWithBytes<'a> { +/// #[avro(with = apache_avro::serde::array_opt::get_schema_in_ctxt::<i32>)] +/// #[serde(with = "apache_avro::serde::array_opt")] +/// array: Option<[i32; 10]>, +/// } +/// ``` +/// +/// [`apache_avro::serde::array`]: mod@array +pub mod array_opt { + use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _}; + use std::collections::HashSet; + + use crate::{ + AvroSchemaComponent, Schema, + schema::{Name, NamespaceRef, RecordField, UnionSchema}, + }; + + /// Returns `Schema::Union(Schema::Null, Schema::Array(T::get_schema_in_ctxt()))` + pub fn get_schema_in_ctxt<T: AvroSchemaComponent>( + named_schemas: &mut HashSet<Name>, + enclosing_namespace: NamespaceRef, + ) -> Schema { + Schema::Union( + UnionSchema::new(vec![ + Schema::Null, + Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace)).build(), + ]) + .expect("This is a valid union"), + ) + } + + /// Returns `None` + pub fn get_record_fields_in_ctxt( + _: &mut HashSet<Name>, + _: NamespaceRef, + ) -> Option<Vec<RecordField>> { + None + } + + pub fn serialize<const N: usize, S, T>( + value: &Option<[T; N]>, + serializer: S, + ) -> Result<S::Ok, S::Error> + where + S: Serializer, + T: Serialize, + { + if let Some(array) = value { + Some(array.as_slice()).serialize(serializer) + } else { + None::<Vec<T>>.serialize(serializer) + } + } + + pub fn deserialize<'de, const N: usize, D, T>( + deserializer: D, + ) -> Result<Option<[T; N]>, D::Error> + where + D: Deserializer<'de>, + T: Deserialize<'de>, + { + let bytes = <Option<Vec<T>> as Deserialize>::deserialize(deserializer)?; + if let Some(bytes) = bytes { + Ok(Some(bytes.try_into().map_err(|v: Vec<T>| { + D::Error::custom(format!( + "Deserialized array has length {} which does not match array length of {N}", + v.len() + )) + })?)) + } else { + Ok(None) + } + } +} + #[cfg(test)] mod tests { use crate::{Schema, from_value, to_value, types::Value};
