avro-salsify-fork 1.9.0.3 → 1.9.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: abbc9eb9ffc71e2b5be94e8371a6dc343fc22fb9
4
- data.tar.gz: e4579b146b036580b5700699accecd3bbb58c835
3
+ metadata.gz: 4148d1a482eea909deae7631c18e93db4a7907c2
4
+ data.tar.gz: 70e111ad5d85ee5ca788b8385fed629d4f37fb3b
5
5
  SHA512:
6
- metadata.gz: 8e797be1c562aeacfa9c449d2c6fc32f51fc3984653288b9937ff38c78ad42d17d9e578d4b73bbaaa8c5faba1db822afab8d9a5df76652364fe4d0b2a93ad40c
7
- data.tar.gz: 35b17279b334db0b08d9679870b4059a7d8e87d033a6ca3d97a33415914b00b0a4c0f3ad445a74cba4ea8f0c65c142da74f670d2287cad8670171b43bd37769e
6
+ metadata.gz: e85db5cae3501913da2497112689d8fa433bf1d48d7f937842b0927deda3d55eaf9c4beb0b26b274cb2d8fac79bdfc820e5e343ec774ddc0a14d508dca7a8105
7
+ data.tar.gz: bbe69d3e6679d4f2c20aea2ca4dc09d63f30b1a75dd40de60b390e500f272a70c7aa34d904f83479965c730c40bdab61846eadbf5ff3f46bceb260440665c912
data/Manifest CHANGED
@@ -14,6 +14,7 @@ lib/avro/protocol.rb
14
14
  lib/avro/schema.rb
15
15
  lib/avro/schema_compatibility.rb
16
16
  lib/avro/schema_normalization.rb
17
+ lib/avro/schema_validator.rb
17
18
  test/case_finder.rb
18
19
  test/random_data.rb
19
20
  test/sample_ipc_client.rb
@@ -29,5 +30,6 @@ test/test_protocol.rb
29
30
  test/test_schema.rb
30
31
  test/test_schema_compatibility.rb
31
32
  test/test_schema_normalization.rb
33
+ test/test_schema_validator.rb
32
34
  test/test_socket_transport.rb
33
35
  test/tool.rb
data/Rakefile CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  require 'rubygems'
18
18
  require 'echoe'
19
- VERSION = '1.9.0.3'
19
+ VERSION = '1.9.0.4'
20
20
  Echoe.new('avro-salsify-fork', VERSION) do |p|
21
21
  p.author = "Apache Software Foundation / Salsify Engineering"
22
22
  p.email = "engineering@salsify.com"
@@ -1,25 +1,25 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: avro-salsify-fork 1.9.0.3 ruby lib
2
+ # stub: avro-salsify-fork 1.9.0.4 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "avro-salsify-fork".freeze
6
- s.version = "1.9.0.3"
6
+ s.version = "1.9.0.4"
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
10
10
  s.authors = ["Apache Software Foundation / Salsify Engineering".freeze]
11
- s.date = "2016-12-17"
11
+ s.date = "2017-01-30"
12
12
  s.description = "Avro is a data serialization and RPC format.\nThis release contains the changes submitted in https://github.com/apache/avro/pull/116\nto support logical types in the Ruby gem.".freeze
13
13
  s.email = "engineering@salsify.com".freeze
