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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +174 -0
  7. data/Rakefile +2 -0
  8. data/avro_turf.gemspec +30 -0
  9. data/circle.yml +4 -0
  10. data/lib/avro_turf.rb +105 -0
  11. data/lib/avro_turf/cached_schema_registry.rb +26 -0
  12. data/lib/avro_turf/core_ext.rb +10 -0
  13. data/lib/avro_turf/core_ext/date.rb +5 -0
  14. data/lib/avro_turf/core_ext/enumerable.rb +5 -0
  15. data/lib/avro_turf/core_ext/false_class.rb +5 -0
  16. data/lib/avro_turf/core_ext/hash.rb +7 -0
  17. data/lib/avro_turf/core_ext/nil_class.rb +5 -0
  18. data/lib/avro_turf/core_ext/numeric.rb +5 -0
  19. data/lib/avro_turf/core_ext/string.rb +5 -0
  20. data/lib/avro_turf/core_ext/symbol.rb +5 -0
  21. data/lib/avro_turf/core_ext/time.rb +5 -0
  22. data/lib/avro_turf/core_ext/true_class.rb +5 -0
  23. data/lib/avro_turf/messaging.rb +102 -0
  24. data/lib/avro_turf/schema_registry.rb +79 -0
  25. data/lib/avro_turf/schema_store.rb +58 -0
  26. data/lib/avro_turf/schema_to_avro_patch.rb +52 -0
  27. data/lib/avro_turf/test/fake_schema_registry_server.rb +84 -0
  28. data/lib/avro_turf/version.rb +3 -0
  29. data/perf/address.avsc +14 -0
  30. data/perf/encoding_size.rb +26 -0
  31. data/perf/encoding_speed.rb +30 -0
  32. data/perf/person.avsc +14 -0
  33. data/spec/avro_turf_spec.rb +161 -0
  34. data/spec/cached_schema_registry_spec.rb +41 -0
  35. data/spec/core_ext/date_spec.rb +6 -0
  36. data/spec/core_ext/enumerable_spec.rb +12 -0
  37. data/spec/core_ext/false_class_spec.rb +5 -0
  38. data/spec/core_ext/hash_spec.rb +8 -0
  39. data/spec/core_ext/nil_class_spec.rb +5 -0
  40. data/spec/core_ext/numeric_spec.rb +6 -0
  41. data/spec/core_ext/string_spec.rb +5 -0
  42. data/spec/core_ext/symbol_spec.rb +5 -0
  43. data/spec/core_ext/time_spec.rb +6 -0
  44. data/spec/core_ext/true_class_spec.rb +5 -0
  45. data/spec/messaging_spec.rb +112 -0
  46. data/spec/schema_registry_spec.rb +9 -0
  47. data/spec/schema_store_spec.rb +253 -0
  48. data/spec/schema_to_avro_patch_spec.rb +66 -0
  49. data/spec/spec_helper.rb +20 -0
  50. data/spec/support/schema_registry_context.rb +190 -0
  51. 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,14 @@
1
+ {
2
+ "name": "person",
3
+ "type": "record",
4
+ "fields": [
5
+ {
6
+ "name": "name",
7
+ "type": "string"
8
+ },
9
+ {
10
+ "name": "address",
11
+ "type": "address"
12
+ }
13
+ ]
14
+ }
@@ -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,6 @@
1
+ describe Date, "#as_avro" do
2
+ it "returns an ISO8601 string describing the time" do
3
+ date = Date.today
4
+ expect(date.as_avro).to eq(date.iso8601)
5
+ end
6
+ end
@@ -0,0 +1,12 @@
1
+ describe Enumerable, "#as_avro" do
2
+ it "returns an array" do
3
+ expect(Set.new.as_avro).to eq []
4
+ end
5
+
6
+ it "coerces the items to Avro" do
7
+ x = double(as_avro: "x")
8
+ y = double(as_avro: "y")
9
+
10
+ expect([x, y].as_avro).to eq ["x", "y"]
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ describe FalseClass, "#as_avro" do
2
+ it "returns itself" do
3
+ expect(false.as_avro).to eq false
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ describe Hash, "#as_avro" do
2
+ it "coerces the keys and values to Avro" do
3
+ x = double(as_avro: "x")
4
+ y = double(as_avro: "y")
5
+
6
+ expect({ x => y }.as_avro).to eq({ "x" => "y" })
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ describe NilClass, "#as_avro" do
2
+ it "returns itself" do
3
+ expect(nil.as_avro).to eq nil
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ describe Numeric, "#as_avro" do
2
+ it "returns the number itself" do
3
+ expect(42.as_avro).to eq 42
4
+ expect(4.2.as_avro).to eq 4.2
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ describe String, "#as_avro" do
2
+ it "returns itself" do
3
+ expect("hello".as_avro).to eq "hello"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ describe Symbol, "#as_avro" do
2
+ it "returns the String representation of the Symbol" do
3
+ expect(:hello.as_avro).to eq("hello")
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ describe Time, "#as_avro" do
2
+ it "returns an ISO8601 string describing the time" do
3
+ time = Time.now
4
+ expect(time.as_avro).to eq(time.iso8601)
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ describe TrueClass, "#as_avro" do
2
+ it "returns itself" do
3
+ expect(true.as_avro).to eq true
4
+ end
5
+ 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