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 3a7786e AVRO-2200: Option to fail when extra fields are in the
payload (#321)
3a7786e is described below
commit 3a7786ed1a3bedf05e6bce7924aea2f75b576912
Author: Daniel Orner <[email protected]>
AuthorDate: Wed Apr 3 04:45:19 2019 -0400
AVRO-2200: Option to fail when extra fields are in the payload (#321)
* AVRO-2200: Option to fail when extra fields are in the payload
* Check for extra fields recursively
---
lang/ruby/lib/avro/schema_validator.rb | 35 +++++++------
lang/ruby/test/test_schema_validator.rb | 87 ++++++++++++++++++++++++++++++++-
2 files changed, 106 insertions(+), 16 deletions(-)
diff --git a/lang/ruby/lib/avro/schema_validator.rb
b/lang/ruby/lib/avro/schema_validator.rb
index 485092c..f9274f0 100644
--- a/lang/ruby/lib/avro/schema_validator.rb
+++ b/lang/ruby/lib/avro/schema_validator.rb
@@ -63,7 +63,7 @@ module Avro
TypeMismatchError = Class.new(ValidationError)
class << self
- def validate!(expected_schema, logical_datum, options = { recursive:
true, encoded: false })
+ def validate!(expected_schema, logical_datum, options = { recursive:
true, encoded: false, fail_on_extra_fields: false })
options ||= {}
options[:recursive] = true unless options.key?(:recursive)
@@ -86,16 +86,23 @@ module Avro
case expected_schema.type_sym
when :array
- validate_array(expected_schema, datum, path, result)
+ validate_array(expected_schema, datum, path, result, options)
when :map
- validate_map(expected_schema, datum, path, result)
+ validate_map(expected_schema, datum, path, result, options)
when :union
- validate_union(expected_schema, datum, path, result)
+ validate_union(expected_schema, datum, path, result, options)
when :record, :error, :request
fail TypeMismatchError unless datum.is_a?(Hash)
expected_schema.fields.each do |field|
deeper_path = deeper_path_for_hash(field.name, path)
- validate_recursive(field.type, datum[field.name], deeper_path,
result)
+ validate_recursive(field.type, datum[field.name], deeper_path,
result, options)
+ end
+ if options[:fail_on_extra_fields]
+ datum_fields = datum.keys.map(&:to_s)
+ schema_fields = expected_schema.fields.map(&:name)
+ (datum_fields - schema_fields).each do |extra_field|
+ result.add_error(path, "extra field '#{extra_field}' - not in
schema")
+ end
end
end
rescue TypeMismatchError
@@ -156,31 +163,31 @@ module Avro
"expected enum with values #{symbols}, got
#{actual_value_message(datum)}"
end
- def validate_array(expected_schema, datum, path, result)
+ def validate_array(expected_schema, datum, path, result, options = {})
fail TypeMismatchError unless datum.is_a?(Array)
datum.each_with_index do |d, i|
- validate_recursive(expected_schema.items, d, path + "[#{i}]", result)
+ validate_recursive(expected_schema.items, d, path + "[#{i}]",
result, options)
end
end
- def validate_map(expected_schema, datum, path, result)
+ def validate_map(expected_schema, datum, path, result, options = {})
fail TypeMismatchError unless datum.is_a?(Hash)
datum.keys.each do |k|
result.add_error(path, "unexpected key type
'#{ruby_to_avro_type(k.class)}' in map") unless k.is_a?(String)
end
datum.each do |k, v|
deeper_path = deeper_path_for_hash(k, path)
- validate_recursive(expected_schema.values, v, deeper_path, result)
+ validate_recursive(expected_schema.values, v, deeper_path, result,
options)
end
end
- def validate_union(expected_schema, datum, path, result)
+ def validate_union(expected_schema, datum, path, result, options = {})
if expected_schema.schemas.size == 1
- validate_recursive(expected_schema.schemas.first, datum, path,
result)
+ validate_recursive(expected_schema.schemas.first, datum, path,
result, options)
return
end
failures = []
- compatible_type = first_compatible_type(datum, expected_schema, path,
failures)
+ compatible_type = first_compatible_type(datum, expected_schema, path,
failures, options)
return unless compatible_type.nil?
complex_type_failed = failures.detect { |r|
COMPLEX_TYPES.include?(r[:type]) }
@@ -192,10 +199,10 @@ module Avro
end
end
- def first_compatible_type(datum, expected_schema, path, failures)
+ def first_compatible_type(datum, expected_schema, path, failures,
options = {})
expected_schema.schemas.find do |schema|
result = Result.new
- validate_recursive(schema, datum, path, result)
+ validate_recursive(schema, datum, path, result, options)
failures << { type: schema.type_sym, result: result } if
result.failure?
!result.failure?
end
diff --git a/lang/ruby/test/test_schema_validator.rb
b/lang/ruby/test/test_schema_validator.rb
index 9b74d61..0126c35 100644
--- a/lang/ruby/test/test_schema_validator.rb
+++ b/lang/ruby/test/test_schema_validator.rb
@@ -17,8 +17,8 @@
require 'test_help'
class TestSchema < Test::Unit::TestCase
- def validate!(schema, value)
- Avro::SchemaValidator.validate!(schema, value)
+ def validate!(schema, value, options=nil)
+ Avro::SchemaValidator.validate!(schema, value, options)
end
def validate_simple!(schema, value)
@@ -468,4 +468,87 @@ class TestSchema < Test::Unit::TestCase
exception.to_s
)
end
+
+ def test_validate_extra_fields
+ schema = hash_to_schema(
+ type: 'record',
+ name: 'fruits',
+ fields: [
+ {
+ name: 'veggies',
+ type: 'string'
+ }
+ ]
+ )
+ exception = assert_raise(Avro::SchemaValidator::ValidationError) do
+ validate!(schema, {'veggies' => 'tomato', 'bread' => 'rye'},
fail_on_extra_fields: true)
+ end
+ assert_equal(1, exception.result.errors.size)
+ assert_equal("at . extra field 'bread' - not in schema",
+ exception.to_s)
+ end
+
+ def test_validate_subrecord_extra_fields
+ schema = hash_to_schema(type: 'record',
+ name: 'top',
+ fields: [
+ {
+ name: 'fruit',
+ type: {
+ name: 'fruit',
+ type: 'record',
+ fields: [{ name: 'name', type: 'string' }]
+ }
+ }
+ ])
+ exception = assert_raise(Avro::SchemaValidator::ValidationError) do
+ validate!(schema, { 'fruit' => { 'name' => 'orange', 'color' => 'orange'
} }, fail_on_extra_fields: true)
+ end
+ assert_equal(1, exception.result.errors.size)
+ assert_equal("at .fruit extra field 'color' - not in schema",
exception.to_s)
+ end
+
+ def test_validate_array_extra_fields
+ schema = hash_to_schema(type: 'array',
+ items: {
+ name: 'fruit',
+ type: 'record',
+ fields: [{ name: 'name', type: 'string' }]
+ })
+ exception = assert_raise(Avro::SchemaValidator::ValidationError) do
+ validate!(schema, [{ 'name' => 'orange', 'color' => 'orange' }],
fail_on_extra_fields: true)
+ end
+ assert_equal(1, exception.result.errors.size)
+ assert_equal("at .[0] extra field 'color' - not in schema", exception.to_s)
+ end
+
+ def test_validate_map_extra_fields
+ schema = hash_to_schema(type: 'map',
+ values: {
+ name: 'fruit',
+ type: 'record',
+ fields: [{ name: 'color', type: 'string' }]
+ })
+ exception = assert_raise(Avro::SchemaValidator::ValidationError) do
+ validate!(schema, { 'apple' => { 'color' => 'green', 'extra' => 1 } },
fail_on_extra_fields: true)
+ end
+ assert_equal(1, exception.result.errors.size)
+ assert_equal("at .apple extra field 'extra' - not in schema",
exception.to_s)
+ end
+
+ def test_validate_union_extra_fields
+ schema = hash_to_schema([
+ 'null',
+ {
+ type: 'record',
+ name: 'fruit',
+ fields: [{ name: 'name', type: 'string' }]
+ }
+ ])
+ exception = assert_raise(Avro::SchemaValidator::ValidationError) do
+ validate!(schema, { 'name' => 'apple', 'color' => 'green' },
fail_on_extra_fields: true)
+ end
+ assert_equal(1, exception.result.errors.size)
+ assert_equal("at . extra field 'color' - not in schema", exception.to_s)
+ end
end