14
- s.extra_rdoc_files = ["CHANGELOG".freeze, "LICENSE".freeze, "lib/avro.rb".freeze, "lib/avro/data_file.rb".freeze, "lib/avro/io.rb".freeze, "lib/avro/ipc.rb".freeze, "lib/avro/logical_types.rb".freeze, "lib/avro/protocol.rb".freeze, "lib/avro/schema.rb".freeze, "lib/avro/schema_compatibility.rb".freeze, "lib/avro/schema_normalization.rb".freeze]
15
- s.files = ["CHANGELOG".freeze, "LICENSE".freeze, "Manifest".freeze, "NOTICE".freeze, "Rakefile".freeze, "avro-salsify-fork.gemspec".freeze, "avro.gemspec".freeze, "interop/test_interop.rb".freeze, "lib/avro.rb".freeze, "lib/avro/data_file.rb".freeze, "lib/avro/io.rb".freeze, "lib/avro/ipc.rb".freeze, "lib/avro/logical_types.rb".freeze, "lib/avro/protocol.rb".freeze, "lib/avro/schema.rb".freeze, "lib/avro/schema_compatibility.rb".freeze, "lib/avro/schema_normalization.rb".freeze, "test/case_finder.rb".freeze, "test/random_data.rb".freeze, "test/sample_ipc_client.rb".freeze, "test/sample_ipc_http_client.rb".freeze, "test/sample_ipc_http_server.rb".freeze, "test/sample_ipc_server.rb".freeze, "test/test_datafile.rb".freeze, "test/test_fingerprints.rb".freeze, "test/test_help.rb".freeze, "test/test_io.rb".freeze, "test/test_logical_types.rb".freeze, "test/test_protocol.rb".freeze, "test/test_schema.rb".freeze, "test/test_schema_compatibility.rb".freeze, "test/test_schema_normalization.rb".freeze, "test/test_socket_transport.rb".freeze, "test/tool.rb".freeze]
14
+ s.extra_rdoc_files = ["CHANGELOG".freeze, "LICENSE".freeze, "lib/avro.rb".freeze, "lib/avro/data_file.rb".freeze, "lib/avro/io.rb".freeze, "lib/avro/ipc.rb".freeze, "lib/avro/logical_types.rb".freeze, "lib/avro/protocol.rb".freeze, "lib/avro/schema.rb".freeze, "lib/avro/schema_compatibility.rb".freeze, "lib/avro/schema_normalization.rb".freeze, "lib/avro/schema_validator.rb".freeze]
15
+ s.files = ["CHANGELOG".freeze, "LICENSE".freeze, "Manifest".freeze, "NOTICE".freeze, "Rakefile".freeze, "avro-salsify-fork.gemspec".freeze, "avro.gemspec".freeze, "interop/test_interop.rb".freeze, "lib/avro.rb".freeze, "lib/avro/data_file.rb".freeze, "lib/avro/io.rb".freeze, "lib/avro/ipc.rb".freeze, "lib/avro/logical_types.rb".freeze, "lib/avro/protocol.rb".freeze, "lib/avro/schema.rb".freeze, "lib/avro/schema_compatibility.rb".freeze, "lib/avro/schema_normalization.rb".freeze, "lib/avro/schema_validator.rb".freeze, "test/case_finder.rb".freeze, "test/random_data.rb".freeze, "test/sample_ipc_client.rb".freeze, "test/sample_ipc_http_client.rb".freeze, "test/sample_ipc_http_server.rb".freeze, "test/sample_ipc_server.rb".freeze, "test/test_datafile.rb".freeze, "test/test_fingerprints.rb".freeze, "test/test_help.rb".freeze, "test/test_io.rb".freeze, "test/test_logical_types.rb".freeze, "test/test_protocol.rb".freeze, "test/test_schema.rb".freeze, "test/test_schema_compatibility.rb".freeze, "test/test_schema_normalization.rb".freeze, "test/test_schema_validator.rb".freeze, "test/test_socket_transport.rb".freeze, "test/tool.rb".freeze]
16
16
  s.homepage = "https://github.com/salsify/avro".freeze
17
17
  s.licenses = ["Apache License 2.0 (Apache-2.0)".freeze]
18
18
  s.rdoc_options = ["--line-numbers".freeze, "--title".freeze, "Avro-salsify-fork".freeze]
19
19
  s.rubyforge_project = "avro-salsify-fork".freeze
20
- s.rubygems_version = "2.6.6".freeze
20
+ s.rubygems_version = "2.6.8".freeze
21
21
  s.summary = "Apache Avro for Ruby with logical types patch".freeze
22
- s.test_files = ["test/test_datafile.rb".freeze, "test/test_fingerprints.rb".freeze, "test/test_help.rb".freeze, "test/test_io.rb".freeze, "test/test_logical_types.rb".freeze, "test/test_protocol.rb".freeze, "test/test_schema.rb".freeze, "test/test_schema_compatibility.rb".freeze, "test/test_schema_normalization.rb".freeze, "test/test_socket_transport.rb".freeze]
22
+ s.test_files = ["test/test_datafile.rb".freeze, "test/test_fingerprints.rb".freeze, "test/test_help.rb".freeze, "test/test_io.rb".freeze, "test/test_logical_types.rb".freeze, "test/test_protocol.rb".freeze, "test/test_schema.rb".freeze, "test/test_schema_compatibility.rb".freeze, "test/test_schema_normalization.rb".freeze, "test/test_schema_validator.rb".freeze, "test/test_socket_transport.rb".freeze]
23
23
 
24
24
  if s.respond_to? :specification_version then
25
25
  s.specification_version = 4
@@ -40,4 +40,5 @@ require 'avro/data_file'
40
40
  require 'avro/protocol'
41
41
  require 'avro/ipc'
42
42
  require 'avro/schema_normalization'
43
+ require 'avro/schema_validator'
43
44
  require 'avro/schema_compatibility'
@@ -95,45 +95,10 @@ module Avro
95
95
 
96
96
  # Determine if a ruby datum is an instance of a schema
97
97
  def self.validate(expected_schema, logical_datum, encoded = false)
