You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by mg...@apache.org on 2022/04/19 17:44:02 UTC

[avro] branch avro-3484-derive-default-value-for-record-field created (now c13ce3045)

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

mgrigorov pushed a change to branch avro-3484-derive-default-value-for-record-field
in repository https://gitbox.apache.org/repos/asf/avro.git


      at c13ce3045 AVRO-3484: Add support for deriving a default value for a record field

This branch includes the following new commits:

     new c13ce3045 AVRO-3484: Add support for deriving a default value for a record field

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[avro] 01/01: AVRO-3484: Add support for deriving a default value for a record field

Posted by mg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

mgrigorov pushed a commit to branch avro-3484-derive-default-value-for-record-field
in repository https://gitbox.apache.org/repos/asf/avro.git

commit c13ce3045b90e51ef781ad7760a6e9b558759b15
Author: Martin Tzvetanov Grigorov <mg...@apache.org>
AuthorDate: Tue Apr 19 10:27:48 2022 +0300

    AVRO-3484: Add support for deriving a default value for a record field
    
    Signed-off-by: Martin Tzvetanov Grigorov <mg...@apache.org>
---
 lang/rust/avro_derive/Cargo.toml      |   5 +-
 lang/rust/avro_derive/src/lib.rs      |  18 +++--
 lang/rust/avro_derive/tests/derive.rs | 130 ++++++++++++++++++++++++++++++++++
 3 files changed, 147 insertions(+), 6 deletions(-)

diff --git a/lang/rust/avro_derive/Cargo.toml b/lang/rust/avro_derive/Cargo.toml
index e16e9ea95..2ba8bc8c0 100644
--- a/lang/rust/avro_derive/Cargo.toml
+++ b/lang/rust/avro_derive/Cargo.toml
@@ -32,10 +32,11 @@ documentation = "https://docs.rs/apache-avro-derive"
 proc-macro = true
 
 [dependencies]
-syn = {version= "1.0.91", features=["full", "fold"]}
+darling = "0.14.0"
 quote = "1.0.18"
+syn = {version= "1.0.91", features=["full", "fold"]}
 proc-macro2 = "1.0.37"
-darling = "0.14.0"
+serde_json = "1.0.79"
 
 [dev-dependencies]
 serde = { version = "1.0.136", features = ["derive"] }
