Skip to content

Commit 50c9ca0

Browse files
committed
Fix #1: Default value in record from json with union schema and docs
1 parent 22d4bf1 commit 50c9ca0

File tree

2 files changed

+58
-16
lines changed

2 files changed

+58
-16
lines changed

src/error.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,6 @@ pub enum AvrowErr {
139139
UnknownSchema,
140140
#[error("Expected record field to be a json object, found {0}")]
141141
InvalidSchema(String),
142-
#[error("{0}")]
143-
InvalidDefaultValue(String),
144142
#[error("Invalid type for {0}")]
145143
InvalidType(String),
146144
#[error("Enum schema parsing failed, found: {0}")]

src/value.rs

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use crate::error::AvrowErr;
44
use crate::schema;
55
use crate::schema::common::validate_name;
6+
use crate::schema::parser::parse_default;
67
use crate::schema::Registry;
78
use crate::util::{encode_long, encode_raw_bytes};
89
use crate::Schema;
@@ -37,7 +38,9 @@ impl FieldValue {
3738
}
3839

3940
#[derive(Debug, Clone, PartialEq, Serialize)]
40-
/// The [record](https://avro.apache.org/docs/current/spec.html#schema_record) avro type
41+
/// The [Record](https://avro.apache.org/docs/current/spec.html#schema_record) avro type.
42+
/// Avro records translates to a struct in Rust. Any struct that implements serde's
43+
/// Serializable trait can be converted to an avro record.
4144
pub struct Record {
4245
pub(crate) name: String,
4346
pub(crate) fields: IndexMap<String, FieldValue>,
@@ -60,7 +63,7 @@ impl Record {
6063
Ok(())
6164
}
6265

63-
/// Sets the ordering of the field.
66+
/// Sets the ordering of the field in the record.
6467
pub fn set_field_order(&mut self, field_name: &str, order: Order) -> Result<(), AvrowErr> {
6568
let a = self
6669
.fields
@@ -71,7 +74,7 @@ impl Record {
7174
}
7275

7376
/// Creates a record from a [BTreeMap](https://doc.rust-lang.org/std/collections/struct.BTreeMap.html) by consuming it.
74-
/// The values in BTreeMap must implement Into<Value>. The name provided must match with the name in the record
77+
/// The values in `BTreeMap` must implement `Into<Value>`. The `name` provided must match with the name in the record
7578
/// schema being provided to the writer.
7679
pub fn from_btree<K: Into<String> + Ord + Display, V: Into<Value>>(
7780
name: &str,
@@ -89,20 +92,37 @@ impl Record {
8992
Ok(record)
9093
}
9194

92-
/// Creates a record from a json object. A confirming record schema must be provided.
95+
/// Creates a record from a JSON object (serde_json::Value). A confirming record schema must be provided.
9396
pub fn from_json(
9497
json: serde_json::Map<String, serde_json::Value>,
9598
schema: &Schema,
9699
) -> Result<Value, AvrowErr> {
97-
// let variant = schema.variant;
98-
if let Variant::Record { name, fields, .. } = &schema.variant {
99-
let mut values = IndexMap::new();
100-
for (k, v) in json {
101-
let parsed_value = crate::schema::parser::parse_default(
102-
&v,
103-
&fields.get(&k).ok_or(AvrowErr::DefaultValueParse)?.ty,
104-
)?;
105-
values.insert(k.to_string(), FieldValue::new(parsed_value));
100+
if let Variant::Record {
101+
name,
102+
fields: record_schema_fields,
103+
..
104+
} = &schema.variant
105+
{
106+
let mut values = IndexMap::with_capacity(record_schema_fields.len());
107+
'fields: for (k, v) in record_schema_fields {
108+
if let Some(default_value) = json.get(k) {
109+
if let Variant::Union { variants } = &v.ty {
110+
for var in variants {
111+
if let Ok(v) = parse_default(&default_value, &var) {
112+
values.insert(k.to_string(), FieldValue::new(v));
113+
continue 'fields;
114+
}
115+
}
116+
return Err(AvrowErr::FailedDefaultUnion);
117+
} else {
118+
let parsed_value = parse_default(&default_value, &v.ty)?;
119+
values.insert(k.to_string(), FieldValue::new(parsed_value));
120+
}
121+
} else if let Some(v) = &v.default {
122+
values.insert(k.to_string(), FieldValue::new(v.clone()));
123+
} else {
124+
return Err(AvrowErr::FieldNotFound);
125+
}
106126
}
107127

108128
Ok(Value::Record(crate::value::Record {
@@ -633,6 +653,7 @@ mod tests {
633653
use super::Record;
634654
use crate::from_value;
635655
use crate::Schema;
656+
use crate::Value;
636657
use serde::{Deserialize, Serialize};
637658
use std::collections::BTreeMap;
638659
use std::str::FromStr;
@@ -743,11 +764,34 @@ mod tests {
743764
let rec = super::Record::from_json(json, &schema).unwrap();
744765
let mut writer = crate::Writer::new(&schema, vec![]).unwrap();
745766
writer.write(rec).unwrap();
746-
// writer.flush().unwrap();
747767
let avro_data = writer.into_inner().unwrap();
748768
let reader = crate::Reader::new(avro_data.as_slice()).unwrap();
749769
for value in reader {
750770
let _mentors: RustMentors = from_value(&value).unwrap();
751771
}
752772
}
773+
774+
#[test]
775+
fn record_has_fields_with_default() {
776+
let schema_str = r##"
777+
{
778+
"namespace": "sensor.data",
779+
"type": "record",
780+
"name": "common",
781+
"fields" : [
782+
{"name": "data", "type": ["null", "string"], "default": null}
783+
]
784+
}
785+
"##;
786+
787+
let sample_data = r#"{
788+
"data": null
789+
}"#;
790+
791+
let serde_json = serde_json::from_str(sample_data).unwrap();
792+
let schema = Schema::from_str(schema_str).unwrap();
793+
let rec = Record::from_json(serde_json, &schema).unwrap();
794+
let field = &rec.as_record().unwrap().fields["data"];
795+
assert_eq!(field.value, Value::Null);
796+
}
753797
}

0 commit comments

Comments
 (0)