This is an automated email from the ASF dual-hosted git repository. mgrigorov pushed a commit to branch branch-1.11 in repository https://gitbox.apache.org/repos/asf/avro.git
commit 522c60409aef60778ab43cb704de1636de137bee Author: Ultrabug <[email protected]> AuthorDate: Fri Jan 7 14:51:55 2022 +0100 AVRO-3232: add matches to deserialize_any union+string/map (#1368) the deserialize_any method is used when using from_value::<T> to deserialize a record which has fields that are not present in <T> the deserialize_any was missing some matches on certain Value variants which caused errors like: Error: DeserializeValue("Unsupported union") Error: DeserializeValue("incorrect value of type: String") this patch adds the missing matches and enhances the error message to help in understanding which type could be missing in the future the test case added showcases the problem while validating that union deserialization is actually working as well Signed-off-by: Ultrabug <[email protected]> (cherry picked from commit e8200cd2d40d9cf4a702905e6d2eeb9d589ec57a) --- lang/rust/src/de.rs | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/lang/rust/src/de.rs b/lang/rust/src/de.rs index cd275bc..4593a5d 100644 --- a/lang/rust/src/de.rs +++ b/lang/rust/src/de.rs @@ -254,13 +254,22 @@ impl<'a, 'de> de::Deserializer<'de> for &'a Deserializer<'de> { Value::Null => visitor.visit_unit(), Value::Boolean(b) => visitor.visit_bool(b), Value::Int(i) => visitor.visit_i32(i), - Value::Long(i) => visitor.visit_i64(i), + Value::Long(i) + | Value::TimeMicros(i) + | Value::TimestampMillis(i) + | Value::TimestampMicros(i) => visitor.visit_i64(i), Value::Float(f) => visitor.visit_f32(f), Value::Double(d) => visitor.visit_f64(d), - _ => Err(de::Error::custom("Unsupported union")), + Value::Record(ref fields) => visitor.visit_map(StructDeserializer::new(fields)), + Value::Array(ref fields) => visitor.visit_seq(SeqDeserializer::new(fields)), + Value::String(ref s) => visitor.visit_str(s), + Value::Map(ref items) => visitor.visit_map(MapDeserializer::new(items)), + _ => Err(de::Error::custom(format!("unsupported union: {:?}", self.input))), }, Value::Record(ref fields) => visitor.visit_map(StructDeserializer::new(fields)), Value::Array(ref fields) => visitor.visit_seq(SeqDeserializer::new(fields)), + Value::String(ref s) => visitor.visit_str(s), + Value::Map(ref items) => visitor.visit_map(MapDeserializer::new(items)), value => Err(de::Error::custom(format!( "incorrect value of type: {:?}", crate::schema::SchemaKind::from(value) @@ -896,4 +905,72 @@ mod tests { assert_eq!(result.as_bytes(), raw_value); Ok(()) } + + #[test] + fn test_from_value_with_union() -> TestResult<()> { + // AVRO-3232 test for deserialize_any on missing fields on the destination struct: + // Error: DeserializeValue("Unsupported union") + // Error: DeserializeValue("incorrect value of type: String") + #[derive(Debug, Deserialize, PartialEq)] + struct RecordInUnion { + record_in_union: i32, + } + + #[derive(Debug, Deserialize, PartialEq)] + struct StructWithMissingFields { + a_string: String, + a_record: Option<RecordInUnion>, + an_array: Option<[bool; 2]>, + a_union_map: Option<HashMap<String, i64>>, + } + + let raw_map: HashMap<String, i64> = [ + ("long_one".to_string(), 1), + ("long_two".to_string(), 2), + ("long_three".to_string(), 3), + ("time_micros_a".to_string(), 123), + ("timestamp_millis_b".to_string(), 234), + ("timestamp_micros_c".to_string(), 345), + ].iter().cloned().collect(); + + let value_map = raw_map.iter() + .map(|(k, v)| match k { + key if key.starts_with("long_") => {(k.clone(), Value::Long(*v))} + key if key.starts_with("time_micros_") => {(k.clone(), Value::TimeMicros(*v))} + key if key.starts_with("timestamp_millis_") => {(k.clone(), Value::TimestampMillis(*v))} + key if key.starts_with("timestamp_micros_") => {(k.clone(), Value::TimestampMicros(*v))} + _ => unreachable!(""), + }) + .collect(); + + let record = Value::Record(vec![ + ("a_string".to_string(), Value::String("a valid message field".to_string())), + ("a_non_existing_string".to_string(), Value::String("a string".to_string())), + ("a_union_string".to_string(), Value::Union(Box::new(Value::String("a union string".to_string())))), + ("a_union_long".to_string(), Value::Union(Box::new(Value::Long(412)))), + ("a_union_long".to_string(), Value::Union(Box::new(Value::Long(412)))), + ("a_time_micros".to_string(), Value::Union(Box::new(Value::TimeMicros(123)))), + ("a_non_existing_time_micros".to_string(), Value::Union(Box::new(Value::TimeMicros(-123)))), + ("a_timestamp_millis".to_string(), Value::Union(Box::new(Value::TimestampMillis(234)))), + ("a_non_existing_timestamp_millis".to_string(), Value::Union(Box::new(Value::TimestampMillis(-234)))), + ("a_timestamp_micros".to_string(), Value::Union(Box::new(Value::TimestampMicros(345)))), + ("a_non_existing_timestamp_micros".to_string(), Value::Union(Box::new(Value::TimestampMicros(-345)))), + ("a_record".to_string(), Value::Union(Box::new(Value::Record(vec!(("record_in_union".to_string(), Value::Int(-2))))))), + ("a_non_existing_record".to_string(), Value::Union(Box::new(Value::Record(vec!(("blah".to_string(), Value::Int(-22))))))), + ("an_array".to_string(), Value::Union(Box::new(Value::Array(vec!(Value::Boolean(true), Value::Boolean(false)))))), + ("a_non_existing_array".to_string(), Value::Union(Box::new(Value::Array(vec!(Value::Boolean(false), Value::Boolean(true)))))), + ("a_union_map".to_string(), Value::Union(Box::new(Value::Map(value_map)))), + ("a_non_existing_union_map".to_string(), Value::Union(Box::new(Value::Map(HashMap::new())))), + ]); + + let deserialized: StructWithMissingFields = crate::from_value(&record)?; + let reference = StructWithMissingFields{ + a_string: "a valid message field".to_string(), + a_record: Some(RecordInUnion { record_in_union: -2 }), + an_array: Some([true, false]), + a_union_map: Some(raw_map) + }; + assert_eq!(deserialized, reference); + Ok(()) + } }
