avro_turf_enchanced 0.7.3
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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +174 -0
- data/Rakefile +2 -0
- data/avro_turf.gemspec +30 -0
- data/circle.yml +4 -0
- data/lib/avro_turf.rb +105 -0
- data/lib/avro_turf/cached_schema_registry.rb +26 -0
- data/lib/avro_turf/core_ext.rb +10 -0
- data/lib/avro_turf/core_ext/date.rb +5 -0
- data/lib/avro_turf/core_ext/enumerable.rb +5 -0
- data/lib/avro_turf/core_ext/false_class.rb +5 -0
- data/lib/avro_turf/core_ext/hash.rb +7 -0
- data/lib/avro_turf/core_ext/nil_class.rb +5 -0
- data/lib/avro_turf/core_ext/numeric.rb +5 -0
- data/lib/avro_turf/core_ext/string.rb +5 -0
- data/lib/avro_turf/core_ext/symbol.rb +5 -0
- data/lib/avro_turf/core_ext/time.rb +5 -0
- data/lib/avro_turf/core_ext/true_class.rb +5 -0
- data/lib/avro_turf/messaging.rb +102 -0
- data/lib/avro_turf/schema_registry.rb +79 -0
- data/lib/avro_turf/schema_store.rb +58 -0
- data/lib/avro_turf/schema_to_avro_patch.rb +52 -0
- data/lib/avro_turf/test/fake_schema_registry_server.rb +84 -0
- data/lib/avro_turf/version.rb +3 -0
- data/perf/address.avsc +14 -0
- data/perf/encoding_size.rb +26 -0
- data/perf/encoding_speed.rb +30 -0
- data/perf/person.avsc +14 -0
- data/spec/avro_turf_spec.rb +161 -0
- data/spec/cached_schema_registry_spec.rb +41 -0
- data/spec/core_ext/date_spec.rb +6 -0
- data/spec/core_ext/enumerable_spec.rb +12 -0
- data/spec/core_ext/false_class_spec.rb +5 -0
- data/spec/core_ext/hash_spec.rb +8 -0
- data/spec/core_ext/nil_class_spec.rb +5 -0
- data/spec/core_ext/numeric_spec.rb +6 -0
- data/spec/core_ext/string_spec.rb +5 -0
- data/spec/core_ext/symbol_spec.rb +5 -0
- data/spec/core_ext/time_spec.rb +6 -0
- data/spec/core_ext/true_class_spec.rb +5 -0
- data/spec/messaging_spec.rb +112 -0
- data/spec/schema_registry_spec.rb +9 -0
- data/spec/schema_store_spec.rb +253 -0
- data/spec/schema_to_avro_patch_spec.rb +66 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/schema_registry_context.rb +190 -0
- metadata +244 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Measures the encoded size of messages of increasing size.
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
|
6
|
+
|
7
|
+
require 'benchmark'
|
8
|
+
require 'avro_turf'
|
9
|
+
|
10
|
+
sizes = [1, 10, 100, 1_000, 10_000]
|
11
|
+
avro = AvroTurf.new(schemas_path: File.dirname(__FILE__))
|
12
|
+
|
13
|
+
sizes.each do |size|
|
14
|
+
data = {
|
15
|
+
"name" => "John" * size,
|
16
|
+
"address" => {
|
17
|
+
"street" => "1st st." * size,
|
18
|
+
"city" => "Citytown" * size
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
result = avro.encode(data, schema_name: "person")
|
23
|
+
encoded_size = result.bytesize
|
24
|
+
encode_factor = result.bytesize / size.to_f
|
25
|
+
puts "size #{size}: #{encoded_size} bytes (encoding factor #{encode_factor})"
|
26
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Measures the time it takes to encode messages of increasing size.
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
|
6
|
+
|
7
|
+
require 'benchmark'
|
8
|
+
require 'avro_turf'
|
9
|
+
|
10
|
+
# Number of iterations per run.
|
11
|
+
N = 10_000
|
12
|
+
|
13
|
+
Benchmark.bm(15) do |x|
|
14
|
+
sizes = [1, 10, 100, 1_000, 10_000]
|
15
|
+
avro = AvroTurf.new(schemas_path: File.dirname(__FILE__))
|
16
|
+
|
17
|
+
sizes.each do |size|
|
18
|
+
data = {
|
19
|
+
"name" => "John" * size,
|
20
|
+
"address" => {
|
21
|
+
"street" => "1st st." * size,
|
22
|
+
"city" => "Citytown" * size
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
x.report("size #{size}:") do
|
27
|
+
N.times { avro.encode(data, schema_name: "person") }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/perf/person.avsc
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
describe AvroTurf do
|
2
|
+
let(:avro) { AvroTurf.new(schemas_path: "spec/schemas/") }
|
3
|
+
|
4
|
+
before do
|
5
|
+
FileUtils.mkdir_p("spec/schemas")
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#encode" do
|
9
|
+
before do
|
10
|
+
define_schema "person.avsc", <<-AVSC
|
11
|
+
{
|
12
|
+
"name": "person",
|
13
|
+
"type": "record",
|
14
|
+
"fields": [
|
15
|
+
{
|
16
|
+
"type": "string",
|
17
|
+
"name": "full_name"
|
18
|
+
}
|
19
|
+
]
|
20
|
+
}
|
21
|
+
AVSC
|
22
|
+
end
|
23
|
+
|
24
|
+
it "encodes data with Avro" do
|
25
|
+
data = {
|
26
|
+
"full_name" => "John Doe"
|
27
|
+
}
|
28
|
+
|
29
|
+
encoded_data = avro.encode(data, schema_name: "person")
|
30
|
+
|
31
|
+
expect(avro.decode(encoded_data)).to eq(data)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "allows specifying a codec that should be used to compress messages" do
|
35
|
+
compressed_avro = AvroTurf.new(schemas_path: "spec/schemas/", codec: "deflate")
|
36
|
+
|
37
|
+
data = {
|
38
|
+
"full_name" => "John Doe" * 100
|
39
|
+
}
|
40
|
+
|
41
|
+
uncompressed_data = avro.encode(data, schema_name: "person")
|
42
|
+
compressed_data = compressed_avro.encode(data, schema_name: "person")
|
43
|
+
|
44
|
+
expect(compressed_data.bytesize).to be < uncompressed_data.bytesize
|
45
|
+
expect(compressed_avro.decode(compressed_data)).to eq(data)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#decode" do
|
50
|
+
it "decodes Avro data using the inlined writer's schema" do
|
51
|
+
define_schema "message.avsc", <<-AVSC
|
52
|
+
{
|
53
|
+
"name": "message",
|
54
|
+
"type": "string"
|
55
|
+
}
|
56
|
+
AVSC
|
57
|
+
|
58
|
+
encoded_data = avro.encode("hello, world", schema_name: "message")
|
59
|
+
|
60
|
+
expect(avro.decode(encoded_data)).to eq "hello, world"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "decodes Avro data using a specified reader's schema" do
|
64
|
+
FileUtils.mkdir_p("spec/schemas/reader")
|
65
|
+
|
66
|
+
define_schema "point.avsc", <<-AVSC
|
67
|
+
{
|
68
|
+
"name": "point",
|
69
|
+
"type": "record",
|
70
|
+
"fields": [
|
71
|
+
{ "name": "x", "type": "long" },
|
72
|
+
{ "name": "y", "type": "long" }
|
73
|
+
]
|
74
|
+
}
|
75
|
+
AVSC
|
76
|
+
|
77
|
+
define_schema "reader/point.avsc", <<-AVSC
|
78
|
+
{
|
79
|
+
"name": "point",
|
80
|
+
"type": "record",
|
81
|
+
"fields": [
|
82
|
+
{ "name": "x", "type": "long" }
|
83
|
+
]
|
84
|
+
}
|
85
|
+
AVSC
|
86
|
+
|
87
|
+
encoded_data = avro.encode({ "x" => 42, "y" => 13 }, schema_name: "point")
|
88
|
+
reader_avro = AvroTurf.new(schemas_path: "spec/schemas/reader")
|
89
|
+
|
90
|
+
expect(reader_avro.decode(encoded_data, schema_name: "point")).to eq({ "x" => 42 })
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#encode_to_stream" do
|
95
|
+
it "writes encoded data to an existing stream" do
|
96
|
+
define_schema "message.avsc", <<-AVSC
|
97
|
+
{
|
98
|
+
"name": "message",
|
99
|
+
"type": "string"
|
100
|
+
}
|
101
|
+
AVSC
|
102
|
+
|
103
|
+
stream = StringIO.new
|
104
|
+
avro.encode_to_stream("hello", stream: stream, schema_name: "message")
|
105
|
+
|
106
|
+
expect(avro.decode(stream.string)).to eq "hello"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "#decode_stream" do
|
111
|
+
it "decodes Avro data from a stream" do
|
112
|
+
define_schema "message.avsc", <<-AVSC
|
113
|
+
{
|
114
|
+
"name": "message",
|
115
|
+
"type": "string"
|
116
|
+
}
|
117
|
+
AVSC
|
118
|
+
|
119
|
+
encoded_data = avro.encode("hello", schema_name: "message")
|
120
|
+
stream = StringIO.new(encoded_data)
|
121
|
+
|
122
|
+
expect(avro.decode_stream(stream)).to eq "hello"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "#valid?" do
|
127
|
+
before do
|
128
|
+
define_schema "message.avsc", <<-AVSC
|
129
|
+
{
|
130
|
+
"name": "message",
|
131
|
+
"type": "string"
|
132
|
+
}
|
133
|
+
AVSC
|
134
|
+
end
|
135
|
+
|
136
|
+
it "returns true if the datum matches the schema" do
|
137
|
+
datum = "hello"
|
138
|
+
expect(avro.valid?(datum, schema_name: "message")).to eq true
|
139
|
+
end
|
140
|
+
|
141
|
+
it "returns false if the datum does not match the schema" do
|
142
|
+
datum = 42
|
143
|
+
expect(avro.valid?(datum, schema_name: "message")).to eq false
|
144
|
+
end
|
145
|
+
|
146
|
+
it "handles symbol keys in hashes" do
|
147
|
+
define_schema "postcard.avsc", <<-AVSC
|
148
|
+
{
|
149
|
+
"name": "postcard",
|
150
|
+
"type": "record",
|
151
|
+
"fields": [
|
152
|
+
{ "name": "message", "type": "string" }
|
153
|
+
]
|
154
|
+
}
|
155
|
+
AVSC
|
156
|
+
|
157
|
+
datum = { message: "hello" }
|
158
|
+
expect(avro.valid?(datum, schema_name: "postcard")).to eq true
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'webmock/rspec'
|
2
|
+
require 'avro_turf/cached_schema_registry'
|
3
|
+
require 'avro_turf/test/fake_schema_registry_server'
|
4
|
+
|
5
|
+
describe AvroTurf::CachedSchemaRegistry do
|
6
|
+
let(:upstream) { instance_double(AvroTurf::SchemaRegistry) }
|
7
|
+
let(:registry) { described_class.new(upstream) }
|
8
|
+
let(:id) { rand(999) }
|
9
|
+
let(:schema) do
|
10
|
+
{
|
11
|
+
type: "record",
|
12
|
+
name: "person",
|
13
|
+
fields: [{ name: "name", type: "string" }]
|
14
|
+
}.to_json
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#fetch" do
|
18
|
+
it "caches the result of fetch" do
|
19
|
+
allow(upstream).to receive(:fetch).with(id).and_return(schema)
|
20
|
+
registry.fetch(id)
|
21
|
+
expect(registry.fetch(id)).to eq(schema)
|
22
|
+
expect(upstream).to have_received(:fetch).exactly(1).times
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#register" do
|
27
|
+
let(:subject_name) { "a_subject" }
|
28
|
+
|
29
|
+
it "caches the result of register" do
|
30
|
+
allow(upstream).to receive(:register).with(subject_name, schema).and_return(id)
|
31
|
+
registry.register(subject_name, schema)
|
32
|
+
expect(registry.register(subject_name, schema)).to eq(id)
|
33
|
+
expect(upstream).to have_received(:register).exactly(1).times
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it_behaves_like "a schema registry client" do
|
38
|
+
let(:upstream) { AvroTurf::SchemaRegistry.new(registry_url, logger: logger) }
|
39
|
+
let(:registry) { described_class.new(upstream) }
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'webmock/rspec'
|
2
|
+
require 'avro_turf/messaging'
|
3
|
+
require 'avro_turf/test/fake_schema_registry_server'
|
4
|
+
|
5
|
+
describe AvroTurf::Messaging do
|
6
|
+
let(:registry_url) { "http://registry.example.com" }
|
7
|
+
let(:logger) { Logger.new(StringIO.new) }
|
8
|
+
|
9
|
+
let(:avro) {
|
10
|
+
AvroTurf::Messaging.new(
|
11
|
+
registry_url: registry_url,
|
12
|
+
schemas_path: "spec/schemas",
|
13
|
+
logger: logger
|
14
|
+
)
|
15
|
+
}
|
16
|
+
|
17
|
+
let(:message) { { "full_name" => "John Doe" } }
|
18
|
+
|
19
|
+
before do
|
20
|
+
FileUtils.mkdir_p("spec/schemas")
|
21
|
+
end
|
22
|
+
|
23
|
+
before do
|
24
|
+
stub_request(:any, /^#{registry_url}/).to_rack(FakeSchemaRegistryServer)
|
25
|
+
FakeSchemaRegistryServer.clear
|
26
|
+
end
|
27
|
+
|
28
|
+
before do
|
29
|
+
define_schema "person.avsc", <<-AVSC
|
30
|
+
{
|
31
|
+
"name": "person",
|
32
|
+
"type": "record",
|
33
|
+
"fields": [
|
34
|
+
{
|
35
|
+
"type": "string",
|
36
|
+
"name": "full_name"
|
37
|
+
}
|
38
|
+
]
|
39
|
+
}
|
40
|
+
AVSC
|
41
|
+
end
|
42
|
+
|
43
|
+
shared_examples_for "encoding and decoding" do
|
44
|
+
it "encodes and decodes messages" do
|
45
|
+
data = avro.encode(message, schema_name: "person")
|
46
|
+
expect(avro.decode(data)).to eq message
|
47
|
+
end
|
48
|
+
|
49
|
+
it "allows specifying a reader's schema" do
|
50
|
+
data = avro.encode(message, schema_name: "person")
|
51
|
+
expect(avro.decode(data, schema_name: "person")).to eq message
|
52
|
+
end
|
53
|
+
|
54
|
+
it "caches parsed schemas for decoding" do
|
55
|
+
data = avro.encode(message, schema_name: "person")
|
56
|
+
avro.decode(data)
|
57
|
+
allow(Avro::Schema).to receive(:parse).and_call_original
|
58
|
+
expect(avro.decode(data)).to eq message
|
59
|
+
expect(Avro::Schema).not_to have_received(:parse)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it_behaves_like "encoding and decoding"
|
64
|
+
|
65
|
+
context "with a provided registry" do
|
66
|
+
let(:registry) { AvroTurf::SchemaRegistry.new(registry_url, logger: logger) }
|
67
|
+
|
68
|
+
let(:avro) do
|
69
|
+
AvroTurf::Messaging.new(
|
70
|
+
registry: registry,
|
71
|
+
schemas_path: "spec/schemas",
|
72
|
+
logger: logger
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
it_behaves_like "encoding and decoding"
|
77
|
+
|
78
|
+
it "uses the provided registry" do
|
79
|
+
allow(registry).to receive(:register).and_call_original
|
80
|
+
message = { "full_name" => "John Doe" }
|
81
|
+
avro.encode(message, schema_name: "person")
|
82
|
+
expect(registry).to have_received(:register).with("person", anything)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "allows specifying a schema registry subject" do
|
86
|
+
allow(registry).to receive(:register).and_call_original
|
87
|
+
message = { "full_name" => "John Doe" }
|
88
|
+
avro.encode(message, schema_name: "person", subject: "people")
|
89
|
+
expect(registry).to have_received(:register).with("people", anything)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "with a provided schema store" do
|
94
|
+
let(:schema_store) { AvroTurf::SchemaStore.new(path: "spec/schemas") }
|
95
|
+
|
96
|
+
let(:avro) do
|
97
|
+
AvroTurf::Messaging.new(
|
98
|
+
registry_url: registry_url,
|
99
|
+
schema_store: schema_store,
|
100
|
+
logger: logger
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
it_behaves_like "encoding and decoding"
|
105
|
+
|
106
|
+
it "uses the provided schema store" do
|
107
|
+
allow(schema_store).to receive(:find).and_call_original
|
108
|
+
avro.encode(message, schema_name: "person")
|
109
|
+
expect(schema_store).to have_received(:find).with("person", nil)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|