avro_turf 1.8.0 → 1.10.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/.github/workflows/ruby.yml +4 -4
- data/CHANGELOG.md +10 -1
- data/avro_turf.gemspec +1 -1
- data/lib/avro_turf/cached_confluent_schema_registry.rb +1 -1
- data/lib/avro_turf/confluent_schema_registry.rb +7 -2
- data/lib/avro_turf/in_memory_cache.rb +2 -2
- data/lib/avro_turf/schema_store.rb +1 -1
- data/lib/avro_turf/test/fake_confluent_schema_registry_server.rb +22 -10
- data/lib/avro_turf/version.rb +1 -1
- data/lib/avro_turf.rb +2 -3
- data/spec/avro_turf_spec.rb +32 -1
- data/spec/messaging_spec.rb +2 -2
- data/spec/schema_store_spec.rb +43 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/confluent_schema_registry_context.rb +39 -1
- data/spec/test/fake_confluent_schema_registry_server_spec.rb +63 -2
- metadata +10 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0907531d40226b59cd545baf7341f3e41613565d50de98684b266b6b33a081f
|
4
|
+
data.tar.gz: e472794d6e7ecbd4a32b1a08f4cc0eb6b0ae810d849483d86ab16a7da929a9fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac1324149cb3299c3e67ffbd287879da88a09c338b967e2098fed63836c9fba5f775623ee6d6ae2ac0ac5e6f6a4ad28ea4c7bb7ece2d1e5979cee2eb379ac43a
|
7
|
+
data.tar.gz: 84709d06f4b3d97a23c1f0a1b94f4757c8b7024d17354ec65c7068be8cca52cdeed832096d7ea43f646d3f52d09ebd0f2286375456503d9da14cf6cb7a37772b
|
data/.github/workflows/ruby.yml
CHANGED
@@ -7,17 +7,17 @@ jobs:
|
|
7
7
|
|
8
8
|
runs-on: ubuntu-latest
|
9
9
|
strategy:
|
10
|
+
fail-fast: false
|
10
11
|
matrix:
|
11
|
-
ruby: [2.
|
12
|
+
ruby: [2.6, 2.7, "3.0", 3.1, 3.2]
|
12
13
|
|
13
14
|
steps:
|
14
|
-
- uses: actions/checkout@
|
15
|
+
- uses: actions/checkout@v3
|
15
16
|
- name: Set up Ruby ${{ matrix.ruby }}
|
16
17
|
uses: ruby/setup-ruby@v1
|
17
18
|
with:
|
18
19
|
ruby-version: ${{ matrix.ruby }}
|
20
|
+
bundler-cache: true
|
19
21
|
- name: Build and test with RSpec
|
20
22
|
run: |
|
21
|
-
gem install bundler
|
22
|
-
bundle install --jobs 4 --retry 3
|
23
23
|
bundle exec rspec
|
data/CHANGELOG.md
CHANGED
@@ -2,9 +2,18 @@
|
|
2
2
|
|
3
3
|
## Unreleased
|
4
4
|
|
5
|
+
## v1.10.0
|
6
|
+
|
7
|
+
- Add `schema_subject_versions` to `ConfluentSchemaRegistry` to retrieve all subject versions for a schema id. (#189)
|
8
|
+
- `FakeConfluentSchemaRegistryServer` now returns same id if identical schema is created for a different subject (#188)
|
9
|
+
|
10
|
+
## v1.9.0
|
11
|
+
|
12
|
+
- Send Accept and User-Agent headers on every request (#184)
|
13
|
+
|
5
14
|
## v1.8.0
|
6
15
|
|
7
|
-
-
|
16
|
+
- Add support for `Date` via appropriate logicalType defintion. This is a backwards incompatible change (#177)
|
8
17
|
- Fixed schema file cache truncation on multiple running instances and parallel access to the cache files.
|
9
18
|
|
10
19
|
## v1.7.0
|
data/avro_turf.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.add_development_dependency "bundler", "~> 2.0"
|
26
26
|
spec.add_development_dependency "rake", "~> 13.0"
|
27
27
|
spec.add_development_dependency "rspec", "~> 3.2"
|
28
|
-
spec.add_development_dependency "fakefs", "
|
28
|
+
spec.add_development_dependency "fakefs", "< 3"
|
29
29
|
spec.add_development_dependency "webmock"
|
30
30
|
spec.add_development_dependency "sinatra"
|
31
31
|
spec.add_development_dependency "json_spec"
|
@@ -17,7 +17,7 @@ class AvroTurf::CachedConfluentSchemaRegistry
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# Delegate the following methods to the upstream
|
20
|
-
%i(subjects subject_versions check compatible?
|
20
|
+
%i(subjects subject_versions schema_subject_versions check compatible?
|
21
21
|
global_config update_global_config subject_config update_subject_config).each do |name|
|
22
22
|
define_method(name) do |*args|
|
23
23
|
instance_variable_get(:@upstream).send(name, *args)
|
@@ -19,9 +19,9 @@ class AvroTurf::ConfluentSchemaRegistry
|
|
19
19
|
)
|
20
20
|
@path_prefix = path_prefix
|
21
21
|
@logger = logger
|
22
|
-
headers =
|
22
|
+
headers = Excon.defaults[:headers].merge(
|
23
23
|
"Content-Type" => CONTENT_TYPE
|
24
|
-
|
24
|
+
)
|
25
25
|
headers[:proxy] = proxy unless proxy.nil?
|
26
26
|
@connection = Excon.new(
|
27
27
|
url,
|
@@ -68,6 +68,11 @@ class AvroTurf::ConfluentSchemaRegistry
|
|
68
68
|
get("/subjects/#{subject}/versions/#{version}")
|
69
69
|
end
|
70
70
|
|
71
|
+
# Get the subject and version for a schema id
|
72
|
+
def schema_subject_versions(schema_id)
|
73
|
+
get("/schemas/ids/#{schema_id}/versions")
|
74
|
+
end
|
75
|
+
|
71
76
|
# Check if a schema exists. Returns nil if not found.
|
72
77
|
def check(subject, schema)
|
73
78
|
data = post("/subjects/#{subject}",
|
@@ -17,12 +17,12 @@ class AvroTurf::InMemoryCache
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def lookup_by_schema(subject, schema)
|
20
|
-
key = [subject, schema
|
20
|
+
key = [subject, schema]
|
21
21
|
@ids_by_schema[key]
|
22
22
|
end
|
23
23
|
|
24
24
|
def store_by_schema(subject, schema, id)
|
25
|
-
key = [subject, schema
|
25
|
+
key = [subject, schema]
|
26
26
|
@ids_by_schema[key] = id
|
27
27
|
end
|
28
28
|
|
@@ -35,23 +35,35 @@ class FakeConfluentSchemaRegistryServer < Sinatra::Base
|
|
35
35
|
|
36
36
|
post "/subjects/:subject/versions" do
|
37
37
|
schema = parse_schema
|
38
|
-
|
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
|
38
|
+
schema_id = SCHEMAS.index(schema)
|
39
|
+
if schema_id.nil?
|
47
40
|
SCHEMAS << schema
|
48
41
|
schema_id = SCHEMAS.size - 1
|
49
|
-
|
42
|
+
end
|
43
|
+
|
44
|
+
subject = params[:subject]
|
45
|
+
unless SUBJECTS[subject].include?(schema_id)
|
46
|
+
SUBJECTS[subject] = SUBJECTS[subject] << schema_id
|
50
47
|
end
|
51
48
|
|
52
49
|
{ id: schema_id }.to_json
|
53
50
|
end
|
54
51
|
|
52
|
+
get "/schemas/ids/:schema_id/versions" do
|
53
|
+
schema_id = params[:schema_id].to_i
|
54
|
+
schema = SCHEMAS.at(schema_id)
|
55
|
+
halt(404, SCHEMA_NOT_FOUND) unless schema
|
56
|
+
|
57
|
+
related_subjects = SUBJECTS.select {|_, vs| vs.include? schema_id }
|
58
|
+
|
59
|
+
related_subjects.map do |subject, versions|
|
60
|
+
{
|
61
|
+
subject: subject,
|
62
|
+
version: versions.find_index(schema_id) + 1
|
63
|
+
}
|
64
|
+
end.to_json
|
65
|
+
end
|
66
|
+
|
55
67
|
get "/schemas/ids/:schema_id" do
|
56
68
|
schema = SCHEMAS.at(params[:schema_id].to_i)
|
57
69
|
halt(404, SCHEMA_NOT_FOUND) unless schema
|
data/lib/avro_turf/version.rb
CHANGED
data/lib/avro_turf.rb
CHANGED
@@ -121,10 +121,9 @@ class AvroTurf
|
|
121
121
|
# namespace - The namespace of the Avro schema (optional).
|
122
122
|
#
|
123
123
|
# Returns true if the data is valid, false otherwise.
|
124
|
-
def valid?(data, schema_name: nil, namespace: @namespace)
|
124
|
+
def valid?(data, schema_name: nil, namespace: @namespace, validate_options: {})
|
125
125
|
schema = schema_name && @schema_store.find(schema_name, namespace)
|
126
|
-
|
127
|
-
Avro::Schema.validate(schema, data.as_avro)
|
126
|
+
Avro::Schema.validate(schema, data.as_avro, **validate_options)
|
128
127
|
end
|
129
128
|
|
130
129
|
# Loads all schema definition files in the `schemas_dir`.
|
data/spec/avro_turf_spec.rb
CHANGED
@@ -360,11 +360,42 @@ describe AvroTurf do
|
|
360
360
|
}
|
361
361
|
AVSC
|
362
362
|
|
363
|
-
datum = {
|
363
|
+
datum = {
|
364
364
|
message: "hello",
|
365
365
|
sent_date: Date.new(2022, 9, 11)
|
366
366
|
}
|
367
367
|
expect(avro.valid?(datum, schema_name: "postcard")).to eq true
|
368
368
|
end
|
369
|
+
|
370
|
+
context "when message contains extra fields (typo in key)" do
|
371
|
+
let(:message) { { "fulll_name" => "John Doe" } }
|
372
|
+
|
373
|
+
before do
|
374
|
+
define_schema "message.avsc", <<-AVSC
|
375
|
+
{
|
376
|
+
"name": "message",
|
377
|
+
"type": "record",
|
378
|
+
"fields": [
|
379
|
+
{ "name": "full_name", "type": "string" }
|
380
|
+
]
|
381
|
+
}
|
382
|
+
AVSC
|
383
|
+
end
|
384
|
+
|
385
|
+
it "is valid" do
|
386
|
+
datum = { "full_name" => "John Doe", "extra" => "extra" }
|
387
|
+
expect(avro.valid?(datum, schema_name: "message")).to eq true
|
388
|
+
end
|
389
|
+
|
390
|
+
it "is invalid when passing fail_on_extra_fields" do
|
391
|
+
datum = { "full_name" => "John Doe", "extra" => "extra" }
|
392
|
+
validate_options = {
|
393
|
+
recursive: true,
|
394
|
+
encoded: false,
|
395
|
+
fail_on_extra_fields: true }
|
396
|
+
valid = avro.valid?(datum, schema_name: "message", validate_options: validate_options)
|
397
|
+
expect(valid).to eq false
|
398
|
+
end
|
399
|
+
end
|
369
400
|
end
|
370
401
|
end
|
data/spec/messaging_spec.rb
CHANGED
@@ -109,7 +109,7 @@ describe AvroTurf::Messaging do
|
|
109
109
|
end
|
110
110
|
|
111
111
|
it 'encodes and decodes messages' do
|
112
|
-
data = avro.encode(message, schema_id:
|
112
|
+
data = avro.encode(message, schema_id: 0)
|
113
113
|
expect(avro.decode(data)).to eq message
|
114
114
|
end
|
115
115
|
|
@@ -118,7 +118,7 @@ describe AvroTurf::Messaging do
|
|
118
118
|
end
|
119
119
|
|
120
120
|
it 'caches parsed schemas for decoding' do
|
121
|
-
data = avro.encode(message, schema_id:
|
121
|
+
data = avro.encode(message, schema_id: 0)
|
122
122
|
avro.decode(data)
|
123
123
|
allow(Avro::Schema).to receive(:parse).and_call_original
|
124
124
|
expect(avro.decode(data)).to eq message
|
data/spec/schema_store_spec.rb
CHANGED
@@ -361,6 +361,49 @@ describe AvroTurf::SchemaStore do
|
|
361
361
|
end
|
362
362
|
|
363
363
|
describe "#load_schemas!" do
|
364
|
+
it "do not try to reload known schemas" do
|
365
|
+
define_schema "first.avsc", <<-AVSC
|
366
|
+
{
|
367
|
+
"name": "first",
|
368
|
+
"type": "record",
|
369
|
+
"fields": [
|
370
|
+
{
|
371
|
+
"type": "string",
|
372
|
+
"name": "a_value"
|
373
|
+
}
|
374
|
+
]
|
375
|
+
}
|
376
|
+
AVSC
|
377
|
+
|
378
|
+
define_schema "second.avsc", <<-AVSC
|
379
|
+
{
|
380
|
+
"name": "second",
|
381
|
+
"type": "record",
|
382
|
+
"fields": [
|
383
|
+
{
|
384
|
+
"type": "first",
|
385
|
+
"name": "full_name"
|
386
|
+
}
|
387
|
+
]
|
388
|
+
}
|
389
|
+
AVSC
|
390
|
+
|
391
|
+
define_schema "third.avsc", <<-AVSC
|
392
|
+
{
|
393
|
+
"name": "third",
|
394
|
+
"type": "record",
|
395
|
+
"fields": [
|
396
|
+
{
|
397
|
+
"type": "second",
|
398
|
+
"name": "embedded_first"
|
399
|
+
}
|
400
|
+
]
|
401
|
+
}
|
402
|
+
AVSC
|
403
|
+
|
404
|
+
expect { store.load_schemas! }.not_to raise_error
|
405
|
+
end
|
406
|
+
|
364
407
|
it "loads schemas defined in the `schemas_path` directory" do
|
365
408
|
define_schema "person.avsc", <<-AVSC
|
366
409
|
{
|
data/spec/spec_helper.rb
CHANGED
@@ -13,9 +13,20 @@ shared_examples_for "a confluent schema registry client" do
|
|
13
13
|
]
|
14
14
|
}.to_json
|
15
15
|
end
|
16
|
+
let(:headers) do
|
17
|
+
{
|
18
|
+
'Accept'=>'*/*',
|
19
|
+
'Content-Type'=> AvroTurf::ConfluentSchemaRegistry::CONTENT_TYPE,
|
20
|
+
'Host'=> "#{URI.parse(registry_url).host}:80",
|
21
|
+
'User-Agent'=> "excon/#{Excon::VERSION}"
|
22
|
+
}
|
23
|
+
end
|
16
24
|
|
17
25
|
before do
|
18
|
-
stub_request(:any, /^#{registry_url}/)
|
26
|
+
stub_request(:any, /^#{registry_url}/)
|
27
|
+
.with(headers: headers)
|
28
|
+
.to_rack(FakeConfluentSchemaRegistryServer)
|
29
|
+
|
19
30
|
FakeConfluentSchemaRegistryServer.clear
|
20
31
|
end
|
21
32
|
|
@@ -66,6 +77,33 @@ shared_examples_for "a confluent schema registry client" do
|
|
66
77
|
end
|
67
78
|
end
|
68
79
|
|
80
|
+
describe "#schema_subject_versions" do
|
81
|
+
it "returns subject and version for a schema id" do
|
82
|
+
schema_id1 = registry.register(subject_name, { type: :record, name: "r1", fields: [] }.to_json)
|
83
|
+
registry.register(subject_name, { type: :record, name: "r2", fields: [] }.to_json)
|
84
|
+
schema_id2 = registry.register("other#{subject_name}", { type: :record, name: "r2", fields: [] }.to_json)
|
85
|
+
expect(registry.schema_subject_versions(schema_id1)).to eq([
|
86
|
+
'subject' => subject_name,
|
87
|
+
'version' => 1
|
88
|
+
])
|
89
|
+
expect(registry.schema_subject_versions(schema_id2)).to include({
|
90
|
+
'subject' => subject_name,
|
91
|
+
'version' => 2
|
92
|
+
},{
|
93
|
+
'subject' => "other#{subject_name}",
|
94
|
+
'version' => 1
|
95
|
+
} )
|
96
|
+
end
|
97
|
+
|
98
|
+
context "when the schema does not exist" do
|
99
|
+
it "raises an error" do
|
100
|
+
expect do
|
101
|
+
registry.schema_subject_versions(-1)
|
102
|
+
end.to raise_error(Excon::Errors::NotFound)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
69
107
|
describe "#subject_versions" do
|
70
108
|
it "lists all the versions for the subject" do
|
71
109
|
2.times do |n|
|
@@ -27,14 +27,75 @@ describe FakeConfluentSchemaRegistryServer do
|
|
27
27
|
expect(JSON.parse(last_response.body).fetch('id')).to eq expected_id
|
28
28
|
end
|
29
29
|
|
30
|
-
it 'returns
|
30
|
+
it 'returns the same schema ID when invoked with same schema and different subject' do
|
31
31
|
post '/subjects/person/versions', { schema: schema }.to_json, 'CONTENT_TYPE' => 'application/vnd.schemaregistry+json'
|
32
32
|
|
33
33
|
original_id = JSON.parse(last_response.body).fetch('id')
|
34
34
|
|
35
35
|
post '/subjects/happy-person/versions', { schema: schema }.to_json, 'CONTENT_TYPE' => 'application/vnd.schemaregistry+json'
|
36
36
|
|
37
|
-
expect(JSON.parse(last_response.body).fetch('id')).
|
37
|
+
expect(JSON.parse(last_response.body).fetch('id')).to eq original_id
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns a different schema ID when invoked with a different schema' do
|
41
|
+
post '/subjects/person/versions', { schema: schema }.to_json, 'CONTENT_TYPE' => 'application/vnd.schemaregistry+json'
|
42
|
+
|
43
|
+
original_id = JSON.parse(last_response.body).fetch('id')
|
44
|
+
|
45
|
+
other_schema = {
|
46
|
+
type: "record",
|
47
|
+
name: "other",
|
48
|
+
fields: [
|
49
|
+
{ name: "name", type: "string" }
|
50
|
+
]
|
51
|
+
}.to_json
|
52
|
+
|
53
|
+
post '/subjects/person/versions', { schema: other_schema }.to_json, 'CONTENT_TYPE' => 'application/vnd.schemaregistry+json'
|
54
|
+
|
55
|
+
expect(JSON.parse(last_response.body).fetch('id')).to_not eq original_id
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'GET /schemas/ids/:id/versions' do
|
60
|
+
def schema(name:)
|
61
|
+
{
|
62
|
+
type: "record",
|
63
|
+
name: name,
|
64
|
+
fields: [
|
65
|
+
{ name: "name", type: "string" },
|
66
|
+
]
|
67
|
+
}.to_json
|
68
|
+
end
|
69
|
+
|
70
|
+
it "returns array containing subjects and versions for given schema id" do
|
71
|
+
schema1 = schema(name: "name1")
|
72
|
+
schema2 = schema(name: "name2")
|
73
|
+
|
74
|
+
post "/subjects/cats/versions", { schema: schema1 }.to_json, 'CONTENT_TYPE' => 'application/vnd.schemaregistry+json'
|
75
|
+
schema1_id = JSON.parse(last_response.body).fetch('id') # Original cats schema
|
76
|
+
|
77
|
+
post "/subjects/dogs/versions", { schema: schema2 }.to_json, 'CONTENT_TYPE' => 'application/vnd.schemaregistry+json'
|
78
|
+
post "/subjects/cats/versions", { schema: schema2 }.to_json, 'CONTENT_TYPE' => 'application/vnd.schemaregistry+json'
|
79
|
+
schema2_id = JSON.parse(last_response.body).fetch('id') # Changed cats schema == Original Dogs schema
|
80
|
+
|
81
|
+
get "/schemas/ids/#{schema1_id}/versions"
|
82
|
+
result = JSON.parse(last_response.body)
|
83
|
+
|
84
|
+
expect(result).to eq [{
|
85
|
+
'subject' => 'cats',
|
86
|
+
'version' => 1
|
87
|
+
}]
|
88
|
+
|
89
|
+
get "/schemas/ids/#{schema2_id}/versions"
|
90
|
+
result = JSON.parse(last_response.body)
|
91
|
+
|
92
|
+
expect(result).to include( {
|
93
|
+
'subject' => 'cats',
|
94
|
+
'version' => 2
|
95
|
+
}, {
|
96
|
+
'subject' => 'dogs',
|
97
|
+
'version' => 1
|
98
|
+
})
|
38
99
|
end
|
39
100
|
end
|
40
101
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: avro_turf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Schierbeck
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-08-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: avro
|
@@ -90,16 +90,16 @@ dependencies:
|
|
90
90
|
name: fakefs
|
91
91
|
requirement: !ruby/object:Gem::Requirement
|
92
92
|
requirements:
|
93
|
-
- - "
|
93
|
+
- - "<"
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version:
|
95
|
+
version: '3'
|
96
96
|
type: :development
|
97
97
|
prerelease: false
|
98
98
|
version_requirements: !ruby/object:Gem::Requirement
|
99
99
|
requirements:
|
100
|
-
- - "
|
100
|
+
- - "<"
|
101
101
|
- !ruby/object:Gem::Version
|
102
|
-
version:
|
102
|
+
version: '3'
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
104
|
name: webmock
|
105
105
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,7 +156,7 @@ dependencies:
|
|
156
156
|
- - ">="
|
157
157
|
- !ruby/object:Gem::Version
|
158
158
|
version: '0'
|
159
|
-
description:
|
159
|
+
description:
|
160
160
|
email:
|
161
161
|
- dasch@zendesk.com
|
162
162
|
executables: []
|
@@ -251,8 +251,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
251
251
|
- !ruby/object:Gem::Version
|
252
252
|
version: '0'
|
253
253
|
requirements: []
|
254
|
-
rubygems_version: 3.3.
|
255
|
-
signing_key:
|
254
|
+
rubygems_version: 3.0.3.1
|
255
|
+
signing_key:
|
256
256
|
specification_version: 4
|
257
257
|
summary: A library that makes it easier to use the Avro serialization format from
|
258
258
|
Ruby
|