diff --git a/lang/rust/avro_derive/src/lib.rs b/lang/rust/avro_derive/src/lib.rs
index 0055249cb..393db8c19 100644
--- a/lang/rust/avro_derive/src/lib.rs
+++ b/lang/rust/avro_derive/src/lib.rs
@@ -30,6 +30,8 @@ use syn::{
 struct FieldOptions {
     #[darling(default)]
     doc: Option<String>,
+    #[darling(default)]
+    default: Option<String>,
 }
 
 #[derive(FromAttributes)]
@@ -117,16 +119,24 @@ fn get_data_struct_schema_def(
         syn::Fields::Named(ref a) => {
             for (position, field) in a.named.iter().enumerate() {
                 let name = field.ident.as_ref().unwrap().to_string(); // we know everything has a name
-                let field_documented =
+                let field_attrs =
                     FieldOptions::from_attributes(&field.attrs[..]).map_err(darling_to_syn)?;
-                let doc = preserve_optional(field_documented.doc);
+                let doc = preserve_optional(field_attrs.doc);
+                let default_value = match field_attrs.default {
+                    Some(default_value) => {
+                        quote! {
+                            Some(serde_json::from_str(#default_value).expect(format!("Invalid JSON: {:?}", #default_value).as_str()))
+                        }
+                    }
+                    None => quote! { None },
+                };
                 let schema_expr = type_to_schema_expr(&field.ty)?;
                 let position = position;
                 record_field_exprs.push(quote! {
                     apache_avro::schema::RecordField {
                             name: #name.to_string(),
                             doc: #doc,
-                            default: Option::None,
+                            default: #default_value,
                             schema: #schema_expr,
                             order: apache_avro::schema::RecordFieldOrder::Ascending,
                             position: #position,
@@ -186,7 +196,7 @@ fn get_data_enum_schema_def(
                 name: apache_avro::schema::Name::new(#full_schema_name).expect(&format!("Unable to parse enum name for schema {}", #full_schema_name)[..]),
                 aliases: #enum_aliases,
                 doc: #doc,
-                symbols: vec![#(#symbols.to_owned()),*]
+                symbols: vec![#(#symbols.to_owned()),*],
             }
         })
     } else {
diff --git a/lang/rust/avro_derive/tests/derive.rs b/lang/rust/avro_derive/tests/derive.rs
index 284323a2a..0be2bd4bf 100644
--- a/lang/rust/avro_derive/tests/derive.rs
+++ b/lang/rust/avro_derive/tests/derive.rs
@@ -1209,4 +1209,134 @@ mod test_derive {
 
         serde_assert(TestBasicEnumWithAliases2::B);
     }
+
+    #[test]
+    fn test_basic_struct_with_defaults() {
+        #[derive(Debug, Deserialize, Serialize, AvroSchema, Clone, PartialEq)]
+        enum MyEnum {
+            Foo,
+            Bar,
+            Baz,
+        }
+
+        #[derive(Debug, Serialize, Deserialize, AvroSchema, Clone, PartialEq)]
+        struct TestBasicStructWithDefaultValues {
+            #[avro(default = "123")]
+            a: i32,
+            #[avro(default = r#""The default value for 'b'""#)]
+            b: String,
+            #[avro(default = "true")]
+            condition: bool,
+            // no default value for 'c'
+            c: f64,
+            #[avro(default = r#"{"a": 1, "b": 2}"#)]
+            map: HashMap<String, i32>,
+
+            #[avro(default = "[1, 2, 3]")]
+            array: Vec<i32>,
+
+            #[avro(default = r#""Foo""#)]
+            myenum: MyEnum,
+        }
+
+        let schema = r#"
+        {
+            "type":"record",
+            "name":"TestBasicStructWithDefaultValues",
+            "fields": [
+                {
+                    "name":"a",
+                    "type":"int",
+                    "default":123
+                },
+                {
+                    "name":"b",
+                    "type":"string",
+                    "default": "The default value for 'b'"
+                },
+                {
+                    "name":"condition",
+                    "type":"boolean",
+                    "default":true
+                },
+                {
+                    "name":"c",
+                    "type":"double"
+                },
+                {
+                    "name":"map",
+                    "type":{
+                        "type":"map",
+                        "values":"int"
+                    },
+                    "default": {
+                        "a": 1,
+                        "b": 2
+                    }
+                },
+                {
+                    "name":"array",
+                    "type":{
+                        "type":"array",
+                        "items":"int"
+                    },
+                    "default": [1, 2, 3]
+                },
+                {
+                    "name":"myenum",
+                    "type":{
+                        "type":"enum",
+                        "name":"MyEnum",
+                        "symbols":["Foo", "Bar", "Baz"]
+                    },
+                    "default":"Foo"
+                }
+            ]
+        }
+        "#;
+
+        let schema = Schema::parse_str(schema).unwrap();
+        if let Schema::Record { name, fields, .. } = TestBasicStructWithDefaultValues::get_schema()
+        {
+            assert_eq!("TestBasicStructWithDefaultValues", name.fullname(None));
+            use serde_json::json;
+            for field in fields {
+                match field.name.as_str() {
+                    "a" => assert_eq!(Some(json!(123_i32)), field.default),
+                    "b" => assert_eq!(
+                        Some(json!(r#"The default value for 'b'"#.to_owned())),
+                        field.default
+                    ),
+                    "condition" => assert_eq!(Some(json!(true)), field.default),
+                    "array" => assert_eq!(Some(json!([1, 2, 3])), field.default),
+                    "map" => assert_eq!(
+                        Some(json!({
+                            "a": 1,
+                            "b": 2
+                        })),
+                        field.default
+                    ),
+                    "c" => assert_eq!(None, field.default),
+                    "myenum" => assert_eq!(Some(json!("Foo")), field.default),
+                    _ => panic!("Unexpected field name"),
+                }
+            }
+        } else {
+            panic!("TestBasicStructWithDefaultValues schema must be a record schema")
+        }
+        assert_eq!(schema, TestBasicStructWithDefaultValues::get_schema());
+
+        serde_assert(TestBasicStructWithDefaultValues {
+            a: 321,
+            b: "A custom value for 'b'".to_owned(),
+            condition: false,
+            c: 987.654,
+            map: [("a".to_owned(), 1), ("b".to_owned(), 2)]
+                .iter()
+                .cloned()
+                .collect(),
+            array: vec![4, 5, 6],
+            myenum: MyEnum::Bar,
+        });
+    }
 }