avro-salsify-fork 1.9.0.3 → 1.9.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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