This is an automated email from the ASF dual-hosted git repository. kriskras99 pushed a commit to branch feat/enums in repository https://gitbox.apache.org/repos/asf/avro-rs.git
commit 983d90a87c2cbda6311dd1c53049a551f3f8e94a Author: default <[email protected]> AuthorDate: Tue Feb 24 13:23:07 2026 +0000 fix tests --- avro/src/bigdecimal.rs | 3 +- avro/src/schema/mod.rs | 8 +- avro/src/schema/union.rs | 4 +- avro/src/serde/mod.rs | 8 +- avro/src/serde/ser.rs | 2 +- avro/src/serde/ser_schema/mod.rs | 61 ------- avro/src/serde/ser_schema2/mod.rs | 303 +++++++++++++++++++------------- avro/src/serde/ser_schema2/record.rs | 3 +- avro/src/serde/ser_schema2/union.rs | 133 +++++++++------ avro/src/serde/with.rs | 322 ++++++++++++++++++++++++++++++++++- 10 files changed, 603 insertions(+), 244 deletions(-) diff --git a/avro/src/bigdecimal.rs b/avro/src/bigdecimal.rs index b887535..7a6a379 100644 --- a/avro/src/bigdecimal.rs +++ b/avro/src/bigdecimal.rs @@ -47,8 +47,7 @@ pub(crate) fn serialize_big_decimal(decimal: &BigDecimal) -> AvroResult<Vec<u8>> Ok(final_buffer) } -pub(crate) fn deserialize_big_decimal(bytes: &Vec<u8>) -> AvroResult<BigDecimal> { - let mut bytes: &[u8] = bytes.as_slice(); +pub(crate) fn deserialize_big_decimal(mut bytes: &[u8]) -> AvroResult<BigDecimal> { let mut big_decimal_buffer = match decode_len(&mut bytes) { Ok(size) => vec![0u8; size], Err(err) => return Err(Details::BigDecimalLen(Box::new(err)).into()), diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs index f098882..f433335 100644 --- a/avro/src/schema/mod.rs +++ b/avro/src/schema/mod.rs @@ -28,14 +28,14 @@ pub(crate) use crate::schema::resolve::{ ResolvedOwnedSchema, resolve_names, resolve_names_with_schemata, }; pub use crate::schema::{ - name::{Alias, Aliases, Name, Names, NamesRef, Namespace}, - record::{ - RecordField, RecordFieldBuilder, RecordFieldOrder, RecordSchema, RecordSchemaBuilder, - }, builders::{ SchemaArrayBuilder, SchemaEnumBuilder, SchemaFixedBuilder, SchemaMapBuilder, SchemaRecordBuilder, }, + name::{Alias, Aliases, Name, Names, NamesRef, Namespace}, + record::{ + RecordField, RecordFieldBuilder, RecordFieldOrder, RecordSchema, RecordSchemaBuilder, + }, resolve::ResolvedSchema, union::UnionSchema, }; diff --git a/avro/src/schema/union.rs b/avro/src/schema/union.rs index 1312e4a..f06815e 100644 --- a/avro/src/schema/union.rs +++ b/avro/src/schema/union.rs @@ -17,8 +17,8 @@ use crate::error::Details; use crate::schema::{ - DecimalSchema, InnerDecimalSchema, Name, Namespace, ResolvedSchema, Schema, - SchemaKind, UuidSchema, + DecimalSchema, InnerDecimalSchema, Name, Namespace, ResolvedSchema, Schema, SchemaKind, + UuidSchema, }; use crate::types; use crate::{AvroResult, Error}; diff --git a/avro/src/serde/mod.rs b/avro/src/serde/mod.rs index 281bf9a..6f70101 100644 --- a/avro/src/serde/mod.rs +++ b/avro/src/serde/mod.rs @@ -118,7 +118,13 @@ 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, +}; + +// TODO: temp +pub use ser_schema2::SchemaAwareSerializer; #[doc(hidden)] pub use derive::get_record_fields_in_ctxt; diff --git a/avro/src/serde/ser.rs b/avro/src/serde/ser.rs index 76c6579..efd6fe8 100644 --- a/avro/src/serde/ser.rs +++ b/avro/src/serde/ser.rs @@ -182,7 +182,7 @@ impl ser::Serializer for Serializer { fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> { match SER_BYTES_TYPE.get() { - BytesType::Bytes => Ok(Value::Bytes(v.to_owned())), + BytesType::Unset | BytesType::Bytes => Ok(Value::Bytes(v.to_owned())), BytesType::Fixed => Ok(Value::Fixed(v.len(), v.to_owned())), } } diff --git a/avro/src/serde/ser_schema/mod.rs b/avro/src/serde/ser_schema/mod.rs index 6c27cf1..679ea7c 100644 --- a/avro/src/serde/ser_schema/mod.rs +++ b/avro/src/serde/ser_schema/mod.rs @@ -3004,67 +3004,6 @@ mod tests { Ok(()) } - #[test] - fn test_serialize_recursive_record() -> TestResult { - let schema = Schema::parse_str( - r#"{ - "type": "record", - "name": "TestRecord", - "fields": [ - {"name": "stringField", "type": "string"}, - {"name": "intField", "type": "int"}, - {"name": "bigDecimalField", "type": {"type": "bytes", "logicalType": "big-decimal"}}, - {"name": "uuidField", "type": "fixed", "size": 16, "logicalType": "uuid"}, - {"name": "innerRecord", "type": ["null", "TestRecord"]} - ] - }"#, - )?; - - #[derive(Serialize)] - #[serde(rename_all = "camelCase")] - struct TestRecord { - string_field: String, - int_field: i32, - big_decimal_field: BigDecimal, - uuid_field: Uuid, - // #[serde(skip_serializing_if = "Option::is_none")] => Never ignore None! - inner_record: Option<Box<TestRecord>>, - } - - assert!(!crate::util::is_human_readable()); - let mut buffer: Vec<u8> = Vec::new(); - let rs = ResolvedSchema::try_from(&schema)?; - let mut serializer = - SchemaAwareWriteSerializer::new(&mut buffer, &schema, rs.get_names(), None); - - let good_record = TestRecord { - string_field: String::from("test"), - int_field: 10, - big_decimal_field: BigDecimal::new(BigInt::new(Sign::Plus, vec![50024]), 2), - uuid_field: "8c28da81-238c-4326-bddd-4e3d00cc5098".parse::<Uuid>()?, - inner_record: Some(Box::new(TestRecord { - string_field: String::from("inner_test"), - int_field: 100, - big_decimal_field: BigDecimal::new(BigInt::new(Sign::Plus, vec![20038]), 2), - uuid_field: "8c28da81-238c-4326-bddd-4e3d00cc5099".parse::<Uuid>()?, - inner_record: None, - })), - }; - good_record.serialize(&mut serializer)?; - - assert_eq!( - buffer.as_slice(), - &[ - 8, 116, 101, 115, 116, 20, 10, 6, 0, 195, 104, 4, 140, 40, 218, 129, 35, 140, 67, - 38, 189, 221, 78, 61, 0, 204, 80, 152, 2, 20, 105, 110, 110, 101, 114, 95, 116, - 101, 115, 116, 200, 1, 8, 4, 78, 70, 4, 140, 40, 218, 129, 35, 140, 67, 38, 189, - 221, 78, 61, 0, 204, 80, 153, 0 - ] - ); - - Ok(()) - } - #[test] fn avro_rs_337_serialize_union_record_variant() -> TestResult { let schema = Schema::parse_str( diff --git a/avro/src/serde/ser_schema2/mod.rs b/avro/src/serde/ser_schema2/mod.rs index a68f912..0d656a7 100644 --- a/avro/src/serde/ser_schema2/mod.rs +++ b/avro/src/serde/ser_schema2/mod.rs @@ -12,6 +12,7 @@ use crate::serde::ser_schema2::map::MapSerializer; use crate::serde::ser_schema2::record::RecordSerializer; use crate::serde::ser_schema2::tuple::TupleSerializer; use crate::serde::ser_schema2::union::UnionAwareSerializer; +use crate::serde::with::{BytesType, SER_BYTES_TYPE}; use crate::{Error, Schema}; use serde::ser::SerializeMap; use serde::{Serialize, Serializer}; @@ -120,7 +121,7 @@ impl<'s, 'w, W: Write> SchemaAwareSerializer<'s, 'w, W> { }) } - fn serialize_int(mut self, original_ty: &'static str, v: i32) -> Result<usize, Error> { + fn serialize_int(self, original_ty: &'static str, v: i32) -> Result<usize, Error> { match self.schema { Schema::Int | Schema::Date | Schema::TimeMillis => encode_int(v, &mut *self.writer), _ => Err(self.error( @@ -130,7 +131,7 @@ impl<'s, 'w, W: Write> SchemaAwareSerializer<'s, 'w, W> { } } - fn serialize_long(mut self, original_ty: &'static str, v: i64) -> Result<usize, Error> { + fn serialize_long(self, original_ty: &'static str, v: i64) -> Result<usize, Error> { match self.schema { Schema::Long | Schema::TimeMicros | Schema::TimestampMillis | Schema::TimestampMicros | Schema::TimestampNanos | Schema::LocalTimestampMillis | Schema::LocalTimestampMicros @@ -171,7 +172,7 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> { type SerializeStruct = RecordSerializer<'s, 'w, W>; type SerializeStructVariant = RecordSerializer<'s, 'w, W>; - fn serialize_bool(mut self, v: bool) -> Result<Self::Ok, Self::Error> { + fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> { let Schema::Boolean = self.schema else { return Err(self.error("bool", "Expected Schema::Boolean")); }; @@ -277,22 +278,22 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> { } fn serialize_bytes(mut self, v: &[u8]) -> Result<Self::Ok, Self::Error> { - match self.schema { - Schema::Bytes | Schema::BigDecimal | Schema::Decimal(DecimalSchema { inner: InnerDecimalSchema::Bytes, .. }) => { + match (SER_BYTES_TYPE.get(), self.schema) { + (BytesType::Unset | BytesType::Bytes, Schema::Bytes | Schema::BigDecimal | Schema::Decimal(DecimalSchema { inner: InnerDecimalSchema::Bytes, .. })) => { self.write_bytes_with_len(v) } - Schema::Fixed(fixed) | Schema::Decimal(DecimalSchema { inner: InnerDecimalSchema::Fixed(fixed), ..}) => { + (BytesType::Unset | BytesType::Fixed, Schema::Fixed(fixed) | Schema::Decimal(DecimalSchema { inner: InnerDecimalSchema::Fixed(fixed), ..}) | Schema::Uuid(UuidSchema::Fixed(fixed)) | Schema::Duration(fixed)) => { if fixed.size != v.len() { Err(self.error("bytes", format!("Fixed size ({}) does not match value length ({})", fixed.size, v.len()))) } else { self.write_bytes(v) } } - _ => Err(self.error("bytes", "Expected Schema::Bytes | Schema::Uuid(Fixed) | Schema::BigDecimal | Schema::Decimal")), + _ => Err(self.error("bytes", "Expected Schema::Bytes | Schema::Uuid(Fixed) | Schema::BigDecimal | Schema::Decimal | Schema::Duration")), } } - fn serialize_none(mut self) -> Result<Self::Ok, Self::Error> { + fn serialize_none(self) -> Result<Self::Ok, Self::Error> { let Schema::Union(union) = self.schema else { return Err(self.error("None", "Expected Schema::Union([null, _])")); }; @@ -305,18 +306,18 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> { encode_int(index as i32, &mut *self.writer) } - fn serialize_some<T>(mut self, value: &T) -> Result<Self::Ok, Self::Error> + fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error> where T: ?Sized + Serialize, { let Schema::Union(union) = self.schema else { - return Err(self.error("None", "Expected Schema::Union([null, _])")); + return Err(self.error("Some", "Expected Schema::Union([null, _])")); }; if union.variants().len() != 2 { - return Err(self.error("None", "Expected Schema::Union([null, _])")); + return Err(self.error("Some", "Expected Schema::Union([null, _])")); } let Some(index) = union.index().get(&SchemaKind::Null).copied() else { - return Err(self.error("None", "Expected Schema::Union([null, _])")); + return Err(self.error("Some", "Expected Schema::Union([null, _])")); }; // Convert the index of null to the other index let index = (index + 1) & 1; @@ -328,7 +329,7 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> { fn serialize_unit(self) -> Result<Self::Ok, Self::Error> { let Schema::Null = self.schema else { - return Err(self.error("()", "Expected Schema::Null")); + return Err(self.error("unit", "Expected Schema::Null")); }; Ok(0) } @@ -350,7 +351,7 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> { } fn serialize_unit_variant( - mut self, + self, name: &'static str, variant_index: u32, variant: &'static str, @@ -460,7 +461,7 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> { } fn serialize_newtype_variant<T>( - mut self, + self, name: &'static str, variant_index: u32, variant: &'static str, @@ -582,7 +583,7 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> { } fn serialize_tuple_variant( - mut self, + self, name: &'static str, variant_index: u32, variant: &'static str, @@ -728,7 +729,7 @@ impl<'s, 'w, W: Write> Serializer for SchemaAwareSerializer<'s, 'w, W> { } fn serialize_struct_variant( - mut self, + self, name: &'static str, variant_index: u32, variant: &'static str, @@ -936,7 +937,11 @@ mod tests { 4u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; 31u16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 13u32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert!( + 13u32 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); 7i8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; (-57i16).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; 129i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; @@ -950,7 +955,7 @@ mod tests { .is_err() ); - assert_eq!(buffer.as_slice(), &[8, 62, 26, 14, 113, 130, 2]); + assert_eq!(buffer.as_slice(), &[8, 62, 14, 113, 130, 2]); Ok(()) } @@ -965,13 +970,35 @@ mod tests { human_readable: false, }; - 4u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 31u16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert!( + 4u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); + assert!( + 31u16 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); 13u32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 291u64.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 7i8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - (-57i16).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 129i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert!( + 291u64 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); + assert!( + 7i8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); + assert!( + (-57i16) + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); + assert!( + 129i32 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); (-432i64).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; assert!( "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) @@ -983,10 +1010,7 @@ mod tests { .is_err() ); - assert_eq!( - buffer.as_slice(), - &[8, 62, 26, 198, 4, 14, 113, 130, 2, 223, 6] - ); + assert_eq!(buffer.as_slice(), &[26, 223, 6]); Ok(()) } @@ -1002,7 +1026,11 @@ mod tests { }; 4.7f32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - (-14.1f64).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert!( + (-14.1f64) + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); assert!( "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) .is_err() @@ -1013,7 +1041,7 @@ mod tests { .is_err() ); - assert_eq!(buffer.as_slice(), &[102, 102, 150, 64, 154, 153, 97, 193]); + assert_eq!(buffer.as_slice(), &[102, 102, 150, 64]); Ok(()) } @@ -1029,7 +1057,12 @@ mod tests { }; 4.7f32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - (-14.1f64).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + (-14.1f32).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert!( + (-14.1f64) + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); assert!( "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) .is_err() @@ -1055,8 +1088,15 @@ mod tests { human_readable: false, }; - 'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - "test".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert!( + 'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); + assert!( + "test" + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); Bytes::new(&[12, 3, 7, 91, 4]).serialize(SchemaAwareSerializer::new( &mut buffer, &schema, @@ -1072,10 +1112,7 @@ mod tests { .is_err() ); - assert_eq!( - buffer.as_slice(), - &[2, b'a', 8, b't', b'e', b's', b't', 10, 12, 3, 7, 91, 4] - ); + assert_eq!(buffer.as_slice(), &[10, 12, 3, 7, 91, 4]); Ok(()) } @@ -1092,11 +1129,11 @@ mod tests { 'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; "test".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - Bytes::new(&[12, 3, 7, 91, 4]).serialize(SchemaAwareSerializer::new( - &mut buffer, - &schema, - config, - )?)?; + assert!( + Bytes::new(&[12, 3, 7, 91, 4]) + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config,)?) + .is_err() + ); assert!( ().serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) .is_err() @@ -1107,10 +1144,7 @@ mod tests { .is_err() ); - assert_eq!( - buffer.as_slice(), - &[2, b'a', 8, b't', b'e', b's', b't', 10, 12, 3, 7, 91, 4] - ); + assert_eq!(buffer.as_slice(), &[2, b'a', 8, b't', b'e', b's', b't']); Ok(()) } @@ -1129,14 +1163,14 @@ mod tests { )?; #[derive(Serialize)] - #[serde(rename_all = "camelCase")] + #[serde(rename_all = "camelCase", rename = "TestRecord")] struct GoodTestRecord { string_field: String, int_field: i32, } #[derive(Serialize)] - #[serde(rename_all = "camelCase")] + #[serde(rename_all = "camelCase", rename = "TestRecord")] struct BadTestRecord { foo_string_field: String, bar_int_field: i32, @@ -1202,6 +1236,7 @@ mod tests { EmptyRecord.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; #[derive(Serialize)] + #[serde(rename = "EmptyRecord")] struct NonEmptyRecord { foo: String, } @@ -1222,8 +1257,8 @@ mod tests { value, schema, }) => { - assert_eq!(value_type, "none"); // serialize_unit() delegates to serialize_none() - assert_eq!(value, "None. Cause: Expected: Record. Got: Null"); + assert_eq!(value_type, "unit"); + assert_eq!(value, "Expected Schema::Null"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), @@ -1245,6 +1280,7 @@ mod tests { )?; #[derive(Serialize)] + #[serde(rename_all = "UPPERCASE")] enum Suit { Spades, Hearts, @@ -1272,8 +1308,8 @@ mod tests { value, schema, }) => { - assert_eq!(value_type, "none"); - assert_eq!(value, "None. Cause: Expected: Enum. Got: Null"); + assert_eq!(value_type, "None"); + assert_eq!(value, "Expected Schema::Union([null, _])"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), @@ -1313,13 +1349,15 @@ mod tests { schema, }) => { assert_eq!(value_type, "f32"); - assert_eq!(value, "1. Cause: Expected: Long. Got: Float"); + assert_eq!(value, "Expected Schema::Float"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), } - assert_eq!(buffer.as_slice(), &[6, 20, 10, 160, 6, 0]); + // The 2 at the end is because the DirectArraySerializer immediately writes the length and doesn't + // know the values will be invalid yet + assert_eq!(buffer.as_slice(), &[6, 20, 10, 160, 6, 0, 2]); Ok(()) } @@ -1357,18 +1395,20 @@ mod tests { value, schema, }) => { - assert_eq!(value_type, "string"); - assert_eq!(value, "value1. Cause: Expected: Long. Got: String"); + assert_eq!(value_type, "str"); + assert_eq!(value, "Expected Schema::String | Schema::Uuid(String)"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), } + // The last 7 bytes are written because the DirectMapSerializer will write immediately and doesn't + // know yet that the value is not a long assert_eq!( buffer.as_slice(), &[ 4, 10, b'i', b't', b'e', b'm', b'1', 20, 10, b'i', b't', b'e', b'm', b'2', 160, 6, - 0 + 0, 2, 10, 105, 116, 101, 109, 49 ] ); @@ -1414,11 +1454,8 @@ mod tests { value, schema, }) => { - assert_eq!(value_type, "string"); - assert_eq!( - value, - "invalid. Cause: Expected one of the union variants [Null, Long]. Got: String" - ); + assert_eq!(value_type, "str"); + assert_eq!(value, "Expected Schema::String | Schema::Uuid(String)"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), @@ -1473,10 +1510,7 @@ mod tests { schema, }) => { assert_eq!(value_type, "f64"); - assert_eq!( - value, - "1. Cause: Cannot find a Double schema in [Null, Long, String]" - ); + assert_eq!(value, "Expected Schema::Double"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), @@ -1526,8 +1560,8 @@ mod tests { assert_eq!(value_type, "bytes"); assert_eq!( value, - "7b. Cause: Fixed schema size (8) does not match the value length (1)" - ); // Bytes represents its values as hexadecimals: '7b' is 123 + "Fixed size (8) does not match value length (1)" + ); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), @@ -1543,8 +1577,9 @@ mod tests { value, schema, }) => { - assert_eq!(value_type, "tuple"); // TODO: why is this 'tuple' ?! - assert_eq!(value, "tuple (len=8). Cause: Expected: Fixed. Got: Array"); + // Arrays are serialized as a tuple by Serde + assert_eq!(value_type, "tuple"); + assert_eq!(value, "Expected Schema::Record(fields.len() == 8)"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), @@ -1560,8 +1595,9 @@ mod tests { value, schema, }) => { - assert_eq!(*value_type, "tuple"); // TODO: why is this 'tuple' ?! - assert_eq!(value, "tuple (len=8). Cause: Expected: Fixed. Got: Array"); + // This is a tuple as Serde serializes array as a tuple + assert_eq!(*value_type, "tuple"); + assert_eq!(value, "Expected Schema::Record(fields.len() == 8)"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), @@ -1599,8 +1635,8 @@ mod tests { value, schema, }) => { - assert_eq!(value_type, "none"); - assert_eq!(value, "None. Cause: Expected: Decimal. Got: Null"); + assert_eq!(value_type, "unit"); + assert_eq!(value, "Expected Schema::Null"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), @@ -1640,8 +1676,8 @@ mod tests { value, schema, }) => { - assert_eq!(value_type, "none"); - assert_eq!(value, "None. Cause: Expected: Decimal. Got: Null"); + assert_eq!(value_type, "unit"); + assert_eq!(value, "Expected Schema::Null"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), @@ -1669,7 +1705,10 @@ mod tests { }; let val = BigDecimal::new(BigInt::new(Sign::Plus, vec![50024]), 2); - val.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + crate::serde::bigdecimal::serialize( + &val, + SchemaAwareSerializer::new(&mut buffer, &schema, config)?, + )?; assert_eq!(buffer.as_slice(), &[10, 6, 0, 195, 104, 4]); @@ -1709,7 +1748,10 @@ mod tests { schema, }) => { assert_eq!(value_type, "u8"); - assert_eq!(value, "1. Cause: Expected: Uuid. Got: Int"); + assert_eq!( + value, + "Expected Schema::Int | Schema::Date | Schema::TimeMillis" + ); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), @@ -1743,7 +1785,11 @@ mod tests { 100_u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; 1000_u16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 10000_u32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert!( + 10000_u32 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); 1000_i16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; 10000_i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; @@ -1757,7 +1803,7 @@ mod tests { schema, }) => { assert_eq!(value_type, "f32"); - assert_eq!(value, "10000. Cause: Expected: Date. Got: Float"); + assert_eq!(value, "Expected Schema::Float"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), @@ -1765,7 +1811,7 @@ mod tests { assert_eq!( buffer.as_slice(), - &[200, 1, 208, 15, 160, 156, 1, 208, 15, 160, 156, 1] + &[200, 1, 208, 15, 208, 15, 160, 156, 1] ); Ok(()) @@ -1789,7 +1835,11 @@ mod tests { 100_u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; 1000_u16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 10000_u32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert!( + 10000_u32 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); 1000_i16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; 10000_i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; @@ -1803,16 +1853,13 @@ mod tests { schema, }) => { assert_eq!(value_type, "f32"); - assert_eq!(value, "10000. Cause: Expected: TimeMillis. Got: Float"); + assert_eq!(value, "Expected Schema::Float"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), } - assert_eq!( - buffer.as_slice(), - &[200, 1, 208, 15, 160, 156, 1, 208, 15, 160, 156, 1] - ); + assert_eq!(buffer.as_slice(), &[200, 1, 208, 15, 208, 15, 160, 156, 1]); Ok(()) } @@ -1833,11 +1880,27 @@ mod tests { human_readable: false, }; - 100_u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 1000_u16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert!( + 100_u8 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); + assert!( + 1000_u16 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); 10000_u32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 1000_i16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 10000_i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert!( + 1000_i16 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); + assert!( + 10000_i32 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); 10000_i64.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; match 10000_f32 @@ -1850,18 +1913,13 @@ mod tests { schema, }) => { assert_eq!(value_type, "f32"); - assert_eq!(value, "10000. Cause: Expected: TimeMicros. Got: Float"); + assert_eq!(value, "Expected Schema::Float"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), } - assert_eq!( - buffer.as_slice(), - &[ - 200, 1, 208, 15, 160, 156, 1, 208, 15, 160, 156, 1, 160, 156, 1 - ] - ); + assert_eq!(buffer.as_slice(), &[160, 156, 1, 160, 156, 1,]); Ok(()) } @@ -1883,11 +1941,27 @@ mod tests { human_readable: false, }; - 100_u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 1000_u16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert!( + 100_u8 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); + assert!( + 1000_u16 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); 10000_u32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 1000_i16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 10000_i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert!( + 1000_i16 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); + assert!( + 10000_i32 + .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) + .is_err() + ); 10000_i64.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; match 10000_f64 @@ -1904,23 +1978,13 @@ mod tests { capital_precision.replace_range(..1, &c.to_uppercase().to_string()); } assert_eq!(value_type, "f64"); - assert_eq!( - value, - format!( - "10000. Cause: Expected: Timestamp{capital_precision}. Got: Double" - ) - ); + assert_eq!(value, format!("Expected Schema::Double")); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), } - assert_eq!( - buffer.as_slice(), - &[ - 200, 1, 208, 15, 160, 156, 1, 208, 15, 160, 156, 1, 160, 156, 1 - ] - ); + assert_eq!(buffer.as_slice(), &[160, 156, 1, 160, 156, 1,]); } Ok(()) @@ -1957,11 +2021,9 @@ mod tests { value, schema, }) => { - assert_eq!(value_type, "tuple"); // TODO: why is this 'tuple' ?! - assert_eq!( - value, - "tuple (len=12). Cause: Expected: Duration. Got: Array" - ); + // This is a tuple because Serde serializes arrays [T; N] as tuples + assert_eq!(value_type, "tuple"); + assert_eq!(value, "Expected Schema::Record(fields.len() == 12)"); assert_eq!(schema, schema); } unexpected => panic!("Expected an error. Got: {unexpected:?}"), @@ -1993,6 +2055,7 @@ mod tests { struct TestRecord { string_field: String, int_field: i32, + #[serde(with = "crate::serde::bigdecimal")] big_decimal_field: BigDecimal, uuid_field: Uuid, // #[serde(skip_serializing_if = "Option::is_none")] => Never ignore None! diff --git a/avro/src/serde/ser_schema2/record.rs b/avro/src/serde/ser_schema2/record.rs index 9f523f1..3ac2845 100644 --- a/avro/src/serde/ser_schema2/record.rs +++ b/avro/src/serde/ser_schema2/record.rs @@ -63,8 +63,7 @@ impl<'s, 'w, W: Write> RecordSerializer<'s, 'w, W> { // Current field position is smaller than this field position, // so we're still missing at least one field, save this field temporarily let mut bytes = Vec::new(); - let value_ser = - SchemaAwareSerializer::new(&mut *self.writer, &field.schema, self.config)?; + let value_ser = SchemaAwareSerializer::new(&mut bytes, &field.schema, self.config)?; value.serialize(value_ser)?; if self.field_cache.insert(field.position, bytes).is_some() { Err(Details::FieldNameDuplicate(field.name.clone()).into()) diff --git a/avro/src/serde/ser_schema2/union.rs b/avro/src/serde/ser_schema2/union.rs index aadbb38..e2f46c7 100644 --- a/avro/src/serde/ser_schema2/union.rs +++ b/avro/src/serde/ser_schema2/union.rs @@ -6,6 +6,7 @@ use crate::serde::ser_schema2::map::MapSerializer; use crate::serde::ser_schema2::record::RecordSerializer; use crate::serde::ser_schema2::tuple::TupleSerializer; use crate::serde::ser_schema2::{Config, SchemaAwareSerializer}; +use crate::serde::with::{BytesType, SER_BYTES_TYPE}; use crate::{Error, Schema}; use serde::{Serialize, Serializer}; use std::io::Write; @@ -34,7 +35,7 @@ impl<'s, 'w, W: Write> UnionAwareSerializer<'s, 'w, W> { }) } - fn serialize_int(mut self, original_ty: &'static str, v: i32) -> Result<usize, Error> { + fn serialize_int(self, original_ty: &'static str, v: i32) -> Result<usize, Error> { let Some(index) = self.union_schema.index().get(&SchemaKind::Int).copied() else { return Err(self.error( original_ty, @@ -46,7 +47,7 @@ impl<'s, 'w, W: Write> UnionAwareSerializer<'s, 'w, W> { Ok(bytes_written) } - fn serialize_long(mut self, original_ty: &'static str, v: i64) -> Result<usize, Error> { + fn serialize_long(self, original_ty: &'static str, v: i64) -> Result<usize, Error> { let Some(index) = self.union_schema.index().get(&SchemaKind::Long).copied() else { return Err(self.error(original_ty, "Expected Schema::Long | Schema::TimeMicros | Schema::{,Local}Timestamp{Millis,Micros,Nanos} in variants")); }; @@ -75,7 +76,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { type SerializeStruct = RecordSerializer<'s, 'w, W>; type SerializeStructVariant = RecordSerializer<'s, 'w, W>; - fn serialize_bool(mut self, v: bool) -> Result<Self::Ok, Self::Error> { + fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> { let Some(index) = self.union_schema.index().get(&SchemaKind::Boolean).copied() else { return Err(self.error("bool", "Expected Schema::Boolean in variants")); }; @@ -103,7 +104,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { self.serialize_long("i64", v) } - fn serialize_i128(mut self, v: i128) -> Result<Self::Ok, Self::Error> { + fn serialize_i128(self, v: i128) -> Result<Self::Ok, Self::Error> { let Some((index, schema)) = self.find_named_schema("i128") else { return Err(self.error( "i128", @@ -128,7 +129,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { self.serialize_long("u32", i64::from(v)) } - fn serialize_u64(mut self, v: u64) -> Result<Self::Ok, Self::Error> { + fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> { let Some((index, schema)) = self.find_named_schema("u64") else { return Err(self.error( "u64", @@ -141,7 +142,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { Ok(bytes_written) } - fn serialize_u128(mut self, v: u128) -> Result<Self::Ok, Self::Error> { + fn serialize_u128(self, v: u128) -> Result<Self::Ok, Self::Error> { let Some((index, schema)) = self.find_named_schema("u128") else { return Err(self.error( "i128", @@ -154,7 +155,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { Ok(bytes_written) } - fn serialize_f32(mut self, v: f32) -> Result<Self::Ok, Self::Error> { + fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> { let Some(index) = self.union_schema.index().get(&SchemaKind::Float).copied() else { return Err(self.error("f32", "Expected Schema::Float in variants")); }; @@ -164,7 +165,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { Ok(bytes_written) } - fn serialize_f64(mut self, v: f64) -> Result<Self::Ok, Self::Error> { + fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> { let Some(index) = self.union_schema.index().get(&SchemaKind::Double).copied() else { return Err(self.error("f64", "Expected Schema::Double in variants")); }; @@ -174,7 +175,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { Ok(bytes_written) } - fn serialize_char(mut self, v: char) -> Result<Self::Ok, Self::Error> { + fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> { let Some(index) = self.union_schema.index().get(&SchemaKind::String).copied() else { return Err(self.error("char", "Expected Schema::String in variants")); }; @@ -184,7 +185,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { Ok(bytes_written) } - fn serialize_str(mut self, v: &str) -> Result<Self::Ok, Self::Error> { + fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> { let Some(index) = self.union_schema.index().get(&SchemaKind::String).copied() else { return Err(self.error("str", "Expected Schema::String in variants")); }; @@ -194,39 +195,71 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { Ok(bytes_written) } - fn serialize_bytes(mut self, v: &[u8]) -> Result<Self::Ok, Self::Error> { - let potential_bytes_index = self.union_schema.index().get(&SchemaKind::Bytes).copied(); - let potential_fixed_index = - self.union_schema - .variants() - .iter() - .enumerate() - .find(|(_i, s)| { - if let Schema::Fixed(f) = s { - f.size == v.len() - } else { - false + fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> { + let (index, schema) = match SER_BYTES_TYPE.get() { + BytesType::Bytes => { + let Some(index) = self.union_schema.index().get(&SchemaKind::Bytes).copied() else { + return Err(self.error("bytes", "Expected Schema::Bytes in variants")); + }; + (index, &Schema::Bytes) + } + BytesType::Fixed => { + let Some((index, schema)) = + self.union_schema + .variants() + .iter() + .enumerate() + .find(|(_i, s)| { + if let Schema::Fixed(f) = s { + f.size == v.len() + } else { + false + } + }) + else { + return Err(self.error( + "bytes", + format!("Expected Schema::Fixed(size = {}) in variants", v.len()), + )); + }; + (index, schema) + } + BytesType::Unset => { + let potential_bytes_index = + self.union_schema.index().get(&SchemaKind::Bytes).copied(); + let potential_fixed_index = + self.union_schema + .variants() + .iter() + .enumerate() + .find(|(_i, s)| { + if let Schema::Fixed(f) = s { + f.size == v.len() + } else { + false + } + }); + match (potential_bytes_index, potential_fixed_index) { + (Some(bytes_index), Some((fixed_index, fixed_schema))) => { + if bytes_index < fixed_index { + (bytes_index, &Schema::Bytes) + } else { + (fixed_index, fixed_schema) + } + } + (Some(bytes_index), None) => (bytes_index, &Schema::Bytes), + (None, Some((fixed_index, fixed_schema))) => (fixed_index, fixed_schema), + (None, None) => { + return Err(self.error( + "bytes", + format!( + "Expected Schema::Bytes or Schema::Fixed(size: {}) in variants", + v.len() + ), + )); } - }); - let (index, schema) = match (potential_bytes_index, potential_fixed_index) { - (Some(bytes_index), Some((fixed_index, fixed_schema))) => { - if bytes_index < fixed_index { - (bytes_index, &Schema::Bytes) - } else { - (fixed_index, fixed_schema) } } - (Some(bytes_index), None) => (bytes_index, &Schema::Bytes), - (None, Some((fixed_index, fixed_schema))) => (fixed_index, fixed_schema), - (None, None) => { - return Err(self.error( - "bytes", - format!( - "Expected Schema::Bytes or Schema::Fixed(size: {}) in variants", - v.len() - ), - )); - } }; let mut bytes_written = encode_int(index as i32, &mut *self.writer)?; let ser = SchemaAwareSerializer::new(self.writer, schema, self.config)?; @@ -269,7 +302,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { } fn serialize_unit_variant( - mut self, + self, name: &'static str, variant_index: u32, variant: &'static str, @@ -287,7 +320,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { } fn serialize_newtype_struct<T>( - mut self, + self, name: &'static str, value: &T, ) -> Result<Self::Ok, Self::Error> @@ -308,7 +341,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { } fn serialize_newtype_variant<T>( - mut self, + self, name: &'static str, variant_index: u32, variant: &'static str, @@ -339,7 +372,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { } } - fn serialize_seq(mut self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> { + fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> { let Some(index) = self.union_schema.index().get(&SchemaKind::Array).copied() else { return Err(self.error("seq", "Expected Schema::Array in variants")); }; @@ -356,7 +389,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { )?) } - fn serialize_tuple(mut self, len: usize) -> Result<Self::SerializeTuple, Self::Error> { + fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> { let Some((index, Schema::Record(schema))) = self .union_schema .variants() @@ -387,7 +420,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { } fn serialize_tuple_struct( - mut self, + self, name: &'static str, len: usize, ) -> Result<Self::SerializeTupleStruct, Self::Error> { @@ -410,7 +443,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { } fn serialize_tuple_variant( - mut self, + self, name: &'static str, variant_index: u32, variant: &'static str, @@ -467,7 +500,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { } } - fn serialize_map(mut self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> { + fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> { let Some(index) = self.union_schema.index().get(&SchemaKind::Map).copied() else { return Err(self.error("map", "Expected Schema::Map in variants")); }; @@ -485,7 +518,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { } fn serialize_struct( - mut self, + self, name: &'static str, _len: usize, ) -> Result<Self::SerializeStruct, Self::Error> { @@ -506,7 +539,7 @@ impl<'s, 'w, W: Write> Serializer for UnionAwareSerializer<'s, 'w, W> { } fn serialize_struct_variant( - mut self, + self, name: &'static str, variant_index: u32, variant: &'static str, diff --git a/avro/src/serde/with.rs b/avro/src/serde/with.rs index d5da690..4fe13b1 100644 --- a/avro/src/serde/with.rs +++ b/avro/src/serde/with.rs @@ -22,7 +22,7 @@ thread_local! { /// [`Value::Bytes`] or [`Value::Fixed`]. /// /// Relies on the fact that serde's serialization process is single-threaded. - pub(crate) static SER_BYTES_TYPE: Cell<BytesType> = const { Cell::new(BytesType::Bytes) }; + pub(crate) static SER_BYTES_TYPE: Cell<BytesType> = const { Cell::new(BytesType::Unset) }; /// A thread local that is used to decide if a [`Value::Bytes`] needs to be deserialized to /// a [`Vec`] or slice. @@ -33,6 +33,7 @@ thread_local! { #[derive(Debug, Clone, Copy)] pub(crate) enum BytesType { + Unset, Bytes, Fixed, } @@ -99,6 +100,7 @@ pub mod bytes { use crate::{ Schema, schema::{Name, Namespace, RecordField}, + serde::with::BytesType, }; /// Returns [`Schema::Bytes`] @@ -119,6 +121,7 @@ pub mod bytes { where S: Serializer, { + let _guard = super::BytesTypeGuard::set(BytesType::Bytes); serde_bytes::serialize(bytes, serializer) } @@ -126,6 +129,7 @@ pub mod bytes { where D: Deserializer<'de>, { + let _guard = super::BytesTypeGuard::set(BytesType::Bytes); serde_bytes::deserialize(deserializer) } } @@ -161,6 +165,7 @@ pub mod bytes_opt { use crate::{ Schema, schema::{Name, Namespace, RecordField, UnionSchema}, + serde::with::BytesType, }; /// Returns `Schema::Union(Schema::Null, Schema::Bytes)` @@ -184,6 +189,7 @@ pub mod bytes_opt { S: Serializer, B: Borrow<[u8]> + serde_bytes::Serialize, { + let _guard = super::BytesTypeGuard::set(BytesType::Bytes); serde_bytes::serialize(bytes, serializer) } @@ -191,6 +197,7 @@ pub mod bytes_opt { where D: Deserializer<'de>, { + let _guard = super::BytesTypeGuard::set(BytesType::Bytes); serde_bytes::deserialize(deserializer) } } @@ -268,6 +275,7 @@ pub mod fixed { where D: Deserializer<'de>, { + let _guard = super::BytesTypeGuard::set(BytesType::Fixed); serde_bytes::deserialize(deserializer) } } @@ -342,6 +350,7 @@ pub mod fixed_opt { where D: Deserializer<'de>, { + let _guard = super::BytesTypeGuard::set(BytesType::Fixed); serde_bytes::deserialize(deserializer) } } @@ -380,6 +389,7 @@ pub mod slice { use crate::{ Schema, schema::{Name, Namespace, RecordField}, + serde::with::BytesType, }; /// Returns [`Schema::Bytes`] @@ -400,6 +410,7 @@ pub mod slice { where S: Serializer, { + let _guard = super::BytesTypeGuard::set(BytesType::Bytes); serde_bytes::serialize(bytes, serializer) } @@ -407,6 +418,7 @@ pub mod slice { where D: Deserializer<'de>, { + let _bytes_guard = super::BytesTypeGuard::set(BytesType::Bytes); let _guard = super::BorrowedGuard::set(true); serde_bytes::deserialize(deserializer) } @@ -445,6 +457,7 @@ pub mod slice_opt { use crate::{ Schema, schema::{Name, Namespace, RecordField, UnionSchema}, + serde::with::BytesType, }; /// Returns `Schema::Union(Schema::Null, Schema::Bytes)` @@ -468,6 +481,7 @@ pub mod slice_opt { S: Serializer, B: Borrow<[u8]> + serde_bytes::Serialize, { + let _guard = super::BytesTypeGuard::set(BytesType::Bytes); serde_bytes::serialize(&bytes, serializer) } @@ -475,11 +489,317 @@ pub mod slice_opt { where D: Deserializer<'de>, { + let _bytes_guard = super::BytesTypeGuard::set(BytesType::Bytes); let _guard = super::BorrowedGuard::set(true); serde_bytes::deserialize(deserializer) } } +/// Efficient (de)serialization of [`BigDecimal`] values. +/// +/// This module is intended to be used through the Serde `with` attribute. +/// +/// Use [`apache_avro::serde::bigdecimal_opt`] for optional decimal 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)] +/// #[serde(with = "apache_avro::serde::bigdecimal")] +/// decimal: BigDecimal, +/// } +/// ``` +/// +/// [`BigDecimal`]: bigdecimal::BigDecimal +/// [`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, Namespace, RecordField}, + serde::with::BytesType, + }; + + /// Returns [`Schema::BigDecimal`] + pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema { + Schema::BigDecimal + } + + /// Returns `None` + pub fn get_record_fields_in_ctxt( + _: usize, + _: &mut HashSet<Name>, + _: &Namespace, + ) -> 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(|e| S::Error::custom(e))?; + 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); + let bytes: &'de [u8] = serde_bytes::deserialize(deserializer)?; + + deserialize_big_decimal(bytes).map_err(|e| D::Error::custom(e)) + } +} + +/// Efficient (de)serialization of optional [`BigDecimal`] values. +/// +/// This module is intended to be used through the Serde `with` attribute. +/// +/// Use [`apache_avro::serde::bigdecimal`] for non-optional decimal 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)] +/// #[serde(with = "apache_avro::serde::bigdecimal_opt")] +/// decimal: Option<BigDecimal>, +/// } +/// ``` +/// +/// [`BigDecimal`]: bigdecimal::BigDecimal +/// [`apache_avro::serde::bigdecimal`]: bigdecimal +pub mod bigdecimal_opt { + use bigdecimal::BigDecimal; + use serde::{Deserializer, Serializer, de::Error as _, ser::Error as _}; + use std::collections::HashSet; + + use crate::{ + Schema, + bigdecimal::{big_decimal_as_bytes, deserialize_big_decimal}, + schema::{Name, Namespace, RecordField, UnionSchema}, + serde::with::BytesType, + }; + + /// Returns `Schema::Union(Schema::Null, Schema::BigDecimal)` + pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> 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( + _: usize, + _: &mut HashSet<Name>, + _: &Namespace, + ) -> Option<Vec<RecordField>> { + None + } + + pub fn serialize<S, B>(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(|e| S::Error::custom(e))?; + 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<&'de [u8]> = serde_bytes::deserialize(deserializer)?; + if let Some(bytes) = bytes { + deserialize_big_decimal(bytes) + .map(Some) + .map_err(|e| D::Error::custom(e)) + } 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)] +/// #[serde(with = "apache_avro::serde::array")] +/// array: [i32; 10], +/// } +/// ``` +/// +/// [`apache_avro::serde::array_opt`]: array_opt +pub mod array { + use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _}; + use std::collections::HashSet; + + use crate::{ + AvroSchema, AvroSchemaComponent, Schema, + schema::{Name, Namespace, RecordField}, + }; + + /// Returns `Schema::Array(T::get_schema())` + pub fn get_schema_in_ctxt<T: AvroSchemaComponent>( + _: &mut HashSet<Name>, + _: &Namespace, + ) -> Schema { + Schema::array(T::get_schema()).build() + } + + /// Returns `None` + pub fn get_record_fields_in_ctxt( + _: usize, + _: &mut HashSet<Name>, + _: &Namespace, + ) -> 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: Deserialize<'de>, + { + 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)] +/// #[serde(with = "apache_avro::serde::array_opt")] +/// array: Option<[i32; 10]>, +/// } +/// ``` +/// +/// [`apache_avro::serde::array`]: array +pub mod array_opt { + use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _}; + use std::collections::HashSet; + + use crate::{ + AvroSchema, AvroSchemaComponent, Schema, + schema::{Name, Namespace, RecordField, UnionSchema}, + }; + + /// Returns `Schema::Union(Schema::Null, Schema::Array(T::get_schema()))` + pub fn get_schema_in_ctxt<T: AvroSchemaComponent>( + _: &mut HashSet<Name>, + _: &Namespace, + ) -> Schema { + Schema::Union( + UnionSchema::new(vec![Schema::Null, Schema::array(T::get_schema()).build()]) + .expect("This is a valid union"), + ) + } + + /// Returns `None` + pub fn get_record_fields_in_ctxt( + _: usize, + _: &mut HashSet<Name>, + _: &Namespace, + ) -> 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};
