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