This is an automated email from the ASF dual-hosted git repository. kriskras99 pushed a commit to branch feat/full_enum_support in repository https://gitbox.apache.org/repos/asf/avro-rs.git
commit c3e182567cd9251df88c44bbf9bf2cd967725ae5 Author: default <[email protected]> AuthorDate: Thu Mar 19 12:47:49 2026 +0000 temp --- avro/src/bigdecimal.rs | 45 +- avro/src/error.rs | 15 +- avro/src/schema/mod.rs | 64 + avro/src/serde/derive.rs | 384 +++++- avro/src/serde/mod.rs | 5 +- avro/src/serde/ser_schema/mod.rs | 1723 ++++++++++--------------- avro/src/serde/ser_schema/record/mod.rs | 58 +- avro/src/serde/with.rs | 309 +++++ avro/src/writer/mod.rs | 2 +- avro/tests/serde_human_readable_false.rs | 2 +- avro/tests/serde_human_readable_true.rs | 4 +- avro_derive/tests/derive.proptest-regressions | 8 + avro_derive/tests/derive.rs | 27 +- avro_derive/tests/serde.rs | 9 +- 14 files changed, 1516 insertions(+), 1139 deletions(-) diff --git a/avro/src/bigdecimal.rs b/avro/src/bigdecimal.rs index 7a6a379..ee26ee1 100644 --- a/avro/src/bigdecimal.rs +++ b/avro/src/bigdecimal.rs @@ -190,6 +190,47 @@ mod tests { big_decimal: BigDecimal, } + let schema_str = r#" + { + "type": "record", + "name": "Test", + "fields": [ + { + "name": "big_decimal", + "type": "string" + } + ] + } + "#; + let schema = Schema::parse_str(schema_str)?; + + let test = Test::default(); + + // write a record + let mut writer = Writer::new(&schema, Vec::new())?; + writer.append_ser(test.clone())?; + + let wrote_data = writer.into_inner()?; + + // read record + let mut reader = Reader::new(&wrote_data[..])?; + + let value = reader.next().unwrap()?; + + assert_eq!(test, from_value::<Test>(&value)?); + + Ok(()) + } + + #[test] + fn avro_rs_338_deserialize_serde_way_with_bigdecimal() -> TestResult { + #[derive(Clone, PartialEq, Eq, Debug, Default, serde::Deserialize, serde::Serialize)] + #[serde(rename = "test")] + struct Test { + #[serde(with = "crate::serde::bigdecimal")] + big_decimal: BigDecimal, + } + let schema_str = r#" { "type": "record", @@ -216,11 +257,11 @@ mod tests { let wrote_data = writer.into_inner()?; // read record - let mut reader = Reader::new(&wrote_data[..])?; + let mut reader = Reader::new(&wrote_data[..])?.into_deser_iter(); let value = reader.next().unwrap()?; - assert_eq!(test, from_value::<Test>(&value)?); + assert_eq!(test, value); Ok(()) } diff --git a/avro/src/error.rs b/avro/src/error.rs index 85c7fd8..e4c09cd 100644 --- a/avro/src/error.rs +++ b/avro/src/error.rs @@ -559,7 +559,7 @@ pub enum Details { #[error("Failed to serialize value into Avro value: {0}")] SerializeValue(String), - #[error("Failed to serialize value of type {value_type} using schema {schema:?}: {value}")] + #[error("Failed to serialize value of type `{value_type}` using Schema::{schema:?}: {value}")] SerializeValueWithSchema { value_type: &'static str, value: String, @@ -572,15 +572,18 @@ pub enum Details { schema: RecordSchema, }, - #[error("Failed to serialize field '{field_name}' for record {record_schema:?}: {error}")] + #[error("Failed to serialize field '{field_name}' of record {record_schema:?}: {error}")] SerializeRecordFieldWithSchema { field_name: String, - record_schema: Schema, - error: Box<Error>, + record_schema: RecordSchema, + error: String, }, - #[error("Missing default for skipped field '{field_name}' for schema {schema:?}")] - MissingDefaultForSkippedField { field_name: String, schema: Schema }, + #[error("Missing default for skipped field '{field_name}' of schema {schema:?}")] + MissingDefaultForSkippedField { + field_name: String, + schema: RecordSchema, + }, #[error("Failed to deserialize Avro value into value: {0}")] DeserializeValue(String), diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs index 40476cb..ccaa9f8 100644 --- a/avro/src/schema/mod.rs +++ b/avro/src/schema/mod.rs @@ -45,6 +45,7 @@ use serde::{ ser::{SerializeMap, SerializeSeq}, }; use serde_json::{Map, Value as JsonValue}; +use std::borrow::Cow; use std::fmt::Formatter; use std::{ collections::{BTreeMap, HashMap, HashSet}, @@ -784,6 +785,69 @@ impl Schema { } Ok(()) } + + /// Derive a name for this schema. + /// + /// The name is a valid schema name and will be unique if the named + /// schemas in this schema have unique names. + pub(crate) fn unique_normalized_name(&self) -> Cow<'static, str> { + match self { + Schema::Null => Cow::Borrowed("n"), + Schema::Boolean => Cow::Borrowed("B"), + Schema::Int => Cow::Borrowed("i"), + Schema::Long => Cow::Borrowed("l"), + Schema::Float => Cow::Borrowed("f"), + Schema::Double => Cow::Borrowed("d"), + Schema::Bytes => Cow::Borrowed("b"), + Schema::String => Cow::Borrowed("s"), + Schema::Array(array) => { + Cow::Owned(format!("a_{}", array.items.unique_normalized_name())) + } + Schema::Map(map) => Cow::Owned(format!("m_{}", map.types.unique_normalized_name())), + Schema::Union(union) => { + let mut name = format!("u{}", union.schemas.len()); + for schema in &union.schemas { + name.push('_'); + name.push_str(&schema.unique_normalized_name()); + } + Cow::Owned(name) + } + Schema::BigDecimal => Cow::Borrowed("bd"), + Schema::Date => Cow::Borrowed("D"), + Schema::TimeMillis => Cow::Borrowed("t"), + Schema::TimeMicros => Cow::Borrowed("tm"), + Schema::TimestampMillis => Cow::Borrowed("T"), + Schema::TimestampMicros => Cow::Borrowed("TM"), + Schema::TimestampNanos => Cow::Borrowed("TN"), + Schema::LocalTimestampMillis => Cow::Borrowed("L"), + Schema::LocalTimestampMicros => Cow::Borrowed("LM"), + Schema::LocalTimestampNanos => Cow::Borrowed("LN"), + Schema::Decimal(DecimalSchema { + inner: InnerDecimalSchema::Bytes, + precision, + scale, + }) => Cow::Owned(format!("db_{precision}_{scale}")), + Schema::Uuid(UuidSchema::Bytes) => Cow::Borrowed("ub"), + Schema::Uuid(UuidSchema::String) => Cow::Borrowed("us"), + Schema::Record(RecordSchema { name, .. }) + | Schema::Enum(EnumSchema { name, .. }) + | Schema::Fixed(FixedSchema { name, .. }) + | Schema::Decimal(DecimalSchema { + inner: InnerDecimalSchema::Fixed(FixedSchema { name, .. }), + .. + }) + | Schema::Uuid(UuidSchema::Fixed(FixedSchema { name, .. })) + | Schema::Duration(FixedSchema { name, .. }) + | Schema::Ref { name } => { + let name: String = name + .to_string() + .chars() + .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) + .collect(); + Cow::Owned(format!("r{}_{}", name.len(), name)) + } + } + } } impl Serialize for Schema { diff --git a/avro/src/serde/derive.rs b/avro/src/serde/derive.rs index 33c4f5b..255da4b 100644 --- a/avro/src/serde/derive.rs +++ b/avro/src/serde/derive.rs @@ -558,27 +558,6 @@ macro_rules! impl_array_schema ( impl_array_schema!([T] where T: AvroSchemaComponent); impl_array_schema!(Vec<T> where T: AvroSchemaComponent); -// This doesn't work as the macro doesn't allow specifying the N parameter -// impl_array_schema!([T; N] where T: AvroSchemaComponent); - -impl<const N: usize, T> AvroSchemaComponent for [T; N] -where - T: AvroSchemaComponent, -{ - fn get_schema_in_ctxt( - named_schemas: &mut HashSet<Name>, - enclosing_namespace: NamespaceRef, - ) -> Schema { - Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace)).build() - } - - fn get_record_fields_in_ctxt( - _: &mut HashSet<Name>, - _: NamespaceRef, - ) -> Option<Vec<RecordField>> { - None - } -} impl<T> AvroSchemaComponent for HashMap<String, T> where @@ -630,27 +609,35 @@ where } impl AvroSchemaComponent for core::time::Duration { - /// The schema is [`Schema::Duration`] with the name `duration`. + /// The schema is [`Schema::Record`] with the name `Duration`. /// - /// This is a lossy conversion as this Avro type does not store the amount of nanoseconds. + /// It has two fields: + /// - `secs` with the schema `Schema::Fixed(name: "u64", size: 8)` + /// - `nanos` with the schema `Schema::Long` fn get_schema_in_ctxt( named_schemas: &mut HashSet<Name>, enclosing_namespace: NamespaceRef, ) -> Schema { - let name = Name::new_with_enclosing_namespace("duration", enclosing_namespace) + let name = Name::new_with_enclosing_namespace("Duration", enclosing_namespace) .expect("Name is valid"); if named_schemas.contains(&name) { Schema::Ref { name } } else { - let schema = Schema::Duration(FixedSchema { - name: name.clone(), - aliases: None, - doc: None, - size: 12, - attributes: Default::default(), - }); - named_schemas.insert(name); - schema + named_schemas.insert(name.clone()); + Schema::record(name) + .fields(vec![ + // Secs is an u64 + RecordField::builder() + .name("secs") + .schema(u64::get_schema_in_ctxt(named_schemas, enclosing_namespace)) + .build(), + // Nanos is an u32 + RecordField::builder() + .name("nanos") + .schema(Schema::Long) + .build(), + ]) + .build() } } @@ -666,6 +653,8 @@ impl AvroSchemaComponent for uuid::Uuid { /// The schema is [`Schema::Uuid`] with the name `uuid`. /// /// The underlying schema is [`Schema::Fixed`] with a size of 16. + /// + /// If you're using `human_readable: true` you need to override this schema with a `Schema::String`. fn get_schema_in_ctxt( named_schemas: &mut HashSet<Name>, enclosing_namespace: NamespaceRef, @@ -788,13 +777,197 @@ impl AvroSchemaComponent for i128 { } } +/// Schema definition for `[T; N]` +/// +/// Schema is defined as follows: +/// - 0-sized arrays: [`Schema::Null`] +/// - 1-sized arrays: `T::get_schema_in_ctxt` +/// - N-sized arrays: [`Schema::Record`] with a field for every index +/// +/// If you need or want a [`Schema::Array`], [`Schema::Bytes`], or [`Schema::Fixed`] instead, +/// use [`apache_avro::serde::array`], [`apache_avro::serde::bytes`], or [`apache_avro::serde::fixed`] respectively. +/// +/// [`apache_avro::serde::array`]: crate::serde::array +/// [`apache_avro::serde::bytes`]: crate::serde::bytes +/// [`apache_avro::serde::fixed`]: crate::serde::fixed +impl<const N: usize, T: AvroSchemaComponent> AvroSchemaComponent for [T; N] { + fn get_schema_in_ctxt( + named_schemas: &mut HashSet<Name>, + enclosing_namespace: NamespaceRef, + ) -> Schema { + if N == 0 { + Schema::Null + } else if N == 1 { + T::get_schema_in_ctxt(named_schemas, enclosing_namespace) + } else { + let t_schema = T::get_schema_in_ctxt(named_schemas, enclosing_namespace); + let name = Name::new_with_enclosing_namespace( + format!("A{N}_{}", t_schema.unique_normalized_name()), + enclosing_namespace, + ) + .expect("Name is valid"); + if named_schemas.contains(&name) { + Schema::Ref { name } + } else { + named_schemas.insert(name.clone()); + + let t_default = T::field_default(); + // If T is a named schema or contains named schemas, they'll now be a reference. + let t_ref = T::get_schema_in_ctxt(named_schemas, enclosing_namespace); + let fields = std::iter::once( + RecordField::builder() + .name("field_0".to_string()) + .schema(t_schema) + .maybe_default(t_default.clone()) + .build(), + ) + .chain((1..N).map(|n| { + RecordField::builder() + .name(format!("field_{n}")) + .schema(t_ref.clone()) + .maybe_default(t_default.clone()) + .build() + })) + .collect(); + + Schema::record(name).fields(fields).build() + } + } + } + + fn get_record_fields_in_ctxt( + named_schemas: &mut HashSet<Name>, + enclosing_namespace: NamespaceRef, + ) -> Option<Vec<RecordField>> { + if N == 0 { + None + } else if N == 1 { + T::get_record_fields_in_ctxt(named_schemas, enclosing_namespace) + } else { + let t_schema = T::get_schema_in_ctxt(named_schemas, enclosing_namespace); + let t_default = T::field_default(); + // If T is a named schema or contains named schemas, they'll now be a reference. + let t_ref = T::get_schema_in_ctxt(named_schemas, enclosing_namespace); + let fields = std::iter::once( + RecordField::builder() + .name("field_0".to_string()) + .schema(t_schema) + .maybe_default(t_default.clone()) + .build(), + ) + .chain((1..N).map(|n| { + RecordField::builder() + .name(format!("field_{n}")) + .schema(t_ref.clone()) + .maybe_default(t_default.clone()) + .build() + })) + .collect(); + Some(fields) + } + } + + /// `None` for 0-sized and N-sized arrays, `T::field_default` for 1-sized arrays + fn field_default() -> Option<serde_json::Value> { + if N == 1 { T::field_default() } else { None } + } +} + +/// Schema definition for `(T₁, T₂, …, Tₙ)`. +/// +/// Implemented for tuples of up to 16 elements. +/// +/// Schema is defined as follows: +/// - 1-tuple: `T::get_schema_in_ctxt` +/// - N-tuple: [`Schema::Record`] with a field for every element +#[cfg_attr(docsrs, doc(fake_variadic))] +impl<T: AvroSchemaComponent> AvroSchemaComponent for (T,) { + fn get_schema_in_ctxt( + named_schemas: &mut HashSet<Name>, + enclosing_namespace: NamespaceRef, + ) -> Schema { + T::get_schema_in_ctxt(named_schemas, enclosing_namespace) + } + + fn get_record_fields_in_ctxt( + named_schemas: &mut HashSet<Name>, + enclosing_namespace: NamespaceRef, + ) -> Option<Vec<RecordField>> { + T::get_record_fields_in_ctxt(named_schemas, enclosing_namespace) + } + + /// `None` for N-tuples, `T::field_default()` for 1-tuple. + fn field_default() -> Option<serde_json::Value> { + T::field_default() + } +} + +macro_rules! tuple_impls { + ($($len:expr => ($($name:ident)+))+) => { + $( + #[cfg_attr(docsrs, doc(hidden))] + impl<$($name: AvroSchemaComponent),+> AvroSchemaComponent for ($($name),+) { + fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: NamespaceRef) -> Schema { + let schemas: [Schema; $len] = [$($name::get_schema_in_ctxt(named_schemas, enclosing_namespace)),+]; + + let mut name = format!("T{}", $len); + for schema in &schemas { + name.push('_'); + name.push_str(&schema.unique_normalized_name()); + } + let name = Name::new_with_enclosing_namespace(name, enclosing_namespace).expect("Name is valid"); + + if named_schemas.contains(&name) { + Schema::Ref { name } + } else { + named_schemas.insert(name.clone()); + + let defaults: [Option<serde_json::Value>; $len] = [$($name::field_default()),+]; + + let fields = schemas.into_iter().zip(defaults.into_iter()).enumerate().map(|(n, (schema, default))| { + RecordField::builder() + .name(format!("field_{n}")) + .schema(schema) + .maybe_default(default) + .build() + }).collect(); + + Schema::record(name).fields(fields).build() + } + } + } + )+ + } +} + +tuple_impls! { + 2 => (T0 T1) + 3 => (T0 T1 T2) + 4 => (T0 T1 T2 T3) + 5 => (T0 T1 T2 T3 T4) + 6 => (T0 T1 T2 T3 T4 T5) + 7 => (T0 T1 T2 T3 T4 T5 T6) + 8 => (T0 T1 T2 T3 T4 T5 T6 T7) + 9 => (T0 T1 T2 T3 T4 T5 T6 T7 T8) + 10 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9) + 11 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10) + 12 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11) + 13 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12) + 14 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13) + 15 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14) + 16 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15) +} + #[cfg(test)] mod tests { + use crate::reader::datum::GenericDatumReader; + use crate::writer::datum::GenericDatumWriter; use crate::{ AvroSchema, Schema, schema::{FixedSchema, Name}, }; use apache_avro_test_helper::TestResult; + use std::io::Cursor; #[test] fn avro_rs_401_str() -> TestResult { @@ -823,23 +996,12 @@ mod tests { Ok(()) } - #[test] - fn avro_rs_401_array() -> TestResult { - let schema = <[u8; 55]>::get_schema(); - assert_eq!(schema, Schema::array(Schema::Int).build()); - - Ok(()) - } - #[test] fn avro_rs_401_option_ref_slice_array() -> TestResult { - let schema = <Option<&[[u8; 55]]>>::get_schema(); + let schema = <Option<&[u8]>>::get_schema(); assert_eq!( schema, - Schema::union(vec![ - Schema::Null, - Schema::array(Schema::array(Schema::Int).build()).build() - ])? + Schema::union(vec![Schema::Null, Schema::array(Schema::Int).build()])? ); Ok(()) @@ -927,4 +1089,134 @@ mod tests { fn avro_rs_489_option_option() { <Option<Option<i32>>>::get_schema(); } + + #[test] + fn avro_rs_xxx_std_time_duration() -> TestResult { + let schema = Schema::parse_str( + r#"{ + "type": "record", + "name": "Duration", + "fields": [ + { "name": "secs", "type": {"type": "fixed", "name": "u64", "size": 8} }, + { "name": "nanos", "type": "long" } + ] + }"#, + )?; + let zero = std::time::Duration::ZERO; + let max = std::time::Duration::MAX; + assert_eq!(schema, std::time::Duration::get_schema()); + + let writer = GenericDatumWriter::builder(&schema).build()?; + let written_zero = writer.write_ser_to_vec(&zero)?; + let written_max = writer.write_ser_to_vec(&max)?; + + let reader = GenericDatumReader::builder(&schema).build()?; + let read_zero = reader.read_deser(&mut Cursor::new(written_zero))?; + assert_eq!(zero, read_zero); + let read_max = reader.read_deser(&mut Cursor::new(written_max))?; + assert_eq!(max, read_max); + Ok(()) + } + + #[test] + fn avro_rs_xxx_0_array() -> TestResult { + assert_eq!(Schema::Null, <[String; 0]>::get_schema()); + assert_eq!(Schema::Null, <[(); 0]>::get_schema()); + assert_eq!(Schema::Null, <[bool; 0]>::get_schema()); + Ok(()) + } + + #[test] + fn avro_rs_xxx_1_array() -> TestResult { + assert_eq!(Schema::String, <[String; 1]>::get_schema()); + assert_eq!(Schema::Null, <[(); 1]>::get_schema()); + assert_eq!(Schema::Boolean, <[bool; 1]>::get_schema()); + Ok(()) + } + + #[test] + fn avro_rs_xxx_n_array() -> TestResult { + let schema = Schema::parse_str( + r#"{ + "type": "record", + "name": "A5_s", + "fields": [ + { "name": "field_0", "type": "string" }, + { "name": "field_1", "type": "string" }, + { "name": "field_2", "type": "string" }, + { "name": "field_3", "type": "string" }, + { "name": "field_4", "type": "string" } + ] + }"#, + )?; + + assert_eq!(schema, <[String; 5]>::get_schema()); + Ok(()) + } + + #[test] + fn avro_rs_xxx_n_array_complex_type() -> TestResult { + let schema = Schema::parse_str( + r#"{ + "type": "record", + "name": "A2_u2_n_r4_uuid", + "fields": [ + { "name": "field_0", "type": ["null", {"type": "fixed", "logicalType": "uuid", "size": 16, "name": "uuid"}], "default": null }, + { "name": "field_1", "type": ["null", "uuid"], "default": null } + ] + }"#, + )?; + + assert_eq!(schema, <[Option<uuid::Uuid>; 2]>::get_schema()); + Ok(()) + } + + #[test] + fn avro_rs_xxx_1_tuple() -> TestResult { + assert_eq!(Schema::String, <(String,)>::get_schema()); + assert_eq!(Schema::Null, <((),)>::get_schema()); + assert_eq!(Schema::Boolean, <(bool,)>::get_schema()); + Ok(()) + } + + #[test] + fn avro_rs_xxx_n_tuple() -> TestResult { + let schema = Schema::parse_str( + r#"{ + "type": "record", + "name": "T5_s_i_l_B_n", + "fields": [ + { "name": "field_0", "type": "string" }, + { "name": "field_1", "type": "int" }, + { "name": "field_2", "type": "long" }, + { "name": "field_3", "type": "boolean" }, + { "name": "field_4", "type": "null" } + ] + }"#, + )?; + + assert_eq!(schema, <(String, i32, i64, bool, ())>::get_schema()); + Ok(()) + } + + #[test] + fn avro_rx_xxx_n_tuple_complex_type() -> TestResult { + let schema = Schema::parse_str( + r#"{ + "type": "record", + "name": "T3_u2_n_r4_uuid_r4_uuid_s", + "fields": [ + { "name": "field_0", "type": ["null", {"type": "fixed", "logicalType": "uuid", "size": 16, "name": "uuid"}], "default": null }, + { "name": "field_1", "type": "uuid" }, + { "name": "field_2", "type": "string" } + ] + }"#, + )?; + + assert_eq!( + schema, + <(Option<uuid::Uuid>, uuid::Uuid, String)>::get_schema() + ); + Ok(()) + } } diff --git a/avro/src/serde/mod.rs b/avro/src/serde/mod.rs index 256963d..3b1718b 100644 --- a/avro/src/serde/mod.rs +++ b/avro/src/serde/mod.rs @@ -118,7 +118,10 @@ mod with; pub use de::from_value; pub use derive::{AvroSchema, AvroSchemaComponent}; pub use ser::to_value; -pub use with::{bytes, bytes_opt, fixed, fixed_opt, slice, slice_opt}; +pub use with::{ + array, array_opt, bigdecimal, bigdecimal_opt, bytes, bytes_opt, fixed, fixed_opt, slice, + slice_opt, +}; #[doc(hidden)] pub use derive::get_record_fields_in_ctxt; diff --git a/avro/src/serde/ser_schema/mod.rs b/avro/src/serde/ser_schema/mod.rs index 829cec4..c971bff 100644 --- a/avro/src/serde/ser_schema/mod.rs +++ b/avro/src/serde/ser_schema/mod.rs @@ -268,7 +268,7 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> Serializer for SchemaAwareSerializer<' } fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> { - self.checked_write_long("i64", i64::from(v)) + self.checked_write_long("u32", i64::from(v)) } fn serialize_u64(mut self, v: u64) -> Result<Self::Ok, Self::Error> { @@ -334,7 +334,7 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> Serializer for SchemaAwareSerializer<' Schema::Union(union) => { UnionSerializer::new(self.writer, union, self.config).serialize_str(v) } - _ => Err(self.error("string", "Expected Schema::String ")), + _ => Err(self.error("str", "Expected Schema::String | Schema::Uuid(String)")), } } @@ -406,36 +406,36 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> Serializer for SchemaAwareSerializer<' } _ => Err(self.error( "unit struct", - format!("Expected Schema::Record(name: {name}, fields: [])"), + format!(r#"Expected Schema::Record(name: "{name}", fields: [])"#), )), } } fn serialize_unit_variant( self, - name: &'static str, + _name: &'static str, variant_index: u32, variant: &'static str, ) -> Result<Self::Ok, Self::Error> { match self.schema { Schema::Enum(enum_schema) => { // Plain enum - if name.as_ptr() == SERIALIZING_SCHEMA_DEFAULT.as_ptr() || enum_schema.symbols[variant_index as usize] == variant { + if variant.as_ptr() == SERIALIZING_SCHEMA_DEFAULT.as_ptr() || enum_schema.symbols[variant_index as usize] == variant { zig_i32(variant_index as i32, &mut *self.writer) } else { - Err(self.error("unit variant", format!(r#"Expected symbol "{name}" at index {variant_index} in enum"#))) + Err(self.error("unit variant", format!(r#"Expected symbol "{variant}" at index {variant_index} in enum"#))) } } Schema::Union(union) => match self.get_resolved_union_variant(union, variant_index)? { // Bare union Schema::Null => zig_i32(variant_index as i32, &mut *self.writer), - Schema::Record(record) if record.fields.is_empty() && record.name.name() == name => { + Schema::Record(record) if record.fields.is_empty() && record.name.name() == variant => { // Union of records zig_i32(variant_index as i32, &mut *self.writer) } - _ => Err(self.error("unit variant", format!("Expected Schema::Null | Schema::Record(name: {name}, fields: []) at index {variant_index} in the union"))), + _ => Err(self.error("unit variant", format!("Expected Schema::Null | Schema::Record(name: {variant}, fields: []) at index {variant_index} in the union"))), } - _ => Err(self.error("unit variant", format!("Expected Schema::Enum(symbols[{variant_index}] == {name}) | Schema::Union(variants[{variant_index}] == Schema::Null | Schema::Record(name: {name}, fields: []))"))), + _ => Err(self.error("unit variant", format!("Expected Schema::Enum(symbols[{variant_index}] == {variant}) | Schema::Union(variants[{variant_index}] == Schema::Null | Schema::Record(name: {variant}, fields: []))"))), } } @@ -527,7 +527,10 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> Serializer for SchemaAwareSerializer<' None, )), // This error case can only happen for len > 1 - _ => Err(self.error("tuple", "Expected Schema::Record(fields.len() == {len}")), + _ => Err(self.error( + "tuple", + format!("Expected Schema::Record(fields.len() == {len})"), + )), } } @@ -725,47 +728,94 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> SerializeMap for MapOrRecordSerializer mod tests { use super::*; use crate::schema::FixedSchema; - use crate::{ - Days, Duration, Millis, Months, Reader, Writer, decimal::Decimal, error::Details, - from_value, schema::ResolvedSchema, - }; + use crate::{Days, Duration, Millis, Months, decimal::Decimal, schema::ResolvedSchema}; use apache_avro_test_helper::TestResult; use bigdecimal::BigDecimal; use num_bigint::{BigInt, Sign}; + use pretty_assertions::assert_eq; use serde::{Deserialize, Serialize}; - use serde_bytes::{ByteArray, Bytes}; + use serde_bytes::Bytes; use std::{ collections::{BTreeMap, HashMap}, marker::PhantomData, }; use uuid::Uuid; + #[track_caller] + fn assert_serialize_err<T: Serialize>( + t: T, + schema: &Schema, + names: &HashMap<Name, &Schema>, + expected: &str, + ) { + let config = Config { + names, + target_block_size: None, + human_readable: false, + }; + let mut buffer = Vec::new(); + let serializer = SchemaAwareSerializer::new(&mut buffer, schema, config).unwrap(); + let error = t + .serialize(serializer) + .expect_err("This should not serialize"); + assert_eq!(error.to_string(), expected); + } + + #[track_caller] + fn assert_serialize<T: Serialize>( + t: T, + schema: &Schema, + names: &HashMap<Name, &Schema>, + expected: &[u8], + ) { + let config = Config { + names, + target_block_size: None, + human_readable: false, + }; + let mut buffer = Vec::new(); + let serializer = SchemaAwareSerializer::new(&mut buffer, schema, config).unwrap(); + let bytes_written = t.serialize(serializer).expect("This should serialize"); + assert_eq!(bytes_written, buffer.len()); + assert_eq!(&buffer, expected); + } + #[test] fn test_serialize_null() -> TestResult { let schema = Schema::Null; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - ().serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - None::<()>.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - None::<i32>.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - None::<String>.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - assert!( - "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize((), &schema, &names, &[]); + assert_serialize_err( + None::<()>, + &schema, + &names, + "Failed to serialize value of type `none` using Schema::Null: Expected Schema::Union([Schema::Null, _])", + ); + assert_serialize_err( + None::<i32>, + &schema, + &names, + "Failed to serialize value of type `none` using Schema::Null: Expected Schema::Union([Schema::Null, _])", ); - assert!( - Some("") - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize_err( + None::<String>, + &schema, + &names, + "Failed to serialize value of type `none` using Schema::Null: Expected Schema::Union([Schema::Null, _])", + ); + assert_serialize_err( + "", + &schema, + &names, + "Failed to serialize value of type `str` using Schema::Null: Expected Schema::String | Schema::Uuid(String)", + ); + assert_serialize_err( + Some(""), + &schema, + &names, + "Failed to serialize value of type `some` using Schema::Null: Expected Schema::Union([Schema::Null, _])", ); - - assert_eq!(buffer.as_slice(), Vec::<u8>::new().as_slice()); Ok(()) } @@ -773,59 +823,54 @@ mod tests { #[test] fn test_serialize_bool() -> TestResult { let schema = Schema::Boolean; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - true.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - false.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - assert!( - "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize(true, &schema, &names, &[1]); + assert_serialize(false, &schema, &names, &[0]); + assert_serialize_err( + "", + &schema, + &names, + "Failed to serialize value of type `str` using Schema::Boolean: Expected Schema::String | Schema::Uuid(String)", ); - assert!( - Some("") - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize_err( + Some(""), + &schema, + &names, + "Failed to serialize value of type `some` using Schema::Boolean: Expected Schema::Union([Schema::Null, _])", ); - assert_eq!(buffer.as_slice(), &[1, 0]); - Ok(()) } #[test] fn test_serialize_int() -> TestResult { let schema = Schema::Int; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - 4u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 31u16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 13u32.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!( - "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize(4u8, &schema, &names, &[8]); + assert_serialize(31u16, &schema, &names, &[62]); + assert_serialize(7i8, &schema, &names, &[14]); + assert_serialize(-57i16, &schema, &names, &[113]); + assert_serialize(129i32, &schema, &names, &[130, 2]); + assert_serialize_err( + 13u32, + &schema, + &names, + "Failed to serialize value of type `u32` using Schema::Int: Expected Schema::Long | Schema::TimeMicros | Schema::{,Local}Timestamp{Millis,Micros,Nanos}", ); - assert!( - Some("") - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize_err( + "", + &schema, + &names, + "Failed to serialize value of type `str` using Schema::Int: Expected Schema::String | Schema::Uuid(String)", + ); + assert_serialize_err( + Some(""), + &schema, + &names, + "Failed to serialize value of type `some` using Schema::Int: Expected Schema::Union([Schema::Null, _])", ); - - assert_eq!(buffer.as_slice(), &[8, 62, 26, 14, 113, 130, 2]); Ok(()) } @@ -833,35 +878,57 @@ mod tests { #[test] fn test_serialize_long() -> TestResult { let schema = Schema::Long; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - 4u8.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 31u16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 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)?)?; - (-432i64).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - assert!( - "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize(13u32, &schema, &names, &[26]); + assert_serialize(-432i64, &schema, &names, &[223, 6]); + assert_serialize_err( + 4u8, + &schema, + &names, + "Failed to serialize value of type `u8` using Schema::Long: Expected Schema::Int | Schema::Date | Schema::TimeMillis", ); - assert!( - Some("") - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize_err( + 31u16, + &schema, + &names, + "Failed to serialize value of type `u16` using Schema::Long: Expected Schema::Int | Schema::Date | Schema::TimeMillis", ); - - assert_eq!( - buffer.as_slice(), - &[8, 62, 26, 198, 4, 14, 113, 130, 2, 223, 6] + assert_serialize_err( + 7i8, + &schema, + &names, + "Failed to serialize value of type `i8` using Schema::Long: Expected Schema::Int | Schema::Date | Schema::TimeMillis", + ); + assert_serialize_err( + -57i16, + &schema, + &names, + "Failed to serialize value of type `i16` using Schema::Long: Expected Schema::Int | Schema::Date | Schema::TimeMillis", + ); + assert_serialize_err( + 129i32, + &schema, + &names, + "Failed to serialize value of type `i32` using Schema::Long: Expected Schema::Int | Schema::Date | Schema::TimeMillis", + ); + assert_serialize_err( + 24u64, + &schema, + &names, + r#"Failed to serialize value of type `u64` using Schema::Long: Expected Schema::Fixed(name: "u64", size: 8)"#, + ); + assert_serialize_err( + "", + &schema, + &names, + "Failed to serialize value of type `str` using Schema::Long: Expected Schema::String | Schema::Uuid(String)", + ); + assert_serialize_err( + Some(""), + &schema, + &names, + "Failed to serialize value of type `some` using Schema::Long: Expected Schema::Union([Schema::Null, _])", ); Ok(()) @@ -870,55 +937,60 @@ mod tests { #[test] fn test_serialize_float() -> TestResult { let schema = Schema::Float; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - 4.7f32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - (-14.1f64).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - assert!( - "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize(4.7f32, &schema, &names, &[102, 102, 150, 64]); + assert_serialize_err( + -14.1f64, + &schema, + &names, + "Failed to serialize value of type `f64` using Schema::Float: Expected Schema::Double", + ); + assert_serialize_err( + "", + &schema, + &names, + "Failed to serialize value of type `str` using Schema::Float: Expected Schema::String | Schema::Uuid(String)", ); - assert!( - Some("") - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize_err( + Some(""), + &schema, + &names, + "Failed to serialize value of type `some` using Schema::Float: Expected Schema::Union([Schema::Null, _])", ); - assert_eq!(buffer.as_slice(), &[102, 102, 150, 64, 154, 153, 97, 193]); - Ok(()) } #[test] fn test_serialize_double() -> TestResult { - let schema = Schema::Float; - let mut buffer: Vec<u8> = Vec::new(); + let schema = Schema::Double; let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - 4.7f32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - (-14.1f64).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - assert!( - "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize( + -14.1f64, + &schema, + &names, + &[51, 51, 51, 51, 51, 51, 44, 192], ); - assert!( - Some("") - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize_err( + 4.7f32, + &schema, + &names, + "Failed to serialize value of type `f32` using Schema::Double: Expected Schema::Float", + ); + assert_serialize_err( + "", + &schema, + &names, + "Failed to serialize value of type `str` using Schema::Double: Expected Schema::String | Schema::Uuid(String)", + ); + assert_serialize_err( + Some(""), + &schema, + &names, + "Failed to serialize value of type `some` using Schema::Double: Expected Schema::Union([Schema::Null, _])", ); - - assert_eq!(buffer.as_slice(), &[102, 102, 150, 64, 154, 153, 97, 193]); Ok(()) } @@ -926,34 +998,43 @@ mod tests { #[test] fn test_serialize_bytes() -> TestResult { let schema = Schema::Bytes; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - '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, + assert_serialize( + Bytes::new(&[12, 3, 7, 91, 4]), &schema, - config, - )?)?; - assert!( - ().serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + &names, + &[10, 12, 3, 7, 91, 4], ); - assert!( - PhantomData::<String> - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize_err( + 'a', + &schema, + &names, + "Failed to serialize value of type `char` using Schema::Bytes: Expected Schema::String", ); - - assert_eq!( - buffer.as_slice(), - &[2, b'a', 8, b't', b'e', b's', b't', 10, 12, 3, 7, 91, 4] + assert_serialize_err( + "test", + &schema, + &names, + "Failed to serialize value of type `str` using Schema::Bytes: Expected Schema::String | Schema::Uuid(String)", + ); + assert_serialize_err( + (), + &schema, + &names, + "Failed to serialize value of type `unit` using Schema::Bytes: Expected Schema::Null", + ); + assert_serialize_err( + PhantomData::<String>, + &schema, + &names, + r#"Failed to serialize value of type `unit struct` using Schema::Bytes: Expected Schema::Record(name: "PhantomData", fields: [])"#, + ); + assert_serialize_err( + Some(""), + &schema, + &names, + "Failed to serialize value of type `some` using Schema::Bytes: Expected Schema::Union([Schema::Null, _])", ); Ok(()) @@ -962,34 +1043,39 @@ mod tests { #[test] fn test_serialize_string() -> TestResult { let schema = Schema::String; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - '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, + assert_serialize('a', &schema, &names, &[2, b'a']); + assert_serialize("test", &schema, &names, &[8, b't', b'e', b's', b't']); + assert_serialize( + BigDecimal::new(BigInt::new(Sign::Plus, vec![50024]), 2), &schema, - config, - )?)?; - assert!( - ().serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + &names, + &[12, b'5', b'0', b'0', b'.', b'2', b'4'], ); - assert!( - PhantomData::<String> - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize_err( + Bytes::new(&[12, 3, 7, 91, 4]), + &schema, + &names, + "Failed to serialize value of type `bytes` using Schema::String: Expected Schema::Bytes | Schema::Fixed | Schema::BigDecimal | Schema::Decimal | Schema::Uuid(Bytes | Fixed) | Schema::Duration", ); - - assert_eq!( - buffer.as_slice(), - &[2, b'a', 8, b't', b'e', b's', b't', 10, 12, 3, 7, 91, 4] + assert_serialize_err( + (), + &schema, + &names, + "Failed to serialize value of type `unit` using Schema::String: Expected Schema::Null", + ); + assert_serialize_err( + PhantomData::<String>, + &schema, + &names, + r#"Failed to serialize value of type `unit struct` using Schema::String: Expected Schema::Record(name: "PhantomData", fields: [])"#, + ); + assert_serialize_err( + Some(""), + &schema, + &names, + "Failed to serialize value of type `some` using Schema::String: Expected Schema::Union([Schema::Null, _])", ); Ok(()) @@ -1009,55 +1095,55 @@ 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, } - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; let good_record = GoodTestRecord { string_field: String::from("test"), int_field: 10, }; - good_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert_serialize( + good_record, + &schema, + &names, + &[8, b't', b'e', b's', b't', 20], + ); let bad_record = BadTestRecord { foo_string_field: String::from("test"), bar_int_field: 10, }; - assert!( - bad_record - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize_err( + bad_record, + &schema, + &names, + r#"Missing field in record: "fooStringField""#, ); - - assert!( - "".serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize_err( + "", + &schema, + &names, + r#"Failed to serialize value of type `str` using Schema::Record(RecordSchema { name: Name { name: "TestRecord", .. }, fields: [RecordField { name: "stringField", schema: String, .. }, RecordField { name: "intField", schema: Int, .. }], .. }): Expected Schema::String | Schema::Uuid(String)"#, ); - assert!( - Some("") - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .is_err() + assert_serialize_err( + Some(""), + &schema, + &names, + r#"Failed to serialize value of type `some` using Schema::Record(RecordSchema { name: Name { name: "TestRecord", .. }, fields: [RecordField { name: "stringField", schema: String, .. }, RecordField { name: "intField", schema: Int, .. }], .. }): Expected Schema::Union([Schema::Null, _])"#, ); - assert_eq!(buffer.as_slice(), &[8, b't', b'e', b's', b't', 20]); - Ok(()) } @@ -1071,47 +1157,45 @@ mod tests { }"#, )?; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; #[derive(Serialize)] struct EmptyRecord; - EmptyRecord.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert_serialize(EmptyRecord, &schema, &names, &[]); #[derive(Serialize)] + #[serde(rename = "EmptyRecord")] struct NonEmptyRecord { foo: String, } let record = NonEmptyRecord { foo: "bar".to_string(), }; - match record - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::FieldName(field_name)) if field_name == "foo" => (), - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - - match ().serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?).map_err(Error::into_details) { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "none"); // serialize_unit() delegates to serialize_none() - assert_eq!(value, "None. Cause: Expected: Record. Got: Null"); - assert_eq!(schema, schema); - } - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - - assert_eq!(buffer.len(), 0); + assert_serialize_err(record, &schema, &names, r#"Missing field in record: "foo""#); + assert_serialize_err( + (), + &schema, + &names, + r#"Failed to serialize value of type `unit` using Schema::Record(RecordSchema { name: Name { name: "EmptyRecord", .. }, fields: [], .. }): Expected Schema::Null"#, + ); + assert_serialize_err( + "", + &schema, + &names, + r#"Failed to serialize value of type `str` using Schema::Record(RecordSchema { name: Name { name: "EmptyRecord", .. }, fields: [], .. }): Expected Schema::String | Schema::Uuid(String)"#, + ); + assert_serialize_err( + PhantomData::<String>, + &schema, + &names, + r#"Failed to serialize value of type `unit struct` using Schema::Record(RecordSchema { name: Name { name: "EmptyRecord", .. }, fields: [], .. }): Expected Schema::Record(name: "PhantomData", fields: [])"#, + ); + assert_serialize_err( + Some(""), + &schema, + &names, + r#"Failed to serialize value of type `some` using Schema::Record(RecordSchema { name: Name { name: "EmptyRecord", .. }, fields: [], .. }): Expected Schema::Union([Schema::Null, _])"#, + ); Ok(()) } @@ -1127,6 +1211,7 @@ mod tests { )?; #[derive(Serialize)] + #[serde(rename_all = "SCREAMING_SNAKE_CASE")] enum Suit { Spades, Hearts, @@ -1134,35 +1219,18 @@ mod tests { Clubs, } - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - - Suit::Spades.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - Suit::Hearts.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - Suit::Diamonds.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - Suit::Clubs.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - match None::<()> - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "none"); - assert_eq!(value, "None. Cause: Expected: Enum. Got: Null"); - assert_eq!(schema, schema); - } - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - assert_eq!(buffer.as_slice(), &[0, 2, 4, 6]); + assert_serialize(Suit::Spades, &schema, &names, &[0]); + assert_serialize(Suit::Hearts, &schema, &names, &[2]); + assert_serialize(Suit::Diamonds, &schema, &names, &[4]); + assert_serialize(Suit::Clubs, &schema, &names, &[6]); + assert_serialize_err( + None::<()>, + &schema, + &names, + r#"Failed to serialize value of type `none` using Schema::Enum(EnumSchema { name: Name { name: "Suit", .. }, symbols: ["SPADES", "HEARTS", "DIAMONDS", "CLUBS"], .. }): Expected Schema::Union([Schema::Null, _])"#, + ); Ok(()) } @@ -1176,34 +1244,20 @@ mod tests { }"#, )?; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - let arr: Vec<i64> = vec![10, 5, 400]; - arr.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - match vec![1_f32] - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "f32"); - assert_eq!(value, "1. Cause: Expected: Long. Got: Float"); - assert_eq!(schema, schema); - } - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - - assert_eq!(buffer.as_slice(), &[6, 20, 10, 160, 6, 0]); + assert_serialize( + vec![10i64, 5, 400], + &schema, + &names, + &[6, 20, 10, 160, 6, 0], + ); + assert_serialize_err( + vec![1_f32], + &schema, + &names, + "Failed to serialize value of type `f32` using Schema::Long: Expected Schema::Float", + ); Ok(()) } @@ -1217,44 +1271,29 @@ mod tests { }"#, )?; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; let mut map: BTreeMap<String, i64> = BTreeMap::new(); map.insert(String::from("item1"), 10); map.insert(String::from("item2"), 400); - map.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert_serialize( + map, + &schema, + &names, + &[ + 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, + ], + ); let mut map: BTreeMap<String, &str> = BTreeMap::new(); map.insert(String::from("item1"), "value1"); - match map - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "string"); - assert_eq!(value, "value1. Cause: Expected: Long. Got: String"); - assert_eq!(schema, schema); - } - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - - 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 - ] + assert_serialize_err( + map, + &schema, + &names, + "Failed to serialize value of type `str` using Schema::Long: Expected Schema::String | Schema::Uuid(String)", ); Ok(()) @@ -1274,43 +1313,20 @@ mod tests { Long(i64), } - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - Some(10i64).serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - None::<i64>.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - NullableLong::Long(400).serialize(SchemaAwareSerializer::new( - &mut buffer, + assert_serialize(Some(10i64), &schema, &names, &[2, 20]); + assert_serialize(None::<i64>, &schema, &names, &[0]); + assert_serialize(NullableLong::Long(400), &schema, &names, &[2, 160, 6]); + assert_serialize(NullableLong::Null, &schema, &names, &[0]); + assert_serialize(400i64, &schema, &names, &[2, 160, 6]); + assert_serialize((), &schema, &names, &[0]); + assert_serialize_err( + "invalid", &schema, - config, - )?)?; - NullableLong::Null.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - match "invalid" - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "string"); - assert_eq!( - value, - "invalid. Cause: Expected one of the union variants [Null, Long]. Got: String" - ); - assert_eq!(schema, schema); - } - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - - assert_eq!(buffer.as_slice(), &[2, 20, 0, 2, 160, 6, 0]); + &names, + "Failed to serialize value of type `str` using Schema::Union(UnionSchema { schemas: [Null, Long] }): Expected Schema::String in variants", + ); Ok(()) } @@ -1330,48 +1346,23 @@ mod tests { Str(String), } - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - - LongOrString::Null.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - LongOrString::Long(400).serialize(SchemaAwareSerializer::new( - &mut buffer, + assert_serialize(LongOrString::Null, &schema, &names, &[0]); + assert_serialize(LongOrString::Long(400), &schema, &names, &[2, 160, 6]); + assert_serialize( + LongOrString::Str("test".into()), &schema, - config, - )?)?; - LongOrString::Str(String::from("test")).serialize(SchemaAwareSerializer::new( - &mut buffer, + &names, + &[4, 8, b't', b'e', b's', b't'], + ); + assert_serialize((), &schema, &names, &[0]); + assert_serialize(400i64, &schema, &names, &[2, 160, 6]); + assert_serialize("test", &schema, &names, &[4, 8, b't', b'e', b's', b't']); + assert_serialize_err( + 1f64, &schema, - config, - )?)?; - - match 1_f64 - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "f64"); - assert_eq!( - value, - "1. Cause: Cannot find a Double schema in [Null, Long, String]" - ); - assert_eq!(schema, schema); - } - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - - assert_eq!( - buffer.as_slice(), - &[0, 2, 160, 6, 4, 8, b't', b'e', b's', b't'] + &names, + "Failed to serialize value of type `f64` using Schema::Union(UnionSchema { schemas: [Null, Long, String] }): Expected Schema::Double in variants", ); Ok(()) @@ -1387,75 +1378,32 @@ mod tests { }"#, )?; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - Bytes::new(&[10, 124, 31, 97, 14, 201, 3, 88]).serialize(SchemaAwareSerializer::new( - &mut buffer, + assert_serialize( + Bytes::new(&[10, 124, 31, 97, 14, 201, 3, 88]), &schema, - config, - )?)?; - - // non-8 size - match Bytes::new(&[123]) - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - 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 - assert_eq!(schema, schema); - } - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - - // array - match [1; 8] - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "tuple"); // TODO: why is this 'tuple' ?! - assert_eq!(value, "tuple (len=8). Cause: Expected: Fixed. Got: Array"); - assert_eq!(schema, schema); - } - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - - // slice - match &[1, 2, 3, 4, 5, 6, 7, 8] - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(*value_type, "tuple"); // TODO: why is this 'tuple' ?! - assert_eq!(value, "tuple (len=8). Cause: Expected: Fixed. Got: Array"); - assert_eq!(schema, schema); - } - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - - assert_eq!(buffer.as_slice(), &[10, 124, 31, 97, 14, 201, 3, 88]); + &names, + &[10, 124, 31, 97, 14, 201, 3, 88], + ); + assert_serialize_err( + Bytes::new(&[123]), + &schema, + &names, + r#"Failed to serialize value of type `bytes` using Schema::Fixed(FixedSchema { name: Name { name: "LongVal", .. }, size: 8, .. }): Fixed size (8) does not match bytes length (1)"#, + ); + assert_serialize_err( + [1u8; 8], + &schema, + &names, + r#"Failed to serialize value of type `tuple` using Schema::Fixed(FixedSchema { name: Name { name: "LongVal", .. }, size: 8, .. }): Expected Schema::Record(fields.len() == 8)"#, + ); + assert_serialize_err( + [1u8, 2, 3, 4, 5, 6, 7, 8].as_slice(), + &schema, + &names, + r#"Failed to serialize value of type `seq` using Schema::Fixed(FixedSchema { name: Name { name: "LongVal", .. }, size: 8, .. }): Expected Schema::Array"#, + ); Ok(()) } @@ -1471,31 +1419,16 @@ mod tests { }"#, )?; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; let val = Decimal::from(&[251, 155]); - val.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - match ().serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?).map_err(Error::into_details) { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "none"); - assert_eq!(value, "None. Cause: Expected: Decimal. Got: Null"); - assert_eq!(schema, schema); - } - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - - assert_eq!(buffer.as_slice(), &[4, 251, 155]); + assert_serialize(val, &schema, &names, &[4, 251, 155]); + assert_serialize_err( + (), + &schema, + &names, + "Failed to serialize value of type `unit` using Schema::Decimal(DecimalSchema { precision: 16, scale: 2, inner: Bytes }): Expected Schema::Null", + ); Ok(()) } @@ -1513,31 +1446,16 @@ mod tests { }"#, )?; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; let val = Decimal::from(&[0, 0, 0, 0, 0, 0, 251, 155]); - val.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - match ().serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?).map_err(Error::into_details) { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "none"); - assert_eq!(value, "None. Cause: Expected: Decimal. Got: Null"); - assert_eq!(schema, schema); - } - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - - assert_eq!(buffer.as_slice(), &[0, 0, 0, 0, 0, 0, 251, 155]); + assert_serialize(val, &schema, &names, &[0, 0, 0, 0, 0, 0, 251, 155]); + assert_serialize_err( + (), + &schema, + &names, + r#"Failed to serialize value of type `unit` using Schema::Decimal(DecimalSchema { precision: 16, scale: 2, inner: Fixed(FixedSchema { name: Name { name: "FixedDecimal", .. }, size: 8, attributes: {"precision": Number(16), "scale": Number(2)}, .. }) }): Expected Schema::Null"#, + ); Ok(()) } @@ -1551,18 +1469,21 @@ mod tests { }"#, )?; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - let val = BigDecimal::new(BigInt::new(Sign::Plus, vec![50024]), 2); - val.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + #[derive(Serialize)] + #[serde(transparent)] + struct BigDecimalWrapper { + // This is needed because the Serialize implementation of BigDecimal serializes to a string. + // The with implementation serializes to bytes. + #[serde(with = "crate::serde::bigdecimal")] + value: BigDecimal, + } - assert_eq!(buffer.as_slice(), &[10, 6, 0, 195, 104, 4]); + let val = BigDecimalWrapper { + value: BigDecimal::new(BigInt::new(Sign::Plus, vec![50024]), 2), + }; + assert_serialize(val, &schema, &names, &[10, 6, 0, 195, 104, 4]); Ok(()) } @@ -1578,40 +1499,25 @@ mod tests { }"#, )?; + // Uuid serialize implementation changes based on this value assert!(!crate::util::is_human_readable()); - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - "8c28da81-238c-4326-bddd-4e3d00cc5099" - .parse::<Uuid>()? - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + let uuid = "8c28da81-238c-4326-bddd-4e3d00cc5099".parse::<Uuid>()?; - match 1_u8 - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "u8"); - assert_eq!(value, "1. Cause: Expected: Uuid. Got: Int"); - assert_eq!(schema, schema); - } - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - - assert_eq!( - buffer.as_slice(), + assert_serialize( + uuid, + &schema, + &names, &[ - 140, 40, 218, 129, 35, 140, 67, 38, 189, 221, 78, 61, 0, 204, 80, 153 - ] + 140, 40, 218, 129, 35, 140, 67, 38, 189, 221, 78, 61, 0, 204, 80, 153, + ], + ); + assert_serialize_err( + 1u8, + &schema, + &names, + r#"Failed to serialize value of type `u8` using Schema::Uuid(Fixed(FixedSchema { name: Name { name: "FixedUuid", .. }, size: 16, .. })): Expected Schema::Int | Schema::Date | Schema::TimeMillis"#, ); Ok(()) @@ -1626,39 +1532,23 @@ mod tests { }"#, )?; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - - 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)?)?; - 1000_i16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 10000_i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - match 10000_f32 - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "f32"); - assert_eq!(value, "10000. Cause: Expected: Date. Got: 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_serialize(100u8, &schema, &names, &[200, 1]); + assert_serialize(1000u16, &schema, &names, &[208, 15]); + assert_serialize(1000i16, &schema, &names, &[208, 15]); + assert_serialize(10000i32, &schema, &names, &[160, 156, 1]); + assert_serialize_err( + 10000u32, + &schema, + &names, + "Failed to serialize value of type `u32` using Schema::Date: Expected Schema::Long | Schema::TimeMicros | Schema::{,Local}Timestamp{Millis,Micros,Nanos}", + ); + assert_serialize_err( + 10000f32, + &schema, + &names, + "Failed to serialize value of type `f32` using Schema::Date: Expected Schema::Float", ); Ok(()) @@ -1673,39 +1563,23 @@ mod tests { }"#, )?; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - - 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)?)?; - 1000_i16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 10000_i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - match 10000_f32 - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "f32"); - assert_eq!(value, "10000. Cause: Expected: TimeMillis. Got: 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_serialize(100u8, &schema, &names, &[200, 1]); + assert_serialize(1000u16, &schema, &names, &[208, 15]); + assert_serialize(1000i16, &schema, &names, &[208, 15]); + assert_serialize(10000i32, &schema, &names, &[160, 156, 1]); + assert_serialize_err( + 10000u32, + &schema, + &names, + "Failed to serialize value of type `u32` using Schema::TimeMillis: Expected Schema::Long | Schema::TimeMicros | Schema::{,Local}Timestamp{Millis,Micros,Nanos}", + ); + assert_serialize_err( + 10000f32, + &schema, + &names, + "Failed to serialize value of type `f32` using Schema::TimeMillis: Expected Schema::Float", ); Ok(()) @@ -1720,42 +1594,39 @@ mod tests { }"#, )?; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - - 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)?)?; - 1000_i16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 10000_i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 10000_i64.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - match 10000_f32 - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "f32"); - assert_eq!(value, "10000. Cause: Expected: TimeMicros. Got: 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_serialize(10000u32, &schema, &names, &[160, 156, 1]); + assert_serialize(10000i64, &schema, &names, &[160, 156, 1]); + assert_serialize_err( + 100u8, + &schema, + &names, + "Failed to serialize value of type `u8` using Schema::TimeMicros: Expected Schema::Int | Schema::Date | Schema::TimeMillis", + ); + assert_serialize_err( + 1000u16, + &schema, + &names, + "Failed to serialize value of type `u16` using Schema::TimeMicros: Expected Schema::Int | Schema::Date | Schema::TimeMillis", + ); + assert_serialize_err( + 1000i16, + &schema, + &names, + "Failed to serialize value of type `i16` using Schema::TimeMicros: Expected Schema::Int | Schema::Date | Schema::TimeMillis", + ); + assert_serialize_err( + 10000i32, + &schema, + &names, + "Failed to serialize value of type `i32` using Schema::TimeMicros: Expected Schema::Int | Schema::Date | Schema::TimeMillis", + ); + assert_serialize_err( + 10000f32, + &schema, + &names, + "Failed to serialize value of type `f32` using Schema::TimeMicros: Expected Schema::Float", ); Ok(()) @@ -1763,7 +1634,11 @@ mod tests { #[test] fn test_serialize_timestamp() -> TestResult { - for precision in ["millis", "micros", "nanos"] { + for (precision, error) in [ + ("millis", "Millis"), + ("micros", "Micros"), + ("nanos", "Nanos"), + ] { let schema = Schema::parse_str(&format!( r#"{{ "type": "long", @@ -1771,50 +1646,49 @@ mod tests { }}"# ))?; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - 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)?)?; - 1000_i16.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 10000_i32.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - 10000_i64.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - match 10000_f64 - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - let mut capital_precision = precision.to_string(); - if let Some(c) = capital_precision.chars().next() { - 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!(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_serialize(10000u32, &schema, &names, &[160, 156, 1]); + assert_serialize(10000i64, &schema, &names, &[160, 156, 1]); + assert_serialize_err( + 100u8, + &schema, + &names, + &format!( + "Failed to serialize value of type `u8` using Schema::Timestamp{error}: Expected Schema::Int | Schema::Date | Schema::TimeMillis" + ), + ); + assert_serialize_err( + 1000u16, + &schema, + &names, + &format!( + "Failed to serialize value of type `u16` using Schema::Timestamp{error}: Expected Schema::Int | Schema::Date | Schema::TimeMillis" + ), + ); + assert_serialize_err( + 1000i16, + &schema, + &names, + &format!( + "Failed to serialize value of type `i16` using Schema::Timestamp{error}: Expected Schema::Int | Schema::Date | Schema::TimeMillis" + ), + ); + assert_serialize_err( + 10000i32, + &schema, + &names, + &format!( + "Failed to serialize value of type `i32` using Schema::Timestamp{error}: Expected Schema::Int | Schema::Date | Schema::TimeMillis" + ), + ); + assert_serialize_err( + 10000f32, + &schema, + &names, + &format!( + "Failed to serialize value of type `f32` using Schema::Timestamp{error}: Expected Schema::Float" + ), ); } @@ -1832,38 +1706,21 @@ mod tests { }"#, )?; - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - let duration_bytes = - ByteArray::new(Duration::new(Months::new(3), Days::new(2), Millis::new(1200)).into()); - duration_bytes.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - match [1; 12] - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .map_err(Error::into_details) - { - Err(Details::SerializeValueWithSchema { - value_type, - value, - schema, - }) => { - assert_eq!(value_type, "tuple"); // TODO: why is this 'tuple' ?! - assert_eq!( - value, - "tuple (len=12). Cause: Expected: Duration. Got: Array" - ); - assert_eq!(schema, schema); - } - unexpected => panic!("Expected an error. Got: {unexpected:?}"), - } - - assert_eq!(buffer.as_slice(), &[3, 0, 0, 0, 2, 0, 0, 0, 176, 4, 0, 0]); + let duration = Duration::new(Months::new(3), Days::new(2), Millis::new(1200)); + assert_serialize( + duration, + &schema, + &names, + &[3, 0, 0, 0, 2, 0, 0, 0, 176, 4, 0, 0], + ); + assert_serialize_err( + [0u8; 12], + &schema, + &names, + r#"Failed to serialize value of type `tuple` using Schema::Duration(FixedSchema { name: Name { name: "duration", .. }, size: 12, .. }): Expected Schema::Record(fields.len() == 12)"#, + ); Ok(()) } @@ -1877,7 +1734,6 @@ mod tests { "fields": [ {"name": "stringField", "type": "string"}, {"name": "intField", "type": "int"}, - {"name": "bigDecimalField", "type": {"type": "bytes", "logicalType": "big-decimal"}}, {"name": "uuidField", "type": {"name": "uuid", "type": "fixed", "size": 16, "logicalType": "uuid"}}, {"name": "innerRecord", "type": ["null", "TestRecord"]} ] @@ -1889,44 +1745,34 @@ mod tests { 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 config = Config { - names: rs.get_names(), - target_block_size: None, - human_readable: false, - }; 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(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - assert_eq!( - buffer.as_slice(), + assert_serialize( + good_record, + &schema, + rs.get_names(), &[ - 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 - ] + 8, 116, 101, 115, 116, 20, 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, 140, + 40, 218, 129, 35, 140, 67, 38, 189, 221, 78, 61, 0, 204, 80, 153, 0, + ], ); Ok(()) @@ -1980,34 +1826,43 @@ mod tests { bar: String, } - let mut buffer: Vec<u8> = Vec::new(); let rs = ResolvedSchema::try_from(&schema)?; - let config = Config { - names: rs.get_names(), - target_block_size: None, - human_readable: false, - }; let foo_record = TestRecord { inner_union: InnerUnion::InnerVariantFoo(InnerRecordFoo { foo: String::from("foo"), }), }; - foo_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert_serialize( + foo_record, + &schema, + rs.get_names(), + &[0, 6, b'f', b'o', b'o'], + ); let bar_record = TestRecord { inner_union: InnerUnion::InnerVariantBar(InnerRecordBar { bar: String::from("bar"), }), }; - bar_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert_serialize( + bar_record, + &schema, + rs.get_names(), + &[2, 6, b'b', b'a', b'r'], + ); let int_record = TestRecord { inner_union: InnerUnion::IntField(1), }; - int_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert_serialize(int_record, &schema, rs.get_names(), &[4, 2]); let string_record = TestRecord { inner_union: InnerUnion::StringField(String::from("string")), }; - string_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert_serialize( + string_record, + &schema, + rs.get_names(), + &[6, 12, b's', b't', b'r', b'i', b'n', b'g'], + ); Ok(()) } @@ -2042,54 +1897,28 @@ mod tests { #[derive(Serialize)] #[serde(untagged)] enum InnerUnion { - InnerVariantFoo(InnerRecordFoo), - InnerVariantBar(InnerRecordBar), IntField(i32), - StringField(String), - } - - #[derive(Serialize)] - #[serde(rename = "innerRecordFoo")] - struct InnerRecordFoo { - foo: String, } - #[derive(Serialize)] - #[serde(rename = "innerRecordBar")] - struct InnerRecordBar { - bar: String, - } - - let mut buffer: Vec<u8> = Vec::new(); let rs = ResolvedSchema::try_from(&schema)?; - let config = Config { - names: rs.get_names(), - target_block_size: None, - human_readable: false, - }; + // Flattening a Option into the underlying union is NOT supported let null_record = TestRecord { inner_union: None }; - null_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert_serialize_err( + null_record, + &schema, + rs.get_names(), + r#"Failed to serialize field 'innerUnion' of record RecordSchema { name: Name { name: "TestRecord", .. }, fields: [RecordField { name: "innerUnion", schema: Union(UnionSchema { schemas: [Null, Record(RecordSchema { name: Name { name: "innerRecordFoo", .. }, fields: [RecordField { name: "foo", schema: String, .. }], .. }), Record(RecordSchema { name: Name { name: "innerRecordBar", .. }, fields: [RecordField { name: "bar", schema: String, .. }], .. }), Int, String] }), .. }], . [...] + ); let foo_record = TestRecord { - inner_union: Some(InnerUnion::InnerVariantFoo(InnerRecordFoo { - foo: String::from("foo"), - })), + inner_union: Some(InnerUnion::IntField(42)), }; - foo_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - let bar_record = TestRecord { - inner_union: Some(InnerUnion::InnerVariantBar(InnerRecordBar { - bar: String::from("bar"), - })), - }; - bar_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - let int_record = TestRecord { - inner_union: Some(InnerUnion::IntField(1)), - }; - int_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - let string_record = TestRecord { - inner_union: Some(InnerUnion::StringField(String::from("string"))), - }; - string_record.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; + assert_serialize_err( + foo_record, + &schema, + rs.get_names(), + r#"Failed to serialize field 'innerUnion' of record RecordSchema { name: Name { name: "TestRecord", .. }, fields: [RecordField { name: "innerUnion", schema: Union(UnionSchema { schemas: [Null, Record(RecordSchema { name: Name { name: "innerRecordFoo", .. }, fields: [RecordField { name: "foo", schema: String, .. }], .. }), Record(RecordSchema { name: Name { name: "innerRecordBar", .. }, fields: [RecordField { name: "bar", schema: String, .. }], .. }), Int, String] }), .. }], . [...] + ); Ok(()) } @@ -2099,9 +1928,6 @@ mod tests { struct Foo { a: String, b: String, - c: usize, - d: f64, - e: usize, } let schema = Schema::parse_str( r#" @@ -2116,65 +1942,34 @@ mod tests { { "name":"a", "type":"string" - }, - { - "name":"d", - "type":"double" - }, - { - "name":"e", - "type":"long" - }, - { - "name":"c", - "type":"long" } ] } "#, )?; - let mut writer = Writer::new(&schema, Vec::new())?; - writer.append_ser(Foo { + let names = HashMap::new(); + let foo = Foo { a: "Hello".into(), b: "World".into(), - c: 42, - d: std::f64::consts::PI, - e: 5, - })?; - let encoded = writer.into_inner()?; - let mut reader = Reader::builder(&encoded[..]) - .reader_schema(&schema) - .build()?; - let decoded = from_value::<Foo>(&reader.next().unwrap()?)?; - assert_eq!( - decoded, - Foo { - a: "Hello".into(), - b: "World".into(), - c: 42, - d: std::f64::consts::PI, - e: 5 - } + }; + // Serializing fields out of order is NOT supported + assert_serialize_err( + foo, + &schema, + &names, + r#"Missing default for skipped field 'b' of schema RecordSchema { name: Name { name: "Foo", .. }, fields: [RecordField { name: "b", schema: String, .. }, RecordField { name: "a", schema: String, .. }], .. }"#, ); + 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 config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - - 'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - assert_eq!(buffer.as_slice(), &[2, b'a']); + assert_serialize('a', &schema, &names, &[2, b'a']); Ok(()) } @@ -2182,18 +1977,14 @@ mod tests { #[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 config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - 'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - assert_eq!(buffer.as_slice(), &[2, b'a']); + assert_serialize_err( + 'a', + &schema, + &names, + "Failed to serialize value of type `char` using Schema::Bytes: Expected Schema::String", + ); Ok(()) } @@ -2207,18 +1998,14 @@ mod tests { size: 4, attributes: Default::default(), }); - - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - 'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - assert_eq!(buffer.as_slice(), &[b'a', 0, 0, 0]); + assert_serialize_err( + 'a', + &schema, + &names, + r#"Failed to serialize value of type `char` using Schema::Fixed(FixedSchema { name: Name { name: "char", .. }, size: 4, .. }): Expected Schema::String"#, + ); Ok(()) } @@ -2226,120 +2013,9 @@ mod tests { #[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 config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - - '👹'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - 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 config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - - '👹'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - 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, - attributes: Default::default(), - }); - - let mut buffer: Vec<u8> = Vec::new(); - let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - - '👹'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - - // 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, - attributes: Default::default(), - }); - - let mut buffer: Vec<u8> = Vec::new(); - let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - - assert!(matches!( - 'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .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, - attributes: Default::default(), - }); - - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - assert!(matches!( - 'a'.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .unwrap_err() - .details(), - Details::SerializeValueWithSchema { .. } - )); + assert_serialize('👹', &schema, &names, &[8, 240, 159, 145, 185]); Ok(()) } @@ -2353,25 +2029,16 @@ mod tests { size: 16, attributes: Default::default(), }); - - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - - let bytes_written = - i128::MAX.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - assert_eq!(bytes_written, 16); - assert_eq!( - buffer.as_slice(), + assert_serialize( + i128::MAX, + &schema, + &names, &[ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0x7F - ] + 0xFF, 0x7F, + ], ); Ok(()) @@ -2386,22 +2053,14 @@ mod tests { size: 16, attributes: Default::default(), }); - - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - assert!(matches!( - i128::MAX - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .unwrap_err() - .details(), - Details::SerializeValueWithSchema { .. } - )); + assert_serialize_err( + i128::MAX, + &schema, + &names, + r#"Failed to serialize value of type `i128` using Schema::Fixed(FixedSchema { name: Name { name: "onehundredtwentyeight", .. }, size: 16, .. }): Expected Schema::Fixed(name: "i128", size: 16)"#, + ); Ok(()) } @@ -2415,22 +2074,14 @@ mod tests { size: 8, attributes: Default::default(), }); - - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - assert!(matches!( - i128::MAX - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .unwrap_err() - .details(), - Details::SerializeValueWithSchema { .. } - )); + assert_serialize_err( + i128::MAX, + &schema, + &names, + r#"Failed to serialize value of type `i128` using Schema::Fixed(FixedSchema { name: Name { name: "i128", .. }, size: 8, .. }): Expected Schema::Fixed(name: "i128", size: 16)"#, + ); Ok(()) } @@ -2444,25 +2095,16 @@ mod tests { size: 16, attributes: Default::default(), }); - - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - let bytes_written = - u128::MAX.serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?)?; - assert_eq!(bytes_written, 16); - - assert_eq!( - buffer.as_slice(), + assert_serialize( + u128::MAX, + &schema, + &names, &[ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF - ] + 0xFF, 0xFF, + ], ); Ok(()) @@ -2477,22 +2119,14 @@ mod tests { size: 16, attributes: Default::default(), }); - - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - assert!(matches!( - u128::MAX - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .unwrap_err() - .details(), - Details::SerializeValueWithSchema { .. } - )); + assert_serialize_err( + u128::MAX, + &schema, + &names, + r#"Failed to serialize value of type `u128` using Schema::Fixed(FixedSchema { name: Name { name: "onehundredtwentyeight", .. }, size: 16, .. }): Expected Schema::Fixed(name: "u128", size: 16)"#, + ); Ok(()) } @@ -2506,22 +2140,14 @@ mod tests { size: 8, attributes: Default::default(), }); - - let mut buffer: Vec<u8> = Vec::new(); let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - assert!(matches!( - u128::MAX - .serialize(SchemaAwareSerializer::new(&mut buffer, &schema, config)?) - .unwrap_err() - .details(), - Details::SerializeValueWithSchema { .. } - )); + assert_serialize_err( + u128::MAX, + &schema, + &names, + r#"Failed to serialize value of type `u128` using Schema::Fixed(FixedSchema { name: Name { name: "u128", .. }, size: 8, .. }): Expected Schema::Fixed(name: "u128", size: 16)"#, + ); Ok(()) } @@ -2533,28 +2159,15 @@ mod tests { { "name": "fixed4", "type": "fixed", "size": 4 }, { "name": "fixed8", "type": "fixed", "size": 8 } ]"#, - ) - .unwrap(); - - let mut buffer: Vec<u8> = Vec::new(); - let names = HashMap::new(); - let config = Config::<'_, Schema> { - names: &names, - target_block_size: None, - human_readable: false, - }; - let bytes_written = crate::serde::fixed::serialize( - &[0, 1, 2, 3], - SchemaAwareSerializer::new(&mut buffer, &schema, config)?, )?; - assert_eq!(bytes_written, 4); - let bytes_written = crate::serde::fixed::serialize( - &[4, 5, 6, 7, 8, 9, 10, 11], - SchemaAwareSerializer::new(&mut buffer, &schema, config)?, - )?; - assert_eq!(bytes_written, 8); - - assert_eq!(buffer, &[0, 0, 1, 2, 3, 2, 4, 5, 6, 7, 8, 9, 10, 11][..]); + let names = HashMap::new(); + assert_serialize(Bytes::new(&[0, 1, 2, 3]), &schema, &names, &[0, 0, 1, 2, 3]); + assert_serialize( + Bytes::new(&[4, 5, 6, 7, 8, 9, 10, 11]), + &schema, + &names, + &[2, 4, 5, 6, 7, 8, 9, 10, 11], + ); Ok(()) } diff --git a/avro/src/serde/ser_schema/record/mod.rs b/avro/src/serde/ser_schema/record/mod.rs index 853867d..0d44828 100644 --- a/avro/src/serde/ser_schema/record/mod.rs +++ b/avro/src/serde/ser_schema/record/mod.rs @@ -53,6 +53,37 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> RecordSerializer<'s, 'w, W, S> { } } + fn field_error(&self, position: usize, error: Error) -> Error { + let field = &self.record.fields[position]; + let error = match error.into_details() { + Details::SerializeValueWithSchema { + value_type, + value, + schema: _, + } => format!("Failed to serialize value of type `{value_type}`: {value}"), + Details::SerializeRecordFieldWithSchema { + field_name, + record_schema, + error, + } => format!( + "Failed to serialize field '{field_name}' of record {}: {error}", + record_schema.name + ), + Details::MissingDefaultForSkippedField { field_name, schema } => { + format!( + "Missing default for skipped field '{field_name}' for record {}", + schema.name + ) + } + details => format!("{details:?}"), + }; + Error::new(Details::SerializeRecordFieldWithSchema { + field_name: field.name.clone(), + record_schema: self.record.clone(), + error, + }) + } + fn serialize_next_field<T: ?Sized + Serialize>( &mut self, position: usize, @@ -60,14 +91,16 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> RecordSerializer<'s, 'w, W, S> { ) -> Result<(), Error> { // Serialize any skipped fields using their default value while self.field_position < position { - self.serialize_default(position)?; + self.serialize_default(self.field_position)?; } let field = &self.record.fields[position]; - self.bytes_written += value.serialize(SchemaAwareSerializer::new( - self.writer, - &field.schema, - self.config, - )?)?; + self.bytes_written += value + .serialize(SchemaAwareSerializer::new( + self.writer, + &field.schema, + self.config, + )?) + .map_err(|e| self.field_error(self.field_position, e))?; self.field_position += 1; Ok(()) @@ -77,21 +110,14 @@ impl<'s, 'w, W: Write, S: Borrow<Schema>> RecordSerializer<'s, 'w, W, S> { let field = &self.record.fields[position]; if let Some(default) = &field.default { self.serialize_next_field( - self.field_position, + position, &SchemaAwareRecordFieldDefault::new(default, &field.schema), ) - .map_err(|e| { - Details::SerializeRecordFieldWithSchema { - field_name: field.name.clone(), - record_schema: Schema::Record(self.record.clone()), - error: Box::new(e), - } - .into() - }) + .map_err(|e| self.field_error(position, e)) } else { Err(Details::MissingDefaultForSkippedField { field_name: field.name.clone(), - schema: Schema::Record(self.record.clone()), + schema: self.record.clone(), } .into()) } diff --git a/avro/src/serde/with.rs b/avro/src/serde/with.rs index 29744e1..74c0a3a 100644 --- a/avro/src/serde/with.rs +++ b/avro/src/serde/with.rs @@ -503,6 +503,315 @@ pub mod slice_opt { } } +/// (De)serialize [`BigDecimal`] as a [`Schema::BigDecimal`] instead of a [`Schema::String`]. +/// +/// This module is intended to be used through the Serde `with` attribute. +/// +/// Use [`apache_avro::serde::bigdecimal_opt`] for optional big decimals values. +/// +/// When used with different serialization formats, this will write bytes. +/// +/// See usage with below example: +/// ``` +/// # use apache_avro::AvroSchema; +/// # use serde::{Deserialize, Serialize}; +/// #[derive(AvroSchema, Serialize, Deserialize)] +/// struct StructWithBigDecimal<'a> { +/// #[avro(with)] +/// #[serde(with = "apache_avro::serde::bigdecimal")] +/// decimal: BigDecimal, +/// } +/// ``` +/// +/// [`BigDecimal`]: ::bigdecimal::BigDecimal +/// [`Schema::BigDecimal`]: crate::Schema::BigDecimal +/// [`Schema::String`]: crate::Schema::String +/// [`apache_avro::serde::bigdecimal_opt`]: bigdecimal_opt +pub mod bigdecimal { + use std::collections::HashSet; + + use bigdecimal::BigDecimal; + use serde::{Deserializer, Serializer, de::Error as _, ser::Error as _}; + + use crate::{ + Schema, + bigdecimal::{big_decimal_as_bytes, deserialize_big_decimal}, + schema::{Name, NamespaceRef, RecordField}, + serde::with::BytesType, + }; + + /// Returns [`Schema::BigDecimal`] + pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Schema { + Schema::BigDecimal + } + + /// Returns `None` + pub fn get_record_fields_in_ctxt( + _: &mut HashSet<Name>, + _: NamespaceRef, + ) -> Option<Vec<RecordField>> { + None + } + + pub fn serialize<S>(decimal: &BigDecimal, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let _guard = super::BytesTypeGuard::set(BytesType::Bytes); + let decimal_bytes = big_decimal_as_bytes(decimal).map_err(S::Error::custom)?; + serde_bytes::serialize(&decimal_bytes, serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result<BigDecimal, D::Error> + where + D: Deserializer<'de>, + { + let _bytes_guard = super::BytesTypeGuard::set(BytesType::Bytes); + let _guard = super::BorrowedGuard::set(true); + // We don't use &'de [u8] here as the deserializer doesn't support that + let bytes: Vec<u8> = serde_bytes::deserialize(deserializer)?; + + deserialize_big_decimal(&bytes).map_err(D::Error::custom) + } +} + +/// (De)serialize [`Option<BigDecimal>`] as a `Schema::Union(Schema::Null, Schema::BigDecimal)` instead of a `Schema::Union(Schema::Null, Schema::String)`. +/// +/// This module is intended to be used through the Serde `with` attribute. +/// +/// Use [`apache_avro::serde::bigdecimal`] for non-optional big decimals values. +/// +/// When used with different serialization formats, this will write bytes. +/// +/// See usage with below example: +/// ``` +/// # use apache_avro::AvroSchema; +/// # use serde::{Deserialize, Serialize}; +/// #[derive(AvroSchema, Serialize, Deserialize)] +/// struct StructWithBigDecimal<'a> { +/// #[avro(with)] +/// #[serde(with = "apache_avro::serde::bigdecimal_opt")] +/// decimal: Option<BigDecimal>, +/// } +/// ``` +/// +/// [`Option<BigDecimal>`]: ::bigdecimal::BigDecimal +/// [`apache_avro::serde::bigdecimal`]: bigdecimal +pub mod bigdecimal_opt { + use std::collections::HashSet; + + use bigdecimal::BigDecimal; + use serde::{Deserializer, Serializer, de::Error as _, ser::Error as _}; + + use crate::{ + Schema, + bigdecimal::{big_decimal_as_bytes, deserialize_big_decimal}, + schema::{Name, NamespaceRef, RecordField, UnionSchema}, + serde::with::BytesType, + }; + + /// Returns `Schema::Union(Schema::Null, Schema::BigDecimal)` + pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Schema { + Schema::Union( + UnionSchema::new(vec![Schema::Null, Schema::BigDecimal]) + .expect("This is a valid union"), + ) + } + + /// Returns `None` + pub fn get_record_fields_in_ctxt( + _: &mut HashSet<Name>, + _: NamespaceRef, + ) -> Option<Vec<RecordField>> { + None + } + + pub fn serialize<S>(decimal: &Option<BigDecimal>, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let _guard = super::BytesTypeGuard::set(BytesType::Bytes); + if let Some(decimal) = decimal { + let decimal_bytes = big_decimal_as_bytes(decimal).map_err(S::Error::custom)?; + serde_bytes::serialize(&Some(decimal_bytes), serializer) + } else { + serde_bytes::serialize(&None::<Vec<u8>>, serializer) + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<BigDecimal>, D::Error> + where + D: Deserializer<'de>, + { + let _bytes_guard = super::BytesTypeGuard::set(BytesType::Bytes); + let _guard = super::BorrowedGuard::set(true); + let bytes: Option<Vec<u8>> = serde_bytes::deserialize(deserializer)?; + if let Some(bytes) = bytes { + deserialize_big_decimal(&bytes) + .map(Some) + .map_err(D::Error::custom) + } else { + Ok(None) + } + } +} + +/// (De)serialize an Rust array (`[T; N]`) as an Avro [`Schema::Array`]. +/// +/// This module is intended to be used through the Serde `with` attribute. +/// +/// Use [`apache_avro::serde::array_opt`] for optional array values. +/// +/// See usage with below example: +/// ``` +/// # use apache_avro::AvroSchema; +/// # use serde::{Deserialize, Serialize}; +/// #[derive(AvroSchema, Serialize, Deserialize)] +/// struct StructWithBytes<'a> { +/// #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<i32>)] +/// #[serde(with = "apache_avro::serde::array")] +/// array: [i32; 10], +/// } +/// ``` +/// +/// [`apache_avro::serde::array_opt`]: array_opt +/// [`Schema::Array`]: crate::schema::Schema::Array +pub mod array { + use crate::{ + AvroSchemaComponent, Schema, + schema::{Name, NamespaceRef, RecordField}, + }; + use serde::de::DeserializeOwned; + use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _}; + use std::collections::HashSet; + + /// Returns `Schema::Array(T::get_schema_in_ctxt())` + pub fn get_schema_in_ctxt<T: AvroSchemaComponent>( + named_schemas: &mut HashSet<Name>, + enclosing_namespace: NamespaceRef, + ) -> Schema { + Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace)).build() + } + + /// Returns `None` + pub fn get_record_fields_in_ctxt( + _: &mut HashSet<Name>, + _: NamespaceRef, + ) -> Option<Vec<RecordField>> { + None + } + + pub fn serialize<const N: usize, S, T>(value: &[T; N], serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + T: Serialize, + { + value.as_slice().serialize(serializer) + } + + pub fn deserialize<'de, const N: usize, D, T>(deserializer: D) -> Result<[T; N], D::Error> + where + D: Deserializer<'de>, + T: DeserializeOwned, + { + let bytes = <Vec<T> as Deserialize>::deserialize(deserializer)?; + bytes.try_into().map_err(|v: Vec<T>| { + D::Error::custom(format!( + "Deserialized array has length {} which does not match array length of {N}", + v.len() + )) + }) + } +} + +/// (De)serialize an optional Rust array (`Option<[T; N]>`) as an Avro `Schema::Union([Schema::Null, Schema::Array])`. +/// +/// This module is intended to be used through the Serde `with` attribute. +/// +/// Use [`apache_avro::serde::array`] for non-optional array values. +/// +/// When used with different serialization formats, this is equivalent to [`serde_bytes`]. +/// +/// See usage with below example: +/// ``` +/// # use apache_avro::AvroSchema; +/// # use serde::{Deserialize, Serialize}; +/// #[derive(AvroSchema, Serialize, Deserialize)] +/// struct StructWithBytes<'a> { +/// #[avro(with = apache_avro::serde::array_opt::get_schema_in_ctxt::<i32>)] +/// #[serde(with = "apache_avro::serde::array_opt")] +/// array: Option<[i32; 10]>, +/// } +/// ``` +/// +/// [`apache_avro::serde::array`]: mod@array +pub mod array_opt { + use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _}; + use std::collections::HashSet; + + use crate::{ + AvroSchemaComponent, Schema, + schema::{Name, NamespaceRef, RecordField, UnionSchema}, + }; + + /// Returns `Schema::Union(Schema::Null, Schema::Array(T::get_schema_in_ctxt()))` + pub fn get_schema_in_ctxt<T: AvroSchemaComponent>( + named_schemas: &mut HashSet<Name>, + enclosing_namespace: NamespaceRef, + ) -> Schema { + Schema::Union( + UnionSchema::new(vec![ + Schema::Null, + Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace)).build(), + ]) + .expect("This is a valid union"), + ) + } + + /// Returns `None` + pub fn get_record_fields_in_ctxt( + _: &mut HashSet<Name>, + _: NamespaceRef, + ) -> Option<Vec<RecordField>> { + None + } + + pub fn serialize<const N: usize, S, T>( + value: &Option<[T; N]>, + serializer: S, + ) -> Result<S::Ok, S::Error> + where + S: Serializer, + T: Serialize, + { + if let Some(array) = value { + Some(array.as_slice()).serialize(serializer) + } else { + None::<Vec<T>>.serialize(serializer) + } + } + + pub fn deserialize<'de, const N: usize, D, T>( + deserializer: D, + ) -> Result<Option<[T; N]>, D::Error> + where + D: Deserializer<'de>, + T: Deserialize<'de>, + { + let bytes = <Option<Vec<T>> as Deserialize>::deserialize(deserializer)?; + if let Some(bytes) = bytes { + Ok(Some(bytes.try_into().map_err(|v: Vec<T>| { + D::Error::custom(format!( + "Deserialized array has length {} which does not match array length of {N}", + v.len() + )) + })?)) + } else { + Ok(None) + } + } +} + #[cfg(test)] mod tests { use crate::{Schema, from_value, to_value, types::Value}; diff --git a/avro/src/writer/mod.rs b/avro/src/writer/mod.rs index 2d255a1..b11bbde 100644 --- a/avro/src/writer/mod.rs +++ b/avro/src/writer/mod.rs @@ -1160,7 +1160,7 @@ mod tests { Err(e) => { assert_eq!( e.to_string(), - r#"Failed to serialize field 'time' for record Record(RecordSchema { name: Name { name: "Conference", .. }, fields: [RecordField { name: "name", schema: String, .. }, RecordField { name: "date", aliases: ["time2", "time"], schema: Union(UnionSchema { schemas: [Null, Long] }), .. }], .. }): Failed to serialize value of type f64 using schema Union(UnionSchema { schemas: [Null, Long] }): 12345678.9. Cause: Cannot find a Double schema in [Null, Long]"# + r#"Failed to serialize field 'date' of record RecordSchema { name: Name { name: "Conference", .. }, fields: [RecordField { name: "name", schema: String, .. }, RecordField { name: "date", aliases: ["time2", "time"], schema: Union(UnionSchema { schemas: [Null, Long] }), .. }], .. }: Failed to serialize value of type `f64`: Expected Schema::Double"# ); } } diff --git a/avro/tests/serde_human_readable_false.rs b/avro/tests/serde_human_readable_false.rs index 2f8be3f..544e09e 100644 --- a/avro/tests/serde_human_readable_false.rs +++ b/avro/tests/serde_human_readable_false.rs @@ -79,7 +79,7 @@ fn avro_rs_440_uuid_string() -> TestResult { let writer = SpecificSingleObjectWriter::new()?; assert_eq!( writer.write(uuid, &mut buffer).unwrap_err().to_string(), - "Failed to serialize value of type bytes using schema Uuid(String): 55e840e29b41d4a7164466554400. Cause: Expected a string, but got 16 bytes. Did you mean to use `Schema::Uuid(UuidSchema::Fixed)` or `utils::serde_set_human_readable(true)`?" + "Failed to serialize value of type `bytes` using Schema::Uuid(String): Expected Schema::Bytes | Schema::Fixed | Schema::BigDecimal | Schema::Decimal | Schema::Uuid(Bytes | Fixed) | Schema::Duration" ); Ok(()) diff --git a/avro/tests/serde_human_readable_true.rs b/avro/tests/serde_human_readable_true.rs index d1c71d7..c0612bb 100644 --- a/avro/tests/serde_human_readable_true.rs +++ b/avro/tests/serde_human_readable_true.rs @@ -103,7 +103,7 @@ fn avro_rs_440_uuid_bytes() -> TestResult { let writer = SpecificSingleObjectWriter::new()?; assert_eq!( writer.write(uuid, &mut buffer).unwrap_err().to_string(), - r#"Failed to serialize value of type string using schema Uuid(Bytes): 550e8400-e29b-41d4-a716-446655440000. Cause: Expected bytes but got a string. Did you mean to use `Schema::Uuid(UuidSchema::String)` or `utils::serde_set_human_readable(false)`?"# + r#"Failed to serialize value of type `str` using Schema::Uuid(Bytes): Expected Schema::String | Schema::Uuid(String)"# ); Ok(()) @@ -129,7 +129,7 @@ fn avro_rs_440_uuid_fixed() -> TestResult { let writer = SpecificSingleObjectWriter::new()?; assert_eq!( writer.write(uuid, &mut buffer).unwrap_err().to_string(), - r#"Failed to serialize value of type string using schema Uuid(Fixed(FixedSchema { name: Name { name: "uuid", .. }, size: 16, .. })): 550e8400-e29b-41d4-a716-446655440000. Cause: Expected bytes but got a string. Did you mean to use `Schema::Uuid(UuidSchema::String)` or `utils::serde_set_human_readable(false)`?"# + r#"Failed to serialize value of type `str` using Schema::Uuid(Fixed(FixedSchema { name: Name { name: "uuid", .. }, size: 16, .. })): Expected Schema::String | Schema::Uuid(String)"# ); Ok(()) diff --git a/avro_derive/tests/derive.proptest-regressions b/avro_derive/tests/derive.proptest-regressions new file mode 100644 index 0000000..d9ec339 --- /dev/null +++ b/avro_derive/tests/derive.proptest-regressions @@ -0,0 +1,8 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc fcee0d6e798e3fea29e4567deeaa845a8868672476574c6f1c6723734e8852b7 # shrinks to a = [], b = [0, 0] +cc 3b35e254022cadeafda5adb27919361b91e7b77a07ed1eea7e0919856aa247d0 # shrinks to a = "", b = [], c = {"": 0} diff --git a/avro_derive/tests/derive.rs b/avro_derive/tests/derive.rs index ae366e3..66d179e 100644 --- a/avro_derive/tests/derive.rs +++ b/avro_derive/tests/derive.rs @@ -33,6 +33,7 @@ use apache_avro::schema::NamespaceRef; use pretty_assertions::assert_eq; /// Takes in a type that implements the right combination of traits and runs it through a Serde Cycle and asserts the result is the same +#[track_caller] fn serde_assert<T>(obj: T) where T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone + PartialEq, @@ -40,6 +41,7 @@ where assert_eq!(obj, serde(obj.clone())); } +#[track_caller] fn serde<T>(obj: T) -> T where T: Serialize + DeserializeOwned + AvroSchema, @@ -47,6 +49,7 @@ where de(ser(obj)) } +#[track_caller] fn ser<T>(obj: T) -> Vec<u8> where T: Serialize + AvroSchema, @@ -59,6 +62,7 @@ where writer.into_inner().unwrap() } +#[track_caller] fn de<T>(encoded: Vec<u8>) -> T where T: DeserializeOwned + AvroSchema, @@ -800,6 +804,8 @@ fn test_cons_generic() { #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)] struct TestSimpleArray { + #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<i32>)] + #[serde(with = "apache_avro::serde::array")] a: [i32; 4], } @@ -828,7 +834,9 @@ fn test_simple_array(a: [i32; 4]) { }} #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)] -struct TestComplexArray<T: AvroSchemaComponent> { +struct TestComplexArray<T: AvroSchemaComponent + Serialize + DeserializeOwned> { + #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<T>)] + #[serde(with = "apache_avro::serde::array")] a: [T; 2], } @@ -882,6 +890,8 @@ fn test_complex_array() { #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)] struct Testu8 { a: Vec<u8>, + #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<u8>)] + #[serde(with = "apache_avro::serde::array")] b: [u8; 2], } @@ -2063,6 +2073,8 @@ fn avro_rs_401_supported_type_variants() { three: &'static i32, four: &'a str, five: &'a mut f64, + #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<u8>)] + #[serde(with = "apache_avro::serde::array")] six: [u8; 5], seven: [u8], } @@ -2390,7 +2402,11 @@ fn avro_rs_476_field_default() { _m: char, _n: Box<Spam>, _o: Vec<bool>, + #[serde(with = "apache_avro::serde::array")] + #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<u8>)] _p: [u8; 5], + #[serde(with = "apache_avro::serde::array")] + #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<Bar>)] _p_alt: [Bar; 5], _q: HashMap<String, String>, _r: Option<f64>, @@ -2406,7 +2422,7 @@ fn avro_rs_476_field_default() { let schema = Foo::get_schema(); assert_eq!( serde_json::to_string(&schema).unwrap(), - r#"{"type":"record","name":"Foo","fields":[{"name":"_a","type":"boolean"},{"name":"_b","type":"int"},{"name":"_c","type":"int"},{"name":"_d","type":"int"},{"name":"_e","type":"long"},{"name":"_f","type":"int"},{"name":"_g","type":"int"},{"name":"_h","type":"long"},{"name":"_i","type":"float"},{"name":"_j","type":"double"},{"name":"_k","type":"string"},{"name":"_l","type":"string"},{"name":"_m","type":"string"},{"name":"_n","type":{"type":"record","name":"Spam","fields":[{"name":" [...] + r#"{"type":"record","name":"Foo","fields":[{"name":"_a","type":"boolean"},{"name":"_b","type":"int"},{"name":"_c","type":"int"},{"name":"_d","type":"int"},{"name":"_e","type":"long"},{"name":"_f","type":"int"},{"name":"_g","type":"int"},{"name":"_h","type":"long"},{"name":"_i","type":"float"},{"name":"_j","type":"double"},{"name":"_k","type":"string"},{"name":"_l","type":"string"},{"name":"_m","type":"string"},{"name":"_n","type":{"type":"record","name":"Spam","fields":[{"name":" [...] ); } @@ -2479,8 +2495,11 @@ fn avro_rs_476_field_default_provided() { _n: Box<Spam>, #[avro(default = "[true, false, true]")] _o: Vec<bool>, - #[avro(default = "[1,2,3,4,5]")] + #[serde(with = "apache_avro::serde::array")] + #[avro(default = "[1,2,3,4,5]", with = apache_avro::serde::array::get_schema_in_ctxt::<u8>)] _p: [u8; 5], + #[avro(with = apache_avro::serde::array::get_schema_in_ctxt::<Spam>)] + #[serde(with = "apache_avro::serde::array")] #[avro( default = r#"[{"_field": true},{"_field": false},{"_field": true},{"_field": false},{"_field": true}]"# )] @@ -2514,7 +2533,7 @@ fn avro_rs_476_field_default_provided() { let schema = Foo::get_schema(); assert_eq!( serde_json::to_string(&schema).unwrap(), - r#"{"type":"record","name":"Foo","fields":[{"name":"_a","type":"boolean","default":true},{"name":"_b","type":"int","default":42},{"name":"_c","type":"int","default":42},{"name":"_d","type":"int","default":42},{"name":"_e","type":"long","default":42},{"name":"_f","type":"int","default":42},{"name":"_g","type":"int","default":42},{"name":"_h","type":"long","default":42},{"name":"_i","type":"float","default":42.0},{"name":"_j","type":"double","default":42.0},{"name":"_k","type":"str [...] + r#"{"type":"record","name":"Foo","fields":[{"name":"_a","type":"boolean","default":true},{"name":"_b","type":"int","default":42},{"name":"_c","type":"int","default":42},{"name":"_d","type":"int","default":42},{"name":"_e","type":"long","default":42},{"name":"_f","type":"int","default":42},{"name":"_g","type":"int","default":42},{"name":"_h","type":"long","default":42},{"name":"_i","type":"float","default":42.0},{"name":"_j","type":"double","default":42.0},{"name":"_k","type":"str [...] ); } diff --git a/avro_derive/tests/serde.rs b/avro_derive/tests/serde.rs index cf11fa4..448d59f 100644 --- a/avro_derive/tests/serde.rs +++ b/avro_derive/tests/serde.rs @@ -20,6 +20,7 @@ use serde::{Deserialize, Serialize, de::DeserializeOwned}; /// Takes in a type that implements the right combination of traits and runs it through a Serde /// round-trip and asserts the result is the same. +#[track_caller] fn serde_assert<T>(obj: T) where T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone + PartialEq, @@ -29,15 +30,13 @@ where /// Takes in a type that implements the right combination of traits and runs it through a Serde /// round-trip and asserts that the error matches the expected string. +#[track_caller] fn serde_assert_err<T>(obj: T, expected: &str) where T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone + PartialEq, { let error = serde(obj).unwrap_err().to_string(); - assert!( - error.contains(expected), - "Error `{error}` does not contain `{expected}`" - ); + assert_eq!(error, expected); } fn serde<T>(obj: T) -> Result<T, Error> @@ -313,7 +312,7 @@ mod container_attributes { a: "spam".to_string(), b: 321, }, - "Invalid field name c", + r#"Missing field in record: "c""#, ); }
