This is an automated email from the ASF dual-hosted git repository.

mgrigorov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/main by this push:
     new 3413ac504 AVRO-3687 [Rust - avro_derive]: Add support for default enum 
values for rust derive macros (#2954)
3413ac504 is described below

commit 3413ac504b74d920a4fbaa1e106529e36ffb512c
Author: John Bell <[email protected]>
AuthorDate: Thu Jul 4 14:16:07 2024 +0100

    AVRO-3687 [Rust - avro_derive]: Add support for default enum values for 
rust derive macros (#2954)
    
    * Add support for default enum values for rust derive macros
    
    * AVRO-3687: Better error messages when multiple enum variants are marked 
as default
    
    Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
    
    ---------
    
    Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
    Co-authored-by: Martin Tzvetanov Grigorov <[email protected]>
---
 lang/rust/avro_derive/src/lib.rs | 130 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 128 insertions(+), 2 deletions(-)

diff --git a/lang/rust/avro_derive/src/lib.rs b/lang/rust/avro_derive/src/lib.rs
index 2b47dddd4..e51d49d0e 100644
--- a/lang/rust/avro_derive/src/lib.rs
+++ b/lang/rust/avro_derive/src/lib.rs
@@ -23,7 +23,10 @@ use darling::FromAttributes;
 use proc_macro2::{Span, TokenStream};
 use quote::quote;
 
-use syn::{parse_macro_input, spanned::Spanned, AttrStyle, Attribute, 
DeriveInput, Type, TypePath};
+use syn::{
+    parse_macro_input, spanned::Spanned, AttrStyle, Attribute, DeriveInput, 
Ident, Meta, Type,
+    TypePath,
+};
 
 #[derive(darling::FromAttributes)]
 #[darling(attributes(avro))]
@@ -214,6 +217,8 @@ fn get_data_enum_schema_def(
     let doc = preserve_optional(doc);
     let enum_aliases = preserve_vec(aliases);
     if e.variants.iter().all(|v| syn::Fields::Unit == v.fields) {
+        let default_value = default_enum_variant(e, error_span)?;
+        let default = preserve_optional(default_value);
         let symbols: Vec<String> = e
             .variants
             .iter()
@@ -225,7 +230,7 @@ fn get_data_enum_schema_def(
                 aliases: #enum_aliases,
                 doc: #doc,
                 symbols: vec![#(#symbols.to_owned()),*],
-                default: None,
+                default: #default,
                 attributes: Default::default(),
             })
         })
@@ -281,6 +286,35 @@ fn type_to_schema_expr(ty: &Type) -> Result<TokenStream, 
Vec<syn::Error>> {
     }
 }
 
+fn default_enum_variant(
+    data_enum: &syn::DataEnum,
+    error_span: Span,
+) -> Result<Option<String>, Vec<syn::Error>> {
+    match data_enum
+        .variants
+        .iter()
+        .filter(|v| v.attrs.iter().any(is_default_attr))
+        .collect::<Vec<_>>()
+    {
+        variants if variants.is_empty() => Ok(None),
+        single if single.len() == 1 => Ok(Some(single[0].ident.to_string())),
+        multiple => Err(vec![syn::Error::new(
+            error_span,
+            format!(
+                "Multiple defaults defined: {:?}",
+                multiple
+                    .iter()
+                    .map(|v| v.ident.to_string())
+                    .collect::<Vec<String>>()
+            ),
+        )]),
+    }
+}
+
+fn is_default_attr(attr: &Attribute) -> bool {
+    matches!(attr, Attribute { meta: Meta::Path(path), .. } if 
path.get_ident().map(Ident::to_string).as_deref() == Some("default"))
+}
+
 /// Generates the schema def expression for fully qualified type paths using 
the associated function
 /// - `A -> <A as 
apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt()`
 /// - `A<T> -> <A<T> as 
apache_avro::schema::derive::AvroSchemaComponent>::get_schema_in_ctxt()`
@@ -433,6 +467,98 @@ mod tests {
         };
     }
 
+    #[test]
+    fn avro_3687_basic_enum_with_default() {
+        let basic_enum = quote! {
+            enum Basic {
+                #[default]
+                A,
+                B,
+                C,
+                D
+            }
+        };
+        match syn::parse2::<DeriveInput>(basic_enum) {
+            Ok(mut input) => {
+                let derived = derive_avro_schema(&mut input);
+                assert!(derived.is_ok());
+                assert_eq!(derived.unwrap().to_string(), quote! {
+                    impl apache_avro::schema::derive::AvroSchemaComponent for 
Basic {
+                        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 {
+                            let name = apache_avro::schema::Name::new("Basic")
+                                .expect(&format!("Unable to parse schema name 
{}", "Basic")[..])
+                                .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("Basic").expect(
+                                        &format!("Unable to parse enum name 
for schema {}", "Basic")[..]
+                                    ),
+                                    aliases: None,
+                                    doc: None,
+                                    symbols: vec![
+                                        "A".to_owned(),
+                                        "B".to_owned(),
+                                        "C".to_owned(),
+                                        "D".to_owned()
+                                    ],
+                                    default: Some("A".into()),
+                                    attributes: Default::default(),
+                                })
+                            }
+                        }
+                    }
+                }.to_string());
+            }
+            Err(error) => panic!(
+                "Failed to parse as derive input when it should be able to. 
Error: {error:?}"
+            ),
+        };
+    }
+
+    #[test]
+    fn avro_3687_basic_enum_with_default_twice() {
+        let non_basic_enum = quote! {
+            enum Basic {
+                #[default]
+                A,
+                B,
+                #[default]
+                C,
+                D
+            }
+        };
+        match syn::parse2::<DeriveInput>(non_basic_enum) {
+            Ok(mut input) => match derive_avro_schema(&mut input) {
+                Ok(_) => {
+                    panic!("Should not be able to derive schema for enum with 
multiple defaults")
+                }
+                Err(errors) => {
+                    assert_eq!(errors.len(), 1);
+                    assert_eq!(
+                        errors[0].to_string(),
+                        r#"Multiple defaults defined: ["A", "C"]"#
+                    );
+                }
+            },
+            Err(error) => panic!(
+                "Failed to parse as derive input when it should be able to. 
Error: {error:?}"
+            ),
+        };
+    }
+
     #[test]
     fn test_non_basic_enum() {
         let non_basic_enum = quote! {

Reply via email to