98
- datum = if encoded
99
- logical_datum
100
- else
101
- expected_schema.type_adapter.encode(logical_datum) rescue nil
102
- end
103
-
104
- case expected_schema.type_sym
105
- when :null
106
- datum.nil?
107
- when :boolean
108
- datum == true || datum == false
109
- when :string, :bytes
110
- datum.is_a? String
111
- when :int
112
- (datum.is_a?(Fixnum) || datum.is_a?(Bignum)) &&
113
- (INT_MIN_VALUE <= datum) && (datum <= INT_MAX_VALUE)
114
- when :long
115
- (datum.is_a?(Fixnum) || datum.is_a?(Bignum)) &&
116
- (LONG_MIN_VALUE <= datum) && (datum <= LONG_MAX_VALUE)
117
- when :float, :double
118
- datum.is_a?(Float) || datum.is_a?(Fixnum) || datum.is_a?(Bignum)
119
- when :fixed
120
- datum.is_a?(String) && datum.bytesize == expected_schema.size
121
- when :enum
122
- expected_schema.symbols.include? datum
123
- when :array
124
- datum.is_a?(Array) &&
125
- datum.all?{|d| validate(expected_schema.items, d) }
126
- when :map
127
- datum.keys.all?{|k| k.is_a? String } &&
128
- datum.values.all?{|v| validate(expected_schema.values, v) }
129
- when :union
130
- expected_schema.schemas.any?{|s| validate(s, datum) }
131
- when :record, :error, :request
132
- datum.is_a?(Hash) &&
133
- expected_schema.fields.all?{|f| validate(f.type, datum[f.name]) }
134
- else
135
- raise "you suck #{expected_schema.inspect} is not allowed."
136
- end
98
+ SchemaValidator.validate!(expected_schema, logical_datum, encoded)
99
+ true
100
+ rescue SchemaValidator::ValidationError
101
+ false
137
102
  end
138
103
 
139
104
  def initialize(type, logical_type=nil)
