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