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