diff --git a/lang/rust/avro_derive/src/lib.rs b/lang/rust/avro_derive/src/lib.rs index 96575e0901b..11ab7a61661 100644 --- a/lang/rust/avro_derive/src/lib.rs +++ b/lang/rust/avro_derive/src/lib.rs @@ -39,6 +39,8 @@ struct NamedTypeOptions { namespace: Option, #[darling(default)] doc: Option, + #[darling(multiple)] + alias: Vec, } #[proc_macro_derive(AvroSchema, attributes(avro))] @@ -64,6 +66,7 @@ fn derive_avro_schema(input: &mut DeriveInput) -> Result Result, + aliases: Vec, s: &syn::DataStruct, error_span: Span, ) -> Result> { @@ -143,6 +147,7 @@ fn get_data_struct_schema_def( } } let record_doc = preserve_optional(record_doc); + let record_aliases = preserve_vec(aliases); Ok(quote! { let schema_fields = vec![#(#record_field_exprs),*]; let name = apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse struct name for schema {}", #full_schema_name)[..]); @@ -152,7 +157,7 @@ fn get_data_struct_schema_def( .collect(); apache_avro::schema::Schema::Record { name, - aliases: None, + aliases: #record_aliases, doc: #record_doc, fields: schema_fields, lookup, @@ -277,6 +282,15 @@ fn preserve_optional(op: Option) -> TokenStream { } } +fn preserve_vec(op: Vec) -> TokenStream { + let items: Vec = op.iter().map(|tt| quote! {#tt.into()}).collect(); + if items.is_empty() { + quote! {None} + } else { + quote! {Some(vec![#(#items),*])} + } +} + fn darling_to_syn(e: darling::Error) -> Vec { let msg = format!("{}", e); let token_errors = e.write_errors(); diff --git a/lang/rust/avro_derive/tests/derive.rs b/lang/rust/avro_derive/tests/derive.rs index 8ac95755f4a..c058c44c10d 100644 --- a/lang/rust/avro_derive/tests/derive.rs +++ b/lang/rust/avro_derive/tests/derive.rs @@ -1063,4 +1063,78 @@ mod test_derive { serde_assert(TestBasicWithU32 { a: u32::MIN }); serde_assert(TestBasicWithU32 { a: 1_u32 }); } + + #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)] + #[avro(alias = "a", alias = "b", alias = "c")] + struct TestBasicWithAliases { + a: i32, + } + + #[test] + fn test_basic_with_aliases() { + let schema = r#" + { + "type":"record", + "name":"TestBasicWithAliases", + "aliases":["a", "b", "c"], + "fields":[ + { + "name":"a", + "type":"int" + } + ] + } + "#; + let schema = Schema::parse_str(schema).unwrap(); + if let Schema::Record { name, aliases, .. } = TestBasicWithAliases::get_schema() { + assert_eq!("TestBasicWithAliases", name.fullname(None)); + assert_eq!( + Some(vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]), + aliases + ); + } else { + panic!("TestBasicWithAliases schema must be a record schema") + } + assert_eq!(schema, TestBasicWithAliases::get_schema()); + + serde_assert(TestBasicWithAliases { a: i32::MAX }); + } + + #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)] + #[avro(alias = "d")] + #[avro(alias = "e")] + #[avro(alias = "f")] + struct TestBasicWithAliases2 { + a: i32, + } + + #[test] + fn test_basic_with_aliases2() { + let schema = r#" + { + "type":"record", + "name":"TestBasicWithAliases2", + "aliases":["d", "e", "f"], + "fields":[ + { + "name":"a", + "type":"int" + } + ] + } + "#; + let schema = Schema::parse_str(schema).unwrap(); + if let Schema::Record { name, aliases, .. } = TestBasicWithAliases2::get_schema() { + assert_eq!("TestBasicWithAliases2", name.fullname(None)); + assert_eq!( + Some(vec!["d".to_owned(), "e".to_owned(), "f".to_owned()]), + aliases + ); + } else { + panic!("TestBasicWithAliases2 schema must be a record schema") + } + assert_eq!(schema, TestBasicWithAliases2::get_schema()); + + serde_assert(TestBasicWithAliases2 { a: i32::MAX }); + } }