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 Attribute, DataEnum, DataStruct, DeriveInput, Expr, Field, Fields, Generics, Ident, Meta, Type,
27 parse_macro_input, spanned::Spanned,
28};
29
30use crate::{
31 attributes::{FieldOptions, NamedTypeOptions, VariantOptions, With},
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 input = parse_macro_input!(input as DeriveInput);
39 derive_avro_schema(input)
40 .unwrap_or_else(to_compile_errors)
41 .into()
42}
43
44fn derive_avro_schema(input: DeriveInput) -> Result<TokenStream, Vec<syn::Error>> {
45 let input_span = input.span();
48 match input.data {
49 syn::Data::Struct(data_struct) => {
50 let named_type_options = NamedTypeOptions::new(&input.ident, &input.attrs, input_span)?;
51 let inner = if named_type_options.transparent {
52 get_transparent_struct_schema_def(data_struct.fields, input_span)?
53 } else {
54 let schema_def =
55 get_struct_schema_def(&named_type_options, data_struct, input.ident.span())?;
56 handle_named_schemas(named_type_options.name, schema_def)
57 };
58 Ok(create_trait_definition(input.ident, &input.generics, inner))
59 }
60 syn::Data::Enum(data_enum) => {
61 let named_type_options = NamedTypeOptions::new(&input.ident, &input.attrs, input_span)?;
62 if named_type_options.transparent {
63 return Err(vec![syn::Error::new(
64 input_span,
65 "AvroSchema: `#[serde(transparent)]` is only supported on structs",
66 )]);
67 }
68 let schema_def =
69 get_data_enum_schema_def(&named_type_options, data_enum, input.ident.span())?;
70 let inner = handle_named_schemas(named_type_options.name, schema_def);
71 Ok(create_trait_definition(input.ident, &input.generics, inner))
72 }
73 syn::Data::Union(_) => Err(vec![syn::Error::new(
74 input_span,
75 "AvroSchema: derive only works for structs and simple enums",
76 )]),
77 }
78}
79
80fn create_trait_definition(
82 ident: Ident,
83 generics: &Generics,
84 implementation: TokenStream,
85) -> TokenStream {
86 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
87 quote! {
88 #[automatically_derived]
89 impl #impl_generics apache_avro::AvroSchemaComponent for #ident #ty_generics #where_clause {
90 fn get_schema_in_ctxt(named_schemas: &mut apache_avro::schema::Names, enclosing_namespace: &Option<String>) -> apache_avro::schema::Schema {
91 #implementation
92 }
93 }
94 }
95}
96
97fn handle_named_schemas(full_schema_name: String, schema_def: TokenStream) -> TokenStream {
99 quote! {
100 let name = apache_avro::schema::Name::new(#full_schema_name).expect(concat!("Unable to parse schema name ", #full_schema_name)).fully_qualified_name(enclosing_namespace);
101 if named_schemas.contains_key(&name) {
102 apache_avro::schema::Schema::Ref{name}
103 } else {
104 let enclosing_namespace = &name.namespace;
105 named_schemas.insert(name.clone(), apache_avro::schema::Schema::Ref{name: name.clone()});
108 let schema = #schema_def;
109 named_schemas.insert(name, schema.clone());
110 schema
111 }
112 }
113}
114
115fn get_struct_schema_def(
117 container_attrs: &NamedTypeOptions,
118 data_struct: DataStruct,
119 ident_span: Span,
120) -> Result<TokenStream, Vec<syn::Error>> {
121 let mut record_field_exprs = vec![];
122 match data_struct.fields {
123 Fields::Named(a) => {
124 for field in a.named {
125 let mut name = field
126 .ident
127 .as_ref()
128 .expect("Field must have a name")
129 .to_string();
130 if let Some(raw_name) = name.strip_prefix("r#") {
131 name = raw_name.to_string();
132 }
133 let field_attrs = FieldOptions::new(&field.attrs, field.span())?;
134 let doc = preserve_optional(field_attrs.doc);
135 match (field_attrs.rename, container_attrs.rename_all) {
136 (Some(rename), _) => {
137 name = rename;
138 }
139 (None, rename_all) if rename_all != RenameRule::None => {
140 name = rename_all.apply_to_field(&name);
141 }
142 _ => {}
143 }
144 if field_attrs.skip {
145 continue;
146 } else if field_attrs.flatten {
147 let flatten_ty = &field.ty;
150 record_field_exprs.push(quote! {
151 if let ::apache_avro::schema::Schema::Record(::apache_avro::schema::RecordSchema { fields, .. }) = #flatten_ty::get_schema() {
152 for mut field in fields {
153 field.position = schema_fields.len();
154 schema_fields.push(field)
155 }
156 } else {
157 panic!("Can only flatten RecordSchema, got {:?}", #flatten_ty::get_schema())
158 }
159 });
160
161 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 = aliases(&field_attrs.alias);
180 let schema_expr = get_field_schema_expr(&field, field_attrs.with)?;
181 record_field_exprs.push(quote! {
182 schema_fields.push(::apache_avro::schema::RecordField {
183 name: #name.to_string(),
184 doc: #doc,
185 default: #default_value,
186 aliases: #aliases,
187 schema: #schema_expr,
188 order: ::apache_avro::schema::RecordFieldOrder::Ascending,
189 position: schema_fields.len(),
190 custom_attributes: Default::default(),
191 });
192 });
193 }
194 }
195 Fields::Unnamed(_) => {
196 return Err(vec![syn::Error::new(
197 ident_span,
198 "AvroSchema derive does not work for tuple structs",
199 )]);
200 }
201 Fields::Unit => {
202 return Err(vec![syn::Error::new(
203 ident_span,
204 "AvroSchema derive does not work for unit structs",
205 )]);
206 }
207 }
208
209 let record_doc = preserve_optional(container_attrs.doc.as_ref());
210 let record_aliases = aliases(&container_attrs.aliases);
211 let full_schema_name = &container_attrs.name;
212
213 let minimum_fields = record_field_exprs.len();
216
217 Ok(quote! {
218 {
219 let mut schema_fields = Vec::with_capacity(#minimum_fields);
220 #(#record_field_exprs)*
221 let schema_field_set: ::std::collections::HashSet<_> = schema_fields.iter().map(|rf| &rf.name).collect();
222 assert_eq!(schema_fields.len(), schema_field_set.len(), "Duplicate field names found: {schema_fields:?}");
223 let name = apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse struct name for schema {}", #full_schema_name)[..]);
224 let lookup: std::collections::BTreeMap<String, usize> = schema_fields
225 .iter()
226 .map(|field| (field.name.to_owned(), field.position))
227 .collect();
228 apache_avro::schema::Schema::Record(apache_avro::schema::RecordSchema {
229 name,
230 aliases: #record_aliases,
231 doc: #record_doc,
232 fields: schema_fields,
233 lookup,
234 attributes: Default::default(),
235 })
236 }
237 })
238}
239
240fn get_transparent_struct_schema_def(
242 fields: Fields,
243 input_span: Span,
244) -> Result<TokenStream, Vec<syn::Error>> {
245 match fields {
246 Fields::Named(fields_named) => {
247 let mut found = None;
248 for field in fields_named.named {
249 let attrs = FieldOptions::new(&field.attrs, field.span())?;
250 if attrs.skip {
251 continue;
252 }
253 if found.replace((field, attrs)).is_some() {
254 return Err(vec![syn::Error::new(
255 input_span,
256 "AvroSchema: #[serde(transparent)] is only allowed on structs with one unskipped field",
257 )]);
258 }
259 }
260
261 if let Some((field, attrs)) = found {
262 get_field_schema_expr(&field, attrs.with)
263 } else {
264 Err(vec![syn::Error::new(
265 input_span,
266 "AvroSchema: #[serde(transparent)] is only allowed on structs with one unskipped field",
267 )])
268 }
269 }
270 Fields::Unnamed(_) => Err(vec![syn::Error::new(
271 input_span,
272 "AvroSchema: derive does not work for tuple structs",
273 )]),
274 Fields::Unit => Err(vec![syn::Error::new(
275 input_span,
276 "AvroSchema: derive does not work for unit structs",
277 )]),
278 }
279}
280
281fn get_field_schema_expr(field: &Field, with: With) -> Result<TokenStream, Vec<syn::Error>> {
282 match with {
283 With::Trait => Ok(type_to_schema_expr(&field.ty)?),
284 With::Serde(path) => {
285 Ok(quote! { #path::get_schema_in_ctxt(named_schemas, enclosing_namespace) })
286 }
287 With::Expr(Expr::Closure(closure)) => {
288 if closure.inputs.is_empty() {
289 Ok(quote! { (#closure)() })
290 } else {
291 Err(vec![syn::Error::new(
292 field.span(),
293 "Expected closure with 0 parameters",
294 )])
295 }
296 }
297 With::Expr(Expr::Path(path)) => Ok(quote! { #path(named_schemas, enclosing_namespace) }),
298 With::Expr(_expr) => Err(vec![syn::Error::new(
299 field.span(),
300 "Invalid expression, expected function or closure",
301 )]),
302 }
303}
304
305fn get_data_enum_schema_def(
307 container_attrs: &NamedTypeOptions,
308 data_enum: DataEnum,
309 ident_span: Span,
310) -> Result<TokenStream, Vec<syn::Error>> {
311 let doc = preserve_optional(container_attrs.doc.as_ref());
312 let enum_aliases = aliases(&container_attrs.aliases);
313 if data_enum.variants.iter().all(|v| Fields::Unit == v.fields) {
314 let default_value = default_enum_variant(&data_enum, ident_span)?;
315 let default = preserve_optional(default_value);
316 let mut symbols = Vec::new();
317 for variant in &data_enum.variants {
318 let field_attrs = VariantOptions::new(&variant.attrs, variant.span())?;
319 let name = match (field_attrs.rename, container_attrs.rename_all) {
320 (Some(rename), _) => rename,
321 (None, rename_all) if !matches!(rename_all, RenameRule::None) => {
322 rename_all.apply_to_variant(&variant.ident.to_string())
323 }
324 _ => variant.ident.to_string(),
325 };
326 symbols.push(name);
327 }
328 let full_schema_name = &container_attrs.name;
329 Ok(quote! {
330 apache_avro::schema::Schema::Enum(apache_avro::schema::EnumSchema {
331 name: apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse enum name for schema {}", #full_schema_name)[..]),
332 aliases: #enum_aliases,
333 doc: #doc,
334 symbols: vec![#(#symbols.to_owned()),*],
335 default: #default,
336 attributes: Default::default(),
337 })
338 })
339 } else {
340 Err(vec![syn::Error::new(
341 ident_span,
342 "AvroSchema: derive does not work for enums with non unit structs",
343 )])
344 }
345}
346
347fn type_to_schema_expr(ty: &Type) -> Result<TokenStream, Vec<syn::Error>> {
349 match ty {
350 Type::Array(_) | Type::Slice(_) | Type::Path(_) | Type::Reference(_) => Ok(
351 quote! {<#ty as apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)},
352 ),
353 Type::Ptr(_) => Err(vec![syn::Error::new_spanned(
354 ty,
355 "AvroSchema: derive does not support raw pointers",
356 )]),
357 Type::Tuple(_) => Err(vec![syn::Error::new_spanned(
358 ty,
359 "AvroSchema: derive does not support tuples",
360 )]),
361 _ => Err(vec![syn::Error::new_spanned(
362 ty,
363 format!(
364 "AvroSchema: Unexpected type encountered! Please open an issue if this kind of type should be supported: {ty:?}"
365 ),
366 )]),
367 }
368}
369
370fn default_enum_variant(
371 data_enum: &syn::DataEnum,
372 error_span: Span,
373) -> Result<Option<String>, Vec<syn::Error>> {
374 match data_enum
375 .variants
376 .iter()
377 .filter(|v| v.attrs.iter().any(is_default_attr))
378 .collect::<Vec<_>>()
379 {
380 variants if variants.is_empty() => Ok(None),
381 single if single.len() == 1 => Ok(Some(single[0].ident.to_string())),
382 multiple => Err(vec![syn::Error::new(
383 error_span,
384 format!(
385 "Multiple defaults defined: {:?}",
386 multiple
387 .iter()
388 .map(|v| v.ident.to_string())
389 .collect::<Vec<String>>()
390 ),
391 )]),
392 }
393}
394
395fn is_default_attr(attr: &Attribute) -> bool {
396 matches!(attr, Attribute { meta: Meta::Path(path), .. } if path.get_ident().map(Ident::to_string).as_deref() == Some("default"))
397}
398
399fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
401 let compile_errors = errors.iter().map(syn::Error::to_compile_error);
402 quote!(#(#compile_errors)*)
403}
404
405fn preserve_optional(op: Option<impl quote::ToTokens>) -> TokenStream {
406 match op {
407 Some(tt) => quote! {Some(#tt.into())},
408 None => quote! {None},
409 }
410}
411
412fn aliases(op: &[impl quote::ToTokens]) -> TokenStream {
413 let items: Vec<TokenStream> = op
414 .iter()
415 .map(|tt| quote! {#tt.try_into().expect("Alias is invalid")})
416 .collect();
417 if items.is_empty() {
418 quote! {None}
419 } else {
420 quote! {Some(vec![#(#items),*])}
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427 use pretty_assertions::assert_eq;
428
429 #[test]
430 fn basic_case() {
431 let test_struct = quote! {
432 struct A {
433 a: i32,
434 b: String
435 }
436 };
437
438 match syn::parse2::<DeriveInput>(test_struct) {
439 Ok(input) => {
440 assert!(derive_avro_schema(input).is_ok())
441 }
442 Err(error) => panic!(
443 "Failed to parse as derive input when it should be able to. Error: {error:?}"
444 ),
445 };
446 }
447
448 #[test]
449 fn tuple_struct_unsupported() {
450 let test_tuple_struct = quote! {
451 struct B (i32, String);
452 };
453
454 match syn::parse2::<DeriveInput>(test_tuple_struct) {
455 Ok(input) => {
456 assert!(derive_avro_schema(input).is_err())
457 }
458 Err(error) => panic!(
459 "Failed to parse as derive input when it should be able to. Error: {error:?}"
460 ),
461 };
462 }
463
464 #[test]
465 fn unit_struct_unsupported() {
466 let test_tuple_struct = quote! {
467 struct AbsoluteUnit;
468 };
469
470 match syn::parse2::<DeriveInput>(test_tuple_struct) {
471 Ok(input) => {
472 assert!(derive_avro_schema(input).is_err())
473 }
474 Err(error) => panic!(
475 "Failed to parse as derive input when it should be able to. Error: {error:?}"
476 ),
477 };
478 }
479
480 #[test]
481 fn struct_with_optional() {
482 let struct_with_optional = quote! {
483 struct Test4 {
484 a : Option<i32>
485 }
486 };
487 match syn::parse2::<DeriveInput>(struct_with_optional) {
488 Ok(input) => {
489 assert!(derive_avro_schema(input).is_ok())
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 test_basic_enum() {
499 let basic_enum = quote! {
500 enum Basic {
501 A,
502 B,
503 C,
504 D
505 }
506 };
507 match syn::parse2::<DeriveInput>(basic_enum) {
508 Ok(input) => {
509 assert!(derive_avro_schema(input).is_ok())
510 }
511 Err(error) => panic!(
512 "Failed to parse as derive input when it should be able to. Error: {error:?}"
513 ),
514 };
515 }
516
517 #[test]
518 fn avro_3687_basic_enum_with_default() {
519 let basic_enum = quote! {
520 enum Basic {
521 #[default]
522 A,
523 B,
524 C,
525 D
526 }
527 };
528 match syn::parse2::<DeriveInput>(basic_enum) {
529 Ok(input) => {
530 let derived = derive_avro_schema(input);
531 assert!(derived.is_ok());
532 assert_eq!(derived.unwrap().to_string(), quote! {
533 #[automatically_derived]
534 impl apache_avro::AvroSchemaComponent for Basic {
535 fn get_schema_in_ctxt(
536 named_schemas: &mut apache_avro::schema::Names,
537 enclosing_namespace: &Option<String>
538 ) -> apache_avro::schema::Schema {
539 let name = apache_avro::schema::Name::new("Basic")
540 .expect(concat!("Unable to parse schema name ", "Basic"))
541 .fully_qualified_name(enclosing_namespace);
542 if named_schemas.contains_key(&name) {
543 apache_avro::schema::Schema::Ref { name }
544 } else {
545 let enclosing_namespace = &name.namespace;
546 named_schemas.insert(name.clone(), apache_avro::schema::Schema::Ref{name: name.clone()});
547 let schema =
548 apache_avro::schema::Schema::Enum(apache_avro::schema::EnumSchema {
549 name: apache_avro::schema::Name::new("Basic").expect(
550 &format!("Unable to parse enum name for schema {}", "Basic")[..]
551 ),
552 aliases: None,
553 doc: None,
554 symbols: vec![
555 "A".to_owned(),
556 "B".to_owned(),
557 "C".to_owned(),
558 "D".to_owned()
559 ],
560 default: Some("A".into()),
561 attributes: Default::default(),
562 });
563 named_schemas.insert(name, schema.clone());
564 schema
565 }
566 }
567 }
568 }.to_string());
569 }
570 Err(error) => panic!(
571 "Failed to parse as derive input when it should be able to. Error: {error:?}"
572 ),
573 };
574 }
575
576 #[test]
577 fn avro_3687_basic_enum_with_default_twice() {
578 let non_basic_enum = quote! {
579 enum Basic {
580 #[default]
581 A,
582 B,
583 #[default]
584 C,
585 D
586 }
587 };
588 match syn::parse2::<DeriveInput>(non_basic_enum) {
589 Ok(input) => match derive_avro_schema(input) {
590 Ok(_) => {
591 panic!("Should not be able to derive schema for enum with multiple defaults")
592 }
593 Err(errors) => {
594 assert_eq!(errors.len(), 1);
595 assert_eq!(
596 errors[0].to_string(),
597 r#"Multiple defaults defined: ["A", "C"]"#
598 );
599 }
600 },
601 Err(error) => panic!(
602 "Failed to parse as derive input when it should be able to. Error: {error:?}"
603 ),
604 };
605 }
606
607 #[test]
608 fn test_non_basic_enum() {
609 let non_basic_enum = quote! {
610 enum Basic {
611 A(i32),
612 B,
613 C,
614 D
615 }
616 };
617 match syn::parse2::<DeriveInput>(non_basic_enum) {
618 Ok(input) => {
619 assert!(derive_avro_schema(input).is_err())
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_namespace() {
629 let test_struct = quote! {
630 #[avro(namespace = "namespace.testing")]
631 struct A {
632 a: i32,
633 b: String
634 }
635 };
636
637 match syn::parse2::<DeriveInput>(test_struct) {
638 Ok(input) => {
639 let schema_token_stream = derive_avro_schema(input);
640 assert!(&schema_token_stream.is_ok());
641 assert!(
642 schema_token_stream
643 .unwrap()
644 .to_string()
645 .contains("namespace.testing")
646 )
647 }
648 Err(error) => panic!(
649 "Failed to parse as derive input when it should be able to. Error: {error:?}"
650 ),
651 };
652 }
653
654 #[test]
655 fn test_reference() {
656 let test_reference_struct = quote! {
657 struct A<'a> {
658 a: &'a Vec<i32>,
659 b: &'static str
660 }
661 };
662
663 match syn::parse2::<DeriveInput>(test_reference_struct) {
664 Ok(input) => {
665 assert!(derive_avro_schema(input).is_ok())
666 }
667 Err(error) => panic!(
668 "Failed to parse as derive input when it should be able to. Error: {error:?}"
669 ),
670 };
671 }
672
673 #[test]
674 fn test_trait_cast() {
675 assert_eq!(type_to_schema_expr(&syn::parse2::<Type>(quote!{i32}).unwrap()).unwrap().to_string(), quote!{<i32 as apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}.to_string());
676 assert_eq!(type_to_schema_expr(&syn::parse2::<Type>(quote!{Vec<T>}).unwrap()).unwrap().to_string(), quote!{<Vec<T> as apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}.to_string());
677 assert_eq!(type_to_schema_expr(&syn::parse2::<Type>(quote!{AnyType}).unwrap()).unwrap().to_string(), quote!{<AnyType as apache_avro::AvroSchemaComponent>::get_schema_in_ctxt(named_schemas, enclosing_namespace)}.to_string());
678 }
679
680 #[test]
681 fn test_avro_3709_record_field_attributes() {
682 let test_struct = quote! {
683 struct A {
684 #[serde(alias = "a1", alias = "a2", rename = "a3")]
685 #[avro(doc = "a doc", default = "123")]
686 a: i32
687 }
688 };
689
690 match syn::parse2::<DeriveInput>(test_struct) {
691 Ok(input) => {
692 let schema_res = derive_avro_schema(input);
693 let expected_token_stream = r#"# [automatically_derived] impl apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas : & mut apache_avro :: schema :: Names , enclosing_namespace : & Option < String >) -> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name :: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) . fully_qualified_name (enclosing_namespace) ; if named_schemas . contains_key (& name) { apache_avro :: schema :: Schema :: Ref { name } } else { let enclosing_namespace = & name . namespace ; named_schemas . insert (name . clone () , apache_avro :: schema :: Schema :: Ref { name : name . clone () }) ; let schema = { 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" . try_into () . expect ("Alias is invalid") , "a2" . try_into () . expect ("Alias is invalid")]) , schema : < i32 as apache_avro :: AvroSchemaComponent > :: get_schema_in_ctxt (named_schemas , enclosing_namespace) , order : :: apache_avro :: schema :: RecordFieldOrder :: Ascending , position : schema_fields . len () , custom_attributes : Default :: default () , }) ; let schema_field_set : :: std :: collections :: HashSet < _ > = schema_fields . iter () . map (| rf | & rf . name) . collect () ; assert_eq ! (schema_fields . len () , schema_field_set . len () , "Duplicate field names found: {schema_fields:?}") ; let name = apache_avro :: schema :: Name :: new ("A") . expect (& format ! ("Unable to parse struct name for schema {}" , "A") [..]) ; let lookup : std :: collections :: BTreeMap < String , usize > = schema_fields . iter () . map (| field | (field . name . to_owned () , field . position)) . collect () ; apache_avro :: schema :: Schema :: Record (apache_avro :: schema :: RecordSchema { name , aliases : None , doc : None , fields : schema_fields , lookup , attributes : Default :: default () , }) } ; named_schemas . insert (name , schema . clone ()) ; schema } } }"#;
694 let schema_token_stream = schema_res.unwrap().to_string();
695 assert_eq!(schema_token_stream, expected_token_stream);
696 }
697 Err(error) => panic!(
698 "Failed to parse as derive input when it should be able to. Error: {error:?}"
699 ),
700 };
701
702 let test_enum = quote! {
703 enum A {
704 #[serde(rename = "A3")]
705 Item1,
706 }
707 };
708
709 match syn::parse2::<DeriveInput>(test_enum) {
710 Ok(input) => {
711 let schema_res = derive_avro_schema(input);
712 let expected_token_stream = r#"# [automatically_derived] impl apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas : & mut apache_avro :: schema :: Names , enclosing_namespace : & Option < String >) -> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name :: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) . fully_qualified_name (enclosing_namespace) ; if named_schemas . contains_key (& name) { apache_avro :: schema :: Schema :: Ref { name } } else { let enclosing_namespace = & name . namespace ; named_schemas . insert (name . clone () , apache_avro :: schema :: Schema :: Ref { name : name . clone () }) ; let schema = 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 () , }) ; named_schemas . insert (name , schema . clone ()) ; schema } } }"#;
713 let schema_token_stream = schema_res.unwrap().to_string();
714 assert_eq!(schema_token_stream, 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
722 #[test]
723 fn test_avro_rs_207_rename_all_attribute() {
724 let test_struct = quote! {
725 #[serde(rename_all="SCREAMING_SNAKE_CASE")]
726 struct A {
727 item: i32,
728 double_item: i32
729 }
730 };
731
732 match syn::parse2::<DeriveInput>(test_struct) {
733 Ok(input) => {
734 let schema_res = derive_avro_schema(input);
735 let expected_token_stream = r#"# [automatically_derived] impl apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas : & mut apache_avro :: schema :: Names , enclosing_namespace : & Option < String >) -> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name :: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) . fully_qualified_name (enclosing_namespace) ; if named_schemas . contains_key (& name) { apache_avro :: schema :: Schema :: Ref { name } } else { let enclosing_namespace = & name . namespace ; named_schemas . insert (name . clone () , apache_avro :: schema :: Schema :: Ref { name : name . clone () }) ; let schema = { 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 : < i32 as apache_avro :: AvroSchemaComponent > :: get_schema_in_ctxt (named_schemas , enclosing_namespace) , 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 : < i32 as apache_avro :: AvroSchemaComponent > :: get_schema_in_ctxt (named_schemas , enclosing_namespace) , order : :: apache_avro :: schema :: RecordFieldOrder :: Ascending , position : schema_fields . len () , custom_attributes : Default :: default () , }) ; let schema_field_set : :: std :: collections :: HashSet < _ > = schema_fields . iter () . map (| rf | & rf . name) . collect () ; assert_eq ! (schema_fields . len () , schema_field_set . len () , "Duplicate field names found: {schema_fields:?}") ; let name = apache_avro :: schema :: Name :: new ("A") . expect (& format ! ("Unable to parse struct name for schema {}" , "A") [..]) ; let lookup : std :: collections :: BTreeMap < String , usize > = schema_fields . iter () . map (| field | (field . name . to_owned () , field . position)) . collect () ; apache_avro :: schema :: Schema :: Record (apache_avro :: schema :: RecordSchema { name , aliases : None , doc : None , fields : schema_fields , lookup , attributes : Default :: default () , }) } ; named_schemas . insert (name , schema . clone ()) ; schema } } }"#;
736 let schema_token_stream = schema_res.unwrap().to_string();
737 assert_eq!(schema_token_stream, expected_token_stream);
738 }
739 Err(error) => panic!(
740 "Failed to parse as derive input when it should be able to. Error: {error:?}"
741 ),
742 };
743
744 let test_enum = quote! {
745 #[serde(rename_all="SCREAMING_SNAKE_CASE")]
746 enum B {
747 Item,
748 DoubleItem,
749 }
750 };
751
752 match syn::parse2::<DeriveInput>(test_enum) {
753 Ok(input) => {
754 let schema_res = derive_avro_schema(input);
755 let expected_token_stream = r#"# [automatically_derived] impl apache_avro :: AvroSchemaComponent for B { fn get_schema_in_ctxt (named_schemas : & mut apache_avro :: schema :: Names , enclosing_namespace : & Option < String >) -> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name :: new ("B") . expect (concat ! ("Unable to parse schema name " , "B")) . fully_qualified_name (enclosing_namespace) ; if named_schemas . contains_key (& name) { apache_avro :: schema :: Schema :: Ref { name } } else { let enclosing_namespace = & name . namespace ; named_schemas . insert (name . clone () , apache_avro :: schema :: Schema :: Ref { name : name . clone () }) ; let schema = 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 () , }) ; named_schemas . insert (name , schema . clone ()) ; schema } } }"#;
756 let schema_token_stream = schema_res.unwrap().to_string();
757 assert_eq!(schema_token_stream, expected_token_stream);
758 }
759 Err(error) => panic!(
760 "Failed to parse as derive input when it should be able to. Error: {error:?}"
761 ),
762 };
763 }
764
765 #[test]
766 fn test_avro_rs_207_rename_attr_has_priority_over_rename_all_attribute() {
767 let test_struct = quote! {
768 #[serde(rename_all="SCREAMING_SNAKE_CASE")]
769 struct A {
770 item: i32,
771 #[serde(rename="DoubleItem")]
772 double_item: i32
773 }
774 };
775
776 match syn::parse2::<DeriveInput>(test_struct) {
777 Ok(input) => {
778 let schema_res = derive_avro_schema(input);
779 let expected_token_stream = r#"# [automatically_derived] impl apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas : & mut apache_avro :: schema :: Names , enclosing_namespace : & Option < String >) -> apache_avro :: schema :: Schema { let name = apache_avro :: schema :: Name :: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) . fully_qualified_name (enclosing_namespace) ; if named_schemas . contains_key (& name) { apache_avro :: schema :: Schema :: Ref { name } } else { let enclosing_namespace = & name . namespace ; named_schemas . insert (name . clone () , apache_avro :: schema :: Schema :: Ref { name : name . clone () }) ; let schema = { 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 : < i32 as apache_avro :: AvroSchemaComponent > :: get_schema_in_ctxt (named_schemas , enclosing_namespace) , 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 : < i32 as apache_avro :: AvroSchemaComponent > :: get_schema_in_ctxt (named_schemas , enclosing_namespace) , order : :: apache_avro :: schema :: RecordFieldOrder :: Ascending , position : schema_fields . len () , custom_attributes : Default :: default () , }) ; let schema_field_set : :: std :: collections :: HashSet < _ > = schema_fields . iter () . map (| rf | & rf . name) . collect () ; assert_eq ! (schema_fields . len () , schema_field_set . len () , "Duplicate field names found: {schema_fields:?}") ; let name = apache_avro :: schema :: Name :: new ("A") . expect (& format ! ("Unable to parse struct name for schema {}" , "A") [..]) ; let lookup : std :: collections :: BTreeMap < String , usize > = schema_fields . iter () . map (| field | (field . name . to_owned () , field . position)) . collect () ; apache_avro :: schema :: Schema :: Record (apache_avro :: schema :: RecordSchema { name , aliases : None , doc : None , fields : schema_fields , lookup , attributes : Default :: default () , }) } ; named_schemas . insert (name , schema . clone ()) ; schema } } }"#;
780 let schema_token_stream = schema_res.unwrap().to_string();
781 assert_eq!(schema_token_stream, expected_token_stream);
782 }
783 Err(error) => panic!(
784 "Failed to parse as derive input when it should be able to. Error: {error:?}"
785 ),
786 };
787 }
788}