This is an automated email from the ASF dual-hosted git repository. kriskras99 pushed a commit to branch feat/char_u64_u128_i128 in repository https://gitbox.apache.org/repos/asf/avro-rs.git
commit 290a0165f3cef6984e294ed73a8fdf2564eddb42 Author: Kriskras99 <[email protected]> AuthorDate: Mon Jan 19 14:46:06 2026 +0100 feat: Add support for `char`,`u64`,`u128`,`i128` --- avro/src/schema/mod.rs | 138 ++++++++++++++++- avro/src/serde/de.rs | 211 ++++++++++++++++++-------- avro/src/serde/ser.rs | 54 ++++++- avro/src/serde/ser_schema.rs | 347 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 681 insertions(+), 69 deletions(-) diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs index 90a48b8..eec035d 100644 --- a/avro/src/schema/mod.rs +++ b/avro/src/schema/mod.rs @@ -2357,6 +2357,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 +2517,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 +7440,69 @@ mod tests { Schema::union(vec![ Schema::Null, Schema::array(Schema::array(Schema::Int)) - ]) - .unwrap() + ])? + ); + + Ok(()) + } + + #[test] + fn avro_rs_xxx_char() -> TestResult { + let schema = char::get_schema(); + assert_eq!(schema, Schema::String); + + Ok(()) + } + + #[test] + fn avro_rs_xxx_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_xxx_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_xxx_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..6cf8712 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, @@ -385,23 +386,45 @@ impl<'de> de::Deserializer<'de> for &Deserializer<'de> { bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 } - fn deserialize_char<V>(self, _: V) -> Result<V::Value, Self::Error> + fn deserialize_char<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::String(s) => { + if s.chars().count() == 1 { + visitor.visit_char(s.chars().next().expect("There is exactly one char")) + } else { + Err(de::Error::custom("Tried to deserialize char from string, but string was longer than one char")) + } + } + 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("Tried to deserialize char from bytes, but bytes was longer than one char")) + } + } + ), + 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))) + } } 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()), _ => Err(de::Error::custom(format!( "Expected a String|Bytes|Fixed|Uuid, but got {:?}", self.input @@ -413,22 +436,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 +463,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 +489,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 +511,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 +525,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 +567,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 +606,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 +625,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 +650,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 +746,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 +1685,79 @@ mod tests { assert_eq!(raw_bytes.unwrap().0, expected_bytes); Ok(()) } + + #[test] + fn avro_rs_xxx_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_xxx_deserialize_char_from_bytes() -> TestResult { + let value = Value::Bytes('a'.to_string().into_bytes()); + let result = from_value::<char>(&value)?; + assert_eq!(result, 'a'); + + Ok(()) + } + + #[test] + fn avro_rs_xxx_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_xxx_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 string was longer than one char" + ); + + Ok(()) + } + + #[test] + fn avro_rs_xxx_deserialize_char_from_long_bytes() -> TestResult { + let value = Value::Bytes("avro".to_string().into_bytes()); + 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 bytes, but bytes was longer than one char" + ); + + Ok(()) + } + + #[test] + fn avro_rs_xxx_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_xxx_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..679beae 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_xxx_char_to_value() -> TestResult { + let value = to_value('s')?; + + assert_eq!(value, Value::String("s".to_string())); + + Ok(()) + } + + #[test] + fn avro_rs_xxx_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_xxx_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_xxx_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..b7ac03f 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) } @@ -783,6 +785,40 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> { expected => Err(create_error(format!("Expected: {expected}. Got: Int/Long"))), } } + 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(8) + } + 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 matching Int-like or Long-like 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| { @@ -913,6 +949,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,6 +972,10 @@ 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 */ } } } @@ -941,6 +987,40 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> { expected => Err(create_error(format!("Expected {expected}. Got: Int/Long"))), } } + 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(8) + } + 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 matching Int-like or Long-like schema in {:?}", + union_schema.schemas + ))) + } + expected => Err(create_error(format!("Expected {expected}. Got: u128"))), + } + } fn serialize_f32_with_schema(&mut self, value: f32, schema: &Schema) -> Result<usize, Error> { let create_error = |cause: String| { @@ -1027,6 +1107,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,6 +1120,10 @@ 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 */ } } } @@ -1756,6 +1846,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 +1866,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 +2016,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 +3245,250 @@ mod tests { ); Ok(()) } + + #[test] + fn avro_rs_xxx_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_xxx_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_xxx_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_xxx_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_xxx_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_xxx_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); + + i128::MAX.serialize(&mut serializer)?; + + 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_xxx_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_xxx_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_xxx_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); + + u128::MAX.serialize(&mut serializer)?; + + 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_xxx_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_xxx_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(()) + } }
