avro_turf 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2e286ef91f22f8b2d66e7d91a7fb7eefef94911c
4
- data.tar.gz: 774a729aaa90259a828a5c1080711fa9b95003b1
3
+ metadata.gz: f8eb00c2c29b7e52a48030bca31e7bebf9e84b49
4
+ data.tar.gz: f651f0a33989a08b6b17b43b75fa493e083954ba
5
5
  SHA512:
6
- metadata.gz: ee5f9aaa4bf4df9025818a6de776e673386f8de7a64f5bfa8487e32bf276775a7e2c97f59e0791426b10cea27d0b7ab49a67973bd89fcf46924f989547d17191
7
- data.tar.gz: f1ad05167aa4c72cc727d7d2788240fa19a69144a6c09bb67e4cbb8b08e97319eeb5341172ba9ca28b980dfcfa5d3b6d34c027c5a8f4c411e326c0b185980621
6
+ metadata.gz: 5011cc4ad6e53bf61d3fb88f3c89c17cb195044d37fa9bf91ec5f30003f3cea59b0edd9e7618aaae57e307aadc2cf000057032d87432fb91a8118ba8609d6f7f
7
+ data.tar.gz: 9ffb5c68c4ba50dd19ccf550e3fc861e9ffd7bbaa7117f57f0d7d0a987954f00f95f1385ff6ddf7d5953090623be38f6e2549f89ab1669c81741f66ed7d6292a
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
- spec.add_dependency "avro", "~> 1.7.7"
20
+ spec.add_dependency "avro", ">= 1.7.7", "< 1.9"
21
21
  spec.add_dependency "excon", "~> 0.45.4"
22
22
 
23
23
  spec.add_development_dependency "bundler", "~> 1.7"
@@ -26,4 +26,5 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency "fakefs", "~> 0.6.7"
27
27
  spec.add_development_dependency "webmock"
28
28
  spec.add_development_dependency "sinatra"
29
+ spec.add_development_dependency "json_spec"
29
30
  end
@@ -1,11 +1,21 @@
1
+ require 'avro_turf/schema_registry'
2
+
1
3
  # Caches registrations and lookups to the schema registry in memory.
2
4
  class AvroTurf::CachedSchemaRegistry
5
+
3
6
  def initialize(upstream)
4
7
  @upstream = upstream
5
8
  @schemas_by_id = {}
6
9
  @ids_by_schema = {}
7
10
  end
8
11
 
12
+ # Delegate the following methods to the upstream
13
+ %i(subjects subject_versions subject_version check).each do |name|
14
+ define_method(name) do |*args|
15
+ instance_variable_get(:@upstream).send(name, *args)
16
+ end
17
+ end
18
+
9
19
  def fetch(id)
10
20
  @schemas_by_id[id] ||= @upstream.fetch(id)
11
21
  end
@@ -18,7 +18,7 @@ class AvroTurf::SchemaRegistry
18
18
 
19
19
  def register(subject, schema)
20
20
  data = post("/subjects/#{subject}/versions", body: {
21
- schema: schema
21
+ schema: schema.to_s
22
22
  }.to_json)
23
23
 
24
24
  id = data.fetch("id")
@@ -28,6 +28,29 @@ class AvroTurf::SchemaRegistry
28
28
  id
29
29
  end
30
30
 
31
+ # List all subjects
32
+ def subjects
33
+ get('/subjects')
34
+ end
35
+
36
+ # List all versions for a subject
37
+ def subject_versions(subject)
38
+ get("/subjects/#{subject}/versions")
39
+ end
40
+
41
+ # Get a specific version for a subject
42
+ def subject_version(subject, version = 'latest')
43
+ get("/subjects/#{subject}/versions/#{version}")
44
+ end
45
+
46
+ # Check if a schema exists. Returns nil if not found.
47
+ def check(subject, schema)
48
+ data = post("/subjects/#{subject}",
49
+ expects: [200, 404],
50
+ body: { schema: schema.to_s }.to_json)
51
+ data unless data.has_key?("error_code")
52
+ end
53
+
31
54
  private
32
55
 
33
56
  def get(path, **options)
@@ -39,7 +62,8 @@ class AvroTurf::SchemaRegistry
39
62
  end
40
63
 
41
64
  def request(path, **options)
42
- response = @connection.request(path: path, expects: 200, **options)
65
+ options = { expects: 200 }.merge!(options)
66
+ response = @connection.request(path: path, **options)
43
67
  JSON.parse(response.body)
44
68
  end
45
69
  end
@@ -36,7 +36,7 @@ class AvroTurf::SchemaStore
36
36
  else
37
37
  raise
38
38
  end
39
- rescue Errno::ENOENT
39
+ rescue Errno::ENOENT, Errno::ENAMETOOLONG
40
40
  raise AvroTurf::SchemaNotFoundError, "could not find Avro schema at `#{schema_path}'"
41
41
  end
42
42
 
@@ -46,7 +46,7 @@ class AvroTurf::SchemaStore
46
46
 
47
47
  Dir.glob(pattern) do |schema_path|
48
48
  # Remove the path prefix.
49
- schema_path.sub!(/^\/#{@path}\//, "")
49
+ schema_path.sub!(/^\/?#{@path}\//, "")
50
50
 
51
51
  # Replace `/` with `.` and chop off the file extension.
52
52
  schema_name = File.basename(schema_path.tr("/", "."), ".avsc")
@@ -1,3 +1,3 @@
1
1
  class AvroTurf
2
- VERSION = "0.6.1"
2
+ VERSION = "0.6.2"
3
3
  end
@@ -0,0 +1,41 @@
1
+ require 'webmock/rspec'
2
+ require 'avro_turf/cached_schema_registry'
3
+ require_relative '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
@@ -1,25 +1,84 @@
1
1
  require 'sinatra/base'
2
2
 
3
3
  class FakeSchemaRegistryServer < Sinatra::Base
4
+ SUBJECTS = Hash.new { Array.new }
4
5
  SCHEMAS = []
6
+ SUBJECT_NOT_FOUND = { error_code: 40401, message: 'Subject not found' }.to_json.freeze
7
+ VERSION_NOT_FOUND = { error_code: 40402, message: 'Version not found' }.to_json.freeze
8
+ SCHEMA_NOT_FOUND = { error_code: 40403, message: 'Schema not found' }.to_json.freeze
9
+
10
+ helpers do
11
+ def parse_schema
12
+ request.body.rewind
13
+ JSON.parse(request.body.read).fetch("schema").tap do |schema|
14
+ Avro::Schema.parse(schema)
15
+ end
16
+ end
17
+ end
5
18
 
6
19
  post "/subjects/:subject/versions" do
7
- request.body.rewind
8
- schema = JSON.parse(request.body.read).fetch("schema")
20
+ SCHEMAS << parse_schema
9
21
 
10
- SCHEMAS << schema
11
22
  schema_id = SCHEMAS.size - 1
12
-
23
+ SUBJECTS[params[:subject]] = SUBJECTS[params[:subject]] << schema_id
13
24
  { id: schema_id }.to_json
14
25
  end
15
26
 
16
27
  get "/schemas/ids/:schema_id" do
17
28
  schema = SCHEMAS.at(params[:schema_id].to_i)
18
-
29
+ halt(404, SCHEMA_NOT_FOUND) unless schema
19
30
  { schema: schema }.to_json
20
31
  end
21
32
 
33
+ get "/subjects" do
34
+ SUBJECTS.keys.to_json
35
+ end
36
+
37
+ get "/subjects/:subject/versions" do
38
+ schema_ids = SUBJECTS[params[:subject]]
39
+ halt(404, SUBJECT_NOT_FOUND) if schema_ids.empty?
40
+ (1..schema_ids.size).to_a.to_json
41
+ end
42
+
43
+ get "/subjects/:subject/versions/:version" do
44
+ schema_ids = SUBJECTS[params[:subject]]
45
+ halt(404, SUBJECT_NOT_FOUND) if schema_ids.empty?
46
+
47
+ schema_id = if params[:version] == 'latest'
48
+ schema_ids.last
49
+ else
50
+ schema_ids.at(Integer(params[:version]) - 1)
51
+ end
52
+ halt(404, VERSION_NOT_FOUND) unless schema_id
53
+
54
+ schema = SCHEMAS.at(schema_id)
55
+
56
+ {
57
+ name: params[:subject],
58
+ version: schema_ids.index(schema_id) + 1,
59
+ schema: schema
60
+ }.to_json
61
+ end
62
+
63
+ post "/subjects/:subject" do
64
+ schema = parse_schema
65
+
66
+ # Note: this does not actually handle the same schema registered under
67
+ # multiple subjects
68
+ schema_id = SCHEMAS.index(schema)
69
+
70
+ halt(404, SCHEMA_NOT_FOUND) unless schema_id
71
+
72
+ {
73
+ subject: params[:subject],
74
+ id: schema_id,
75
+ version: SUBJECTS[params[:subject]].index(schema_id) + 1,
76
+ schema: schema
77
+ }.to_json
78
+ end
79
+
22
80
  def self.clear
81
+ SUBJECTS.clear
23
82
  SCHEMAS.clear
24
83
  end
25
84
  end
@@ -49,4 +49,31 @@ describe AvroTurf::Messaging do
49
49
  data = avro.encode(message, schema_name: "person")
50
50
  expect(avro.decode(data, schema_name: "person")).to eq message
51
51
  end
52
+
53
+ context "when active_support/core_ext is present" do
54
+ let(:avro) do
55
+ super().tap do |messaging|
56
+ # Simulate the presence of active_support/core_ext by monkey patching
57
+ # the schema store to monkey patch #to_json on the returned schema.
58
+ schema_store = messaging.instance_variable_get(:@schema_store)
59
+ def schema_store.find(*)
60
+ super.extend(Module.new do
61
+ # Replace to_json on the returned schema with an implementation
62
+ # that returns something similar to active_support/core_ext/json
63
+ def to_json(*args)
64
+ instance_variables.each_with_object(Hash.new) do |ivar, result|
65
+ result[ivar.to_s.sub('@','')] = instance_variable_get(ivar)
66
+ end.to_json(*args)
67
+ end
68
+ end)
69
+ end
70
+ end
71
+ end
72
+
73
+ it "encodes and decodes messages" do
74
+ message = { "full_name" => "John Doe" }
75
+ data = avro.encode(message, schema_name: "person")
76
+ expect(avro.decode(data)).to eq message
77
+ end
78
+ end
52
79
  end
@@ -3,30 +3,7 @@ require 'avro_turf/schema_registry'
3
3
  require_relative 'fake_schema_registry_server'
4
4
 
5
5
  describe AvroTurf::SchemaRegistry do
6
- let(:registry_url) { "http://registry.example.com" }
7
-
8
- before do
9
- stub_request(:any, /^#{registry_url}/).to_rack(FakeSchemaRegistryServer)
10
- FakeSchemaRegistryServer.clear
11
- end
12
-
13
- it "allows registering a schema" do
14
- logger = Logger.new(StringIO.new)
15
- registry = described_class.new(registry_url, logger: logger)
16
-
17
- schema = <<-JSON
18
- {
19
- "type": "record",
20
- "name": "person",
21
- "fields": [
22
- { "name": "name", "type": "string" }
23
- ]
24
- }
25
- JSON
26
-
27
- id = registry.register("some-subject", schema)
28
- fetched_schema = registry.fetch(id)
29
-
30
- expect(JSON.parse(fetched_schema)).to eq JSON.parse(schema)
6
+ it_behaves_like "a schema registry client" do
7
+ let(:registry) { described_class.new(registry_url, logger: logger) }
31
8
  end
32
9
  end
@@ -1,8 +1,11 @@
1
1
  require 'bundler/setup'
2
2
  require 'logger'
3
+ require 'json_spec'
3
4
  require 'fakefs/spec_helpers'
4
5
  require 'avro_turf'
5
6
 
7
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
8
+
6
9
  module Helpers
7
10
  def define_schema(path, content)
8
11
  File.open(File.join("spec/schemas", path), "w") do |f|
@@ -0,0 +1,144 @@
1
+ # This shared example expects a registry variable to be defined
2
+ # with an instance of the registry class being tested.
3
+ shared_examples_for "a schema registry client" do
4
+ let(:logger) { Logger.new(StringIO.new) }
5
+ let(:registry_url) { "http://registry.example.com" }
6
+ let(:subject_name) { "some-subject" }
7
+ let(:schema) do
8
+ {
9
+ type: "record",
10
+ name: "person",
11
+ fields: [
12
+ { name: "name", type: "string" }
13
+ ]
14
+ }.to_json
15
+ end
16
+
17
+ before do
18
+ stub_request(:any, /^#{registry_url}/).to_rack(FakeSchemaRegistryServer)
19
+ FakeSchemaRegistryServer.clear
20
+ end
21
+
22
+ describe "#register and #fetch" do
23
+ it "allows registering a schema" do
24
+ id = registry.register(subject_name, schema)
25
+ fetched_schema = registry.fetch(id)
26
+
27
+ expect(fetched_schema).to eq(schema)
28
+ end
29
+ end
30
+
31
+ describe "#fetch" do
32
+ context "when the schema does not exist" do
33
+ it "raises an error" do
34
+ expect do
35
+ registry.fetch(-1)
36
+ end.to raise_error(Excon::Errors::NotFound)
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "#subjects" do
42
+ it "lists the subjects in the registry" do
43
+ subjects = Array.new(2) { |n| "subject#{n}" }
44
+ subjects.each { |subject| registry.register(subject, schema) }
45
+ expect(registry.subjects).to be_json_eql(subjects.to_json)
46
+ end
47
+ end
48
+
49
+ describe "#subject_versions" do
50
+ it "lists all the versions for the subject" do
51
+ 2.times do |n|
52
+ registry.register(subject_name,
53
+ { type: :record, name: "r#{n}", fields: [] }.to_json)
54
+ end
55
+ expect(registry.subject_versions(subject_name))
56
+ .to be_json_eql((1..2).to_a.to_json)
57
+ end
58
+
59
+ context "when the subject does not exist" do
60
+ let(:subject_name) { 'missing' }
61
+
62
+ it "raises an error" do
63
+ expect do
64
+ registry.subject_versions(subject_name).inspect
65
+ end.to raise_error(Excon::Errors::NotFound)
66
+ end
67
+ end
68
+ end
69
+
70
+ describe "#subject_version" do
71
+ before do
72
+ 2.times do |n|
73
+ registry.register(subject_name,
74
+ { type: :record, name: "r#{n}", fields: [] }.to_json)
75
+ end
76
+ end
77
+ let(:expected) do
78
+ {
79
+ name: subject_name,
80
+ version: 1,
81
+ schema: { type: :record, name: "r0", fields: [] }.to_json
82
+ }.to_json
83
+ end
84
+
85
+ it "returns a specific version of a schema" do
86
+ expect(registry.subject_version(subject_name, 1))
87
+ .to eq(JSON.parse(expected))
88
+ end
89
+
90
+ context "when the version is not specified" do
91
+ let(:expected) do
92
+ {
93
+ name: subject_name,
94
+ version: 2,
95
+ schema: { type: :record, name: "r1", fields: [] }.to_json
96
+ }.to_json
97
+ end
98
+
99
+ it "returns the latest version" do
100
+ expect(registry.subject_version(subject_name))
101
+ .to eq(JSON.parse(expected))
102
+ end
103
+ end
104
+
105
+ context "when the subject does not exist" do
106
+ it "raises an error" do
107
+ expect do
108
+ registry.subject_version('missing')
109
+ end.to raise_error(Excon::Errors::NotFound)
110
+ end
111
+ end
112
+
113
+ context "when the version does not exist" do
114
+ it "raises an error" do
115
+ expect do
116
+ registry.subject_version(subject_name, 3)
117
+ end.to raise_error(Excon::Errors::NotFound)
118
+ end
119
+ end
120
+ end
121
+
122
+ describe "#check" do
123
+ context "when the schema exists" do
124
+ let!(:schema_id) { registry.register(subject_name, schema) }
125
+ let(:expected) do
126
+ {
127
+ subject: subject_name,
128
+ id: schema_id,
129
+ version: 1,
130
+ schema: schema
131
+ }.to_json
132
+ end
133
+ it "returns the schema details" do
134
+ expect(registry.check(subject_name, schema)).to eq(JSON.parse(expected))
135
+ end
136
+ end
137
+
138
+ context "when the schema is not registered" do
139
+ it "returns nil" do
140
+ expect(registry.check("missing", schema)).to be_nil
141
+ end
142
+ end
143
+ end
144
+ end
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: avro_turf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Schierbeck
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-02 00:00:00.000000000 Z
11
+ date: 2016-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: avro
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 1.7.7
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '1.9'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: 1.7.7
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.9'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: excon
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +128,20 @@ dependencies:
122
128
  - - ">="
123
129
  - !ruby/object:Gem::Version
124
130
  version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: json_spec
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
125
145
  description:
126
146
  email:
127
147
  - dasch@zendesk.com
@@ -159,6 +179,7 @@ files:
159
179
  - perf/encoding_speed.rb
160
180
  - perf/person.avsc
161
181
  - spec/avro_turf_spec.rb
182
+ - spec/cached_schema_registry_spec.rb
162
183
  - spec/core_ext/date_spec.rb
163
184
  - spec/core_ext/enumerable_spec.rb
164
185
  - spec/core_ext/false_class_spec.rb
@@ -174,6 +195,7 @@ files:
174
195
  - spec/schema_registry_spec.rb
175
196
  - spec/schema_store_spec.rb
176
197
  - spec/spec_helper.rb
198
+ - spec/support/schema_registry_context.rb
177
199
  homepage: https://github.com/dasch/avro_turf
178
200
  licenses:
179
201
  - MIT
@@ -201,6 +223,7 @@ summary: A library that makes it easier to use the Avro serialization format fro
201
223
  Ruby
202
224
  test_files:
203
225
  - spec/avro_turf_spec.rb
226
+ - spec/cached_schema_registry_spec.rb
204
227
  - spec/core_ext/date_spec.rb
205
228
  - spec/core_ext/enumerable_spec.rb
206
229
  - spec/core_ext/false_class_spec.rb
@@ -216,4 +239,5 @@ test_files:
216
239
  - spec/schema_registry_spec.rb
217
240
  - spec/schema_store_spec.rb
218
241
  - spec/spec_helper.rb
242
+ - spec/support/schema_registry_context.rb
219
243
  has_rdoc: