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 fec1951 feat!: Add support for `char`,`u64`,`u128`,`i128` (#414)
fec1951 is described below
commit fec19517dc1bde4ac726a03d02e9c76add116295
Author: Kriskras99 <[email protected]>
AuthorDate: Tue Jan 20 08:10:33 2026 +0100
feat!: Add support for `char`,`u64`,`u128`,`i128` (#414)
* feat: Add support for `char`,`u64`,`u128`,`i128`
* fix: Return the correct amount of bytes written
* fix: proper deserialisation for `u64`/`u128`/`i128`
* fix: Fix error types and descriptions
---
avro/src/error.rs | 12 ++
avro/src/schema/mod.rs | 146 +++++++++++++--
avro/src/serde/de.rs | 406 +++++++++++++++++++++++++++++++++++-------
avro/src/serde/ser.rs | 54 +++++-
avro/src/serde/ser_schema.rs | 411 ++++++++++++++++++++++++++++++++++++++++++-
avro_derive/tests/derive.rs | 58 ++++++
6 files changed, 1006 insertions(+), 81 deletions(-)
diff --git a/avro/src/error.rs b/avro/src/error.rs
index 8e42ce1..cbb00c0 100644
--- a/avro/src/error.rs
+++ b/avro/src/error.rs
@@ -329,6 +329,18 @@ pub enum Details {
#[error("Cannot convert i32 to usize: {1}")]
ConvertI32ToUsize(#[source] std::num::TryFromIntError, i32),
+ #[error("Cannot convert i64 to u64: {1}")]
+ ConvertI64ToU64(#[source] std::num::TryFromIntError, i64),
+
+ #[error("Cannot convert i32 to u64: {1}")]
+ ConvertI32ToU64(#[source] std::num::TryFromIntError, i32),
+
+ #[error("Cannot convert i64 to u128: {1}")]
+ ConvertI64ToU128(#[source] std::num::TryFromIntError, i64),
+
+ #[error("Cannot convert i32 to u128: {1}")]
+ ConvertI32ToU128(#[source] std::num::TryFromIntError, i32),
+
#[error("Invalid JSON value for decimal precision/scale integer: {0}")]
GetPrecisionOrScaleFromJson(serde_json::Number),
diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs
index 90a48b8..42a0b80 100644
--- a/avro/src/schema/mod.rs
+++ b/avro/src/schema/mod.rs
@@ -2258,10 +2258,6 @@ fn field_ordering_position(field: &str) -> Option<usize>
{
/// through `derive` feature. Do not implement directly!
/// Implement [`AvroSchemaComponent`] to get this trait
/// through a blanket implementation.
-///
-/// Note: This trait is **not** implemented for `char` and `u64`. `char` is a
32-bit value
-/// that does not have a logical mapping to an Avro schema. `u64` is too large
to fit in a
-/// Avro `long`.
pub trait AvroSchema {
fn get_schema() -> Schema;
}
@@ -2270,10 +2266,6 @@ pub trait AvroSchema {
/// implementation available through `derive` feature. This is what is
implemented by
/// the `derive(AvroSchema)` macro.
///
-/// Note: This trait is **not** implemented for `char` and `u64`. `char` is a
32-bit value
-/// that does not have a logical mapping to an Avro schema. `u64` is too large
to fit in a
-/// Avro `long`.
-///
/// # Implementation guide
///
/// ### Simple implementation
@@ -2357,6 +2349,7 @@ impl_schema!(f32, Schema::Float);
impl_schema!(f64, Schema::Double);
impl_schema!(String, Schema::String);
impl_schema!(str, Schema::String);
+impl_schema!(char, Schema::String);
impl<T> AvroSchemaComponent for &T
where
@@ -2516,6 +2509,78 @@ 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 {
+ let name = Name::new("u64")
+ .expect("Name is valid")
+ .fully_qualified_name(enclosing_namespace);
+ if named_schemas.contains_key(&name) {
+ Schema::Ref { name }
+ } else {
+ let schema = Schema::Fixed(FixedSchema {
+ name: name.clone(),
+ aliases: None,
+ doc: None,
+ size: 8,
+ default: None,
+ attributes: Default::default(),
+ });
+ named_schemas.insert(name, schema.clone());
+ schema
+ }
+ }
+}
+
+impl AvroSchemaComponent for u128 {
+ /// The schema is [`Schema::Fixed`] of size 16 with the name `u128`.
+ #[expect(clippy::map_entry, reason = "We don't use the value from the
map")]
+ fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace:
&Namespace) -> Schema {
+ let name = Name::new("u128")
+ .expect("Name is valid")
+ .fully_qualified_name(enclosing_namespace);
+ if named_schemas.contains_key(&name) {
+ Schema::Ref { name }
+ } else {
+ let schema = Schema::Fixed(FixedSchema {
+ name: name.clone(),
+ aliases: None,
+ doc: None,
+ size: 16,
+ default: None,
+ attributes: Default::default(),
+ });
+ named_schemas.insert(name, schema.clone());
+ schema
+ }
+ }
+}
+
+impl AvroSchemaComponent for i128 {
+ /// The schema is [`Schema::Fixed`] of size 16 with the name `i128`.
+ #[expect(clippy::map_entry, reason = "We don't use the value from the
map")]
+ fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace:
&Namespace) -> Schema {
+ let name = Name::new("i128")
+ .expect("Name is valid")
+ .fully_qualified_name(enclosing_namespace);
+ if named_schemas.contains_key(&name) {
+ Schema::Ref { name }
+ } else {
+ let schema = Schema::Fixed(FixedSchema {
+ name: name.clone(),
+ aliases: None,
+ doc: None,
+ size: 16,
+ default: None,
+ attributes: Default::default(),
+ });
+ named_schemas.insert(name, schema.clone());
+ schema
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -7367,8 +7432,69 @@ mod tests {
Schema::union(vec![
Schema::Null,
Schema::array(Schema::array(Schema::Int))
- ])
- .unwrap()
+ ])?
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_char() -> TestResult {
+ let schema = char::get_schema();
+ assert_eq!(schema, Schema::String);
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_u64() -> TestResult {
+ let schema = u64::get_schema();
+ assert_eq!(
+ schema,
+ Schema::Fixed(FixedSchema {
+ name: Name::new("u64")?,
+ aliases: None,
+ doc: None,
+ size: 8,
+ default: None,
+ attributes: Default::default(),
+ })
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_i128() -> TestResult {
+ let schema = i128::get_schema();
+ assert_eq!(
+ schema,
+ Schema::Fixed(FixedSchema {
+ name: Name::new("i128")?,
+ aliases: None,
+ doc: None,
+ size: 16,
+ default: None,
+ attributes: Default::default(),
+ })
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_u128() -> TestResult {
+ let schema = u128::get_schema();
+ assert_eq!(
+ schema,
+ Schema::Fixed(FixedSchema {
+ name: Name::new("u128")?,
+ aliases: None,
+ doc: None,
+ size: 16,
+ default: None,
+ attributes: Default::default(),
+ })
);
Ok(())
diff --git a/avro/src/serde/de.rs b/avro/src/serde/de.rs
index ce42576..257dd6a 100644
--- a/avro/src/serde/de.rs
+++ b/avro/src/serde/de.rs
@@ -22,6 +22,7 @@ use serde::{
de::{self, DeserializeSeed, Deserializer as _, Visitor},
forward_to_deserialize_any,
};
+use std::ops::Deref;
use std::{
collections::{
HashMap,
@@ -382,26 +383,241 @@ impl<'de> de::Deserializer<'de> for &Deserializer<'de> {
}
forward_to_deserialize_any! {
- bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64
+ bool i8 i16 i32 i64 u8 u16 u32 f32 f64
}
- fn deserialize_char<V>(self, _: V) -> Result<V::Value, Self::Error>
+ fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
- Err(de::Error::custom("avro does not support char"))
+ match self.input {
+ Value::Int(i) | Value::Date(i) | Value::TimeMillis(i) => {
+ let n = u64::try_from(*i).map_err(|e|
Details::ConvertI32ToU64(e, *i))?;
+ visitor.visit_u64(n)
+ }
+ Value::Long(i)
+ | Value::TimeMicros(i)
+ | Value::TimestampMillis(i)
+ | Value::TimestampMicros(i)
+ | Value::TimestampNanos(i)
+ | Value::LocalTimestampMillis(i)
+ | Value::LocalTimestampMicros(i)
+ | Value::LocalTimestampNanos(i) => {
+ let n = u64::try_from(*i).map_err(|e|
Details::ConvertI64ToU64(e, *i))?;
+ visitor.visit_u64(n)
+ }
+ Value::Fixed(8, bytes) => {
+ let n =
u64::from_le_bytes(bytes.as_slice().try_into().expect("Size is 8"));
+ visitor.visit_u64(n)
+ }
+ Value::Union(_i, x) => match x.deref() {
+ Value::Int(i) | Value::Date(i) | Value::TimeMillis(i) => {
+ let n = u64::try_from(*i).map_err(|e|
Details::ConvertI32ToU64(e, *i))?;
+ visitor.visit_u64(n)
+ }
+ Value::Long(i)
+ | Value::TimeMicros(i)
+ | Value::TimestampMillis(i)
+ | Value::TimestampMicros(i)
+ | Value::TimestampNanos(i)
+ | Value::LocalTimestampMillis(i)
+ | Value::LocalTimestampMicros(i)
+ | Value::LocalTimestampNanos(i) => {
+ let n = u64::try_from(*i).map_err(|e|
Details::ConvertI64ToU64(e, *i))?;
+ visitor.visit_u64(n)
+ }
+ Value::Fixed(8, bytes) => {
+ let n =
u64::from_le_bytes(bytes.as_slice().try_into().expect("Size is 8"));
+ visitor.visit_u64(n)
+ }
+ _ => Err(de::Error::custom(format!(
+ "Expected a Int|Long|Fixed(8), but got {:?}",
+ self.input
+ ))),
+ },
+ _ => Err(de::Error::custom(format!(
+ "Expected a Int|Long|Fixed(8), but got {:?}",
+ self.input
+ ))),
+ }
+ }
+
+ fn deserialize_u128<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+ where
+ V: Visitor<'de>,
+ {
+ match self.input {
+ Value::Int(i) | Value::Date(i) | Value::TimeMillis(i) => {
+ let n = u128::try_from(*i).map_err(|e|
Details::ConvertI32ToU128(e, *i))?;
+ visitor.visit_u128(n)
+ }
+ Value::Long(i)
+ | Value::TimeMicros(i)
+ | Value::TimestampMillis(i)
+ | Value::TimestampMicros(i)
+ | Value::TimestampNanos(i)
+ | Value::LocalTimestampMillis(i)
+ | Value::LocalTimestampMicros(i)
+ | Value::LocalTimestampNanos(i) => {
+ let n = u128::try_from(*i).map_err(|e|
Details::ConvertI64ToU128(e, *i))?;
+ visitor.visit_u128(n)
+ }
+ Value::Fixed(16, bytes) => {
+ let n =
u128::from_le_bytes(bytes.as_slice().try_into().expect("Size is 16"));
+ visitor.visit_u128(n)
+ }
+ Value::Union(_i, x) => match x.deref() {
+ Value::Int(i) | Value::Date(i) | Value::TimeMillis(i) => {
+ let n = u128::try_from(*i).map_err(|e|
Details::ConvertI32ToU128(e, *i))?;
+ visitor.visit_u128(n)
+ }
+ Value::Long(i)
+ | Value::TimeMicros(i)
+ | Value::TimestampMillis(i)
+ | Value::TimestampMicros(i)
+ | Value::TimestampNanos(i)
+ | Value::LocalTimestampMillis(i)
+ | Value::LocalTimestampMicros(i)
+ | Value::LocalTimestampNanos(i) => {
+ let n = u128::try_from(*i).map_err(|e|
Details::ConvertI64ToU128(e, *i))?;
+ visitor.visit_u128(n)
+ }
+ Value::Fixed(16, bytes) => {
+ let n =
u128::from_le_bytes(bytes.as_slice().try_into().expect("Size is 16"));
+ visitor.visit_u128(n)
+ }
+ _ => Err(de::Error::custom(format!(
+ "Expected a Int|Long|Fixed(16), but got {:?}",
+ self.input
+ ))),
+ },
+ _ => Err(de::Error::custom(format!(
+ "Expected a Int|Long|Fixed(16), but got {:?}",
+ self.input
+ ))),
+ }
+ }
+
+ fn deserialize_i128<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+ where
+ V: Visitor<'de>,
+ {
+ match self.input {
+ Value::Int(i) | Value::Date(i) | Value::TimeMillis(i) => {
+ visitor.visit_i128(i128::from(*i))
+ }
+ Value::Long(i)
+ | Value::TimeMicros(i)
+ | Value::TimestampMillis(i)
+ | Value::TimestampMicros(i)
+ | Value::TimestampNanos(i)
+ | Value::LocalTimestampMillis(i)
+ | Value::LocalTimestampMicros(i)
+ | Value::LocalTimestampNanos(i) =>
visitor.visit_i128(i128::from(*i)),
+ Value::Fixed(16, bytes) => {
+ let n =
i128::from_le_bytes(bytes.as_slice().try_into().expect("Size is 16"));
+ visitor.visit_i128(n)
+ }
+ Value::Union(_i, x) => match x.deref() {
+ Value::Int(i) | Value::Date(i) | Value::TimeMillis(i) => {
+ visitor.visit_i128(i128::from(*i))
+ }
+ Value::Long(i)
+ | Value::TimeMicros(i)
+ | Value::TimestampMillis(i)
+ | Value::TimestampMicros(i)
+ | Value::TimestampNanos(i)
+ | Value::LocalTimestampMillis(i)
+ | Value::LocalTimestampMicros(i)
+ | Value::LocalTimestampNanos(i) =>
visitor.visit_i128(i128::from(*i)),
+ Value::Fixed(16, bytes) => {
+ let n =
i128::from_le_bytes(bytes.as_slice().try_into().expect("Size is 16"));
+ visitor.visit_i128(n)
+ }
+ _ => Err(de::Error::custom(format!(
+ "Expected a Int|Long|Fixed(16), but got {:?}",
+ self.input
+ ))),
+ },
+ _ => Err(de::Error::custom(format!(
+ "Expected a Int|Long|Fixed(16), but got {:?}",
+ self.input
+ ))),
+ }
+ }
+
+ fn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+ where
+ V: Visitor<'de>,
+ {
+ match self.input {
+ Value::String(s) => {
+ if s.chars().count() == 1 {
+ visitor.visit_char(s.chars().next().expect("There is
exactly one char"))
+ } else {
+ Err(de::Error::custom(format!("Tried to deserialize char
from string, but the string was longer than one char: {s}")))
+ }
+ }
+ Value::Bytes(bytes) => std::str::from_utf8(bytes)
+ .map_err(|e| de::Error::custom(e.to_string()))
+ .and_then(|s| {
+ if s.chars().count() == 1 {
+ visitor.visit_char(s.chars().next().expect("There is
exactly one char"))
+ } else {
+ Err(de::Error::custom(format!("Tried to deserialize
char from a byte array, but the byte array was longer than one char: {}",
s.len())))
+ }
+ }
+ ),
+ Value::Fixed(4, bytes) => {
+
visitor.visit_char(char::from_u32(u32::from_le_bytes(bytes.as_slice().try_into().expect("Size
is 4"))).ok_or_else(|| <Self::Error as de::Error>::custom("Tried to
deserialize char from fixed, but was invalid value"))?)
+ }
+ Value::Union(_i, x) => match x.deref() {
+ Value::String(s) => {
+ if s.chars().count() == 1 {
+ visitor.visit_char(s.chars().next().expect("There is
exactly one char"))
+ } else {
+ Err(de::Error::custom(format!("Tried to deserialize
char from string, but the string was longer than one char: {s}")))
+ }
+ }
+ Value::Bytes(bytes) => std::str::from_utf8(bytes)
+ .map_err(|e| de::Error::custom(e.to_string()))
+ .and_then(|s| {
+ if s.chars().count() == 1 {
+ visitor.visit_char(s.chars().next().expect("There
is exactly one char"))
+ } else {
+ Err(de::Error::custom(format!("Tried to
deserialize char from a byte array, but the byte array was longer than one
char: {}", s.len())))
+ }
+ }
+ ),
+ Value::Fixed(4, bytes) => {
+
visitor.visit_char(char::from_u32(u32::from_le_bytes(bytes.as_slice().try_into().expect("Size
is 4"))).ok_or_else(|| <Self::Error as de::Error>::custom("Tried to
deserialize char from fixed, but was invalid value"))?)
+ }
+ _ => Err(de::Error::custom(format!("Expected a
String|Bytes|Fixed(4) for char, but got {:?}", self.input)))
+ },
+ _ => Err(de::Error::custom(format!("Expected a
String|Bytes|Fixed(4) for char, but got {:?}", self.input)))
+ }
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
- match *self.input {
- Value::String(ref s) => visitor.visit_borrowed_str(s),
- Value::Bytes(ref bytes) | Value::Fixed(_, ref bytes) =>
::std::str::from_utf8(bytes)
+ match self.input {
+ Value::String(s) => visitor.visit_borrowed_str(s),
+ Value::Bytes(bytes) | Value::Fixed(_, bytes) =>
std::str::from_utf8(bytes)
.map_err(|e| de::Error::custom(e.to_string()))
.and_then(|s| visitor.visit_borrowed_str(s)),
- Value::Uuid(ref u) => visitor.visit_str(&u.to_string()),
+ Value::Uuid(u) => visitor.visit_str(&u.to_string()),
+ Value::Union(_i, x) => match x.deref() {
+ Value::String(s) => visitor.visit_borrowed_str(s),
+ Value::Bytes(bytes) | Value::Fixed(_, bytes) =>
std::str::from_utf8(bytes)
+ .map_err(|e| de::Error::custom(e.to_string()))
+ .and_then(|s| visitor.visit_borrowed_str(s)),
+ Value::Uuid(u) => visitor.visit_str(&u.to_string()),
+ _ => Err(de::Error::custom(format!(
+ "Expected a String|Bytes|Fixed|Uuid, but got {x:?}"
+ ))),
+ },
_ => Err(de::Error::custom(format!(
"Expected a String|Bytes|Fixed|Uuid, but got {:?}",
self.input
@@ -413,22 +629,18 @@ impl<'de> de::Deserializer<'de> for &Deserializer<'de> {
where
V: Visitor<'de>,
{
- match *self.input {
- Value::Enum(_, ref s) | Value::String(ref s) =>
visitor.visit_borrowed_str(s),
- Value::Bytes(ref bytes) | Value::Fixed(_, ref bytes) => {
- String::from_utf8(bytes.to_owned())
+ match self.input {
+ Value::Enum(_, s) | Value::String(s) =>
visitor.visit_borrowed_str(s),
+ Value::Bytes(bytes) | Value::Fixed(_, bytes) =>
String::from_utf8(bytes.to_owned())
+ .map_err(|e| de::Error::custom(e.to_string()))
+ .and_then(|s| visitor.visit_string(s)),
+ Value::Uuid(u) => visitor.visit_str(&u.to_string()),
+ Value::Union(_i, x) => match x.deref() {
+ Value::String(s) => visitor.visit_borrowed_str(s),
+ Value::Bytes(bytes) | Value::Fixed(_, bytes) =>
String::from_utf8(bytes.to_owned())
.map_err(|e| de::Error::custom(e.to_string()))
- .and_then(|s| visitor.visit_string(s))
- }
- Value::Uuid(ref u) => visitor.visit_str(&u.to_string()),
- Value::Union(_i, ref x) => match **x {
- Value::String(ref s) => visitor.visit_borrowed_str(s),
- Value::Bytes(ref bytes) | Value::Fixed(_, ref bytes) => {
- String::from_utf8(bytes.to_owned())
- .map_err(|e| de::Error::custom(e.to_string()))
- .and_then(|s| visitor.visit_string(s))
- }
- Value::Uuid(ref u) => visitor.visit_str(&u.to_string()),
+ .and_then(|s| visitor.visit_string(s)),
+ Value::Uuid(u) => visitor.visit_str(&u.to_string()),
_ => Err(de::Error::custom(format!(
"Expected a String|Bytes|Fixed|Uuid, but got {x:?}"
))),
@@ -444,18 +656,18 @@ impl<'de> de::Deserializer<'de> for &Deserializer<'de> {
where
V: Visitor<'de>,
{
- match *self.input {
- Value::String(ref s) => visitor.visit_bytes(s.as_bytes()),
- Value::Bytes(ref bytes) | Value::Fixed(_, ref bytes) => {
+ match self.input {
+ Value::String(s) => visitor.visit_bytes(s.as_bytes()),
+ Value::Bytes(bytes) | Value::Fixed(_, bytes) => {
if DE_BYTES_BORROWED.get() {
visitor.visit_borrowed_bytes(bytes)
} else {
visitor.visit_bytes(bytes)
}
}
- Value::Uuid(ref u) => visitor.visit_bytes(u.as_bytes()),
- Value::Decimal(ref d) => visitor.visit_bytes(&d.to_vec()?),
- Value::Duration(ref d) => {
+ Value::Uuid(u) => visitor.visit_bytes(u.as_bytes()),
+ Value::Decimal(d) => visitor.visit_bytes(&d.to_vec()?),
+ Value::Duration(d) => {
let d_bytes: [u8; 12] = d.into();
visitor.visit_bytes(&d_bytes[..])
}
@@ -470,14 +682,14 @@ impl<'de> de::Deserializer<'de> for &Deserializer<'de> {
where
V: Visitor<'de>,
{
- match *self.input {
- Value::String(ref s) =>
visitor.visit_byte_buf(s.clone().into_bytes()),
- Value::Bytes(ref bytes) | Value::Fixed(_, ref bytes) => {
+ match self.input {
+ Value::String(s) => visitor.visit_byte_buf(s.clone().into_bytes()),
+ Value::Bytes(bytes) | Value::Fixed(_, bytes) => {
visitor.visit_byte_buf(bytes.to_owned())
}
- Value::Uuid(ref u) =>
visitor.visit_byte_buf(Vec::from(u.as_bytes())),
- Value::Decimal(ref d) => visitor.visit_byte_buf(d.to_vec()?),
- Value::Duration(ref d) => {
+ Value::Uuid(u) => visitor.visit_byte_buf(Vec::from(u.as_bytes())),
+ Value::Decimal(d) => visitor.visit_byte_buf(d.to_vec()?),
+ Value::Duration(d) => {
let d_bytes: [u8; 12] = d.into();
visitor.visit_byte_buf(Vec::from(d_bytes))
}
@@ -492,9 +704,9 @@ impl<'de> de::Deserializer<'de> for &Deserializer<'de> {
where
V: Visitor<'de>,
{
- match *self.input {
- Value::Union(_i, ref inner) if inner.as_ref() == &Value::Null =>
visitor.visit_none(),
- Value::Union(_i, ref inner) =>
visitor.visit_some(&Deserializer::new(inner)),
+ match self.input {
+ Value::Union(_i, inner) if inner.as_ref() == &Value::Null =>
visitor.visit_none(),
+ Value::Union(_i, inner) =>
visitor.visit_some(&Deserializer::new(inner)),
_ => Err(de::Error::custom(format!(
"Expected a Union, but got {:?}",
self.input
@@ -506,9 +718,9 @@ impl<'de> de::Deserializer<'de> for &Deserializer<'de> {
where
V: Visitor<'de>,
{
- match *self.input {
+ match self.input {
Value::Null => visitor.visit_unit(),
- Value::Union(_i, ref x) => match **x {
+ Value::Union(_i, x) => match **x {
Value::Null => visitor.visit_unit(),
_ => Err(de::Error::custom(format!(
"Expected a Null, but got {:?}",
@@ -548,10 +760,10 @@ impl<'de> de::Deserializer<'de> for &Deserializer<'de> {
where
V: Visitor<'de>,
{
- match *self.input {
- Value::Array(ref items) =>
visitor.visit_seq(SeqDeserializer::new(items)),
- Value::Union(_i, ref inner) => match **inner {
- Value::Array(ref items) =>
visitor.visit_seq(SeqDeserializer::new(items)),
+ match self.input {
+ Value::Array(items) =>
visitor.visit_seq(SeqDeserializer::new(items)),
+ Value::Union(_i, inner) => match inner.deref() {
+ Value::Array(items) =>
visitor.visit_seq(SeqDeserializer::new(items)),
Value::Null => visitor.visit_seq(SeqDeserializer::new(&[])),
_ => Err(de::Error::custom(format!(
"Expected an Array or Null, but got: {inner:?}"
@@ -587,9 +799,9 @@ impl<'de> de::Deserializer<'de> for &Deserializer<'de> {
where
V: Visitor<'de>,
{
- match *self.input {
- Value::Map(ref items) =>
visitor.visit_map(MapDeserializer::new(items)),
- Value::Record(ref fields) =>
visitor.visit_map(RecordDeserializer::new(fields)),
+ match self.input {
+ Value::Map(items) =>
visitor.visit_map(MapDeserializer::new(items)),
+ Value::Record(fields) =>
visitor.visit_map(RecordDeserializer::new(fields)),
_ => Err(de::Error::custom(format_args!(
"Expected a record or a map. Got: {:?}",
&self.input
@@ -606,10 +818,10 @@ impl<'de> de::Deserializer<'de> for &Deserializer<'de> {
where
V: Visitor<'de>,
{
- match *self.input {
- Value::Record(ref fields) =>
visitor.visit_map(RecordDeserializer::new(fields)),
- Value::Union(_i, ref inner) => match **inner {
- Value::Record(ref fields) =>
visitor.visit_map(RecordDeserializer::new(fields)),
+ match self.input {
+ Value::Record(fields) =>
visitor.visit_map(RecordDeserializer::new(fields)),
+ Value::Union(_i, inner) => match inner.deref() {
+ Value::Record(fields) =>
visitor.visit_map(RecordDeserializer::new(fields)),
Value::Null => visitor.visit_map(RecordDeserializer::new(&[])),
_ => Err(de::Error::custom(format!(
"Expected a Record or Null, got: {inner:?}"
@@ -631,26 +843,26 @@ impl<'de> de::Deserializer<'de> for &Deserializer<'de> {
where
V: Visitor<'de>,
{
- match *self.input {
+ match self.input {
// This branch can be anything...
- Value::Record(ref fields) =>
visitor.visit_enum(EnumDeserializer::new(fields)),
- Value::String(ref field) =>
visitor.visit_enum(EnumUnitDeserializer::new(field)),
- Value::Union(idx, ref inner) => {
- if (idx as usize) < variants.len() {
+ Value::Record(fields) =>
visitor.visit_enum(EnumDeserializer::new(fields)),
+ Value::String(field) =>
visitor.visit_enum(EnumUnitDeserializer::new(field)),
+ Value::Union(idx, inner) => {
+ if (*idx as usize) < variants.len() {
visitor.visit_enum(UnionDeserializer::new(
- variants[idx as usize],
+ variants[*idx as usize],
inner.as_ref(),
))
} else {
Err(Details::GetUnionVariant {
- index: idx as i64,
+ index: *idx as i64,
num_variants: variants.len(),
}
.into())
}
}
// This has to be a unit Enum
- Value::Enum(_index, ref field) =>
visitor.visit_enum(EnumUnitDeserializer::new(field)),
+ Value::Enum(_index, field) =>
visitor.visit_enum(EnumUnitDeserializer::new(field)),
_ => Err(de::Error::custom(format!(
"Expected a Record|Enum, but got {:?}",
self.input
@@ -727,8 +939,7 @@ impl<'de> de::MapAccess<'de> for RecordDeserializer<'de> {
K: DeserializeSeed<'de>,
{
match self.input.next() {
- Some(item) => {
- let (ref field, ref value) = *item;
+ Some((field, value)) => {
self.value = Some(value);
seed.deserialize(StringDeserializer {
input: field.clone(),
@@ -1667,4 +1878,79 @@ mod tests {
assert_eq!(raw_bytes.unwrap().0, expected_bytes);
Ok(())
}
+
+ #[test]
+ fn avro_rs_414_deserialize_char_from_string() -> TestResult {
+ let value = Value::String('a'.to_string());
+ let result = from_value::<char>(&value)?;
+ assert_eq!(result, 'a');
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_deserialize_char_from_bytes() -> TestResult {
+ let value = Value::Bytes([b'a'].to_vec());
+ let result = from_value::<char>(&value)?;
+ assert_eq!(result, 'a');
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_deserialize_char_from_fixed() -> TestResult {
+ let value = Value::Fixed(4, [b'a', 0, 0, 0].to_vec());
+ let result = from_value::<char>(&value)?;
+ assert_eq!(result, 'a');
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_deserialize_char_from_long_string() -> TestResult {
+ let value = Value::String("avro".to_string());
+ let result = from_value::<char>(&value).unwrap_err().to_string();
+ assert_eq!(
+ result,
+ "Failed to deserialize Avro value into value: Tried to deserialize
char from string, but the string was longer than one char: avro"
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_deserialize_char_from_long_bytes() -> TestResult {
+ let value = Value::Bytes(b"avro".to_vec());
+ let result = from_value::<char>(&value).unwrap_err().to_string();
+ assert_eq!(
+ result,
+ "Failed to deserialize Avro value into value: Tried to deserialize
char from a byte array, but the byte array was longer than one char: 4"
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_deserialize_char_from_long_fixed() -> TestResult {
+ let value = Value::Fixed(5, [b'a', 0, 0, 0, 0].to_vec());
+ let result = from_value::<char>(&value).unwrap_err().to_string();
+ assert_eq!(
+ result,
+ "Failed to deserialize Avro value into value: Expected a
String|Bytes|Fixed(4) for char, but got Fixed(5, [97, 0, 0, 0, 0])"
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_deserialize_char_from_short_fixed() -> TestResult {
+ let value = Value::Fixed(3, [b'a', 0, 0].to_vec());
+ let result = from_value::<char>(&value).unwrap_err().to_string();
+ assert_eq!(
+ result,
+ "Failed to deserialize Avro value into value: Expected a
String|Bytes|Fixed(4) for char, but got Fixed(3, [97, 0, 0])"
+ );
+
+ Ok(())
+ }
}
diff --git a/avro/src/serde/ser.rs b/avro/src/serde/ser.rs
index d78f501..b983358 100644
--- a/avro/src/serde/ser.rs
+++ b/avro/src/serde/ser.rs
@@ -22,7 +22,7 @@ use crate::{
types::Value,
};
use serde::{Serialize, ser};
-use std::{collections::HashMap, iter::once};
+use std::collections::HashMap;
#[derive(Clone, Default)]
pub struct Serializer {}
@@ -154,11 +154,15 @@ impl<'b> ser::Serializer for &'b mut Serializer {
}
fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> {
- if v <= i64::MAX as u64 {
- self.serialize_i64(v as i64)
- } else {
- Err(ser::Error::custom("u64 is too large"))
- }
+ Ok(Value::Fixed(8, v.to_le_bytes().to_vec()))
+ }
+
+ fn serialize_i128(self, v: i128) -> Result<Self::Ok, Self::Error> {
+ Ok(Value::Fixed(16, v.to_le_bytes().to_vec()))
+ }
+
+ fn serialize_u128(self, v: u128) -> Result<Self::Ok, Self::Error> {
+ Ok(Value::Fixed(16, v.to_le_bytes().to_vec()))
}
fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> {
@@ -170,7 +174,7 @@ impl<'b> ser::Serializer for &'b mut Serializer {
}
fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> {
- self.serialize_str(&once(v).collect::<String>())
+ self.serialize_str(&v.to_string())
}
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
@@ -1033,4 +1037,40 @@ mod tests {
assert!(!ser.is_human_readable());
}
+
+ #[test]
+ fn avro_rs_414_char_to_value() -> TestResult {
+ let value = to_value('s')?;
+
+ assert_eq!(value, Value::String("s".to_string()));
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_u64_to_value() -> TestResult {
+ let value = to_value(u64::MAX)?;
+
+ assert_eq!(value, Value::Fixed(8, u64::MAX.to_le_bytes().to_vec()));
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_u128_to_value() -> TestResult {
+ let value = to_value(u128::MAX)?;
+
+ assert_eq!(value, Value::Fixed(16, u128::MAX.to_le_bytes().to_vec()));
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_i128_to_value() -> TestResult {
+ let value = to_value(i128::MAX)?;
+
+ assert_eq!(value, Value::Fixed(16, i128::MAX.to_le_bytes().to_vec()));
+
+ Ok(())
+ }
}
diff --git a/avro/src/serde/ser_schema.rs b/avro/src/serde/ser_schema.rs
index 3909875..5a092c5 100644
--- a/avro/src/serde/ser_schema.rs
+++ b/avro/src/serde/ser_schema.rs
@@ -647,7 +647,9 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
let mut bytes_written: usize = 0;
bytes_written += encode_long(bytes.len() as i64, &mut self.writer)?;
- bytes_written +=
self.writer.write(bytes).map_err(Details::WriteBytes)?;
+ // write_all() will retry when the error is ErrorKind::Interrupted
(happens mostly on network storage)
+ self.writer.write_all(bytes).map_err(Details::WriteBytes)?;
+ bytes_written += bytes.len();
Ok(bytes_written)
}
@@ -784,6 +786,41 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
}
}
+ fn serialize_i128_with_schema(&mut self, value: i128, schema: &Schema) ->
Result<usize, Error> {
+ let create_error = |cause: String| {
+ Error::new(Details::SerializeValueWithSchema {
+ value_type: "i128",
+ value: format!("{value}. Cause: {cause}"),
+ schema: schema.clone(),
+ })
+ };
+
+ match schema {
+ Schema::Fixed(fixed) if fixed.size == 16 && fixed.name.name ==
"i128" => {
+ self.writer
+ .write_all(&value.to_le_bytes())
+ .map_err(Details::WriteBytes)?;
+ Ok(16)
+ }
+ Schema::Union(union_schema) => {
+ for (i, variant_schema) in
union_schema.schemas.iter().enumerate() {
+ match variant_schema {
+ Schema::Fixed(fixed) if fixed.size == 16 &&
fixed.name.name == "i128" => {
+ encode_int(i as i32, &mut *self.writer)?;
+ return self.serialize_i128_with_schema(value,
variant_schema);
+ }
+ _ => { /* skip */ }
+ }
+ }
+ Err(create_error(format!(
+ "Cannot find a Fixed(size = 16, name = \"i128\") schema in
{:?}",
+ union_schema.schemas
+ )))
+ }
+ expected => Err(create_error(format!("Expected {expected}. Got:
i128"))),
+ }
+ }
+
fn serialize_u8_with_schema(&mut self, value: u8, schema: &Schema) ->
Result<usize, Error> {
let create_error = |cause: String| {
Error::new(Details::SerializeValueWithSchema {
@@ -913,6 +950,12 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
i64::try_from(value).map_err(|cause|
create_error(cause.to_string()))?;
encode_long(long_value, &mut self.writer)
}
+ Schema::Fixed(fixed) if fixed.size == 8 && fixed.name.name ==
"u64" => {
+ self.writer
+ .write_all(&value.to_le_bytes())
+ .map_err(Details::WriteBytes)?;
+ Ok(8)
+ }
Schema::Union(union_schema) => {
for (i, variant_schema) in
union_schema.schemas.iter().enumerate() {
match variant_schema {
@@ -930,15 +973,54 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
encode_int(i as i32, &mut *self.writer)?;
return self.serialize_u64_with_schema(value,
variant_schema);
}
+ Schema::Fixed(fixed) if fixed.size == 8 &&
fixed.name.name == "u64" => {
+ encode_int(i as i32, &mut *self.writer)?;
+ return self.serialize_u64_with_schema(value,
variant_schema);
+ }
_ => { /* skip */ }
}
}
Err(create_error(format!(
- "Cannot find a matching Int-like or Long-like schema in
{:?}",
+ "Cannot find a matching Int-like, Long-like or Fixed(size
= 8, name \"u64\") schema in {:?}",
union_schema.schemas
)))
}
- expected => Err(create_error(format!("Expected {expected}. Got:
Int/Long"))),
+ expected => Err(create_error(format!("Expected {expected}. Got:
u64"))),
+ }
+ }
+
+ fn serialize_u128_with_schema(&mut self, value: u128, schema: &Schema) ->
Result<usize, Error> {
+ let create_error = |cause: String| {
+ Error::new(Details::SerializeValueWithSchema {
+ value_type: "u128",
+ value: format!("{value}. Cause: {cause}"),
+ schema: schema.clone(),
+ })
+ };
+
+ match schema {
+ Schema::Fixed(fixed) if fixed.size == 16 && fixed.name.name ==
"u128" => {
+ self.writer
+ .write_all(&value.to_le_bytes())
+ .map_err(Details::WriteBytes)?;
+ Ok(16)
+ }
+ Schema::Union(union_schema) => {
+ for (i, variant_schema) in
union_schema.schemas.iter().enumerate() {
+ match variant_schema {
+ Schema::Fixed(fixed) if fixed.size == 16 &&
fixed.name.name == "u128" => {
+ encode_int(i as i32, &mut *self.writer)?;
+ return self.serialize_u128_with_schema(value,
variant_schema);
+ }
+ _ => { /* skip */ }
+ }
+ }
+ Err(create_error(format!(
+ "Cannot find a Fixed(size = 16, name = \"u128\") schema in
{:?}",
+ union_schema.schemas
+ )))
+ }
+ expected => Err(create_error(format!("Expected {expected}. Got:
u128"))),
}
}
@@ -1027,6 +1109,12 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
match schema {
Schema::String | Schema::Bytes =>
self.write_bytes(String::from(value).as_bytes()),
+ Schema::Fixed(fixed) if fixed.size == 4 && fixed.name.name ==
"char" => {
+ self.writer
+ .write_all(&u32::from(value).to_le_bytes())
+ .map_err(Details::WriteBytes)?;
+ Ok(4)
+ }
Schema::Union(union_schema) => {
for (i, variant_schema) in
union_schema.schemas.iter().enumerate() {
match variant_schema {
@@ -1034,11 +1122,15 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
encode_int(i as i32, &mut *self.writer)?;
return self.serialize_char_with_schema(value,
variant_schema);
}
+ Schema::Fixed(fixed) if fixed.size == 4 &&
fixed.name.name == "char" => {
+ encode_int(i as i32, &mut *self.writer)?;
+ return self.serialize_char_with_schema(value,
variant_schema);
+ }
_ => { /* skip */ }
}
}
Err(create_error(format!(
- "Cannot find a matching String or Bytes schema in
{union_schema:?}"
+ "Cannot find a matching String, Bytes or Fixed(size = 4,
name = \"char\") schema in {union_schema:?}"
)))
}
expected => Err(create_error(format!("Expected {expected}. Got:
char"))),
@@ -1756,6 +1848,10 @@ impl<'a, 's, W: Write> ser::Serializer for &'a mut
SchemaAwareWriteSerializer<'s
self.serialize_i64_with_schema(v, self.root_schema)
}
+ fn serialize_i128(self, v: i128) -> Result<Self::Ok, Self::Error> {
+ self.serialize_i128_with_schema(v, self.root_schema)
+ }
+
fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {
self.serialize_u8_with_schema(v, self.root_schema)
}
@@ -1772,6 +1868,10 @@ impl<'a, 's, W: Write> ser::Serializer for &'a mut
SchemaAwareWriteSerializer<'s
self.serialize_u64_with_schema(v, self.root_schema)
}
+ fn serialize_u128(self, v: u128) -> Result<Self::Ok, Self::Error> {
+ self.serialize_u128_with_schema(v, self.root_schema)
+ }
+
fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> {
self.serialize_f32_with_schema(v, self.root_schema)
}
@@ -1918,6 +2018,7 @@ impl<'a, 's, W: Write> ser::Serializer for &'a mut
SchemaAwareWriteSerializer<'s
#[cfg(test)]
mod tests {
use super::*;
+ use crate::schema::FixedSchema;
use crate::{
Days, Duration, Millis, Months, Reader, Writer, decimal::Decimal,
error::Details,
from_value, schema::ResolvedSchema,
@@ -3146,4 +3247,306 @@ mod tests {
);
Ok(())
}
+
+ #[test]
+ fn avro_rs_414_serialize_char_as_string() -> TestResult {
+ let schema = Schema::String;
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ 'a'.serialize(&mut serializer)?;
+
+ assert_eq!(buffer.as_slice(), &[2, b'a']);
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_serialize_char_as_bytes() -> TestResult {
+ let schema = Schema::Bytes;
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ 'a'.serialize(&mut serializer)?;
+
+ assert_eq!(buffer.as_slice(), &[2, b'a']);
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_serialize_char_as_fixed() -> TestResult {
+ let schema = Schema::Fixed(FixedSchema {
+ name: Name::new("char")?,
+ aliases: None,
+ doc: None,
+ size: 4,
+ default: None,
+ attributes: Default::default(),
+ });
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ 'a'.serialize(&mut serializer)?;
+
+ assert_eq!(buffer.as_slice(), &[b'a', 0, 0, 0]);
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_serialize_emoji_char_as_string() -> TestResult {
+ let schema = Schema::String;
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ '👹'.serialize(&mut serializer)?;
+
+ assert_eq!(buffer.as_slice(), &[8, 240, 159, 145, 185]);
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_serialize_emoji_char_as_bytes() -> TestResult {
+ let schema = Schema::Bytes;
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ '👹'.serialize(&mut serializer)?;
+
+ assert_eq!(buffer.as_slice(), &[8, 240, 159, 145, 185]);
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_serialize_emoji_char_as_fixed() -> TestResult {
+ let schema = Schema::Fixed(FixedSchema {
+ name: Name::new("char")?,
+ aliases: None,
+ doc: None,
+ size: 4,
+ default: None,
+ attributes: Default::default(),
+ });
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ '👹'.serialize(&mut serializer)?;
+
+ // This is a different byte value than the tests above. This is
because by creating a String
+ // the unicode value is normalized by Rust
+ assert_eq!(buffer.as_slice(), &[121, 244, 1, 0]);
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_serialize_char_as_fixed_wrong_name() -> TestResult {
+ let schema = Schema::Fixed(FixedSchema {
+ name: Name::new("characters")?,
+ aliases: None,
+ doc: None,
+ size: 4,
+ default: None,
+ attributes: Default::default(),
+ });
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ assert!(matches!(
+ 'a'.serialize(&mut serializer).unwrap_err().details(),
+ Details::SerializeValueWithSchema { .. }
+ ));
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_serialize_char_as_fixed_wrong_size() -> TestResult {
+ let schema = Schema::Fixed(FixedSchema {
+ name: Name::new("char")?,
+ aliases: None,
+ doc: None,
+ size: 1,
+ default: None,
+ attributes: Default::default(),
+ });
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ assert!(matches!(
+ 'a'.serialize(&mut serializer).unwrap_err().details(),
+ Details::SerializeValueWithSchema { .. }
+ ));
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_serialize_i128_as_fixed() -> TestResult {
+ let schema = Schema::Fixed(FixedSchema {
+ name: Name::new("i128")?,
+ aliases: None,
+ doc: None,
+ size: 16,
+ default: None,
+ attributes: Default::default(),
+ });
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ let bytes_written = i128::MAX.serialize(&mut serializer)?;
+ assert_eq!(bytes_written, 16);
+
+ assert_eq!(
+ buffer.as_slice(),
+ &[
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x7F
+ ]
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_serialize_i128_as_fixed_wrong_name() -> TestResult {
+ let schema = Schema::Fixed(FixedSchema {
+ name: Name::new("onehundredtwentyeight")?,
+ aliases: None,
+ doc: None,
+ size: 16,
+ default: None,
+ attributes: Default::default(),
+ });
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ assert!(matches!(
+ i128::MAX.serialize(&mut serializer).unwrap_err().details(),
+ Details::SerializeValueWithSchema { .. }
+ ));
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_serialize_i128_as_fixed_wrong_size() -> TestResult {
+ let schema = Schema::Fixed(FixedSchema {
+ name: Name::new("i128")?,
+ aliases: None,
+ doc: None,
+ size: 8,
+ default: None,
+ attributes: Default::default(),
+ });
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ assert!(matches!(
+ i128::MAX.serialize(&mut serializer).unwrap_err().details(),
+ Details::SerializeValueWithSchema { .. }
+ ));
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_serialize_u128_as_fixed() -> TestResult {
+ let schema = Schema::Fixed(FixedSchema {
+ name: Name::new("u128")?,
+ aliases: None,
+ doc: None,
+ size: 16,
+ default: None,
+ attributes: Default::default(),
+ });
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ let bytes_written = u128::MAX.serialize(&mut serializer)?;
+ assert_eq!(bytes_written, 16);
+
+ assert_eq!(
+ buffer.as_slice(),
+ &[
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF
+ ]
+ );
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_serialize_u128_as_fixed_wrong_name() -> TestResult {
+ let schema = Schema::Fixed(FixedSchema {
+ name: Name::new("onehundredtwentyeight")?,
+ aliases: None,
+ doc: None,
+ size: 16,
+ default: None,
+ attributes: Default::default(),
+ });
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ assert!(matches!(
+ u128::MAX.serialize(&mut serializer).unwrap_err().details(),
+ Details::SerializeValueWithSchema { .. }
+ ));
+
+ Ok(())
+ }
+
+ #[test]
+ fn avro_rs_414_serialize_u128_as_fixed_wrong_size() -> TestResult {
+ let schema = Schema::Fixed(FixedSchema {
+ name: Name::new("u128")?,
+ aliases: None,
+ doc: None,
+ size: 8,
+ default: None,
+ attributes: Default::default(),
+ });
+
+ let mut buffer: Vec<u8> = Vec::new();
+ let names = HashMap::new();
+ let mut serializer = SchemaAwareWriteSerializer::new(&mut buffer,
&schema, &names, None);
+
+ assert!(matches!(
+ u128::MAX.serialize(&mut serializer).unwrap_err().details(),
+ Details::SerializeValueWithSchema { .. }
+ ));
+
+ Ok(())
+ }
}
diff --git a/avro_derive/tests/derive.rs b/avro_derive/tests/derive.rs
index 74c0e1e..a9012ed 100644
--- a/avro_derive/tests/derive.rs
+++ b/avro_derive/tests/derive.rs
@@ -2101,3 +2101,61 @@ fn avro_rs_401_supported_type_variants() {
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, Foo::get_schema());
}
+
+#[test]
+fn avro_rs_414_round_trip_char_u64_u128_i128() {
+ let schema = Schema::parse_str(
+ r#"
+ {
+ "type":"record",
+ "name":"Foo",
+ "fields": [
+ {
+ "name": "a",
+ "type": "string"
+ },
+ {
+ "name": "b",
+ "type": {
+ "name": "u64",
+ "type": "fixed",
+ "size": 8
+ }
+ },
+ {
+ "name": "c",
+ "type": {
+ "name": "u128",
+ "type": "fixed",
+ "size": 16
+ }
+ },
+ {
+ "name": "d",
+ "type": {
+ "name": "i128",
+ "type": "fixed",
+ "size": 16
+ }
+ }
+ ]
+ }"#,
+ )
+ .unwrap();
+
+ #[derive(AvroSchema, Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo {
+ a: char,
+ b: u64,
+ c: u128,
+ d: i128,
+ }
+
+ assert_eq!(schema, Foo::get_schema());
+ serde_assert(Foo {
+ a: '👺',
+ b: u64::MAX,
+ c: u128::MAX,
+ d: i128::MAX,
+ });
+}