apache_avro_derive/
lib.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18#![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))]
36// Templated from Serde
37pub 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                    // Inline the fields of the child record at runtime, as we don't have access to
138                    // the schema here.
139                    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                    // Don't add this field as it's been replaced by the child record fields
152                    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    // When flatten is involved, there will be more but we don't know how many. This optimises for
201    // the most common case where there is no flatten.
202    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
267/// Takes in the Tokens of a type and returns the tokens of an expression with return type `Schema`
268fn 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            } // Can't guarantee serialization type
291            _ => {
292                // Fails when the type does not implement AvroSchemaComponent directly
293                // TODO check and error report with something like https://docs.rs/quote/1.0.15/quote/macro.quote_spanned.html#example
294                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
340/// Generates the schema def expression for fully qualified type paths using the associated function
341/// - `A -> <A as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt()`
342/// - `A<T> -> <A<T> as apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt()`
343fn 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
347/// Stolen from serde
348fn 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}