@@ -0,0 +1,194 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Avro
18
+ class SchemaValidator
19
+ ROOT_IDENTIFIER = '.'.freeze
20
+ PATH_SEPARATOR = '.'.freeze
21
+ INT_RANGE = Schema::INT_MIN_VALUE..Schema::INT_MAX_VALUE
22
+ LONG_RANGE = Schema::LONG_MIN_VALUE..Schema::LONG_MAX_VALUE
23
+ COMPLEX_TYPES = [:array, :error, :map, :record, :request]
24
+
25
+ class Result
26
+ attr_reader :errors
27
+
28
+ def initialize
29
+ @errors = []
30
+ end
31
+
32
+ def <<(error)
33
+ @errors << error
34
+ end
35
+
36
+ def add_error(path, message)
37
+ self << "at #{path} #{message}"
38
+ end
39
+
40
+ def failure?
41
+ @errors.any?
42
+ end
43
+
44
+ def to_s
45
+ errors.join("\n")
46
+ end
47
+ end
48
+
49
+ class ValidationError < StandardError
50
+ attr_reader :result
51
+
52
+ def initialize(result = Result.new)
53
+ @result = result
54
+ super
55
+ end
56
+
57
+ def to_s
58
+ result.to_s
59
+ end
60
+ end
61
+
62
+ TypeMismatchError = Class.new(ValidationError)
63
+
64
+ class << self
65
+ def validate!(expected_schema, logical_datum, encoded = false)
66
+ result = Result.new
67
+ validate_recursive(expected_schema, logical_datum, ROOT_IDENTIFIER, result, encoded)
68
+ fail ValidationError, result if result.failure?
69
+ result
70
+ end
71
+
72
+ private
73
+
74
+ def validate_recursive(expected_schema, logical_datum, path, result, encoded = false)
75
+ datum = if encoded
76
+ logical_datum
77
+ else
78
+ expected_schema.type_adapter.encode(logical_datum) rescue nil
79
+ end
80
+
81
+ case expected_schema.type_sym
82
+ when :null
83
+ fail TypeMismatchError unless datum.nil?
84
+ when :boolean
85
+ fail TypeMismatchError unless [true, false].include?(datum)
86
+ when :string, :bytes
87
+ fail TypeMismatchError unless datum.is_a?(String)
88
+ when :int
89
+ fail TypeMismatchError unless datum.is_a?(Fixnum) || datum.is_a?(Bignum)
90
+ result.add_error(path, "out of bound value #{datum}") unless INT_RANGE.cover?(datum)
91
+ when :long
92
+ fail TypeMismatchError unless datum.is_a?(Fixnum) || datum.is_a?(Bignum)
93
+ result.add_error(path, "out of bound value #{datum}") unless LONG_RANGE.cover?(datum)
94
+ when :float, :double
95
+ fail TypeMismatchError unless [Float, Fixnum, Bignum].any?(&datum.method(:is_a?))
96
+ when :fixed
97
+ if datum.is_a? String
98
+ message = "expected fixed with size #{expected_schema.size}, got \"#{datum}\" with size #{datum.size}"
99
+ result.add_error(path, message) unless datum.bytesize == expected_schema.size
100
+ else
101
+ result.add_error(path, "expected fixed with size #{expected_schema.size}, got #{actual_value_message(datum)}")
102
+ end
103
+ when :enum
104
+ message = "expected enum with values #{expected_schema.symbols}, got #{actual_value_message(datum)}"
105
+ result.add_error(path, message) unless expected_schema.symbols.include?(datum)
106
+ when :array
107
+ validate_array(expected_schema, datum, path, result)
108
+ when :map
109
+ validate_map(expected_schema, datum, path, result)
110
+ when :union
111
+ validate_union(expected_schema, datum, path, result)
112
+ when :record, :error, :request
113
+ fail TypeMismatchError unless datum.is_a?(Hash)
114
+ expected_schema.fields.each do |field|
115
+ deeper_path = deeper_path_for_hash(field.name, path)
116
+ validate_recursive(field.type, datum[field.name], deeper_path, result)
117
+ end
118
+ else
119
+ fail "Unexpected schema type #{expected_schema.type_sym} #{expected_schema.inspect}"
120
+ end
121
+ rescue TypeMismatchError
122
+ result.add_error(path, "expected type #{expected_schema.type_sym}, got #{actual_value_message(datum)}")
123
+ end
124
+
125
+ def validate_array(expected_schema, datum, path, result)
126
+ fail TypeMismatchError unless datum.is_a?(Array)
127
+ datum.each_with_index do |d, i|
128
+ validate_recursive(expected_schema.items, d, path + "[#{i}]", result)
129
+ end
130
+ end
131
+
132
+ def validate_map(expected_schema, datum, path, result)
133
+ datum.keys.each do |k|
134
+ result.add_error(path, "unexpected key type '#{ruby_to_avro_type(k.class)}' in map") unless k.is_a?(String)
135
+ end
136
+ datum.each do |k, v|
137
+ deeper_path = deeper_path_for_hash(k, path)
138
+ validate_recursive(expected_schema.values, v, deeper_path, result)
139
+ end
140
+ end
141
+
142
+ def validate_union(expected_schema, datum, path, result)
143
+ if expected_schema.schemas.size == 1
144
+ validate_recursive(expected_schema.schemas.first, datum, path, result)
145
+ return
146
+ end
147
+ types_and_results = validate_possible_types(datum, expected_schema, path)
148
+ failures, successes = types_and_results.partition { |r| r[:result].failure? }
149
+ return if successes.any?
150
+ complex_type_failed = failures.detect { |r| COMPLEX_TYPES.include?(r[:type]) }
151
+ if complex_type_failed
152
+ complex_type_failed[:result].errors.each { |error| result << error }
153
+ else
154
+ types = expected_schema.schemas.map { |s| "'#{s.type_sym}'" }.join(', ')
155
+ result.add_error(path, "expected union of [#{types}], got #{actual_value_message(datum)}")
156
+ end
157
+ end
158
+
159
+ def validate_possible_types(datum, expected_schema, path)
160
+ expected_schema.schemas.map do |schema|
161
+ result = Result.new
162
+ validate_recursive(schema, datum, path, result)
163
+ { type: schema.type_sym, result: result }
164
+ end
165
+ end
166
+
167
+ def deeper_path_for_hash(sub_key, path)
168
+ "#{path}#{PATH_SEPARATOR}#{sub_key}".squeeze(PATH_SEPARATOR)
169
+ end
170
+
171
+ private
172
+
173
+ def actual_value_message(value)
174
+ avro_type = ruby_to_avro_type(value.class)
175
+ if value.nil?
176
+ avro_type
177
+ else
178
+ "#{avro_type} with value #{value.inspect}"
179
+ end
180
+ end
181
+
182
+ def ruby_to_avro_type(ruby_class)
183
+ {
184
+ NilClass => 'null',
185
+ String => 'string',
186
+ Fixnum => 'int',
187
+ Bignum => 'long',
188
+ Float => 'float',
189
+ Hash => 'record'
190
+ }.fetch(ruby_class, ruby_class)
191
+ end
192
+ end
193
+ end
194
+ end
@@ -27,13 +27,13 @@ class TestSchema < Test::Unit::TestCase
27
27
  ]}
28
28
  SCHEMA
29
29
 
30
- assert_equal schema.name, 'OuterRecord'
31
- assert_equal schema.fullname, 'OuterRecord'
30
+ assert_equal 'OuterRecord', schema.name
31
+ assert_equal 'OuterRecord', schema.fullname
32
32
  assert_nil schema.namespace
33
33
 
34
34
  schema.fields.each do |field|
35
- assert_equal field.type.name, 'InnerRecord'
36
- assert_equal field.type.fullname, 'InnerRecord'
35
+ assert_equal 'InnerRecord', field.type.name
36
+ assert_equal 'InnerRecord', field.type.fullname
37
37
  assert_nil field.type.namespace
38
38
  end
39
39
  end
@@ -50,13 +50,13 @@ class TestSchema < Test::Unit::TestCase
50
50
  ]}
51
51
  SCHEMA
52
52
 
53
- assert_equal schema.name, 'OuterRecord'
54
- assert_equal schema.fullname, 'my.name.space.OuterRecord'
55
- assert_equal schema.namespace, 'my.name.space'
53
+ assert_equal 'OuterRecord', schema.name
54
+ assert_equal 'my.name.space.OuterRecord', schema.fullname
55
+ assert_equal 'my.name.space', schema.namespace
56
56
  schema.fields.each do |field|
