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