| // 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) |
| } |
| } |
| } |