This is an automated email from the ASF dual-hosted git repository.
mgrigorov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/avro.git
The following commit(s) were added to refs/heads/master by this push:
new e8200cd AVRO-3232: add matches to deserialize_any union+string/map
(#1368)
e8200cd is described below
commit e8200cd2d40d9cf4a702905e6d2eeb9d589ec57a
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]>
---
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(())
+ }
}