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 +4 -4
- data/Manifest +2 -0
- data/Rakefile +1 -1
- data/avro-salsify-fork.gemspec +7 -7
- data/lib/avro.rb +1 -0
- data/lib/avro/schema.rb +4 -39
- data/lib/avro/schema_validator.rb +194 -0
- data/test/test_schema.rb +28 -28
- data/test/test_schema_validator.rb +402 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4148d1a482eea909deae7631c18e93db4a7907c2
|
4
|
+
data.tar.gz: 70e111ad5d85ee5ca788b8385fed629d4f37fb3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/avro-salsify-fork.gemspec
CHANGED
@@ -1,25 +1,25 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: avro-salsify-fork 1.9.0.
|
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.
|
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 = "
|
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.
|
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
|
data/lib/avro.rb
CHANGED
data/lib/avro/schema.rb
CHANGED
@@ -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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
data/test/test_schema.rb
CHANGED
@@ -27,13 +27,13 @@ class TestSchema < Test::Unit::TestCase
|
|
27
27
|
]}
|
28
28
|
SCHEMA
|
29
29
|
|
30
|
-
assert_equal schema.name
|
31
|
-
assert_equal schema.fullname
|
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
|
36
|
-
assert_equal field.type.fullname
|
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
|
54
|
-
assert_equal
|
55
|
-
assert_equal
|
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
|
58
|
-
assert_equal
|
59
|
-
assert_equal
|
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
|
75
|
-
assert_equal
|
76
|
-
assert_equal
|
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
|
79
|
-
assert_equal
|
80
|
-
assert_equal
|
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
|
100
|
-
assert_equal
|
101
|
-
assert_equal schema.namespace
|
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
|
104
|
-
assert_equal middle.
|
105
|
-
assert_equal 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
|
108
|
-
assert_equal
|
109
|
-
assert_equal inner.namespace
|
110
|
-
assert_equal inner.fields.first.type
|
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
|
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.
|
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:
|
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.
|
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
|