avro_turf 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
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: