blob: a2ac0b6bfc7c32f0e220f30a1a04f72c49e229fa [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use apache_avro::{
from_value,
schema::{derive::AvroSchemaComponent, AvroSchema},
Reader, Schema, Writer,
};
use apache_avro_derive::*;
use proptest::prelude::*;
use serde::{de::DeserializeOwned, ser::Serialize};
use std::collections::HashMap;
#[macro_use]
extern crate serde;
#[cfg(test)]
mod test_derive {
use apache_avro::schema::Alias;
use std::{
borrow::{Borrow, Cow},
sync::Mutex,
};
use super::*;
/// 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
fn serde_assert<T>(obj: T)
where
T: std::fmt::Debug + Serialize + DeserializeOwned + AvroSchema + Clone + PartialEq,
{
assert_eq!(obj, serde(obj.clone()));
}
fn serde<T>(obj: T) -> T
where
T: Serialize + DeserializeOwned + AvroSchema,
{
de(ser(obj))
}
fn ser<T>(obj: T) -> Vec<u8>
where
T: Serialize + AvroSchema,
{
let schema = T::get_schema();
let mut writer = Writer::new(&schema, Vec::new());
if let Err(e) = writer.append_ser(obj) {
panic!("{:?}", e);
}
writer.into_inner().unwrap()
}
fn de<T>(encoded: Vec<u8>) -> T
where
T: DeserializeOwned + AvroSchema,
{
assert!(!encoded.is_empty());
let schema = T::get_schema();
let reader = Reader::with_schema(&schema, &encoded[..]).unwrap();
for res in reader {
match res {
Ok(value) => {
return from_value::<T>(&value).unwrap();
}
Err(e) => panic!("{:?}", e),
}
}
unreachable!()
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
struct TestBasic {
a: i32,
b: String,
}
proptest! {
#[test]
fn test_smoke_test(a: i32, b: String) {
let schema = r#"
{
"type":"record",
"name":"TestBasic",
"fields":[
{
"name":"a",
"type":"int"
},
{
"name":"b",
"type":"string"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, TestBasic::get_schema());
let test = TestBasic {
a,
b,
};
serde_assert(test);
}}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
#[avro(namespace = "com.testing.namespace")]
struct TestBasicNamespace {
a: i32,
b: String,
}
#[test]
fn test_basic_namespace() {
let schema = r#"
{
"type":"record",
"name":"com.testing.namespace.TestBasicNamespace",
"fields":[
{
"name":"a",
"type":"int"
},
{
"name":"b",
"type":"string"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, TestBasicNamespace::get_schema());
if let Schema::Record { name, .. } = TestBasicNamespace::get_schema() {
assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap())
} else {
panic!("TestBasicNamespace schema must be a record schema")
}
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
#[avro(namespace = "com.testing.complex.namespace")]
struct TestComplexNamespace {
a: TestBasicNamespace,
b: String,
}
#[test]
fn test_complex_namespace() {
let schema = r#"
{
"type":"record",
"name":"com.testing.complex.namespace.TestComplexNamespace",
"fields":[
{
"name":"a",
"type":{
"type":"record",
"name":"com.testing.namespace.TestBasicNamespace",
"fields":[
{
"name":"a",
"type":"int"
},
{
"name":"b",
"type":"string"
}
]
}
},
{
"name":"b",
"type":"string"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, TestComplexNamespace::get_schema());
if let Schema::Record { name, fields, .. } = TestComplexNamespace::get_schema() {
assert_eq!(
"com.testing.complex.namespace".to_owned(),
name.namespace.unwrap()
);
let inner_schema = fields
.iter()
.filter(|field| field.name == "a")
.map(|field| &field.schema)
.next();
if let Some(Schema::Record { name, .. }) = inner_schema {
assert_eq!(
"com.testing.namespace".to_owned(),
name.namespace.clone().unwrap()
)
} else {
panic!("Field 'a' must have a record schema")
}
} else {
panic!("TestComplexNamespace schema must be a record schema")
}
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
struct TestAllSupportedBaseTypes {
//Basics test
a: bool,
b: i8,
c: i16,
d: i32,
e: u8,
f: u16,
g: i64,
h: f32,
i: f64,
j: String,
}
proptest! {
#[test]
fn test_basic_types(a: bool, b: i8, c: i16, d: i32, e: u8, f: u16, g: i64, h: f32, i: f64, j: String) {
let schema = r#"
{
"type":"record",
"name":"TestAllSupportedBaseTypes",
"fields":[
{
"name":"a",
"type": "boolean"
},
{
"name":"b",
"type":"int"
},
{
"name":"c",
"type":"int"
},
{
"name":"d",
"type":"int"
},
{
"name":"e",
"type":"int"
},
{
"name":"f",
"type":"int"
},
{
"name":"g",
"type":"long"
},
{
"name":"h",
"type":"float"
},
{
"name":"i",
"type":"double"
},
{
"name":"j",
"type":"string"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, TestAllSupportedBaseTypes::get_schema());
let all_basic = TestAllSupportedBaseTypes {
a,
b,
c,
d,
e,
f,
g,
h,
i,
j,
};
serde_assert(all_basic);
}}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
struct TestNested {
a: i32,
b: TestAllSupportedBaseTypes,
}
proptest! {
#[test]
fn test_inner_struct(a: bool, b: i8, c: i16, d: i32, e: u8, f: u16, g: i64, h: f32, i: f64, j: String, aa: i32) {
let schema = r#"
{
"type":"record",
"name":"TestNested",
"fields":[
{
"name":"a",
"type":"int"
},
{
"name":"b",
"type":{
"type":"record",
"name":"TestAllSupportedBaseTypes",
"fields":[
{
"name":"a",
"type": "boolean"
},
{
"name":"b",
"type":"int"
},
{
"name":"c",
"type":"int"
},
{
"name":"d",
"type":"int"
},
{
"name":"e",
"type":"int"
},
{
"name":"f",
"type":"int"
},
{
"name":"g",
"type":"long"
},
{
"name":"h",
"type":"float"
},
{
"name":"i",
"type":"double"
},
{
"name":"j",
"type":"string"
}
]
}
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, TestNested::get_schema());
let all_basic = TestAllSupportedBaseTypes {
a,
b,
c,
d,
e,
f,
g,
h,
i,
j,
};
let inner_struct = TestNested {
a: aa,
b: all_basic,
};
serde_assert(inner_struct);
}}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
struct TestOptional {
a: Option<i32>,
}
proptest! {
#[test]
fn test_optional_field_some(a: i32) {
let schema = r#"
{
"type":"record",
"name":"TestOptional",
"fields":[
{
"name":"a",
"type":["null","int"]
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, TestOptional::get_schema());
let optional_field = TestOptional { a: Some(a) };
serde_assert(optional_field);
}}
#[test]
fn test_optional_field_none() {
let optional_field = TestOptional { a: None };
serde_assert(optional_field);
}
/// Generic Containers
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
struct TestGeneric<T: AvroSchemaComponent> {
a: String,
b: Vec<T>,
c: HashMap<String, T>,
}
proptest! {
#[test]
fn test_generic_container_1(a: String, b: Vec<i32>, c: HashMap<String, i32>) {
let schema = r#"
{
"type":"record",
"name":"TestGeneric",
"fields":[
{
"name":"a",
"type":"string"
},
{
"name":"b",
"type": {
"type":"array",
"items":"int"
}
},
{
"name":"c",
"type": {
"type":"map",
"values":"int"
}
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, TestGeneric::<i32>::get_schema());
let test_generic = TestGeneric::<i32> {
a,
b,
c,
};
serde_assert(test_generic);
}}
proptest! {
#[test]
fn test_generic_container_2(a: bool, b: i8, c: i16, d: i32, e: u8, f: u16, g: i64, h: f32, i: f64, j: String) {
let schema = r#"
{
"type":"record",
"name":"TestGeneric",
"fields":[
{
"name":"a",
"type":"string"
},
{
"name":"b",
"type": {
"type":"array",
"items":{
"type":"record",
"name":"TestAllSupportedBaseTypes",
"fields":[
{
"name":"a",
"type": "boolean"
},
{
"name":"b",
"type":"int"
},
{
"name":"c",
"type":"int"
},
{
"name":"d",
"type":"int"
},
{
"name":"e",
"type":"int"
},
{
"name":"f",
"type":"int"
},
{
"name":"g",
"type":"long"
},
{
"name":"h",
"type":"float"
},
{
"name":"i",
"type":"double"
},
{
"name":"j",
"type":"string"
}
]
}
}
},
{
"name":"c",
"type": {
"type":"map",
"values":"TestAllSupportedBaseTypes"
}
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(
schema,
TestGeneric::<TestAllSupportedBaseTypes>::get_schema()
);
let test_generic = TestGeneric::<TestAllSupportedBaseTypes> {
a: "testing".to_owned(),
b: vec![TestAllSupportedBaseTypes {
a,
b,
c,
d,
e,
f,
g,
h,
i,
j: j.clone(),
}],
c: vec![(
"key".to_owned(),
TestAllSupportedBaseTypes {
a,
b,
c,
d,
e,
f,
g,
h,
i,
j,
},
)]
.into_iter()
.collect(),
};
serde_assert(test_generic);
}}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
enum TestAllowedEnum {
A,
B,
C,
D,
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
struct TestAllowedEnumNested {
a: TestAllowedEnum,
b: String,
}
#[test]
fn test_enum() {
let schema = r#"
{
"type":"record",
"name":"TestAllowedEnumNested",
"fields":[
{
"name":"a",
"type": {
"type":"enum",
"name":"TestAllowedEnum",
"symbols":["A","B","C","D"]
}
},
{
"name":"b",
"type":"string"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, TestAllowedEnumNested::get_schema());
let enum_included = TestAllowedEnumNested {
a: TestAllowedEnum::B,
b: "hey".to_owned(),
};
serde_assert(enum_included);
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
struct ConsList {
value: i32,
next: Option<Box<ConsList>>,
}
#[test]
fn test_cons() {
let schema = r#"
{
"type":"record",
"name":"ConsList",
"fields":[
{
"name":"value",
"type":"int"
},
{
"name":"next",
"type":["null","ConsList"]
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, ConsList::get_schema());
let list = ConsList {
value: 34,
next: Some(Box::new(ConsList {
value: 42,
next: None,
})),
};
serde_assert(list)
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
struct ConsListGeneric<T: AvroSchemaComponent> {
value: T,
next: Option<Box<ConsListGeneric<T>>>,
}
#[test]
fn test_cons_generic() {
let schema = r#"
{
"type":"record",
"name":"ConsListGeneric",
"fields":[
{
"name":"value",
"type":{
"type":"record",
"name":"TestAllowedEnumNested",
"fields":[
{
"name":"a",
"type": {
"type":"enum",
"name":"TestAllowedEnum",
"symbols":["A","B","C","D"]
}
},
{
"name":"b",
"type":"string"
}
]
}
},
{
"name":"next",
"type":["null","ConsListGeneric"]
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(
schema,
ConsListGeneric::<TestAllowedEnumNested>::get_schema()
);
let list = ConsListGeneric::<TestAllowedEnumNested> {
value: TestAllowedEnumNested {
a: TestAllowedEnum::B,
b: "testing".into(),
},
next: Some(Box::new(ConsListGeneric::<TestAllowedEnumNested> {
value: TestAllowedEnumNested {
a: TestAllowedEnum::D,
b: "testing2".into(),
},
next: None,
})),
};
serde_assert(list)
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
struct TestSimpleArray {
a: [i32; 4],
}
proptest! {
#[test]
fn test_simple_array(a: [i32; 4]) {
let schema = r#"
{
"type":"record",
"name":"TestSimpleArray",
"fields":[
{
"name":"a",
"type": {
"type":"array",
"items":"int"
}
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, TestSimpleArray::get_schema());
let test = TestSimpleArray { a };
serde_assert(test)
}}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
struct TestComplexArray<T: AvroSchemaComponent> {
a: [T; 2],
}
#[test]
fn test_complex_array() {
let schema = r#"
{
"type":"record",
"name":"TestComplexArray",
"fields":[
{
"name":"a",
"type": {
"type":"array",
"items":{
"type":"record",
"name":"TestBasic",
"fields":[
{
"name":"a",
"type":"int"
},
{
"name":"b",
"type":"string"
}
]
}
}
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, TestComplexArray::<TestBasic>::get_schema());
let test = TestComplexArray::<TestBasic> {
a: [
TestBasic {
a: 27,
b: "foo".to_owned(),
},
TestBasic {
a: 28,
b: "bar".to_owned(),
},
],
};
serde_assert(test)
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
struct Testu8 {
a: Vec<u8>,
b: [u8; 2],
}
proptest! {
#[test]
fn test_bytes_handled(a: Vec<u8>, b: [u8; 2]) {
let test = Testu8 {
a,
b,
};
serde_assert(test)
// don't check for schema equality to allow for transitioning to bytes or fixed types in the future
}}
#[derive(Debug, Serialize, Deserialize, AvroSchema)]
struct TestSmartPointers<'a> {
a: String,
b: Mutex<Vec<i64>>,
c: Cow<'a, i32>,
}
#[test]
fn test_smart_pointers() {
let schema = r#"
{
"type":"record",
"name":"TestSmartPointers",
"fields":[
{
"name":"a",
"type": "string"
},
{
"name":"b",
"type":{
"type":"array",
"items":"long"
}
},
{
"name":"c",
"type":"int"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, TestSmartPointers::get_schema());
let test = TestSmartPointers {
a: "hey".into(),
b: Mutex::new(vec![42]),
c: Cow::Owned(32),
};
// test serde with manual equality for mutex
let test = serde(test);
assert_eq!("hey", test.a);
assert_eq!(vec![42], *test.b.borrow().lock().unwrap());
assert_eq!(Cow::Owned::<i32>(32), test.c);
}
#[derive(Debug, Serialize, AvroSchema, Clone, PartialEq)]
struct TestReference<'a> {
a: &'a Vec<i32>,
b: &'static str,
c: &'a f64,
}
proptest! {
#[test]
fn test_reference_struct(a: Vec<i32>, c: f64) {
let schema = r#"
{
"type":"record",
"name":"TestReference",
"fields":[
{
"name":"a",
"type": {
"type":"array",
"items":"int"
}
},
{
"name":"b",
"type":"string"
},
{
"name":"c",
"type":"double"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
assert_eq!(schema, TestReference::get_schema());
// let a = vec![34];
// let c = 4.55555555_f64;
let test = TestReference {
a: &a,
b: "testing_static",
c: &c,
};
ser(test);
}}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
#[avro(namespace = "com.testing.namespace", doc = "A Documented Record")]
struct TestBasicWithAttributes {
#[avro(doc = "Milliseconds since Queen released Bohemian Rhapsody")]
a: i32,
#[avro(doc = "Full lyrics of Bohemian Rhapsody")]
b: String,
}
#[test]
fn test_basic_with_attributes() {
let schema = r#"
{
"type":"record",
"name":"com.testing.namespace.TestBasicWithAttributes",
"doc":"A Documented Record",
"fields":[
{
"name":"a",
"type":"int",
"doc":"Milliseconds since Queen released Bohemian Rhapsody"
},
{
"name":"b",
"type": "string",
"doc": "Full lyrics of Bohemian Rhapsody"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
if let Schema::Record { name, doc, .. } = TestBasicWithAttributes::get_schema() {
assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap());
assert_eq!("A Documented Record", doc.unwrap())
} else {
panic!("TestBasicWithAttributes schema must be a record schema")
}
assert_eq!(schema, TestBasicWithAttributes::get_schema());
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
#[avro(namespace = "com.testing.namespace")]
/// A Documented Record
struct TestBasicWithOuterDocAttributes {
#[avro(doc = "Milliseconds since Queen released Bohemian Rhapsody")]
a: i32,
#[avro(doc = "Full lyrics of Bohemian Rhapsody")]
b: String,
}
#[test]
fn test_basic_with_out_doc_attributes() {
let schema = r#"
{
"type":"record",
"name":"com.testing.namespace.TestBasicWithOuterDocAttributes",
"doc":"A Documented Record",
"fields":[
{
"name":"a",
"type":"int",
"doc":"Milliseconds since Queen released Bohemian Rhapsody"
},
{
"name":"b",
"type": "string",
"doc": "Full lyrics of Bohemian Rhapsody"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
if let Schema::Record { name, doc, .. } = TestBasicWithOuterDocAttributes::get_schema() {
assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap());
assert_eq!("A Documented Record", doc.unwrap())
} else {
panic!("TestBasicWithOuterDocAttributes schema must be a record schema")
}
assert_eq!(schema, TestBasicWithOuterDocAttributes::get_schema());
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
#[avro(namespace = "com.testing.namespace")]
/// A Documented Record
/// that spans
/// multiple lines
struct TestBasicWithLargeDoc {
#[avro(doc = "Milliseconds since Queen released Bohemian Rhapsody")]
a: i32,
#[avro(doc = "Full lyrics of Bohemian Rhapsody")]
b: String,
}
#[test]
fn test_basic_with_large_doc() {
let schema = r#"
{
"type":"record",
"name":"com.testing.namespace.TestBasicWithLargeDoc",
"doc":"A Documented Record",
"fields":[
{
"name":"a",
"type":"int",
"doc":"Milliseconds since Queen released Bohemian Rhapsody"
},
{
"name":"b",
"type": "string",
"doc": "Full lyrics of Bohemian Rhapsody"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
if let Schema::Record { name, doc, .. } = TestBasicWithLargeDoc::get_schema() {
assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap());
assert_eq!(
"A Documented Record\nthat spans\nmultiple lines",
doc.unwrap()
)
} else {
panic!("TestBasicWithLargeDoc schema must be a record schema")
}
assert_eq!(schema, TestBasicWithLargeDoc::get_schema());
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
struct TestBasicWithBool {
a: bool,
b: Option<bool>,
}
proptest! {
#[test]
fn avro_3634_test_basic_with_bool(a in any::<bool>(), b in any::<Option<bool>>()) {
let schema = r#"
{
"type":"record",
"name":"TestBasicWithBool",
"fields":[
{
"name":"a",
"type":"boolean"
},
{
"name":"b",
"type":["null","boolean"]
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
let derived_schema = TestBasicWithBool::get_schema();
if let Schema::Record { name, .. } = derived_schema {
assert_eq!("TestBasicWithBool", name.fullname(None))
} else {
panic!("TestBasicWithBool schema must be a record schema")
}
assert_eq!(schema, TestBasicWithBool::get_schema());
serde_assert(TestBasicWithBool { a, b });
}}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
struct TestBasicWithU32 {
a: u32,
}
proptest! {
#[test]
fn test_basic_with_u32(a in any::<u32>()) {
let schema = r#"
{
"type":"record",
"name":"TestBasicWithU32",
"fields":[
{
"name":"a",
"type":"long"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
if let Schema::Record { name, .. } = TestBasicWithU32::get_schema() {
assert_eq!("TestBasicWithU32", name.fullname(None))
} else {
panic!("TestBasicWithU32 schema must be a record schema")
}
assert_eq!(schema, TestBasicWithU32::get_schema());
serde_assert(TestBasicWithU32 { a });
}}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
#[avro(alias = "a", alias = "b", alias = "c")]
struct TestBasicStructWithAliases {
a: i32,
}
#[test]
fn test_basic_struct_with_aliases() {
let schema = r#"
{
"type":"record",
"name":"TestBasicStructWithAliases",
"aliases":["a", "b", "c"],
"fields":[
{
"name":"a",
"type":"int"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
if let Schema::Record { name, aliases, .. } = TestBasicStructWithAliases::get_schema() {
assert_eq!("TestBasicStructWithAliases", name.fullname(None));
assert_eq!(
Some(vec![
Alias::new("a").unwrap(),
Alias::new("b").unwrap(),
Alias::new("c").unwrap()
]),
aliases
);
} else {
panic!("TestBasicStructWithAliases schema must be a record schema")
}
assert_eq!(schema, TestBasicStructWithAliases::get_schema());
serde_assert(TestBasicStructWithAliases { a: i32::MAX });
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
#[avro(alias = "d")]
#[avro(alias = "e")]
#[avro(alias = "f")]
struct TestBasicStructWithAliases2 {
a: i32,
}
#[test]
fn test_basic_struct_with_aliases2() {
let schema = r#"
{
"type":"record",
"name":"TestBasicStructWithAliases2",
"aliases":["d", "e", "f"],
"fields":[
{
"name":"a",
"type":"int"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
if let Schema::Record { name, aliases, .. } = TestBasicStructWithAliases2::get_schema() {
assert_eq!("TestBasicStructWithAliases2", name.fullname(None));
assert_eq!(
Some(vec![
Alias::new("d").unwrap(),
Alias::new("e").unwrap(),
Alias::new("f").unwrap()
]),
aliases
);
} else {
panic!("TestBasicStructWithAliases2 schema must be a record schema")
}
assert_eq!(schema, TestBasicStructWithAliases2::get_schema());
serde_assert(TestBasicStructWithAliases2 { a: i32::MAX });
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
#[avro(alias = "a", alias = "b", alias = "c")]
enum TestBasicEnumWithAliases {
A,
B,
}
#[test]
fn test_basic_enum_with_aliases() {
let schema = r#"
{
"type":"enum",
"name":"TestBasicEnumWithAliases",
"aliases":["a", "b", "c"],
"symbols":[
"A",
"B"
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
if let Schema::Enum { name, aliases, .. } = TestBasicEnumWithAliases::get_schema() {
assert_eq!("TestBasicEnumWithAliases", name.fullname(None));
assert_eq!(
Some(vec![
Alias::new("a").unwrap(),
Alias::new("b").unwrap(),
Alias::new("c").unwrap()
]),
aliases
);
} else {
panic!("TestBasicEnumWithAliases schema must be an enum schema")
}
assert_eq!(schema, TestBasicEnumWithAliases::get_schema());
serde_assert(TestBasicEnumWithAliases::A);
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq, Eq)]
#[avro(alias = "d")]
#[avro(alias = "e")]
#[avro(alias = "f")]
enum TestBasicEnumWithAliases2 {
A,
B,
}
#[test]
fn test_basic_enum_with_aliases2() {
let schema = r#"
{
"type":"enum",
"name":"TestBasicEnumWithAliases2",
"aliases":["d", "e", "f"],
"symbols":[
"A",
"B"
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
if let Schema::Enum { name, aliases, .. } = TestBasicEnumWithAliases2::get_schema() {
assert_eq!("TestBasicEnumWithAliases2", name.fullname(None));
assert_eq!(
Some(vec![
Alias::new("d").unwrap(),
Alias::new("e").unwrap(),
Alias::new("f").unwrap()
]),
aliases
);
} else {
panic!("TestBasicEnumWithAliases2 schema must be an enum schema")
}
assert_eq!(schema, TestBasicEnumWithAliases2::get_schema());
serde_assert(TestBasicEnumWithAliases2::B);
}
#[test]
fn test_basic_struct_with_defaults() {
#[derive(Debug, Deserialize, Serialize, AvroSchema, Clone, PartialEq, Eq)]
enum MyEnum {
Foo,
Bar,
Baz,
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
struct TestBasicStructWithDefaultValues {
#[avro(default = "123")]
a: i32,
#[avro(default = r#""The default value for 'b'""#)]
b: String,
#[avro(default = "true")]
condition: bool,
// no default value for 'c'
c: f64,
#[avro(default = r#"{"a": 1, "b": 2}"#)]
map: HashMap<String, i32>,
#[avro(default = "[1, 2, 3]")]
array: Vec<i32>,
#[avro(default = r#""Foo""#)]
myenum: MyEnum,
}
let schema = r#"
{
"type":"record",
"name":"TestBasicStructWithDefaultValues",
"fields": [
{
"name":"a",
"type":"int",
"default":123
},
{
"name":"b",
"type":"string",
"default": "The default value for 'b'"
},
{
"name":"condition",
"type":"boolean",
"default":true
},
{
"name":"c",
"type":"double"
},
{
"name":"map",
"type":{
"type":"map",
"values":"int"
},
"default": {
"a": 1,
"b": 2
}
},
{
"name":"array",
"type":{
"type":"array",
"items":"int"
},
"default": [1, 2, 3]
},
{
"name":"myenum",
"type":{
"type":"enum",
"name":"MyEnum",
"symbols":["Foo", "Bar", "Baz"]
},
"default":"Foo"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
if let Schema::Record { name, fields, .. } = TestBasicStructWithDefaultValues::get_schema()
{
assert_eq!("TestBasicStructWithDefaultValues", name.fullname(None));
use serde_json::json;
for field in fields {
match field.name.as_str() {
"a" => assert_eq!(Some(json!(123_i32)), field.default),
"b" => assert_eq!(
Some(json!(r#"The default value for 'b'"#.to_owned())),
field.default
),
"condition" => assert_eq!(Some(json!(true)), field.default),
"array" => assert_eq!(Some(json!([1, 2, 3])), field.default),
"map" => assert_eq!(
Some(json!({
"a": 1,
"b": 2
})),
field.default
),
"c" => assert_eq!(None, field.default),
"myenum" => assert_eq!(Some(json!("Foo")), field.default),
_ => panic!("Unexpected field name"),
}
}
} else {
panic!("TestBasicStructWithDefaultValues schema must be a record schema")
}
assert_eq!(schema, TestBasicStructWithDefaultValues::get_schema());
serde_assert(TestBasicStructWithDefaultValues {
a: 321,
b: "A custom value for 'b'".to_owned(),
condition: false,
c: 987.654,
map: [("a".to_owned(), 1), ("b".to_owned(), 2)]
.iter()
.cloned()
.collect(),
array: vec![4, 5, 6],
myenum: MyEnum::Bar,
});
}
#[test]
fn avro_3633_test_basic_struct_with_skip_attribute() {
// Note: If using the skip attribute together with serialization,
// the serde's skip attribute needs also to be added
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
struct TestBasicStructNoSchema {
field: bool,
}
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
struct TestBasicStructWithSkipAttribute {
#[avro(skip)]
#[serde(skip)]
condition: bool,
#[avro(skip = false)]
a: f64,
#[avro(skip)]
#[serde(skip)]
map: HashMap<String, i32>,
array: Vec<i32>,
#[avro(skip = true)]
#[serde(skip)]
mystruct: TestBasicStructNoSchema,
b: i32,
}
let schema = r#"
{
"type":"record",
"name":"TestBasicStructWithSkipAttribute",
"fields": [
{
"name":"a",
"type":"double"
},
{
"name":"array",
"type":{
"type":"array",
"items":"int"
}
},
{
"name":"b",
"type":"int"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
let derived_schema = TestBasicStructWithSkipAttribute::get_schema();
if let Schema::Record { name, fields, .. } = &derived_schema {
assert_eq!("TestBasicStructWithSkipAttribute", name.fullname(None));
for field in fields {
match field.name.as_str() {
"condition" => panic!("Unexpected field 'condition'"),
"mystruct" => panic!("Unexpected field 'mystruct'"),
"map" => panic!("Unexpected field 'map'"),
_ => {}
}
}
} else {
panic!(
"TestBasicStructWithSkipAttribute schema must be a record schema: {:?}",
derived_schema
)
}
assert_eq!(schema, derived_schema);
// Note: If serde's `skip` attribute is used on a field, the field's type
// needs the trait 'Default' to be implemented, since it is skipping the serialization process.
// Copied or cloned objects within 'serde_assert()' doesn't "copy" (serialize/deserialze)
// these fields, so no values are initialized here for skipped fields.
serde_assert(TestBasicStructWithSkipAttribute {
condition: bool::default(), // <- skipped
a: 987.654,
map: HashMap::default(), // <- skipped
array: vec![4, 5, 6],
mystruct: TestBasicStructNoSchema::default(), // <- skipped
b: 321,
});
}
#[test]
fn avro_3633_test_basic_struct_with_rename_attribute() {
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
struct TestBasicStructWithRenameAttribute {
#[avro(rename = "a1")]
#[serde(rename = "a1")]
a: bool,
b: i32,
#[avro(rename = "c1")]
#[serde(rename = "c1")]
c: f32,
}
let schema = r#"
{
"type":"record",
"name":"TestBasicStructWithRenameAttribute",
"fields": [
{
"name":"a1",
"type":"boolean"
},
{
"name":"b",
"type":"int"
},
{
"name":"c1",
"type":"float"
}
]
}
"#;
let schema = Schema::parse_str(schema).unwrap();
let derived_schema = TestBasicStructWithRenameAttribute::get_schema();
if let Schema::Record { name, fields, .. } = &derived_schema {
assert_eq!("TestBasicStructWithRenameAttribute", name.fullname(None));
for field in fields {
match field.name.as_str() {
"a" => panic!("Unexpected field name 'a': must be 'a1'"),
"c" => panic!("Unexpected field name 'c': must be 'c1'"),
_ => {}
}
}
} else {
panic!(
"TestBasicStructWithRenameAttribute schema must be a record schema: {:?}",
derived_schema
)
}
assert_eq!(schema, derived_schema);
serde_assert(TestBasicStructWithRenameAttribute {
a: true,
b: 321,
c: 987.654,
});
}
#[test]
fn test_avro_3663_raw_identifier_field_name() {
#[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
struct TestRawIdent {
r#type: bool,
}
let derived_schema = TestRawIdent::get_schema();
if let Schema::Record { fields, .. } = derived_schema {
let field = fields.get(0).expect("TestRawIdent must contain a field");
assert_eq!(field.name, "type");
} else {
panic!("Unexpected schema type for {:?}", derived_schema)
}
}
}