1mod case;
19use case::RenameRule;
20use darling::FromAttributes;
21use proc_macro2::{Span, TokenStream};
22use quote::quote;
23
24use syn::{
25 AttrStyle, Attribute, DeriveInput, Ident, Meta, Type, TypePath, parse_macro_input,
26 spanned::Spanned,
27};
28
29#[derive(darling::FromAttributes)]
30#[darling(attributes(avro))]
31struct FieldOptions {
32 #[darling(default)]
33 doc: Option<String>,
34 #[darling(default)]
35 default: Option<String>,
36 #[darling(multiple)]
37 alias: Vec<String>,
38 #[darling(default)]
39 rename: Option<String>,
40 #[darling(default)]
41 skip: Option<bool>,
42}
43
44#[derive(darling::FromAttributes)]
45#[darling(attributes(avro))]
46struct VariantOptions {
47 #[darling(default)]
48 rename: Option<String>,
49}
50
51#[derive(darling::FromAttributes)]
52#[darling(attributes(avro))]
53struct NamedTypeOptions {
54 #[darling(default)]
55 name: Option<String>,
56 #[darling(default)]
57 namespace: Option<String>,
58 #[darling(default)]
59 doc: Option<String>,
60 #[darling(multiple)]
61 alias: Vec<String>,
62 #[darling(default)]
63 rename_all: Option<String>,
64}
65
66#[proc_macro_derive(AvroSchema, attributes(avro))]
67pub fn proc_macro_derive_avro_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
69 let mut input = parse_macro_input!(input as DeriveInput);
70 derive_avro_schema(&mut input)
71 .unwrap_or_else(to_compile_errors)
72 .into()
73}
74
75fn derive_avro_schema(input: &mut DeriveInput) -> Result<TokenStream, Vec<syn::Error>> {
76 let named_type_options =
77 NamedTypeOptions::from_attributes(&input.attrs[..]).map_err(darling_to_syn)?;
78
79 let rename_all = parse_case(named_type_options.rename_all.as_deref(), input.span())?;
80 let name = named_type_options.name.unwrap_or(input.ident.to_string());
81
82 let full_schema_name = vec![named_type_options.namespace, Some(name)]
83 .into_iter()
84 .flatten()
85 .collect::<Vec<String>>()
86 .join(".");
87 let schema_def = match &input.data {
88 syn::Data::Struct(s) => get_data_struct_schema_def(
89 &full_schema_name,
90 named_type_options
91 .doc
92 .or_else(|| extract_outer_doc(&input.attrs)),
93 named_type_options.alias,
94 rename_all,
95 s,
96 input.ident.span(),
97 )?,
98 syn::Data::Enum(e) => get_data_enum_schema_def(
99 &full_schema_name,
100 named_type_options
101 .doc
102 .or_else(|| extract_outer_doc(&input.attrs)),
103 named_type_options.alias,
104 rename_all,
105 e,
106 input.ident.span(),
107 )?,
108 _ => {
109 return Err(vec![syn::Error::new(
110 input.ident.span(),
111 "AvroSchema derive only works for structs and simple enums ",
112 )]);
113 }
114 };
115 let ident = &input.ident;
116 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
117 Ok(quote! {
118 #[automatically_derived]
119 impl #impl_generics apache_avro::schema::derive::AvroSchemaComponent for #ident #ty_generics #where_clause {
120 fn get_schema_in_ctxt(named_schemas: &mut std::collections::HashMap<apache_avro::schema::Name, apache_avro::schema::Schema>, enclosing_namespace: &Option<String>) -> apache_avro::schema::Schema {
121 let name = apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse schema name {}", #full_schema_name)[..]).fully_qualified_name(enclosing_namespace);
122 let enclosing_namespace = &name.namespace;
123 if named_schemas.contains_key(&name) {
124 apache_avro::schema::Schema::Ref{name: name.clone()}
125 } else {
126 named_schemas.insert(name.clone(), apache_avro::schema::Schema::Ref{name: name.clone()});
127 #schema_def
128 }
129 }
130 }
131 })
132}
133
134fn get_data_struct_schema_def(
135 full_schema_name: &str,
136 record_doc: Option<String>,
137 aliases: Vec<String>,
138 rename_all: RenameRule,
139 s: &syn::DataStruct,
140 error_span: Span,
141) -> Result<TokenStream, Vec<syn::Error>> {
142 let mut record_field_exprs = vec![];
143 match s.fields {
144 syn::Fields::Named(ref a) => {
145 let mut index: usize = 0;
146 for field in a.named.iter() {
147 let mut name = field.ident.as_ref().unwrap().to_string(); if let Some(raw_name) = name.strip_prefix("r#") {
149 name = raw_name.to_string();
150 }
151 let field_attrs =
152 FieldOptions::from_attributes(&field.attrs[..]).map_err(darling_to_syn)?;
153 let doc =
154 preserve_optional(field_attrs.doc.or_else(|| extract_outer_doc(&field.attrs)));
155 match (field_attrs.rename, rename_all) {
156 (Some(rename), _) => {
157 name = rename;
158 }
159 (None, rename_all) if !matches!(rename_all, RenameRule::None) => {
160 name = rename_all.apply_to_field(&name);
161 }
162 _ => {}
163 }
164 if let Some(true) = field_attrs.skip {
165 continue;
166 }
167 let default_value = match field_attrs.default {
168 Some(default_value) => {
169 let _: serde_json::Value = serde_json::from_str(&default_value[..])
170 .map_err(|e| {
171 vec![syn::Error::new(
172 field.ident.span(),
173 format!("Invalid avro default json: \n{e}"),
174 )]
175 })?;
176 quote! {
177 Some(serde_json::from_str(#default_value).expect(format!("Invalid JSON: {:?}", #default_value).as_str()))
178 }
179 }
180 None => quote! { None },
181 };
182 let aliases = preserve_vec(field_attrs.alias);
183 let schema_expr = type_to_schema_expr(&field.ty)?;
184 let position = index;
185 record_field_exprs.push(quote! {
186 apache_avro::schema::RecordField {
187 name: #name.to_string(),
188 doc: #doc,
189 default: #default_value,
190 aliases: #aliases,
191 schema: #schema_expr,
192 order: apache_avro::schema::RecordFieldOrder::Ascending,
193 position: #position,
194 custom_attributes: Default::default(),
195 }
196 });
197 index += 1;
198 }
199 }
200 syn::Fields::Unnamed(_) => {
201 return Err(vec![syn::Error::new(
202 error_span,
203 "AvroSchema derive does not work for tuple structs",
204 )]);
205 }
206 syn::Fields::Unit => {
207 return Err(vec![syn::Error::new(
208 error_span,
209 "AvroSchema derive does not work for unit structs",
210 )]);
211 }
212 }
213 let record_doc = preserve_optional(record_doc);
214 let record_aliases = preserve_vec(aliases);
215 Ok(quote! {
216 let schema_fields = vec![#(#record_field_exprs),*];
217 let name = apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse struct name for schema {}", #full_schema_name)[..]);
218 let lookup: std::collections::BTreeMap<String, usize> = schema_fields
219 .iter()
220 .map(|field| (field.name.to_owned(), field.position))
221 .collect();
222 apache_avro::schema::Schema::Record(apache_avro::schema::RecordSchema {
223 name,
224 aliases: #record_aliases,
225 doc: #record_doc,
226 fields: schema_fields,
227 lookup,
228 attributes: Default::default(),
229 })
230 })
231}
232
233fn get_data_enum_schema_def(
234 full_schema_name: &str,
235 doc: Option<String>,
236 aliases: Vec<String>,
237 rename_all: RenameRule,
238 e: &syn::DataEnum,
239 error_span: Span,
240) -> Result<TokenStream, Vec<syn::Error>> {
241 let doc = preserve_optional(doc);
242 let enum_aliases = preserve_vec(aliases);
243 if e.variants.iter().all(|v| syn::Fields::Unit == v.fields) {
244 let default_value = default_enum_variant(e, error_span)?;
245 let default = preserve_optional(default_value);
246 let mut symbols = Vec::new();
247 for variant in &e.variants {
248 let field_attrs =
249 VariantOptions::from_attributes(&variant.attrs[..]).map_err(darling_to_syn)?;
250 let name = match (field_attrs.rename, rename_all) {
251 (Some(rename), _) => rename,
252 (None, rename_all) if !matches!(rename_all, RenameRule::None) => {
253 rename_all.apply_to_variant(&variant.ident.to_string())
254 }
255 _ => variant.ident.to_string(),
256 };
257 symbols.push(name);
258 }
259 Ok(quote! {
260 apache_avro::schema::Schema::Enum(apache_avro::schema::EnumSchema {
261 name: apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse enum name for schema {}", #full_schema_name)[..]),
262 aliases: #enum_aliases,
263 doc: #doc,
264 symbols: vec![#(#symbols.to_owned()),*],
265 default: #default,
266 attributes: Default::default(),
267 })
268 })
269 } else {
270 Err(vec![syn::Error::new(
271 error_span,
272 "AvroSchema derive does not work for enums with non unit structs",
273 )])
274 }
275}
276
277fn type_to_schema_expr(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
279 if let Type::Path(p) = ty {
280 let type_string = p.path.segments.last().unwrap().ident.to_string();
281
282 let schema = match &type_string[..] {
283 "bool" => quote! {apache_avro::schema::Schema::Boolean},
284 "i8" | "i16" | "i32" | "u8" | "u16" => quote! {apache_avro::schema::Schema::Int},
285 "u32" | "i64" => quote! {apache_avro::schema::Schema::Long},
286 "f32" => quote! {apache_avro::schema::Schema::Float},
287 "f64" => quote! {apache_avro::schema::Schema::Double},
288 "String" | "str" => quote! {apache_avro::schema::Schema::String},
289 "char" => {
290 return Err(vec![syn::Error::new_spanned(
291 ty,
292 "AvroSchema: Cannot guarantee successful deserialization of this type",
293 )]);
294 }
295 "u64" => {
296 return Err(vec![syn::Error::new_spanned(
297 ty,
298 "Cannot guarantee successful serialization of this type due to overflow concerns",
299 )]);
300 } _ => {
302 type_path_schema_expr(p)
305 }
306 };
307 Ok(schema)
308 } else if let Type::Array(ta) = ty {
309 let inner_schema_expr = type_to_schema_expr(&ta.elem)?;
310 Ok(quote! {apache_avro::schema::Schema::array(#inner_schema_expr)})
311 } else if let Type::Reference(tr) = ty {
312 type_to_schema_expr(&tr.elem)
313 } else {
314 Err(vec![syn::Error::new_spanned(
315 ty,
316 format!("Unable to generate schema for type: {ty:?}"),
317 )])
318 }
319}
320
321fn default_enum_variant(
322 data_enum: &syn::DataEnum,
323 error_span: Span,
324) -> Result<Option<String>, Vec<syn::Error>> {
325 match data_enum
326 .variants
327 .iter()
328 .filter(|v| v.attrs.iter().any(is_default_attr))
329 .collect::<Vec<_>>()
330 {
331 variants if variants.is_empty() => Ok(None),
332 single if single.len() == 1 => Ok(Some(single[0].ident.to_string())),
333 multiple => Err(vec![syn::Error::new(
334 error_span,
335 format!(
336 "Multiple defaults defined: {:?}",
337 multiple
338 .iter()
339 .map(|v| v.ident.to_string())
340 .collect::<Vec<String>>()
341 ),
342 )]),
343 }
344}
345
346fn is_default_attr(attr: &Attribute) -> bool {
347 matches!(attr, Attribute { meta: Meta::Path(path), .. } if path.get_ident().map(Ident::to_string).as_deref() == Some("default"))
348}
349
350fn type_path_schema_expr(p: &TypePath) -> TokenStream {
354 quote! {<#p as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}
355}
356
357fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
359 let compile_errors = errors.iter().map(syn::Error::to_compile_error);
360 quote!(#(#compile_errors)*)
361}
362
363fn extract_outer_doc(attributes: &[Attribute]) -> Option<String> {
364 let doc = attributes
365 .iter()
366 .filter(|attr| attr.style == AttrStyle::Outer && attr.path().is_ident("doc"))
367 .filter_map(|attr| {
368 let name_value = attr.meta.require_name_value();
369 match name_value {
370 Ok(name_value) => match &name_value.value {
371 syn::Expr::Lit(expr_lit) => match expr_lit.lit {
372 syn::Lit::Str(ref lit_str) => Some(lit_str.value().trim().to_string()),
373 _ => None,
374 },
375 _ => None,
376 },
377 Err(_) => None,
378 }
379 })
380 .collect::<Vec<String>>()
381 .join("\n");
382 if doc.is_empty() { None } else { Some(doc) }
383}
384
385fn preserve_optional(op: Option<impl quote::ToTokens>) -> TokenStream {
386 match op {
387 Some(tt) => quote! {Some(#tt.into())},
388 None => quote! {None},
389 }
390}
391
392fn preserve_vec(op: Vec<impl quote::ToTokens>) -> TokenStream {
393 let items: Vec<TokenStream> = op.iter().map(|tt| quote! {#tt.into()}).collect();
394 if items.is_empty() {
395 quote! {None}
396 } else {
397 quote! {Some(vec![#(#items),*])}
398 }
399}
400
401fn darling_to_syn(e: darling::Error) -> Vec<syn::Error> {
402 let msg = format!("{e}");
403 let token_errors = e.write_errors();
404 vec![syn::Error::new(token_errors.span(), msg)]
405}
406
407fn parse_case(case: Option<&str>, span: Span) -> Result<RenameRule, Vec<syn::Error>> {
408 match case {
409 None => Ok(RenameRule::None),
410 Some(case) => {
411 Ok(RenameRule::from_str(case)
412 .map_err(|e| vec![syn::Error::new(span, e.to_string())])?)
413 }
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420 #[test]
421 fn basic_case() {
422 let test_struct = quote! {
423 struct A {
424 a: i32,
425 b: String
426 }
427 };
428
429 match syn::parse2::<DeriveInput>(test_struct) {
430 Ok(mut input) => {
431 assert!(derive_avro_schema(&mut input).is_ok())
432 }
433 Err(error) => panic!(
434 "Failed to parse as derive input when it should be able to. Error: {error:?}"
435 ),
436 };
437 }
438
439 #[test]
440 fn tuple_struct_unsupported() {
441 let test_tuple_struct = quote! {
442 struct B (i32, String);
443 };
444
445 match syn::parse2::<DeriveInput>(test_tuple_struct) {
446 Ok(mut input) => {
447 assert!(derive_avro_schema(&mut input).is_err())
448 }
449 Err(error) => panic!(
450 "Failed to parse as derive input when it should be able to. Error: {error:?}"
451 ),
452 };
453 }
454
455 #[test]
456 fn unit_struct_unsupported() {
457 let test_tuple_struct = quote! {
458 struct AbsoluteUnit;
459 };
460
461 match syn::parse2::<DeriveInput>(test_tuple_struct) {
462 Ok(mut input) => {
463 assert!(derive_avro_schema(&mut input).is_err())
464 }
465 Err(error) => panic!(
466 "Failed to parse as derive input when it should be able to. Error: {error:?}"
467 ),
468 };
469 }
470
471 #[test]
472 fn struct_with_optional() {
473 let struct_with_optional = quote! {
474 struct Test4 {
475 a : Option<i32>
476 }
477 };
478 match syn::parse2::<DeriveInput>(struct_with_optional) {
479 Ok(mut input) => {
480 assert!(derive_avro_schema(&mut input).is_ok())
481 }
482 Err(error) => panic!(
483 "Failed to parse as derive input when it should be able to. Error: {error:?}"
484 ),
485 };
486 }
487
488 #[test]
489 fn test_basic_enum() {
490 let basic_enum = quote! {
491 enum Basic {
492 A,
493 B,
494 C,
495 D
496 }
497 };
498 match syn::parse2::<DeriveInput>(basic_enum) {
499 Ok(mut input) => {
500 assert!(derive_avro_schema(&mut input).is_ok())
501 }
502 Err(error) => panic!(
503 "Failed to parse as derive input when it should be able to. Error: {error:?}"
504 ),
505 };
506 }
507
508 #[test]
509 fn avro_3687_basic_enum_with_default() {
510 let basic_enum = quote! {
511 enum Basic {
512 #[default]
513 A,
514 B,
515 C,
516 D
517 }
518 };
519 match syn::parse2::<DeriveInput>(basic_enum) {
520 Ok(mut input) => {
521 let derived = derive_avro_schema(&mut input);
522 assert!(derived.is_ok());
523 assert_eq!(derived.unwrap().to_string(), quote! {
524 #[automatically_derived]
525 impl apache_avro::schema::derive::AvroSchemaComponent for Basic {
526 fn get_schema_in_ctxt(
527 named_schemas: &mut std::collections::HashMap<
528 apache_avro::schema::Name,
529 apache_avro::schema::Schema
530 >,
531 enclosing_namespace: &Option<String>
532 ) -> apache_avro::schema::Schema {
533 let name = apache_avro::schema::Name::new("Basic")
534 .expect(&format!("Unable to parse schema name {}", "Basic")[..])
535 .fully_qualified_name(enclosing_namespace);
536 let enclosing_namespace = &name.namespace;
537 if named_schemas.contains_key(&name) {
538 apache_avro::schema::Schema::Ref { name: name.clone() }
539 } else {
540 named_schemas.insert(
541 name.clone(),
542 apache_avro::schema::Schema::Ref { name: name.clone() }
543 );
544 apache_avro::schema::Schema::Enum(apache_avro::schema::EnumSchema {
545 name: apache_avro::schema::Name::new("Basic").expect(
546 &format!("Unable to parse enum name for schema {}", "Basic")[..]
547 ),
548 aliases: None,
549 doc: None,
550 symbols: vec![
551 "A".to_owned(),
552 "B".to_owned(),
553 "C".to_owned(),
554 "D".to_owned()
555 ],
556 default: Some("A".into()),
557 attributes: Default::default(),
558 })
559 }
560 }
561 }
562 }.to_string());
563 }
564 Err(error) => panic!(
565 "Failed to parse as derive input when it should be able to. Error: {error:?}"
566 ),
567 };
568 }
569
570 #[test]
571 fn avro_3687_basic_enum_with_default_twice() {
572 let non_basic_enum = quote! {
573 enum Basic {
574 #[default]
575 A,
576 B,
577 #[default]
578 C,
579 D
580 }
581 };
582 match syn::parse2::<DeriveInput>(non_basic_enum) {
583 Ok(mut input) => match derive_avro_schema(&mut input) {
584 Ok(_) => {
585 panic!("Should not be able to derive schema for enum with multiple defaults")
586 }
587 Err(errors) => {
588 assert_eq!(errors.len(), 1);
589 assert_eq!(
590 errors[0].to_string(),
591 r#"Multiple defaults defined: ["A", "C"]"#
592 );
593 }
594 },
595 Err(error) => panic!(
596 "Failed to parse as derive input when it should be able to. Error: {error:?}"
597 ),
598 };
599 }
600
601 #[test]
602 fn test_non_basic_enum() {
603 let non_basic_enum = quote! {
604 enum Basic {
605 A(i32),
606 B,
607 C,
608 D
609 }
610 };
611 match syn::parse2::<DeriveInput>(non_basic_enum) {
612 Ok(mut input) => {
613 assert!(derive_avro_schema(&mut input).is_err())
614 }
615 Err(error) => panic!(
616 "Failed to parse as derive input when it should be able to. Error: {error:?}"
617 ),
618 };
619 }
620
621 #[test]
622 fn test_namespace() {
623 let test_struct = quote! {
624 #[avro(namespace = "namespace.testing")]
625 struct A {
626 a: i32,
627 b: String
628 }
629 };
630
631 match syn::parse2::<DeriveInput>(test_struct) {
632 Ok(mut input) => {
633 let schema_token_stream = derive_avro_schema(&mut input);
634 assert!(&schema_token_stream.is_ok());
635 assert!(
636 schema_token_stream
637 .unwrap()
638 .to_string()
639 .contains("namespace.testing")
640 )
641 }
642 Err(error) => panic!(
643 "Failed to parse as derive input when it should be able to. Error: {error:?}"
644 ),
645 };
646 }
647
648 #[test]
649 fn test_reference() {
650 let test_reference_struct = quote! {
651 struct A<'a> {
652 a: &'a Vec<i32>,
653 b: &'static str
654 }
655 };
656
657 match syn::parse2::<DeriveInput>(test_reference_struct) {
658 Ok(mut input) => {
659 assert!(derive_avro_schema(&mut input).is_ok())
660 }
661 Err(error) => panic!(
662 "Failed to parse as derive input when it should be able to. Error: {error:?}"
663 ),
664 };
665 }
666
667 #[test]
668 fn test_trait_cast() {
669 assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{i32}).unwrap()).to_string(), quote!{<i32 as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}.to_string());
670 assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{Vec<T>}).unwrap()).to_string(), quote!{<Vec<T> as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}.to_string());
671 assert_eq!(type_path_schema_expr(&syn::parse2::<TypePath>(quote!{AnyType}).unwrap()).to_string(), quote!{<AnyType as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}.to_string());
672 }
673
674 #[test]
675 fn test_avro_3709_record_field_attributes() {
676 let test_struct = quote! {
677 struct A {
678 #[avro(alias = "a1", alias = "a2", doc = "a doc", default = "123", rename = "a3")]
679 a: i32
680 }
681 };
682
683 match syn::parse2::<DeriveInput>(test_struct) {
684 Ok(mut input) => {
685 let schema_res = derive_avro_schema(&mut input);
686 let expected_token_stream = r#"let schema_fields = vec ! [apache_avro :: schema :: RecordField { name : "a3" . to_string () , doc : Some ("a doc" . into ()) , default : Some (serde_json :: from_str ("123") . expect (format ! ("Invalid JSON: {:?}" , "123") . as_str ())) , aliases : Some (vec ! ["a1" . into () , "a2" . into ()]) , schema : apache_avro :: schema :: Schema :: Int , order : apache_avro :: schema :: RecordFieldOrder :: Ascending , position : 0usize , custom_attributes : Default :: default () , }] ;"#;
687 let schema_token_stream = schema_res.unwrap().to_string();
688 assert!(schema_token_stream.contains(expected_token_stream));
689 }
690 Err(error) => panic!(
691 "Failed to parse as derive input when it should be able to. Error: {error:?}"
692 ),
693 };
694
695 let test_enum = quote! {
696 enum A {
697 #[avro(rename = "A3")]
698 Item1,
699 }
700 };
701
702 match syn::parse2::<DeriveInput>(test_enum) {
703 Ok(mut input) => {
704 let schema_res = derive_avro_schema(&mut input);
705 let expected_token_stream = r#"let name = apache_avro :: schema :: Name :: new ("A") . expect (& format ! ("Unable to parse schema name {}" , "A") [..]) . fully_qualified_name (enclosing_namespace) ; let enclosing_namespace = & name . namespace ; if named_schemas . contains_key (& name) { apache_avro :: schema :: Schema :: Ref { name : name . clone () } } else { named_schemas . insert (name . clone () , apache_avro :: schema :: Schema :: Ref { name : name . clone () }) ; apache_avro :: schema :: Schema :: Enum (apache_avro :: schema :: EnumSchema { name : apache_avro :: schema :: Name :: new ("A") . expect (& format ! ("Unable to parse enum name for schema {}" , "A") [..]) , aliases : None , doc : None , symbols : vec ! ["A3" . to_owned ()] , default : None , attributes : Default :: default () , })"#;
706 let schema_token_stream = schema_res.unwrap().to_string();
707 assert!(schema_token_stream.contains(expected_token_stream));
708 }
709 Err(error) => panic!(
710 "Failed to parse as derive input when it should be able to. Error: {error:?}"
711 ),
712 };
713 }
714
715 #[test]
716 fn test_avro_rs_207_rename_all_attribute() {
717 let test_struct = quote! {
718 #[avro(rename_all="SCREAMING_SNAKE_CASE")]
719 struct A {
720 item: i32,
721 double_item: i32
722 }
723 };
724
725 match syn::parse2::<DeriveInput>(test_struct) {
726 Ok(mut input) => {
727 let schema_res = derive_avro_schema(&mut input);
728 let expected_token_stream = r#"let name = apache_avro :: schema :: Name :: new ("A") . expect (& format ! ("Unable to parse schema name {}" , "A") [..]) . fully_qualified_name (enclosing_namespace) ; let enclosing_namespace = & name . namespace ; if named_schemas . contains_key (& name) { apache_avro :: schema :: Schema :: Ref { name : name . clone () } } else { named_schemas . insert (name . clone () , apache_avro :: schema :: Schema :: Ref { name : name . clone () }) ; let schema_fields = vec ! [apache_avro :: schema :: RecordField { name : "ITEM" . to_string () , doc : None , default : None , aliases : None , schema : apache_avro :: schema :: Schema :: Int , order : apache_avro :: schema :: RecordFieldOrder :: Ascending , position : 0usize , custom_attributes : Default :: default () , } , apache_avro :: schema :: RecordField { name : "DOUBLE_ITEM" . to_string () , doc : None , default : None , aliases : None , schema : apache_avro :: schema :: Schema :: Int , order : apache_avro :: schema :: RecordFieldOrder :: Ascending , position : 1usize , custom_attributes : Default :: default () , }] ;"#;
729 let schema_token_stream = schema_res.unwrap().to_string();
730 assert!(schema_token_stream.contains(expected_token_stream));
731 }
732 Err(error) => panic!(
733 "Failed to parse as derive input when it should be able to. Error: {error:?}"
734 ),
735 };
736
737 let test_enum = quote! {
738 #[avro(rename_all="SCREAMING_SNAKE_CASE")]
739 enum B {
740 Item,
741 DoubleItem,
742 }
743 };
744
745 match syn::parse2::<DeriveInput>(test_enum) {
746 Ok(mut input) => {
747 let schema_res = derive_avro_schema(&mut input);
748 let expected_token_stream = r#"let name = apache_avro :: schema :: Name :: new ("B") . expect (& format ! ("Unable to parse schema name {}" , "B") [..]) . fully_qualified_name (enclosing_namespace) ; let enclosing_namespace = & name . namespace ; if named_schemas . contains_key (& name) { apache_avro :: schema :: Schema :: Ref { name : name . clone () } } else { named_schemas . insert (name . clone () , apache_avro :: schema :: Schema :: Ref { name : name . clone () }) ; apache_avro :: schema :: Schema :: Enum (apache_avro :: schema :: EnumSchema { name : apache_avro :: schema :: Name :: new ("B") . expect (& format ! ("Unable to parse enum name for schema {}" , "B") [..]) , aliases : None , doc : None , symbols : vec ! ["ITEM" . to_owned () , "DOUBLE_ITEM" . to_owned ()] , default : None , attributes : Default :: default () , })"#;
749 let schema_token_stream = schema_res.unwrap().to_string();
750 assert!(schema_token_stream.contains(expected_token_stream));
751 }
752 Err(error) => panic!(
753 "Failed to parse as derive input when it should be able to. Error: {error:?}"
754 ),
755 };
756 }
757
758 #[test]
759 fn test_avro_rs_207_rename_attr_has_priority_over_rename_all_attribute() {
760 let test_struct = quote! {
761 #[avro(rename_all="SCREAMING_SNAKE_CASE")]
762 struct A {
763 item: i32,
764 #[avro(rename="DoubleItem")]
765 double_item: i32
766 }
767 };
768
769 match syn::parse2::<DeriveInput>(test_struct) {
770 Ok(mut input) => {
771 let schema_res = derive_avro_schema(&mut input);
772 let expected_token_stream = r#"let name = apache_avro :: schema :: Name :: new ("A") . expect (& format ! ("Unable to parse schema name {}" , "A") [..]) . fully_qualified_name (enclosing_namespace) ; let enclosing_namespace = & name . namespace ; if named_schemas . contains_key (& name) { apache_avro :: schema :: Schema :: Ref { name : name . clone () } } else { named_schemas . insert (name . clone () , apache_avro :: schema :: Schema :: Ref { name : name . clone () }) ; let schema_fields = vec ! [apache_avro :: schema :: RecordField { name : "ITEM" . to_string () , doc : None , default : None , aliases : None , schema : apache_avro :: schema :: Schema :: Int , order : apache_avro :: schema :: RecordFieldOrder :: Ascending , position : 0usize , custom_attributes : Default :: default () , } , apache_avro :: schema :: RecordField { name : "DoubleItem" . to_string () , doc : None , default : None , aliases : None , schema : apache_avro :: schema :: Schema :: Int , order : apache_avro :: schema :: RecordFieldOrder :: Ascending , position : 1usize , custom_attributes : Default :: default () , }] ;"#;
773 let schema_token_stream = schema_res.unwrap().to_string();
774 assert!(schema_token_stream.contains(expected_token_stream));
775 }
776 Err(error) => panic!(
777 "Failed to parse as derive input when it should be able to. Error: {error:?}"
778 ),
779 };
780 }
781}