avro_turf 0.8.1 → 0.11.0
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 +4 -4
- data/.circleci/config.yml +36 -0
- data/.github/workflows/ruby.yml +20 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile +0 -3
- data/README.md +20 -0
- data/avro_turf.gemspec +6 -5
- data/lib/avro_turf.rb +10 -1
- data/lib/avro_turf/cached_confluent_schema_registry.rb +18 -6
- data/lib/avro_turf/confluent_schema_registry.rb +23 -4
- data/lib/avro_turf/disk_cache.rb +83 -0
- data/lib/avro_turf/in_memory_cache.rb +38 -0
- data/lib/avro_turf/messaging.rb +109 -16
- data/lib/avro_turf/schema_store.rb +35 -20
- data/lib/avro_turf/test/fake_confluent_schema_registry_server.rb +15 -3
- data/lib/avro_turf/version.rb +1 -1
- data/spec/cached_confluent_schema_registry_spec.rb +24 -2
- data/spec/confluent_schema_registry_spec.rb +13 -1
- data/spec/disk_cached_confluent_schema_registry_spec.rb +159 -0
- data/spec/messaging_spec.rb +205 -17
- data/spec/schema_store_spec.rb +36 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/confluent_schema_registry_context.rb +8 -5
- data/spec/test/fake_confluent_schema_registry_server_spec.rb +40 -0
- metadata +34 -13
- data/circle.yml +0 -4
@@ -3,6 +3,7 @@ class AvroTurf::SchemaStore
|
|
3
3
|
def initialize(path: nil)
|
4
4
|
@path = path or raise "Please specify a schema path"
|
5
5
|
@schemas = Hash.new
|
6
|
+
@mutex = Mutex.new
|
6
7
|
end
|
7
8
|
|
8
9
|
# Resolves and returns a schema.
|
@@ -12,9 +13,40 @@ class AvroTurf::SchemaStore
|
|
12
13
|
# Returns an Avro::Schema.
|
13
14
|
def find(name, namespace = nil)
|
14
15
|
fullname = Avro::Name.make_fullname(name, namespace)
|
15
|
-
|
16
|
+
# Optimistic non-blocking read from @schemas
|
17
|
+
# No sense to lock the resource when all the schemas already loaded
|
16
18
|
return @schemas[fullname] if @schemas.key?(fullname)
|
17
19
|
|
20
|
+
# Pessimistic blocking write to @schemas
|
21
|
+
@mutex.synchronize do
|
22
|
+
# Still need to check is the schema already loaded
|
23
|
+
return @schemas[fullname] if @schemas.key?(fullname)
|
24
|
+
|
25
|
+
load_schema!(fullname, namespace)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Loads all schema definition files in the `schemas_dir`.
|
30
|
+
def load_schemas!
|
31
|
+
pattern = [@path, "**", "*.avsc"].join("/")
|
32
|
+
|
33
|
+
Dir.glob(pattern) do |schema_path|
|
34
|
+
# Remove the path prefix.
|
35
|
+
schema_path.sub!(/^\/?#{@path}\//, "")
|
36
|
+
|
37
|
+
# Replace `/` with `.` and chop off the file extension.
|
38
|
+
schema_name = File.basename(schema_path.tr("/", "."), ".avsc")
|
39
|
+
|
40
|
+
# Load and cache the schema.
|
41
|
+
find(schema_name)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Loads single schema
|
48
|
+
# Such method is not thread-safe, do not call it of from mutex synchronization routine
|
49
|
+
def load_schema!(fullname, namespace = nil)
|
18
50
|
*namespace, schema_name = fullname.split(".")
|
19
51
|
schema_path = File.join(@path, *namespace, schema_name + ".avsc")
|
20
52
|
schema_json = JSON.parse(File.read(schema_path))
|
@@ -29,32 +61,15 @@ class AvroTurf::SchemaStore
|
|
29
61
|
# This is a hack in order to figure out exactly which type was missing. The
|
30
62
|
# Avro gem ought to provide this data directly.
|
31
63
|
if e.to_s =~ /"([\w\.]+)" is not a schema we know about/
|
32
|
-
|
64
|
+
load_schema!($1)
|
33
65
|
|
34
66
|
# Re-resolve the original schema now that the dependency has been resolved.
|
35
67
|
@schemas.delete(fullname)
|
36
|
-
|
68
|
+
load_schema!(fullname)
|
37
69
|
else
|
38
70
|
raise
|
39
71
|
end
|
40
72
|
rescue Errno::ENOENT, Errno::ENAMETOOLONG
|
41
73
|
raise AvroTurf::SchemaNotFoundError, "could not find Avro schema at `#{schema_path}'"
|
42
74
|
end
|
43
|
-
|
44
|
-
# Loads all schema definition files in the `schemas_dir`.
|
45
|
-
def load_schemas!
|
46
|
-
pattern = [@path, "**", "*.avsc"].join("/")
|
47
|
-
|
48
|
-
Dir.glob(pattern) do |schema_path|
|
49
|
-
# Remove the path prefix.
|
50
|
-
schema_path.sub!(/^\/?#{@path}\//, "")
|
51
|
-
|
52
|
-
# Replace `/` with `.` and chop off the file extension.
|
53
|
-
schema_name = File.basename(schema_path.tr("/", "."), ".avsc")
|
54
|
-
|
55
|
-
# Load and cache the schema.
|
56
|
-
find(schema_name)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
75
|
end
|
@@ -34,10 +34,21 @@ class FakeConfluentSchemaRegistryServer < Sinatra::Base
|
|
34
34
|
end
|
35
35
|
|
36
36
|
post "/subjects/:subject/versions" do
|
37
|
-
|
37
|
+
schema = parse_schema
|
38
|
+
ids_for_subject = SUBJECTS[params[:subject]]
|
39
|
+
|
40
|
+
schemas_for_subject =
|
41
|
+
SCHEMAS.select
|
42
|
+
.with_index { |_, i| ids_for_subject.include?(i) }
|
43
|
+
|
44
|
+
if schemas_for_subject.include?(schema)
|
45
|
+
schema_id = SCHEMAS.index(schema)
|
46
|
+
else
|
47
|
+
SCHEMAS << schema
|
48
|
+
schema_id = SCHEMAS.size - 1
|
49
|
+
SUBJECTS[params[:subject]] = SUBJECTS[params[:subject]] << schema_id
|
50
|
+
end
|
38
51
|
|
39
|
-
schema_id = SCHEMAS.size - 1
|
40
|
-
SUBJECTS[params[:subject]] = SUBJECTS[params[:subject]] << schema_id
|
41
52
|
{ id: schema_id }.to_json
|
42
53
|
end
|
43
54
|
|
@@ -73,6 +84,7 @@ class FakeConfluentSchemaRegistryServer < Sinatra::Base
|
|
73
84
|
{
|
74
85
|
name: params[:subject],
|
75
86
|
version: schema_ids.index(schema_id) + 1,
|
87
|
+
id: schema_id,
|
76
88
|
schema: schema
|
77
89
|
}.to_json
|
78
90
|
end
|
data/lib/avro_turf/version.rb
CHANGED
@@ -16,8 +16,9 @@ describe AvroTurf::CachedConfluentSchemaRegistry do
|
|
16
16
|
|
17
17
|
describe "#fetch" do
|
18
18
|
it "caches the result of fetch" do
|
19
|
+
# multiple calls return same result, with only one upstream call
|
19
20
|
allow(upstream).to receive(:fetch).with(id).and_return(schema)
|
20
|
-
registry.fetch(id)
|
21
|
+
expect(registry.fetch(id)).to eq(schema)
|
21
22
|
expect(registry.fetch(id)).to eq(schema)
|
22
23
|
expect(upstream).to have_received(:fetch).exactly(1).times
|
23
24
|
end
|
@@ -27,13 +28,34 @@ describe AvroTurf::CachedConfluentSchemaRegistry do
|
|
27
28
|
let(:subject_name) { "a_subject" }
|
28
29
|
|
29
30
|
it "caches the result of register" do
|
31
|
+
# multiple calls return same result, with only one upstream call
|
30
32
|
allow(upstream).to receive(:register).with(subject_name, schema).and_return(id)
|
31
|
-
registry.register(subject_name, schema)
|
33
|
+
expect(registry.register(subject_name, schema)).to eq(id)
|
32
34
|
expect(registry.register(subject_name, schema)).to eq(id)
|
33
35
|
expect(upstream).to have_received(:register).exactly(1).times
|
34
36
|
end
|
35
37
|
end
|
36
38
|
|
39
|
+
describe '#subject_version' do
|
40
|
+
let(:subject_name) { 'a_subject' }
|
41
|
+
let(:version) { 1 }
|
42
|
+
let(:schema_with_meta) do
|
43
|
+
{
|
44
|
+
subject: subject_name,
|
45
|
+
id: 1,
|
46
|
+
version: 1,
|
47
|
+
schema: schema
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'caches the result of subject_version' do
|
52
|
+
allow(upstream).to receive(:subject_version).with(subject_name, version).and_return(schema_with_meta)
|
53
|
+
registry.subject_version(subject_name, version)
|
54
|
+
registry.subject_version(subject_name, version)
|
55
|
+
expect(upstream).to have_received(:subject_version).exactly(1).times
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
37
59
|
it_behaves_like "a confluent schema registry client" do
|
38
60
|
let(:upstream) { AvroTurf::ConfluentSchemaRegistry.new(registry_url, logger: logger) }
|
39
61
|
let(:registry) { described_class.new(upstream) }
|
@@ -3,7 +3,19 @@ require 'avro_turf/confluent_schema_registry'
|
|
3
3
|
require 'avro_turf/test/fake_confluent_schema_registry_server'
|
4
4
|
|
5
5
|
describe AvroTurf::ConfluentSchemaRegistry do
|
6
|
+
let(:client_cert) { "test client cert" }
|
7
|
+
let(:client_key) { "test client key" }
|
8
|
+
let(:client_key_pass) { "test client key password" }
|
9
|
+
|
6
10
|
it_behaves_like "a confluent schema registry client" do
|
7
|
-
let(:registry) {
|
11
|
+
let(:registry) {
|
12
|
+
described_class.new(
|
13
|
+
registry_url,
|
14
|
+
logger: logger,
|
15
|
+
client_cert: client_cert,
|
16
|
+
client_key: client_key,
|
17
|
+
client_key_pass: client_key_pass
|
18
|
+
)
|
19
|
+
}
|
8
20
|
end
|
9
21
|
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'webmock/rspec'
|
2
|
+
require 'avro_turf/cached_confluent_schema_registry'
|
3
|
+
require 'avro_turf/test/fake_confluent_schema_registry_server'
|
4
|
+
|
5
|
+
describe AvroTurf::CachedConfluentSchemaRegistry do
|
6
|
+
let(:upstream) { instance_double(AvroTurf::ConfluentSchemaRegistry) }
|
7
|
+
let(:cache) { AvroTurf::DiskCache.new("spec/cache")}
|
8
|
+
let(:registry) { described_class.new(upstream, cache: cache) }
|
9
|
+
let(:id) { rand(999) }
|
10
|
+
let(:schema) do
|
11
|
+
{
|
12
|
+
type: "record",
|
13
|
+
name: "person",
|
14
|
+
fields: [{ name: "name", type: "string" }]
|
15
|
+
}.to_json
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:city_id) { rand(999) }
|
19
|
+
let(:city_schema) do
|
20
|
+
{
|
21
|
+
type: "record",
|
22
|
+
name: "city",
|
23
|
+
fields: [{ name: "name", type: "string" }]
|
24
|
+
}.to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:subject) { 'subject' }
|
28
|
+
let(:version) { rand(999) }
|
29
|
+
let(:subject_version_schema) do
|
30
|
+
{
|
31
|
+
subject: subject,
|
32
|
+
version: version,
|
33
|
+
id: id,
|
34
|
+
schema: {
|
35
|
+
type: "record",
|
36
|
+
name: "city",
|
37
|
+
fields: { name: "name", type: "string" }
|
38
|
+
}
|
39
|
+
}.to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
before do
|
43
|
+
FileUtils.mkdir_p("spec/cache")
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#fetch" do
|
47
|
+
let(:cache_before) do
|
48
|
+
{
|
49
|
+
"#{id}" => "#{schema}"
|
50
|
+
}
|
51
|
+
end
|
52
|
+
let(:cache_after) do
|
53
|
+
{
|
54
|
+
"#{id}" => "#{schema}",
|
55
|
+
"#{city_id}" => "#{city_schema}"
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
# setup the disk cache to avoid performing the upstream fetch
|
60
|
+
before do
|
61
|
+
store_cache("schemas_by_id.json", cache_before)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "uses preloaded disk cache" do
|
65
|
+
# multiple calls return same result, with zero upstream calls
|
66
|
+
allow(upstream).to receive(:fetch).with(id).and_return(schema)
|
67
|
+
expect(registry.fetch(id)).to eq(schema)
|
68
|
+
expect(registry.fetch(id)).to eq(schema)
|
69
|
+
expect(upstream).to have_received(:fetch).exactly(0).times
|
70
|
+
expect(load_cache("schemas_by_id.json")).to eq cache_before
|
71
|
+
end
|
72
|
+
|
73
|
+
it "writes thru to disk cache" do
|
74
|
+
# multiple calls return same result, with only one upstream call
|
75
|
+
allow(upstream).to receive(:fetch).with(city_id).and_return(city_schema)
|
76
|
+
expect(registry.fetch(city_id)).to eq(city_schema)
|
77
|
+
expect(registry.fetch(city_id)).to eq(city_schema)
|
78
|
+
expect(upstream).to have_received(:fetch).exactly(1).times
|
79
|
+
expect(load_cache("schemas_by_id.json")).to eq cache_after
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#register" do
|
84
|
+
let(:subject_name) { "a_subject" }
|
85
|
+
let(:cache_before) do
|
86
|
+
{
|
87
|
+
"#{subject_name}#{schema}" => id
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
let(:city_name) { "a_city" }
|
92
|
+
let(:cache_after) do
|
93
|
+
{
|
94
|
+
"#{subject_name}#{schema}" => id,
|
95
|
+
"#{city_name}#{city_schema}" => city_id
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
# setup the disk cache to avoid performing the upstream register
|
100
|
+
before do
|
101
|
+
store_cache("ids_by_schema.json", cache_before)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "uses preloaded disk cache" do
|
105
|
+
# multiple calls return same result, with zero upstream calls
|
106
|
+
allow(upstream).to receive(:register).with(subject_name, schema).and_return(id)
|
107
|
+
expect(registry.register(subject_name, schema)).to eq(id)
|
108
|
+
expect(registry.register(subject_name, schema)).to eq(id)
|
109
|
+
expect(upstream).to have_received(:register).exactly(0).times
|
110
|
+
expect(load_cache("ids_by_schema.json")).to eq cache_before
|
111
|
+
end
|
112
|
+
|
113
|
+
it "writes thru to disk cache" do
|
114
|
+
# multiple calls return same result, with only one upstream call
|
115
|
+
allow(upstream).to receive(:register).with(city_name, city_schema).and_return(city_id)
|
116
|
+
expect(registry.register(city_name, city_schema)).to eq(city_id)
|
117
|
+
expect(registry.register(city_name, city_schema)).to eq(city_id)
|
118
|
+
expect(upstream).to have_received(:register).exactly(1).times
|
119
|
+
expect(load_cache("ids_by_schema.json")).to eq cache_after
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "#subject_version" do
|
124
|
+
it "writes thru to disk cache" do
|
125
|
+
# multiple calls return same result, with zero upstream calls
|
126
|
+
allow(upstream).to receive(:subject_version).with(subject, version).and_return(subject_version_schema)
|
127
|
+
expect(File).not_to exist("./spec/cache/schemas_by_subject_version.json")
|
128
|
+
|
129
|
+
expect(registry.subject_version(subject, version)).to eq(subject_version_schema)
|
130
|
+
|
131
|
+
json = JSON.parse(File.read("./spec/cache/schemas_by_subject_version.json"))["#{subject}#{version}"]
|
132
|
+
expect(json).to eq(subject_version_schema)
|
133
|
+
|
134
|
+
expect(registry.subject_version(subject, version)).to eq(subject_version_schema)
|
135
|
+
expect(upstream).to have_received(:subject_version).exactly(1).times
|
136
|
+
end
|
137
|
+
|
138
|
+
it "reads from disk cache and populates mem cache" do
|
139
|
+
allow(upstream).to receive(:subject_version).with(subject, version).and_return(subject_version_schema)
|
140
|
+
key = "#{subject}#{version}"
|
141
|
+
hash = {key => subject_version_schema}
|
142
|
+
cache.send(:write_to_disk_cache, "./spec/cache/schemas_by_subject_version.json", hash)
|
143
|
+
|
144
|
+
cached_schema = cache.instance_variable_get(:@schemas_by_subject_version)
|
145
|
+
expect(cached_schema).to eq({})
|
146
|
+
|
147
|
+
expect(registry.subject_version(subject, version)).to eq(subject_version_schema)
|
148
|
+
expect(upstream).to have_received(:subject_version).exactly(0).times
|
149
|
+
|
150
|
+
cached_schema = cache.instance_variable_get(:@schemas_by_subject_version)
|
151
|
+
expect(cached_schema).to eq({key => subject_version_schema})
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
it_behaves_like "a confluent schema registry client" do
|
156
|
+
let(:upstream) { AvroTurf::ConfluentSchemaRegistry.new(registry_url, logger: logger) }
|
157
|
+
let(:registry) { described_class.new(upstream) }
|
158
|
+
end
|
159
|
+
end
|
data/spec/messaging_spec.rb
CHANGED
@@ -4,29 +4,25 @@ require 'avro_turf/test/fake_confluent_schema_registry_server'
|
|
4
4
|
|
5
5
|
describe AvroTurf::Messaging do
|
6
6
|
let(:registry_url) { "http://registry.example.com" }
|
7
|
+
let(:client_cert) { "test client cert" }
|
8
|
+
let(:client_key) { "test client key" }
|
9
|
+
let(:client_key_pass) { "test client key password" }
|
7
10
|
let(:logger) { Logger.new(StringIO.new) }
|
8
11
|
|
9
12
|
let(:avro) {
|
10
13
|
AvroTurf::Messaging.new(
|
11
14
|
registry_url: registry_url,
|
12
15
|
schemas_path: "spec/schemas",
|
13
|
-
logger: logger
|
16
|
+
logger: logger,
|
17
|
+
client_cert: client_cert,
|
18
|
+
client_key: client_key,
|
19
|
+
client_key_pass: client_key_pass
|
14
20
|
)
|
15
21
|
}
|
16
22
|
|
17
23
|
let(:message) { { "full_name" => "John Doe" } }
|
18
|
-
|
19
|
-
|
20
|
-
FileUtils.mkdir_p("spec/schemas")
|
21
|
-
end
|
22
|
-
|
23
|
-
before do
|
24
|
-
stub_request(:any, /^#{registry_url}/).to_rack(FakeConfluentSchemaRegistryServer)
|
25
|
-
FakeConfluentSchemaRegistryServer.clear
|
26
|
-
end
|
27
|
-
|
28
|
-
before do
|
29
|
-
define_schema "person.avsc", <<-AVSC
|
24
|
+
let(:schema_json) do
|
25
|
+
<<-AVSC
|
30
26
|
{
|
31
27
|
"name": "person",
|
32
28
|
"type": "record",
|
@@ -39,8 +35,22 @@ describe AvroTurf::Messaging do
|
|
39
35
|
}
|
40
36
|
AVSC
|
41
37
|
end
|
38
|
+
let(:schema) { Avro::Schema.parse(schema_json) }
|
42
39
|
|
43
|
-
|
40
|
+
before do
|
41
|
+
FileUtils.mkdir_p("spec/schemas")
|
42
|
+
end
|
43
|
+
|
44
|
+
before do
|
45
|
+
stub_request(:any, /^#{registry_url}/).to_rack(FakeConfluentSchemaRegistryServer)
|
46
|
+
FakeConfluentSchemaRegistryServer.clear
|
47
|
+
end
|
48
|
+
|
49
|
+
before do
|
50
|
+
define_schema "person.avsc", schema_json
|
51
|
+
end
|
52
|
+
|
53
|
+
shared_examples_for "encoding and decoding with the schema from schema store" do
|
44
54
|
it "encodes and decodes messages" do
|
45
55
|
data = avro.encode(message, schema_name: "person")
|
46
56
|
expect(avro.decode(data)).to eq message
|
@@ -60,7 +70,66 @@ describe AvroTurf::Messaging do
|
|
60
70
|
end
|
61
71
|
end
|
62
72
|
|
63
|
-
|
73
|
+
shared_examples_for 'encoding and decoding with the schema from registry' do
|
74
|
+
before do
|
75
|
+
registry = AvroTurf::ConfluentSchemaRegistry.new(registry_url, logger: logger)
|
76
|
+
registry.register('person', schema)
|
77
|
+
registry.register('people', schema)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'encodes and decodes messages' do
|
81
|
+
data = avro.encode(message, subject: 'person', version: 1)
|
82
|
+
expect(avro.decode(data)).to eq message
|
83
|
+
end
|
84
|
+
|
85
|
+
it "allows specifying a reader's schema by subject and version" do
|
86
|
+
data = avro.encode(message, subject: 'person', version: 1)
|
87
|
+
expect(avro.decode(data, schema_name: 'person')).to eq message
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'raises AvroTurf::SchemaNotFoundError when the schema does not exist on registry' do
|
91
|
+
expect { avro.encode(message, subject: 'missing', version: 1) }.to raise_error(AvroTurf::SchemaNotFoundError)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'caches parsed schemas for decoding' do
|
95
|
+
data = avro.encode(message, subject: 'person', version: 1)
|
96
|
+
avro.decode(data)
|
97
|
+
allow(Avro::Schema).to receive(:parse).and_call_original
|
98
|
+
expect(avro.decode(data)).to eq message
|
99
|
+
expect(Avro::Schema).not_to have_received(:parse)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
shared_examples_for 'encoding and decoding with the schema_id from registry' do
|
104
|
+
before do
|
105
|
+
registry = AvroTurf::ConfluentSchemaRegistry.new(registry_url, logger: logger)
|
106
|
+
registry.register('person', schema)
|
107
|
+
registry.register('people', schema)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'encodes and decodes messages' do
|
111
|
+
data = avro.encode(message, schema_id: 1)
|
112
|
+
expect(avro.decode(data)).to eq message
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'raises AvroTurf::SchemaNotFoundError when the schema does not exist on registry' do
|
116
|
+
expect { avro.encode(message, schema_id: 5) }.to raise_error(AvroTurf::SchemaNotFoundError)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'caches parsed schemas for decoding' do
|
120
|
+
data = avro.encode(message, schema_id: 1)
|
121
|
+
avro.decode(data)
|
122
|
+
allow(Avro::Schema).to receive(:parse).and_call_original
|
123
|
+
expect(avro.decode(data)).to eq message
|
124
|
+
expect(Avro::Schema).not_to have_received(:parse)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it_behaves_like "encoding and decoding with the schema from schema store"
|
129
|
+
|
130
|
+
it_behaves_like 'encoding and decoding with the schema from registry'
|
131
|
+
|
132
|
+
it_behaves_like 'encoding and decoding with the schema_id from registry'
|
64
133
|
|
65
134
|
context "with a provided registry" do
|
66
135
|
let(:registry) { AvroTurf::ConfluentSchemaRegistry.new(registry_url, logger: logger) }
|
@@ -73,7 +142,11 @@ describe AvroTurf::Messaging do
|
|
73
142
|
)
|
74
143
|
end
|
75
144
|
|
76
|
-
it_behaves_like "encoding and decoding"
|
145
|
+
it_behaves_like "encoding and decoding with the schema from schema store"
|
146
|
+
|
147
|
+
it_behaves_like 'encoding and decoding with the schema from registry'
|
148
|
+
|
149
|
+
it_behaves_like 'encoding and decoding with the schema_id from registry'
|
77
150
|
|
78
151
|
it "uses the provided registry" do
|
79
152
|
allow(registry).to receive(:register).and_call_original
|
@@ -101,7 +174,7 @@ describe AvroTurf::Messaging do
|
|
101
174
|
)
|
102
175
|
end
|
103
176
|
|
104
|
-
it_behaves_like "encoding and decoding"
|
177
|
+
it_behaves_like "encoding and decoding with the schema from schema store"
|
105
178
|
|
106
179
|
it "uses the provided schema store" do
|
107
180
|
allow(schema_store).to receive(:find).and_call_original
|
@@ -109,4 +182,119 @@ describe AvroTurf::Messaging do
|
|
109
182
|
expect(schema_store).to have_received(:find).with("person", nil)
|
110
183
|
end
|
111
184
|
end
|
185
|
+
|
186
|
+
describe 'decoding with #decode_message' do
|
187
|
+
shared_examples_for "encoding and decoding with the schema from schema store" do
|
188
|
+
it "encodes and decodes messages" do
|
189
|
+
data = avro.encode(message, schema_name: "person")
|
190
|
+
result = avro.decode_message(data)
|
191
|
+
expect(result.message).to eq message
|
192
|
+
expect(result.schema_id).to eq 0
|
193
|
+
expect(result.writer_schema).to eq schema
|
194
|
+
expect(result.reader_schema).to eq nil
|
195
|
+
end
|
196
|
+
|
197
|
+
it "allows specifying a reader's schema" do
|
198
|
+
data = avro.encode(message, schema_name: "person")
|
199
|
+
result = avro.decode_message(data, schema_name: "person")
|
200
|
+
expect(result.message).to eq message
|
201
|
+
expect(result.writer_schema).to eq schema
|
202
|
+
expect(result.reader_schema).to eq schema
|
203
|
+
end
|
204
|
+
|
205
|
+
it "caches parsed schemas for decoding" do
|
206
|
+
data = avro.encode(message, schema_name: "person")
|
207
|
+
avro.decode_message(data)
|
208
|
+
allow(Avro::Schema).to receive(:parse).and_call_original
|
209
|
+
expect(avro.decode_message(data).message).to eq message
|
210
|
+
expect(Avro::Schema).not_to have_received(:parse)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
shared_examples_for 'encoding and decoding with the schema from registry' do
|
215
|
+
before do
|
216
|
+
registry = AvroTurf::ConfluentSchemaRegistry.new(registry_url, logger: logger)
|
217
|
+
registry.register('person', schema)
|
218
|
+
registry.register('people', schema)
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'encodes and decodes messages' do
|
222
|
+
data = avro.encode(message, subject: 'person', version: 1)
|
223
|
+
result = avro.decode_message(data)
|
224
|
+
expect(result.message).to eq message
|
225
|
+
expect(result.schema_id).to eq 0
|
226
|
+
end
|
227
|
+
|
228
|
+
it "allows specifying a reader's schema by subject and version" do
|
229
|
+
data = avro.encode(message, subject: 'person', version: 1)
|
230
|
+
expect(avro.decode_message(data, schema_name: 'person').message).to eq message
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'raises AvroTurf::SchemaNotFoundError when the schema does not exist on registry' do
|
234
|
+
expect { avro.encode(message, subject: 'missing', version: 1) }.to raise_error(AvroTurf::SchemaNotFoundError)
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'caches parsed schemas for decoding' do
|
238
|
+
data = avro.encode(message, subject: 'person', version: 1)
|
239
|
+
avro.decode_message(data)
|
240
|
+
allow(Avro::Schema).to receive(:parse).and_call_original
|
241
|
+
expect(avro.decode_message(data).message).to eq message
|
242
|
+
expect(Avro::Schema).not_to have_received(:parse)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
it_behaves_like "encoding and decoding with the schema from schema store"
|
247
|
+
|
248
|
+
it_behaves_like 'encoding and decoding with the schema from registry'
|
249
|
+
|
250
|
+
context "with a provided registry" do
|
251
|
+
let(:registry) { AvroTurf::ConfluentSchemaRegistry.new(registry_url, logger: logger) }
|
252
|
+
|
253
|
+
let(:avro) do
|
254
|
+
AvroTurf::Messaging.new(
|
255
|
+
registry: registry,
|
256
|
+
schemas_path: "spec/schemas",
|
257
|
+
logger: logger
|
258
|
+
)
|
259
|
+
end
|
260
|
+
|
261
|
+
it_behaves_like "encoding and decoding with the schema from schema store"
|
262
|
+
|
263
|
+
it_behaves_like 'encoding and decoding with the schema from registry'
|
264
|
+
|
265
|
+
it "uses the provided registry" do
|
266
|
+
allow(registry).to receive(:register).and_call_original
|
267
|
+
message = { "full_name" => "John Doe" }
|
268
|
+
avro.encode(message, schema_name: "person")
|
269
|
+
expect(registry).to have_received(:register).with("person", anything)
|
270
|
+
end
|
271
|
+
|
272
|
+
it "allows specifying a schema registry subject" do
|
273
|
+
allow(registry).to receive(:register).and_call_original
|
274
|
+
message = { "full_name" => "John Doe" }
|
275
|
+
avro.encode(message, schema_name: "person", subject: "people")
|
276
|
+
expect(registry).to have_received(:register).with("people", anything)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
context "with a provided schema store" do
|
281
|
+
let(:schema_store) { AvroTurf::SchemaStore.new(path: "spec/schemas") }
|
282
|
+
|
283
|
+
let(:avro) do
|
284
|
+
AvroTurf::Messaging.new(
|
285
|
+
registry_url: registry_url,
|
286
|
+
schema_store: schema_store,
|
287
|
+
logger: logger
|
288
|
+
)
|
289
|
+
end
|
290
|
+
|
291
|
+
it_behaves_like "encoding and decoding with the schema from schema store"
|
292
|
+
|
293
|
+
it "uses the provided schema store" do
|
294
|
+
allow(schema_store).to receive(:find).and_call_original
|
295
|
+
avro.encode(message, schema_name: "person")
|
296
|
+
expect(schema_store).to have_received(:find).with("person", nil)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
112
300
|
end
|