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 2244da098 AVRO-3851: [Rust] Validate default value for record fields
and enums on parsing (#2481)
2244da098 is described below
commit 2244da098710393061263d3edee84c45e9adaf84
Author: Kousuke Saruta <[email protected]>
AuthorDate: Thu Sep 7 23:47:48 2023 +0900
AVRO-3851: [Rust] Validate default value for record fields and enums on
parsing (#2481)
---
lang/rust/avro/src/error.rs | 3 +
lang/rust/avro/src/schema.rs | 420 +++++++++++++++++++++++++++++++++++++----
lang/rust/avro/src/types.rs | 2 +-
lang/rust/avro/tests/schema.rs | 393 +++++++++++++++++++++++++++++++++++++-
4 files changed, 779 insertions(+), 39 deletions(-)
diff --git a/lang/rust/avro/src/error.rs b/lang/rust/avro/src/error.rs
index 5ec21cecd..447d2711a 100644
--- a/lang/rust/avro/src/error.rs
+++ b/lang/rust/avro/src/error.rs
@@ -235,6 +235,9 @@ pub enum Error {
#[error("One union type {0:?} must match the `default`'s value type
{1:?}")]
GetDefaultUnion(SchemaKind, ValueKind),
+ #[error("`default`'s value type of field {0:?} in {1:?} must be {2:?}")]
+ GetDefaultRecordField(String, String, String),
+
#[error("JSON value {0} claims to be u64 but cannot be converted")]
GetU64FromJson(serde_json::Number),
diff --git a/lang/rust/avro/src/schema.rs b/lang/rust/avro/src/schema.rs
index 9cae97cff..139a62597 100644
--- a/lang/rust/avro/src/schema.rs
+++ b/lang/rust/avro/src/schema.rs
@@ -637,7 +637,7 @@ impl RecordField {
field: &Map<String, Value>,
position: usize,
parser: &mut Parser,
- enclosing_namespace: &Namespace,
+ enclosing_record: &Name,
) -> AvroResult<Self> {
let name = field.name().ok_or(Error::GetNameFieldFromRecord)?;
@@ -646,9 +646,16 @@ impl RecordField {
}
// TODO: "type" = "<record name>"
- let schema = parser.parse_complex(field, enclosing_namespace)?;
+ let schema = parser.parse_complex(field, &enclosing_record.namespace)?;
let default = field.get("default").cloned();
+ Self::resolve_default_value(
+ &schema,
+ &name,
+ &enclosing_record.fullname(None),
+ &parser.parsed_schemas,
+ &default,
+ )?;
let aliases = field.get("aliases").and_then(|aliases| {
aliases.as_array().map(|aliases| {
@@ -678,6 +685,56 @@ impl RecordField {
})
}
+ fn resolve_default_value(
+ field_schema: &Schema,
+ field_name: &str,
+ record_name: &str,
+ names: &Names,
+ default: &Option<Value>,
+ ) -> AvroResult<()> {
+ if let Some(value) = default {
+ let avro_value = types::Value::from(value.clone());
+ match field_schema {
+ Schema::Union(union_schema) => {
+ let schemas = &union_schema.schemas;
+ let resolved = schemas.iter().any(|schema| {
+ avro_value
+ .to_owned()
+ .resolve_internal(schema, names,
&schema.namespace(), &None)
+ .is_ok()
+ });
+
+ if !resolved {
+ let schema: Option<&Schema> = schemas.get(0);
+ return match schema {
+ Some(first_schema) => Err(Error::GetDefaultUnion(
+ SchemaKind::from(first_schema),
+ types::ValueKind::from(avro_value),
+ )),
+ None => Err(Error::EmptyUnion),
+ };
+ }
+ }
+ _ => {
+ let resolved = avro_value
+ .to_owned()
+ .resolve_internal(field_schema, names,
&field_schema.namespace(), &None)
+ .is_ok();
+
+ if !resolved {
+ return Err(Error::GetDefaultRecordField(
+ field_name.to_string(),
+ record_name.to_string(),
+ field_schema.canonical_form(),
+ ));
+ }
+ }
+ };
+ }
+
+ Ok(())
+ }
+
fn get_field_custom_attributes(field: &Map<String, Value>) ->
BTreeMap<String, Value> {
let mut custom_attributes: BTreeMap<String, Value> = BTreeMap::new();
for (key, value) in field {
@@ -1066,7 +1123,7 @@ impl Parser {
match *value {
Value::String(ref t) => self.parse_known_schema(t.as_str(),
enclosing_namespace),
Value::Object(ref data) => self.parse_complex(data,
enclosing_namespace),
- Value::Array(ref data) => self.parse_union(data,
enclosing_namespace, None),
+ Value::Array(ref data) => self.parse_union(data,
enclosing_namespace),
_ => Err(Error::ParseSchemaFromValidJson),
}
}
@@ -1355,10 +1412,7 @@ impl Parser {
other => self.parse_known_schema(other, enclosing_namespace),
},
Some(Value::Object(data)) => self.parse_complex(data,
enclosing_namespace),
- Some(Value::Array(variants)) => {
- let default = complex.get("default");
- self.parse_union(variants, enclosing_namespace, default)
- }
+ Some(Value::Array(variants)) => self.parse_union(variants,
enclosing_namespace),
Some(unknown) => Err(Error::GetComplexType(unknown.clone())),
None => Err(Error::GetComplexTypeField),
}
@@ -1453,7 +1507,7 @@ impl Parser {
.filter_map(|field| field.as_object())
.enumerate()
.map(|(position, field)| {
- RecordField::parse(field, position, self,
&fully_qualified_name.namespace)
+ RecordField::parse(field, position, self,
&fully_qualified_name)
})
.collect::<Result<_, _>>()
})?;
@@ -1553,6 +1607,19 @@ impl Parser {
}
}
+ if let Some(ref value) = default {
+ let resolved = types::Value::from(value.clone())
+ .to_owned()
+ .resolve_enum(&symbols, &Some(value.to_string()), &None)
+ .is_ok();
+ if !resolved {
+ return Err(Error::GetEnumDefault {
+ symbol: value.to_string(),
+ symbols,
+ });
+ }
+ }
+
let schema = Schema::Enum(EnumSchema {
name: fully_qualified_name.clone(),
aliases: aliases.clone(),
@@ -1601,40 +1668,11 @@ impl Parser {
&mut self,
items: &[Value],
enclosing_namespace: &Namespace,
- default: Option<&Value>,
) -> AvroResult<Schema> {
items
.iter()
.map(|v| self.parse(v, enclosing_namespace))
.collect::<Result<Vec<_>, _>>()
- .and_then(|schemas| {
- if let Some(default_value) = default.cloned() {
- let avro_value = types::Value::from(default_value);
- let resolved = schemas.iter().any(|schema| {
- avro_value
- .to_owned()
- .resolve_internal(
- schema,
- &self.parsed_schemas,
- &schema.namespace(),
- &None,
- )
- .is_ok()
- });
-
- if !resolved {
- let schema: Option<&Schema> = schemas.get(0);
- return match schema {
- Some(first_schema) => Err(Error::GetDefaultUnion(
- SchemaKind::from(first_schema),
- types::ValueKind::from(avro_value),
- )),
- None => Err(Error::EmptyUnion),
- };
- }
- }
- Ok(schemas)
- })
.and_then(|schemas| Ok(Schema::Union(UnionSchema::new(schemas)?)))
}
@@ -5469,4 +5507,312 @@ mod tests {
Ok(())
}
+
+ #[test]
+ fn test_avro_3851_validate_default_value_of_simple_record_field() ->
TestResult {
+ let schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": "int",
+ "default": "invalid"
+ }
+ ]
+ }
+ "#;
+ let expected = Error::GetDefaultRecordField(
+ "f1".to_string(),
+ "ns.record1".to_string(),
+ r#""int""#.to_string(),
+ )
+ .to_string();
+ let result = Schema::parse_str(schema_str);
+ assert!(result.is_err());
+ let err = result
+ .map_err(|e| e.to_string())
+ .err()
+ .unwrap_or_else(|| "unexpected".to_string());
+ assert_eq!(expected, err);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_avro_3851_validate_default_value_of_nested_record_field() ->
TestResult {
+ let schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": {
+ "name": "record2",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1_1",
+ "type": "int"
+ }
+ ]
+ },
+ "default": "invalid"
+ }
+ ]
+ }
+ "#;
+ let expected = Error::GetDefaultRecordField(
+ "f1".to_string(),
+ "ns.record1".to_string(),
+
r#"{"name":"ns.record2","type":"record","fields":[{"name":"f1_1","type":"int"}]}"#
+ .to_string(),
+ )
+ .to_string();
+ let result = Schema::parse_str(schema_str);
+ assert!(result.is_err());
+ let err = result
+ .map_err(|e| e.to_string())
+ .err()
+ .unwrap_or_else(|| "unexpected".to_string());
+ assert_eq!(expected, err);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_avro_3851_validate_default_value_of_enum_record_field() ->
TestResult {
+ let schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": {
+ "name": "enum1",
+ "type": "enum",
+ "symbols": ["a", "b", "c"]
+ },
+ "default": "invalid"
+ }
+ ]
+ }
+ "#;
+ let expected = Error::GetDefaultRecordField(
+ "f1".to_string(),
+ "ns.record1".to_string(),
+
r#"{"name":"ns.enum1","type":"enum","symbols":["a","b","c"]}"#.to_string(),
+ )
+ .to_string();
+ let result = Schema::parse_str(schema_str);
+ assert!(result.is_err());
+ let err = result
+ .map_err(|e| e.to_string())
+ .err()
+ .unwrap_or_else(|| "unexpected".to_string());
+ assert_eq!(expected, err);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_avro_3851_validate_default_value_of_fixed_record_field() ->
TestResult {
+ let schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": {
+ "name": "fixed1",
+ "type": "fixed",
+ "size": 3
+ },
+ "default": 100
+ }
+ ]
+ }
+ "#;
+ let expected = Error::GetDefaultRecordField(
+ "f1".to_string(),
+ "ns.record1".to_string(),
+ r#"{"name":"ns.fixed1","type":"fixed","size":3}"#.to_string(),
+ )
+ .to_string();
+ let result = Schema::parse_str(schema_str);
+ assert!(result.is_err());
+ let err = result
+ .map_err(|e| e.to_string())
+ .err()
+ .unwrap_or_else(|| "unexpected".to_string());
+ assert_eq!(expected, err);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_avro_3851_validate_default_value_of_array_record_field() ->
TestResult {
+ let schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": "array",
+ "items": "int",
+ "default": "invalid"
+ }
+ ]
+ }
+ "#;
+ let expected = Error::GetDefaultRecordField(
+ "f1".to_string(),
+ "ns.record1".to_string(),
+ r#"{"type":"array","items":"int"}"#.to_string(),
+ )
+ .to_string();
+ let result = Schema::parse_str(schema_str);
+ assert!(result.is_err());
+ let err = result
+ .map_err(|e| e.to_string())
+ .err()
+ .unwrap_or_else(|| "unexpected".to_string());
+ assert_eq!(expected, err);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_avro_3851_validate_default_value_of_map_record_field() ->
TestResult {
+ let schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": "map",
+ "values": "string",
+ "default": "invalid"
+ }
+ ]
+ }
+ "#;
+ let expected = Error::GetDefaultRecordField(
+ "f1".to_string(),
+ "ns.record1".to_string(),
+ r#"{"type":"map","values":"string"}"#.to_string(),
+ )
+ .to_string();
+ let result = Schema::parse_str(schema_str);
+ assert!(result.is_err());
+ let err = result
+ .map_err(|e| e.to_string())
+ .err()
+ .unwrap_or_else(|| "unexpected".to_string());
+ assert_eq!(expected, err);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_avro_3851_validate_default_value_of_ref_record_field() ->
TestResult {
+ let schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": {
+ "name": "record2",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1_1",
+ "type": "int"
+ }
+ ]
+ }
+ }, {
+ "name": "f2",
+ "type": "ns.record2",
+ "default": { "f1_1": true }
+ }
+ ]
+ }
+ "#;
+ let expected = Error::GetDefaultRecordField(
+ "f2".to_string(),
+ "ns.record1".to_string(),
+ r#""ns.record2""#.to_string(),
+ )
+ .to_string();
+ let result = Schema::parse_str(schema_str);
+ assert!(result.is_err());
+ let err = result
+ .map_err(|e| e.to_string())
+ .err()
+ .unwrap_or_else(|| "unexpected".to_string());
+ assert_eq!(expected, err);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_avro_3851_validate_default_value_of_enum() -> TestResult {
+ let schema_str = r#"
+ {
+ "name": "enum1",
+ "namespace": "ns",
+ "type": "enum",
+ "symbols": ["a", "b", "c"],
+ "default": 100
+ }
+ "#;
+ let expected = Error::EnumDefaultWrongType(100.into()).to_string();
+ let result = Schema::parse_str(schema_str);
+ assert!(result.is_err());
+ let err = result
+ .map_err(|e| e.to_string())
+ .err()
+ .unwrap_or_else(|| "unexpected".to_string());
+ assert_eq!(expected, err);
+
+ let schema_str = r#"
+ {
+ "name": "enum1",
+ "namespace": "ns",
+ "type": "enum",
+ "symbols": ["a", "b", "c"],
+ "default": "d"
+ }
+ "#;
+ let expected = Error::GetEnumDefault {
+ symbol: "d".to_string(),
+ symbols: vec!["a".to_string(), "b".to_string(), "c".to_string()],
+ }
+ .to_string();
+ let result = Schema::parse_str(schema_str);
+ assert!(result.is_err());
+ let err = result
+ .map_err(|e| e.to_string())
+ .err()
+ .unwrap_or_else(|| "unexpected".to_string());
+ assert_eq!(expected, err);
+
+ Ok(())
+ }
}
diff --git a/lang/rust/avro/src/types.rs b/lang/rust/avro/src/types.rs
index e9009d03b..11bf63564 100644
--- a/lang/rust/avro/src/types.rs
+++ b/lang/rust/avro/src/types.rs
@@ -872,7 +872,7 @@ impl Value {
}
}
- fn resolve_enum(
+ pub(crate) fn resolve_enum(
self,
symbols: &[String],
enum_default: &Option<String>,
diff --git a/lang/rust/avro/tests/schema.rs b/lang/rust/avro/tests/schema.rs
index f0d11e954..1d2cb8b4d 100644
--- a/lang/rust/avro/tests/schema.rs
+++ b/lang/rust/avro/tests/schema.rs
@@ -15,7 +15,10 @@
// specific language governing permissions and limitations
// under the License.
-use std::io::{Cursor, Read};
+use std::{
+ collections::HashMap,
+ io::{Cursor, Read},
+};
use apache_avro::{
from_avro_datum, from_value,
@@ -2225,3 +2228,391 @@ fn
test_avro_3847_union_field_with_default_value_of_ref_with_enclosing_namespace
Ok(())
}
+
+fn write_schema_for_default_value_test() -> apache_avro::AvroResult<Vec<u8>> {
+ let writer_schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": "int"
+ }
+ ]
+ }
+ "#;
+ let writer_schema = Schema::parse_str(writer_schema_str)?;
+ let mut writer = Writer::new(&writer_schema, Vec::new());
+ let mut record = Record::new(writer.schema())
+ .ok_or("Expected Some(Record), but got None")
+ .unwrap();
+ record.put("f1", 10);
+ writer.append(record)?;
+
+ writer.into_inner()
+}
+
+#[test]
+fn test_avro_3851_read_default_value_for_simple_record_field() -> TestResult {
+ let reader_schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": "int"
+ }, {
+ "name": "f2",
+ "type": "int",
+ "default": 20
+ }
+ ]
+ }
+ "#;
+ let reader_schema = Schema::parse_str(reader_schema_str)?;
+ let input = write_schema_for_default_value_test()?;
+ let reader = Reader::with_schema(&reader_schema, &input[..])?;
+ let result = reader.collect::<Result<Vec<_>, _>>()?;
+
+ assert_eq!(1, result.len());
+
+ let expected = Value::Record(vec![
+ ("f1".to_string(), Value::Int(10)),
+ ("f2".to_string(), Value::Int(20)),
+ ]);
+
+ assert_eq!(expected, result[0]);
+
+ Ok(())
+}
+
+#[test]
+fn test_avro_3851_read_default_value_for_nested_record_field() -> TestResult {
+ let reader_schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": "int"
+ }, {
+ "name": "f2",
+ "type": {
+ "name": "record2",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1_1",
+ "type": "int"
+ }
+ ]
+ },
+ "default": {
+ "f1_1": 100
+ }
+ }
+ ]
+ }
+ "#;
+ let reader_schema = Schema::parse_str(reader_schema_str)?;
+ let input = write_schema_for_default_value_test()?;
+ let reader = Reader::with_schema(&reader_schema, &input[..])?;
+ let result = reader.collect::<Result<Vec<_>, _>>()?;
+
+ assert_eq!(1, result.len());
+
+ let expected = Value::Record(vec![
+ ("f1".to_string(), Value::Int(10)),
+ (
+ "f2".to_string(),
+ Value::Record(vec![("f1_1".to_string(), 100.into())]),
+ ),
+ ]);
+
+ assert_eq!(expected, result[0]);
+
+ Ok(())
+}
+
+#[test]
+fn test_avro_3851_read_default_value_for_enum_record_field() -> TestResult {
+ let reader_schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": "int"
+ }, {
+ "name": "f2",
+ "type": {
+ "name": "enum1",
+ "type": "enum",
+ "symbols": ["a", "b", "c"]
+ },
+ "default": "a"
+ }
+ ]
+ }
+ "#;
+ let reader_schema = Schema::parse_str(reader_schema_str)?;
+ let input = write_schema_for_default_value_test()?;
+ let reader = Reader::with_schema(&reader_schema, &input[..])?;
+ let result = reader.collect::<Result<Vec<_>, _>>()?;
+
+ assert_eq!(1, result.len());
+
+ let expected = Value::Record(vec![
+ ("f1".to_string(), Value::Int(10)),
+ ("f2".to_string(), Value::Enum(0, "a".to_string())),
+ ]);
+
+ assert_eq!(expected, result[0]);
+
+ Ok(())
+}
+
+#[test]
+fn test_avro_3851_read_default_value_for_fixed_record_field() -> TestResult {
+ let reader_schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": "int"
+ }, {
+ "name": "f2",
+ "type": {
+ "name": "fixed1",
+ "type": "fixed",
+ "size": 3
+ },
+ "default": "abc"
+ }
+ ]
+ }
+ "#;
+ let reader_schema = Schema::parse_str(reader_schema_str)?;
+ let input = write_schema_for_default_value_test()?;
+ let reader = Reader::with_schema(&reader_schema, &input[..])?;
+ let result = reader.collect::<Result<Vec<_>, _>>()?;
+
+ assert_eq!(1, result.len());
+
+ let expected = Value::Record(vec![
+ ("f1".to_string(), Value::Int(10)),
+ ("f2".to_string(), Value::Fixed(3, vec![b'a', b'b', b'c'])),
+ ]);
+
+ assert_eq!(expected, result[0]);
+
+ Ok(())
+}
+
+#[test]
+fn test_avro_3851_read_default_value_for_array_record_field() -> TestResult {
+ let reader_schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": "int"
+ }, {
+ "name": "f2",
+ "type": "array",
+ "items": "int",
+ "default": [1, 2, 3]
+ }
+ ]
+ }
+ "#;
+ let reader_schema = Schema::parse_str(reader_schema_str)?;
+ let input = write_schema_for_default_value_test()?;
+ let reader = Reader::with_schema(&reader_schema, &input[..])?;
+ let result = reader.collect::<Result<Vec<_>, _>>()?;
+
+ assert_eq!(1, result.len());
+
+ let expected = Value::Record(vec![
+ ("f1".to_string(), Value::Int(10)),
+ (
+ "f2".to_string(),
+ Value::Array(vec![1.into(), 2.into(), 3.into()]),
+ ),
+ ]);
+
+ assert_eq!(expected, result[0]);
+
+ Ok(())
+}
+
+#[test]
+fn test_avro_3851_read_default_value_for_map_record_field() -> TestResult {
+ let reader_schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": "int"
+ }, {
+ "name": "f2",
+ "type": "map",
+ "values": "string",
+ "default": { "a": "A", "b": "B", "c": "C" }
+ }
+ ]
+ }
+ "#;
+ let reader_schema = Schema::parse_str(reader_schema_str)?;
+ let input = write_schema_for_default_value_test()?;
+ let reader = Reader::with_schema(&reader_schema, &input[..])?;
+ let result = reader.collect::<Result<Vec<_>, _>>()?;
+
+ assert_eq!(1, result.len());
+
+ let map = HashMap::from_iter([
+ ("a".to_string(), "A".into()),
+ ("b".to_string(), "B".into()),
+ ("c".to_string(), "C".into()),
+ ]);
+ let expected = Value::Record(vec![
+ ("f1".to_string(), Value::Int(10)),
+ ("f2".to_string(), Value::Map(map)),
+ ]);
+
+ assert_eq!(expected, result[0]);
+
+ Ok(())
+}
+
+#[test]
+fn test_avro_3851_read_default_value_for_ref_record_field() -> TestResult {
+ let writer_schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": {
+ "name": "record2",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1_1",
+ "type": "int"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ "#;
+ let writer_schema = Schema::parse_str(writer_schema_str)?;
+ let mut writer = Writer::new(&writer_schema, Vec::new());
+ let mut record = Record::new(writer.schema()).ok_or("Expected
Some(Record), but got None")?;
+ record.put("f1", Value::Record(vec![("f1_1".to_string(), 10.into())]));
+ writer.append(record)?;
+
+ let reader_schema_str = r#"
+ {
+ "name": "record1",
+ "namespace": "ns",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1",
+ "type": {
+ "name": "record2",
+ "type": "record",
+ "fields": [
+ {
+ "name": "f1_1",
+ "type": "int"
+ }
+ ]
+ }
+ }, {
+ "name": "f2",
+ "type": "ns.record2",
+ "default": { "f1_1": 100 }
+ }
+ ]
+ }
+ "#;
+ let reader_schema = Schema::parse_str(reader_schema_str)?;
+ let input = writer.into_inner()?;
+ let reader = Reader::with_schema(&reader_schema, &input[..])?;
+ let result = reader.collect::<Result<Vec<_>, _>>()?;
+
+ assert_eq!(1, result.len());
+
+ let expected = Value::Record(vec![
+ (
+ "f1".to_string(),
+ Value::Record(vec![("f1_1".to_string(), 10.into())]),
+ ),
+ (
+ "f2".to_string(),
+ Value::Record(vec![("f1_1".to_string(), 100.into())]),
+ ),
+ ]);
+
+ assert_eq!(expected, result[0]);
+
+ Ok(())
+}
+
+#[test]
+fn test_avro_3851_read_default_value_for_enum() -> TestResult {
+ let writer_schema_str = r#"
+ {
+ "name": "enum1",
+ "namespace": "ns",
+ "type": "enum",
+ "symbols": ["a", "b", "c"]
+ }
+ "#;
+ let writer_schema = Schema::parse_str(writer_schema_str)?;
+ let mut writer = Writer::new(&writer_schema, Vec::new());
+ writer.append("c")?;
+
+ let reader_schema_str = r#"
+ {
+ "name": "enum1",
+ "namespace": "ns",
+ "type": "enum",
+ "symbols": ["a", "b"],
+ "default": "a"
+ }
+ "#;
+ let reader_schema = Schema::parse_str(reader_schema_str)?;
+ let input = writer.into_inner()?;
+ let reader = Reader::with_schema(&reader_schema, &input[..])?;
+ let result = reader.collect::<Result<Vec<_>, _>>()?;
+
+ assert_eq!(1, result.len());
+
+ let expected = Value::Enum(0, "a".to_string());
+ assert_eq!(expected, result[0]);
+
+ Ok(())
+}