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

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


The following commit(s) were added to refs/heads/master by this push:
     new 392c761  AVRO-2199: Validate that field defaults have the correct type 
 (#497)
392c761 is described below

commit 392c761952ec7f679b29ae28129403ea8cf307ea
Author: Tim Perkins <[email protected]>
AuthorDate: Sun Mar 31 12:47:16 2019 -0400

    AVRO-2199: Validate that field defaults have the correct type  (#497)
    
    * AVRO-2199:  Validate that field defaults have the correct type
    
    * AVRO-2199: Code review comments
    
    * AVRO-2199: Make it configurable whether field default validation is 
enforced for Ruby
---
 lang/ruby/.gitignore            |   2 +
 lang/ruby/lib/avro.rb           |   9 +++
 lang/ruby/lib/avro/schema.rb    |  15 ++++
 lang/ruby/test/test_datafile.rb |   2 +-
 lang/ruby/test/test_schema.rb   | 149 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 176 insertions(+), 1 deletion(-)

diff --git a/lang/ruby/.gitignore b/lang/ruby/.gitignore
index 63c96e1..7a61fe1 100644
--- a/lang/ruby/.gitignore
+++ b/lang/ruby/.gitignore
@@ -5,3 +5,5 @@ Gemfile.lock
 .gem/
 avro.gemspec
 pkg/
+.ruby-version
+.ruby-gemset
diff --git a/lang/ruby/lib/avro.rb b/lang/ruby/lib/avro.rb
index 81afbda..5a64566 100644
--- a/lang/ruby/lib/avro.rb
+++ b/lang/ruby/lib/avro.rb
@@ -32,6 +32,15 @@ module Avro
       super(msg)
     end
   end
+
+  class << self
+    attr_writer :disable_field_default_validation
+
+    def disable_field_default_validation
+      @disable_field_default_validation ||=
+        ENV.fetch('AVRO_DISABLE_FIELD_DEFAULT_VALIDATION', '') != ''
+    end
+  end
 end
 
 require 'avro/schema'
diff --git a/lang/ruby/lib/avro/schema.rb b/lang/ruby/lib/avro/schema.rb
index 86e5c45..ec9af8f 100644
--- a/lang/ruby/lib/avro/schema.rb
+++ b/lang/ruby/lib/avro/schema.rb
@@ -374,6 +374,7 @@ module Avro
         @default = default
         @order = order
         @doc = doc
+        validate_default! if default? && !Avro.disable_field_default_validation
       end
 
       def default?
@@ -387,6 +388,20 @@ module Avro
           avro['doc'] = doc if doc
         end
       end
+
+      private
+
+      def validate_default!
+        type_for_default = if type.type_sym == :union
+                             type.schemas.first
+                           else
+                             type
+                           end
+
+        Avro::SchemaValidator.validate!(type_for_default, default)
+      rescue Avro::SchemaValidator::ValidationError => e
+        raise Avro::SchemaParseError, "Error validating default for #{name}: 
#{e.message}"
+      end
     end
   end
 
diff --git a/lang/ruby/test/test_datafile.rb b/lang/ruby/test/test_datafile.rb
index 0b98abb..0f13df3 100644
--- a/lang/ruby/test/test_datafile.rb
+++ b/lang/ruby/test/test_datafile.rb
@@ -38,7 +38,7 @@ class TestDataFile < Test::Unit::TestCase
   "fields" : [
     {"name": "username", "type": "string"},
     {"name": "age", "type": "int"},
-    {"name": "verified", "type": "boolean", "default": "false"}
+    {"name": "verified", "type": "boolean", "default": false}
   ]}
 JSON
 
diff --git a/lang/ruby/test/test_schema.rb b/lang/ruby/test/test_schema.rb
index 5663643..ed1c8ee 100644
--- a/lang/ruby/test/test_schema.rb
+++ b/lang/ruby/test/test_schema.rb
@@ -17,6 +17,10 @@
 require 'test_help'
 
 class TestSchema < Test::Unit::TestCase
+  def hash_to_schema(hash)
+    Avro::Schema.parse(hash.to_json)
+  end
+
   def test_default_namespace
     schema = Avro::Schema.parse <<-SCHEMA
       {"type": "record", "name": "OuterRecord", "fields": [
@@ -307,4 +311,149 @@ class TestSchema < Test::Unit::TestCase
     assert_true(schema.mutual_read?(schema))
     assert_true(default1.mutual_read?(default2))
   end
+
+  def test_validate_defaults
+    exception = assert_raise(Avro::SchemaParseError) do
+      hash_to_schema(
+        type: 'record',
+        name: 'fruits',
+        fields: [
+          {
+            name: 'veggies',
+            type: 'string',
+            default: nil
+          }
+        ]
+      )
+    end
+    assert_equal('Error validating default for veggies: at . expected type 
string, got null',
+                 exception.to_s)
+  end
+
+  def test_field_default_validation_disabled
+    Avro.disable_field_default_validation = true
+    assert_nothing_raised do
+      hash_to_schema(
+        type: 'record',
+        name: 'fruits',
+        fields: [
+          {
+            name: 'veggies',
+            type: 'string',
+            default: nil
+          }
+        ]
+      )
+    end
+  ensure
+    Avro.disable_field_default_validation = false
+  end
+
+  def test_field_default_validation_disabled_via_env
+    Avro.disable_field_default_validation = false
+    ENV['AVRO_DISABLE_FIELD_DEFAULT_VALIDATION'] = "1"
+
+    assert_nothing_raised do
+      hash_to_schema(
+        type: 'record',
+        name: 'fruits',
+        fields: [
+          {
+            name: 'veggies',
+            type: 'string',
+            default: nil
+          }
+        ]
+      )
+    end
+  ensure
+    ENV.delete('AVRO_DISABLE_FIELD_DEFAULT_VALIDATION')
+    Avro.disable_field_default_validation = false
+  end
+
+  def test_validate_record_valid_default
+    assert_nothing_raised(Avro::SchemaParseError) do
+      hash_to_schema(
+        type: 'record',
+        name: 'with_subrecord',
+        fields: [
+          {
+            name: 'sub',
+            type: {
+              name: 'subrecord',
+              type: 'record',
+              fields: [
+                { type: 'string', name: 'x' }
+              ]
+            },
+            default: {
+              x: "y"
+            }
+          }
+        ]
+      )
+    end
+  end
+
+  def test_validate_record_invalid_default
+    exception = assert_raise(Avro::SchemaParseError) do
+      hash_to_schema(
+        type: 'record',
+        name: 'with_subrecord',
+        fields: [
+          {
+            name: 'sub',
+            type: {
+              name: 'subrecord',
+              type: 'record',
+              fields: [
+                { type: 'string', name: 'x' }
+              ]
+            },
+            default: {
+              a: 1
+            }
+          }
+        ]
+      )
+    end
+    assert_equal('Error validating default for sub: at .x expected type 
string, got null',
+                 exception.to_s)
+  end
+
+  def test_validate_union_defaults
+    exception = assert_raise(Avro::SchemaParseError) do
+      hash_to_schema(
+        type: 'record',
+        name: 'fruits',
+        fields: [
+          {
+            name: 'veggies',
+            type: %w(string null),
+            default: 5
+          }
+        ]
+      )
+    end
+    assert_equal('Error validating default for veggies: at . expected type 
string, got int with value 5',
+                 exception.to_s)
+  end
+
+  def test_validate_union_default_first_type
+    exception = assert_raise(Avro::SchemaParseError) do
+      hash_to_schema(
+        type: 'record',
+        name: 'fruits',
+        fields: [
+          {
+            name: 'veggies',
+            type: %w(null string),
+            default: 'apple'
+          }
+        ]
+      )
+    end
+    assert_equal('Error validating default for veggies: at . expected type 
null, got string with value "apple"',
+                 exception.to_s)
+    end
 end

Reply via email to