57
- assert_equal field.type.name, 'InnerRecord'
58
- assert_equal field.type.fullname, 'my.name.space.InnerRecord'
59
- assert_equal field.type.namespace, 'my.name.space'
57
+ assert_equal 'InnerRecord', field.type.name
58
+ assert_equal 'my.name.space.InnerRecord', field.type.fullname
59
+ assert_equal 'my.name.space', field.type.namespace
60
60
  end
61
61
  end
62
62
 
@@ -71,13 +71,13 @@ class TestSchema < Test::Unit::TestCase
71
71
  ]}
72
72
  SCHEMA
73
73
 
74
- assert_equal schema.name, 'OuterRecord'
75
- assert_equal schema.fullname, 'my.name.space.OuterRecord'
76
- assert_equal schema.namespace, 'my.name.space'
74
+ assert_equal 'OuterRecord', schema.name
75
+ assert_equal 'my.name.space.OuterRecord', schema.fullname
76
+ assert_equal 'my.name.space', schema.namespace
77
77
  schema.fields.each do |field|
78
- assert_equal field.type.name, 'InnerEnum'
79
- assert_equal field.type.fullname, 'my.name.space.InnerEnum'
80
- assert_equal field.type.namespace, 'my.name.space'
78
+ assert_equal 'InnerEnum', field.type.name
79
+ assert_equal 'my.name.space.InnerEnum', field.type.fullname
80
+ assert_equal 'my.name.space', field.type.namespace
81
81
  end
82
82
  end
83
83
 
@@ -96,18 +96,18 @@ class TestSchema < Test::Unit::TestCase
96
96
  ]}
97
97
  SCHEMA
98
98
 
99
- assert_equal schema.name, 'OuterRecord'
100
- assert_equal schema.fullname, 'outer.OuterRecord'
101
- assert_equal schema.namespace, 'outer'
99
+ assert_equal 'OuterRecord', schema.name
100
+ assert_equal 'outer.OuterRecord', schema.fullname
101
+ assert_equal 'outer', schema.namespace
102
102
  middle = schema.fields.first.type
103
- assert_equal middle.name, 'MiddleRecord'
104
- assert_equal middle.fullname, 'middle.MiddleRecord'
105
- assert_equal middle.namespace, 'middle'
103
+ assert_equal 'MiddleRecord', middle.name
104
+ assert_equal 'middle.MiddleRecord', middle.fullname
105
+ assert_equal 'middle', middle.namespace
106
106
  inner = middle.fields.first.type
107
- assert_equal inner.name, 'InnerRecord'
108
- assert_equal inner.fullname, 'middle.InnerRecord'
109
- assert_equal inner.namespace, 'middle'
110
- assert_equal inner.fields.first.type, middle
107
+ assert_equal 'InnerRecord', inner.name
108
+ assert_equal 'middle.InnerRecord', inner.fullname
109
+ assert_equal 'middle', inner.namespace
110
+ assert_equal middle, inner.fields.first.type
111
111
  end
112
112
 
113
113
  def test_to_avro_includes_namespaces
@@ -120,7 +120,7 @@ class TestSchema < Test::Unit::TestCase
120
120
  ]}
121
121
  SCHEMA
122
122
 
123
- assert_equal schema.to_avro, {
123
+ assert_equal({
124
124
  'type' => 'record', 'name' => 'OuterRecord', 'namespace' => 'my.name.space',
125
125
  'fields' => [
126
126
  {'name' => 'definition', 'type' => {
@@ -129,7 +129,7 @@ class TestSchema < Test::Unit::TestCase
129
129
  }},
130
130
  {'name' => 'reference', 'type' => 'my.name.space.InnerFixed'}
131
131
  ]
132
- }
132
+ }, schema.to_avro)
133
133
  end
134
134
 
135
135
  def test_to_avro_includes_logical_type
@@ -0,0 +1,402 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'test_help'
18
+
19
+ class TestSchema < Test::Unit::TestCase
20
+ def validate!(schema, value)
21
+ Avro::SchemaValidator.validate!(schema, value)
22
+ end
23
+
24
+ def hash_to_schema(hash)
25
+ Avro::Schema.parse(hash.to_json)
26
+ end
27
+
28
+ def assert_failed_validation(messages)
29
+ error = assert_raise(Avro::SchemaValidator::ValidationError) { yield }
30
+
31
+ assert_messages = [messages].flatten
32
+ result_errors = error.result.errors
33
+ assert_messages.each do |message|
34
+ assert(result_errors.include?(message), "expected '#{message}' to be in '#{result_errors}'")
35
+ end
36
+ assert_equal(assert_messages.size, result_errors.size)
37
+ end
38
+
39
+ def assert_valid_schema(schema, valid, invalid)
40
+ valid.each do |value|
41
+ assert_nothing_raised { Avro::SchemaValidator.validate!(schema, value) }
42
+ end
43
+
44
+ invalid.each do |value|
45
+ assert_raise { Avro::SchemaValidator.validate!(schema, value) }
46
+ end
47
+ end
48
+
49
+ def test_validate_nil
50
+ schema = hash_to_schema(type: 'null', name: 'name')
51
+
52
+ assert_nothing_raised { validate!(schema, nil) }
53
+
54
+ assert_failed_validation('at . expected type null, got int with value 1') do
55
+ validate!(schema, 1)
56
+ end
57
+ end
58
+
59
+ def test_validate_boolean
60
+ schema = hash_to_schema(type: 'boolean', name: 'name')
61
+
62
+ assert_nothing_raised { validate!(schema, true) }
63
+ assert_nothing_raised { validate!(schema, false) }
64
+
65
+ assert_failed_validation('at . expected type boolean, got int with value 1') do
66
+ validate!(schema, 1)
67
+ end
68
+
69
+ assert_failed_validation('at . expected type boolean, got null') do
70
+ validate!(schema, nil)
71
+ end
72
+ end
73
+
74
+ def test_fixed_size_string
75
+ schema = hash_to_schema(type: 'fixed', name: 'some', size: 3)
76
+
77
+ assert_nothing_raised { validate!(schema, 'baf') }
78
+
79
+ assert_failed_validation('at . expected fixed with size 3, got "some" with size 4') do
80
+ validate!(schema, 'some')
81
+ end
82
+
83
+ assert_failed_validation('at . expected fixed with size 3, got null') do
84
+ validate!(schema, nil)
85
+ end
86
+ end
87
+
88
+ def test_original_validate_nil
89
+ schema = hash_to_schema(type: 'null', name: 'name')
90
+
91
+ assert_valid_schema(schema, [nil], ['something'])
92
+ end
93
+
94
+ def test_original_validate_boolean
95
+ schema = hash_to_schema(type: 'boolean', name: 'name')
96
+
97
+ assert_valid_schema(schema, [true, false], [nil, 1])
98
+ end
99
+
100
+ def test_validate_string
101
+ schema = hash_to_schema(type: 'string', name: 'name')
102
+
103
+ assert_valid_schema(schema, ['string'], [nil, 1])
104
+ end
105
+
106
+ def test_validate_bytes
107
+ schema = hash_to_schema(type: 'bytes', name: 'name')
108
+
109
+ assert_valid_schema(schema, ['string'], [nil, 1])
110
+ end
111
+
112
+ def test_validate_int
113
+ schema = hash_to_schema(type: 'int', name: 'name')
114
+
115
+ assert_valid_schema(
116
+ schema,
117
+ [Avro::Schema::INT_MIN_VALUE, Avro::Schema::INT_MAX_VALUE, 1],
118
+ [Avro::Schema::LONG_MIN_VALUE, Avro::Schema::LONG_MAX_VALUE, 'string']
119
+ )
120
+ assert_failed_validation('at . out of bound value 9223372036854775807') do
121
+ validate!(schema, Avro::Schema::LONG_MAX_VALUE)
122
+ end
123
+ end
124
+
125
+ def test_validate_long
126
+ schema = hash_to_schema(type: 'long', name: 'name')
127
+
128
+ assert_valid_schema(schema, [Avro::Schema::LONG_MIN_VALUE, Avro::Schema::LONG_MAX_VALUE, 1], [1.1, 'string'])
129
+ end
130
+
131
+ def test_validate_float
132
+ schema = hash_to_schema(type: 'float', name: 'name')
133
+
134
+ assert_valid_schema(schema, [1.1, 1, Avro::Schema::LONG_MAX_VALUE], ['string'])
135
+ end
136
+
137
+ def test_validate_double
138
+ schema = hash_to_schema(type: 'double', name: 'name')
139
+
140
+ assert_valid_schema(schema, [1.1, 1, Avro::Schema::LONG_MAX_VALUE], ['string'])
141
+ end
142
+
143
+ def test_validate_fixed
144
+ schema = hash_to_schema(type: 'fixed', name: 'name', size: 3)
145
+
146
+ assert_valid_schema(schema, ['abc'], ['ab', 1, 1.1, true])
147
+ end
148
+
149
+ def test_validate_original_num
150
+ schema = hash_to_schema(type: 'enum', name: 'name', symbols: %w(a b))
151
+
152
+ assert_valid_schema(schema, ['a', 'b'], ['c'])
153
+ end
154
+
155
+ def test_validate_record
156
+ schema = hash_to_schema(type: 'record', name: 'name', fields: [{ type: 'null', name: 'sub' }])
157
+
158
+ assert_valid_schema(schema, [{ 'sub' => nil }], [{ 'sub' => 1 }])
159
+ end
160
+
161
+ def test_validate_shallow_record
162
+ schema = hash_to_schema(
163
+ type: 'record', name: 'name', fields: [{ type: 'int', name: 'sub' }]
164
+ )
165
+
166
+ assert_nothing_raised { validate!(schema, 'sub' => 1) }
167
+
168
+ assert_failed_validation('at .sub expected type int, got null') do
169
+ validate!(schema, {})
170
+ end
171
+
172
+ assert_failed_validation('at . expected type record, got float with value 1.2') do
173
+ validate!(schema, 1.2)
174
+ end
175
+
176
+ assert_failed_validation('at .sub expected type int, got float with value 1.2') do
177
+ validate!(schema, 'sub' => 1.2)
178
+ end
179
+
180
+ assert_failed_validation('at .sub expected type int, got null') do
181
+ validate!(schema, {})
182
+ end
183
+ end
184
+
185
+ def test_validate_array
186
+ schema = hash_to_schema(type: 'array',
187
+ name: 'person',
188
+ items: [{ type: 'int', name: 'height' }])
189
+
190
+ assert_nothing_raised { validate!(schema, []) }
191
+
192
+ assert_failed_validation 'at . expected type array, got null' do
193
+ validate!(schema, nil)
194
+ end
195
+
196
+ assert_failed_validation('at .[0] expected type int, got null') do
197
+ validate!(schema, [nil])
198
+ end
199
+
200
+ assert_failed_validation('at .[3] expected type int, got string with value "so wrong"') do
201
+ validate!(schema, [1, 3, 9, 'so wrong'])
202
+ end
203
+ end
204
+
205
+ def test_validate_enum
206
+ schema = hash_to_schema(type: 'enum',
207
+ name: 'person',
208
+ symbols: %w(one two three))
209
+
210
+ assert_nothing_raised { validate!(schema, 'one') }
211
+
212
+ assert_failed_validation('at . expected enum with values ["one", "two", "three"], got string with value "five"') do
213
+ validate!(schema, 'five')
214
+ end
215
+ end
216
+
217
+ def test_validate_union_on_primitive_types
218
+ schema = hash_to_schema(
219
+ name: 'should_not_matter',
220
+ type: 'record',
221
+ fields: [
222
+ { name: 'what_ever', type: %w(long string) }
223
+ ]
224
+ )
225
+
226
+ assert_failed_validation('at .what_ever expected union of [\'long\', \'string\'], got null') {
227
+ validate!(schema, 'what_ever' => nil)
228
+ }
229
+ end
230
+
231
+ def test_validate_union_of_nil_and_record_inside_array
232
+ schema = hash_to_schema(
233
+ name: 'this does not matter',
234
+ type: 'record',
235
+ fields: [
236
+ {
237
+ name: 'person',
238
+ type: {
239
+ name: 'person_entry',
240
+ type: 'record',
241
+ fields: [
242
+ {
243
+ name: 'houses',
244
+ type: [
245
+ 'null',
246
+ {
247
+ name: 'houses_entry',
248
+ type: 'array',
249
+ items: [
250
+ {
251
+ name: 'house_entry',
252
+ type: 'record',
253
+ fields: [
254
+ { name: 'number_of_rooms', type: 'long' }
255
+ ]
256
+ }
257
+ ]
258
+ }
259
+ ],
260
+ }
261
+ ]
262
+ }
263
+ }
264
+ ]
265
+ )
266
+
267
+ assert_failed_validation('at .person expected type record, got null') {
268
+ validate!(schema, 'not at all' => nil)
269
+ }
270
+ assert_nothing_raised { validate!(schema, 'person' => {}) }
271
+ assert_nothing_raised { validate!(schema, 'person' => { houses: [] }) }
272
+ assert_nothing_raised { validate!(schema, 'person' => { 'houses' => [{ 'number_of_rooms' => 1 }] }) }
273
+
274
+ message = 'at .person.houses[1].number_of_rooms expected type long, got string with value "not valid at all"'
275
+ assert_failed_validation(message) do
276
+ validate!(
277
+ schema,
278
+ 'person' => {
279
+ 'houses' => [
280
+ { 'number_of_rooms' => 2 },
281
+ { 'number_of_rooms' => 'not valid at all' }
282
+ ]
283
+ }
284
+ )
285
+ end
286
+ end
287
+
288
+ def test_validate_map
289
+ schema = hash_to_schema(type: 'map',
290
+ name: 'numbers',
291
+ values: [
292
+ { name: 'some', type: 'int' }
293
+ ])
294
+
295
+ assert_nothing_raised { validate!(schema, 'some' => 1) }
296
+
297
+ assert_failed_validation('at .some expected type int, got string with value "nope"') do
298
+ validate!(schema, 'some' => 'nope')
299
+ end
300
+
301
+ assert_failed_validation("at . unexpected key type 'Symbol' in map") do
302
+ validate!(schema, some: 1)
303
+ end
304
+ end
305
+
306
+ def test_validate_deep_record
307
+ schema = hash_to_schema(type: 'record',
308
+ name: 'person',
309
+ fields: [
310
+ {
311
+ name: 'head',
312
+ type: {
313
+ name: 'head',
314
+ type: 'record',
315
+ fields: [
316
+ {
317
+ name: 'hair',
318
+ type: {
319
+ name: 'hair',
320
+ type: 'record',
321
+ fields: [
322
+ {
323
+ name: 'color',
324
+ type: 'string'
325
+ }
326
+ ]
327
+ }
328
+ }
329
+ ]
330
+ }
331
+ }
332
+ ])
333
+
334
+ assert_nothing_raised { validate!(schema, 'head' => { 'hair' => { 'color' => 'black' } }) }
335
+
336
+ assert_failed_validation('at .head.hair.color expected type string, got null') do
337
+ validate!(schema, 'head' => { 'hair' => { 'color' => nil } })
338
+ end
339
+
340
+ assert_failed_validation('at .head.hair.color expected type string, got null') do
341
+ validate!(schema, 'head' => { 'hair' => {} })
342
+ end
343
+
344
+ assert_failed_validation('at .head.hair expected type record, got null') do
345
+ validate!(schema, 'head' => {})
346
+ end
347
+
348
+ assert_failed_validation('at . expected type record, got null') do
349
+ validate!(schema, nil)
350
+ end
351
+ end
352
+
353
+ def test_validate_deep_record_with_array
354
+ schema = hash_to_schema(type: 'record',
355
+ name: 'fruits',
356
+ fields: [
357
+ {
358
+ name: 'fruits',
359
+ type: {
360
+ name: 'fruits',
361
+ type: 'array',
362
+ items: [
363
+ {
364
+ name: 'fruit',
365
+ type: 'record',
366
+ fields: [
367
+ { name: 'name', type: 'string' },
368
+ { name: 'weight', type: 'float' }
369
+ ]
370
+ }
371
+ ]
372
+ }
373
+ }
374
+ ])
375
+ assert_nothing_raised { validate!(schema, 'fruits' => [{ 'name' => 'apple', 'weight' => 30.2 }]) }
376
+
377
+ assert_failed_validation('at .fruits[0].name expected type string, got null') do
378
+ validate!(schema, 'fruits' => [{ 'name' => nil, 'weight' => 30.2 }])
379
+ end
380
+
381
+ assert_failed_validation('at .fruits expected type array, got int with value 1') do
382
+ validate!(schema, 'fruits' => 1)
383
+ end
384
+ end
385
+
386
+ def test_validate_multiple_errors
387
+ schema = hash_to_schema(type: 'array',
388
+ name: 'ages',
389
+ items: [
390
+ { type: 'int', name: 'age' }
391
+ ])
392
+
393
+ exception = assert_raise(Avro::SchemaValidator::ValidationError) do
394
+ validate!(schema, [nil, 'e'])
395
+ end
396
+ assert_equal 2, exception.result.errors.size
397
+ assert_equal(
398
+ "at .[0] expected type int, got null\nat .[1] expected type int, got string with value \"e\"",
399
+ exception.to_s
400
+ )
401
+ end
402
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: avro-salsify-fork
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.0.3
4
+ version: 1.9.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Apache Software Foundation / Salsify Engineering
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-17 00:00:00.000000000 Z
11
+ date: 2017-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -43,6 +43,7 @@ extra_rdoc_files:
43
43
  - lib/avro/schema.rb
44
44
  - lib/avro/schema_compatibility.rb
45
45
  - lib/avro/schema_normalization.rb
46
+ - lib/avro/schema_validator.rb
46
47
  files:
47
48
  - CHANGELOG
48
49
  - LICENSE
@@ -61,6 +62,7 @@ files:
61
62
  - lib/avro/schema.rb
62
63
  - lib/avro/schema_compatibility.rb
63
64
  - lib/avro/schema_normalization.rb
65
+ - lib/avro/schema_validator.rb
64
66
  - test/case_finder.rb
65
67
  - test/random_data.rb
66
68
  - test/sample_ipc_client.rb
@@ -76,6 +78,7 @@ files:
76
78
  - test/test_schema.rb
77
79
  - test/test_schema_compatibility.rb
78
80
  - test/test_schema_normalization.rb
81
+ - test/test_schema_validator.rb
79
82
  - test/test_socket_transport.rb
80
83
  - test/tool.rb
81
84
  homepage: https://github.com/salsify/avro
@@ -101,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
104
  version: '1.2'
102
105
  requirements: []
103
106
  rubyforge_project: avro-salsify-fork
104
- rubygems_version: 2.6.6
107
+ rubygems_version: 2.6.8
105
108
  signing_key:
106
109
  specification_version: 4
107
110
  summary: Apache Avro for Ruby with logical types patch
@@ -115,4 +118,5 @@ test_files:
115
118
  - test/test_schema.rb
116
119
  - test/test_schema_compatibility.rb
117
120
  - test/test_schema_normalization.rb
121
+ - test/test_schema_validator.rb
118
122
  - test/test_socket_transport.rb