avro_turf 0.9.0 → 1.2.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 +2 -1
- data/.github/workflows/ruby.yml +20 -0
- data/.github/workflows/stale.yml +19 -0
- data/CHANGELOG.md +24 -1
- data/Gemfile +0 -3
- data/README.md +75 -1
- data/avro_turf.gemspec +6 -6
- data/lib/avro_turf/confluent_schema_registry.rb +23 -4
- data/lib/avro_turf/disk_cache.rb +45 -0
- data/lib/avro_turf/messaging.rb +90 -22
- data/lib/avro_turf/schema_store.rb +25 -6
- data/lib/avro_turf/version.rb +1 -1
- data/spec/confluent_schema_registry_spec.rb +13 -1
- data/spec/disk_cached_confluent_schema_registry_spec.rb +47 -0
- data/spec/messaging_spec.rb +256 -3
- data/spec/schema_store_spec.rb +98 -0
- metadata +18 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8fc2d29f1112e649cc2cd5ec96f2126a0ea5fa8f46ebee01d908cdceeac8da0d
|
4
|
+
data.tar.gz: e1989abd9d9b0e5db24f360e96e7bce7ce18e5f488cc794d25a2b3fae6102e66
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f562a66ba746d2c0e4bee8b8f3a0e7fcf65900f261eca444986c8ff6c68231e67aa9c4dc659213721d0d09c65fd9273e12e158938c0773baa48cf86598d6bef9
|
7
|
+
data.tar.gz: 53e3640900a3b64038fe063f5dadab2049417490be1833bb5f17124307abe628af64ea0c68b5bdc2bd4ef2f6e58bbfac287696eaf5ece9a35ddb7c2ab8e5127e
|
data/.circleci/config.yml
CHANGED
@@ -17,6 +17,7 @@ jobs:
|
|
17
17
|
- v1-dep-master-
|
18
18
|
# Any branch if there are none on the default branch - this should be unnecessary if you have your default branch configured correctly
|
19
19
|
- v1-dep-
|
20
|
+
- run: gem install bundler --no-document
|
20
21
|
- run: 'bundle check --path=vendor/bundle || bundle install --path=vendor/bundle --jobs=4 --retry=3'
|
21
22
|
# Save dependency cache
|
22
23
|
- save_cache:
|
@@ -26,7 +27,7 @@ jobs:
|
|
26
27
|
- ~/.bundle
|
27
28
|
- run: mkdir -p $CIRCLE_TEST_REPORTS/rspec
|
28
29
|
- run:
|
29
|
-
command: bundle exec rspec --color --require spec_helper --format
|
30
|
+
command: bundle exec rspec --color --require spec_helper --format progress
|
30
31
|
- store_test_results:
|
31
32
|
path: /tmp/circleci-test-results
|
32
33
|
- store_artifacts:
|
@@ -0,0 +1,20 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
|
10
|
+
steps:
|
11
|
+
- uses: actions/checkout@v1
|
12
|
+
- name: Set up Ruby 2.6
|
13
|
+
uses: actions/setup-ruby@v1
|
14
|
+
with:
|
15
|
+
ruby-version: 2.6.x
|
16
|
+
- name: Build and test with RSpec
|
17
|
+
run: |
|
18
|
+
gem install bundler
|
19
|
+
bundle install --jobs 4 --retry 3
|
20
|
+
bundle exec rspec
|
@@ -0,0 +1,19 @@
|
|
1
|
+
name: Mark stale issues and pull requests
|
2
|
+
|
3
|
+
on:
|
4
|
+
schedule:
|
5
|
+
- cron: "0 0 * * *"
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
stale:
|
9
|
+
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
|
12
|
+
steps:
|
13
|
+
- uses: actions/stale@v1
|
14
|
+
with:
|
15
|
+
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
16
|
+
stale-issue-message: 'Stale issue message'
|
17
|
+
stale-pr-message: 'Stale pull request message'
|
18
|
+
stale-issue-label: 'no-issue-activity'
|
19
|
+
stale-pr-label: 'no-pr-activity'
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,30 @@
|
|
1
|
-
#
|
1
|
+
# AvroTurf
|
2
2
|
|
3
3
|
## Unreleased
|
4
4
|
|
5
|
+
## v1.2.0
|
6
|
+
|
7
|
+
- Expose `fetch_schema`, `fetch_schema_by_id` and `register_schema` schema in `Messaging` interface (#117, #119)
|
8
|
+
- Add ability to validate message before encoding in `Messaging#encode` interface (#116, #118)
|
9
|
+
|
10
|
+
## v1.1.0
|
11
|
+
|
12
|
+
- Compatibility with Avro v1.10.x.
|
13
|
+
|
14
|
+
## v1.0.0
|
15
|
+
|
16
|
+
- Stop caching nested sub-schemas (#111)
|
17
|
+
|
18
|
+
## v0.11.0
|
19
|
+
|
20
|
+
- Add proxy support (#107)
|
21
|
+
- Adding support for client certs (#109)
|
22
|
+
|
23
|
+
## v0.10.0
|
24
|
+
|
25
|
+
- Add more disk caching (#103)
|
26
|
+
- Include schema information when decoding (#100, #101, #104)
|
27
|
+
|
5
28
|
## v0.9.0
|
6
29
|
|
7
30
|
- Compatibility with Avro v1.9.0 (#94)
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -16,6 +16,48 @@ These classes have been renamed to `AvroTurf::ConfluentSchemaRegistry`,
|
|
16
16
|
|
17
17
|
The aliases for the original names will be removed in a future release.
|
18
18
|
|
19
|
+
## Note about finding nested schemas
|
20
|
+
|
21
|
+
As of AvroTurf version 0.12.0, only top-level schemas that have their own .avsc file will be loaded and resolvable by the `AvroTurf::SchemaStore#find` method. This change will likely not affect most users. However, if you use `AvroTurf::SchemaStore#load_schemas!` to pre-cache all your schemas and then rely on `AvroTurf::SchemaStore#find` to access nested schemas that are not defined by their own .avsc files, your code may stop working when you upgrade to v0.12.0.
|
22
|
+
|
23
|
+
As an example, if you have a `person` schema (defined in `my/schemas/contacts/person.avsc`) that defines a nested `address` schema like this:
|
24
|
+
|
25
|
+
```json
|
26
|
+
{
|
27
|
+
"name": "person",
|
28
|
+
"namespace": "contacts",
|
29
|
+
"type": "record",
|
30
|
+
"fields": [
|
31
|
+
{
|
32
|
+
"name": "address",
|
33
|
+
"type": {
|
34
|
+
"name": "address",
|
35
|
+
"type": "record",
|
36
|
+
"fields": [
|
37
|
+
{ "name": "addr1", "type": "string" },
|
38
|
+
{ "name": "addr2", "type": "string" },
|
39
|
+
{ "name": "city", "type": "string" },
|
40
|
+
{ "name": "zip", "type": "string" }
|
41
|
+
]
|
42
|
+
}
|
43
|
+
}
|
44
|
+
]
|
45
|
+
}
|
46
|
+
```
|
47
|
+
...this will no longer work in v0.12.0:
|
48
|
+
```ruby
|
49
|
+
store = AvroTurf::SchemaStore.new(path: 'my/schemas')
|
50
|
+
store.load_schemas!
|
51
|
+
|
52
|
+
# Accessing 'person' is correct and works fine.
|
53
|
+
person = store.find('person', 'contacts') # my/schemas/contacts/person.avsc exists
|
54
|
+
|
55
|
+
# Trying to access 'address' raises AvroTurf::SchemaNotFoundError
|
56
|
+
address = store.find('address', 'contacts') # my/schemas/contacts/address.avsc is not found
|
57
|
+
```
|
58
|
+
|
59
|
+
For details and context, see [this pull request](https://github.com/dasch/avro_turf/pull/111).
|
60
|
+
|
19
61
|
## Installation
|
20
62
|
|
21
63
|
Add this line to your application's Gemfile:
|
@@ -126,13 +168,45 @@ data = avro.encode({ "title" => "hello, world" }, schema_name: "greeting")
|
|
126
168
|
|
127
169
|
# If you don't want to automatically register new schemas, you can pass explicitly
|
128
170
|
# subject and version to specify which schema should be used for encoding.
|
129
|
-
# It will fetch that schema from the registry and cache it. Subsequent instances
|
171
|
+
# It will fetch that schema from the registry and cache it. Subsequent instances
|
130
172
|
# of the same schema version will be served by the cache.
|
131
173
|
data = avro.encode({ "title" => "hello, world" }, subject: 'greeting', version: 1)
|
132
174
|
|
175
|
+
# You can also pass explicitly schema_id to specify which schema
|
176
|
+
# should be used for encoding.
|
177
|
+
# It will fetch that schema from the registry and cache it. Subsequent instances
|
178
|
+
# of the same schema version will be served by the cache.
|
179
|
+
data = avro.encode({ "title" => "hello, world" }, schema_id: 2)
|
180
|
+
|
181
|
+
# Message can be validated before encoding to get a description of problem through
|
182
|
+
# Avro::SchemaValidator::ValidationError exception
|
183
|
+
data = avro.encode({ "titl" => "hello, world" }, schema_name: "greeting", validate: true)
|
184
|
+
|
133
185
|
# When decoding, the schema will be fetched from the registry and cached. Subsequent
|
134
186
|
# instances of the same schema id will be served by the cache.
|
135
187
|
avro.decode(data) #=> { "title" => "hello, world" }
|
188
|
+
|
189
|
+
# If you want to get decoded message as well as the schema used to encode the message,
|
190
|
+
# you can use `#decode_message` method.
|
191
|
+
result = avro.decode_message(data)
|
192
|
+
result.message #=> { "title" => "hello, world" }
|
193
|
+
result.schema_id #=> 3
|
194
|
+
result.writer_schema #=> #<Avro::Schema: ...>
|
195
|
+
result.reader_schema #=> nil
|
196
|
+
|
197
|
+
# You can also work with schema through this interface:
|
198
|
+
# Fetch latest schema for subject from registry
|
199
|
+
schema, schema_id = avro.fetch_schema(subject: 'greeting')
|
200
|
+
# Fetch specific version
|
201
|
+
schema, schema_id = avro.fetch_schema(subject: 'greeting', version: 1)
|
202
|
+
# Fetch schema by id
|
203
|
+
schema, schema_id = avro.fetch_schema_by_id(3)
|
204
|
+
# Register schema fetched from store by name
|
205
|
+
schema, schema_id = avro.register_schema(schema_name: 'greeting')
|
206
|
+
# Specify namespace (same as schema_name: 'somewhere.greeting')
|
207
|
+
schema, schema_id = avro.register_schema(schema_name: 'greeting', namespace: 'somewhere')
|
208
|
+
# Customize subject under which to register schema
|
209
|
+
schema, schema_id = avro.register_schema(schema_name: 'greeting', namespace: 'somewhere', subject: 'test')
|
136
210
|
```
|
137
211
|
|
138
212
|
### Confluent Schema Registry Client
|
data/avro_turf.gemspec
CHANGED
@@ -17,13 +17,13 @@ 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", "< 1.
|
21
|
-
spec.add_dependency "excon", "~> 0.
|
20
|
+
spec.add_dependency "avro", ">= 1.7.7", "< 1.11"
|
21
|
+
spec.add_dependency "excon", "~> 0.71"
|
22
22
|
|
23
|
-
spec.add_development_dependency "bundler", "~>
|
24
|
-
spec.add_development_dependency "rake", "~>
|
25
|
-
spec.add_development_dependency "rspec", "~> 3.2
|
26
|
-
spec.add_development_dependency "fakefs", "~> 0.
|
23
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
24
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
26
|
+
spec.add_development_dependency "fakefs", "~> 0.20.0"
|
27
27
|
spec.add_development_dependency "webmock"
|
28
28
|
spec.add_development_dependency "sinatra"
|
29
29
|
spec.add_development_dependency "json_spec"
|
@@ -3,11 +3,30 @@ require 'excon'
|
|
3
3
|
class AvroTurf::ConfluentSchemaRegistry
|
4
4
|
CONTENT_TYPE = "application/vnd.schemaregistry.v1+json".freeze
|
5
5
|
|
6
|
-
def initialize(
|
6
|
+
def initialize(
|
7
|
+
url,
|
8
|
+
logger: Logger.new($stdout),
|
9
|
+
proxy: nil,
|
10
|
+
client_cert: nil,
|
11
|
+
client_key: nil,
|
12
|
+
client_key_pass: nil,
|
13
|
+
client_cert_data: nil,
|
14
|
+
client_key_data: nil
|
15
|
+
)
|
7
16
|
@logger = logger
|
8
|
-
|
9
|
-
"Content-Type" => CONTENT_TYPE
|
10
|
-
}
|
17
|
+
headers = {
|
18
|
+
"Content-Type" => CONTENT_TYPE
|
19
|
+
}
|
20
|
+
headers[:proxy] = proxy if proxy&.present?
|
21
|
+
@connection = Excon.new(
|
22
|
+
url,
|
23
|
+
headers: headers,
|
24
|
+
client_cert: client_cert,
|
25
|
+
client_key: client_key,
|
26
|
+
client_key_pass: client_key_pass,
|
27
|
+
client_cert_data: client_cert_data,
|
28
|
+
client_key_data: client_key_data
|
29
|
+
)
|
11
30
|
end
|
12
31
|
|
13
32
|
def fetch(id)
|
data/lib/avro_turf/disk_cache.rb
CHANGED
@@ -11,6 +11,9 @@ class AvroTurf::DiskCache < AvroTurf::InMemoryCache
|
|
11
11
|
|
12
12
|
@ids_by_schema_path = File.join(disk_path, 'ids_by_schema.json')
|
13
13
|
@ids_by_schema = JSON.parse(File.read(@ids_by_schema_path)) if File.exist?(@ids_by_schema_path)
|
14
|
+
|
15
|
+
@schemas_by_subject_version_path = File.join(disk_path, 'schemas_by_subject_version.json')
|
16
|
+
@schemas_by_subject_version = {}
|
14
17
|
end
|
15
18
|
|
16
19
|
# override
|
@@ -35,4 +38,46 @@ class AvroTurf::DiskCache < AvroTurf::InMemoryCache
|
|
35
38
|
File.write(@ids_by_schema_path, JSON.pretty_generate(@ids_by_schema))
|
36
39
|
return value
|
37
40
|
end
|
41
|
+
|
42
|
+
# checks instance var (in-memory cache) for schema
|
43
|
+
# checks disk cache if in-memory cache doesn't exists
|
44
|
+
# if file exists but no in-memory cache, read from file and sync in-memory cache
|
45
|
+
# finally, if file doesn't exist return nil
|
46
|
+
def lookup_by_version(subject, version)
|
47
|
+
key = "#{subject}#{version}"
|
48
|
+
schema = @schemas_by_subject_version[key]
|
49
|
+
|
50
|
+
return schema unless schema.nil?
|
51
|
+
|
52
|
+
hash = JSON.parse(File.read(@schemas_by_subject_version_path)) if File.exist?(@schemas_by_subject_version_path)
|
53
|
+
if hash
|
54
|
+
@schemas_by_subject_version = hash
|
55
|
+
@schemas_by_subject_version[key]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# check if file exists and parse json into a hash
|
60
|
+
# if file exists take json and overwite/insert schema at key
|
61
|
+
# if file doesn't exist create new hash
|
62
|
+
# write the new/updated hash to file
|
63
|
+
# update instance var (in memory-cache) to match
|
64
|
+
def store_by_version(subject, version, schema)
|
65
|
+
key = "#{subject}#{version}"
|
66
|
+
hash = JSON.parse(File.read(@schemas_by_subject_version_path)) if File.exist?(@schemas_by_subject_version_path)
|
67
|
+
hash = if hash
|
68
|
+
hash[key] = schema
|
69
|
+
hash
|
70
|
+
else
|
71
|
+
{key => schema}
|
72
|
+
end
|
73
|
+
|
74
|
+
write_to_disk_cache(@schemas_by_subject_version_path, hash)
|
75
|
+
|
76
|
+
@schemas_by_subject_version = hash
|
77
|
+
@schemas_by_subject_version[key]
|
78
|
+
end
|
79
|
+
|
80
|
+
private def write_to_disk_cache(path, hash)
|
81
|
+
File.write(path, JSON.pretty_generate(hash))
|
82
|
+
end
|
38
83
|
end
|
data/lib/avro_turf/messaging.rb
CHANGED
@@ -21,21 +21,53 @@ class AvroTurf
|
|
21
21
|
# 1: https://github.com/confluentinc/schema-registry
|
22
22
|
class Messaging
|
23
23
|
MAGIC_BYTE = [0].pack("C").freeze
|
24
|
+
DecodedMessage = Struct.new(:schema_id, :writer_schema, :reader_schema, :message)
|
25
|
+
private_constant(:DecodedMessage)
|
24
26
|
|
25
27
|
# Instantiate a new Messaging instance with the given configuration.
|
26
28
|
#
|
27
|
-
# registry
|
28
|
-
#
|
29
|
-
# registry_url
|
30
|
-
# schema_store
|
31
|
-
# schemas_path
|
32
|
-
# namespace
|
33
|
-
# logger
|
34
|
-
|
29
|
+
# registry - A schema registry object that responds to all methods in the
|
30
|
+
# AvroTurf::ConfluentSchemaRegistry interface.
|
31
|
+
# registry_url - The String URL of the schema registry that should be used.
|
32
|
+
# schema_store - A schema store object that responds to #find(schema_name, namespace).
|
33
|
+
# schemas_path - The String file system path where local schemas are stored.
|
34
|
+
# namespace - The String default schema namespace.
|
35
|
+
# logger - The Logger that should be used to log information (optional).
|
36
|
+
# proxy - Forward the request via proxy (optional).
|
37
|
+
# client_cert - Name of file containing client certificate (optional).
|
38
|
+
# client_key - Name of file containing client private key to go with client_cert (optional).
|
39
|
+
# client_key_pass - Password to go with client_key (optional).
|
40
|
+
# client_cert_data - In-memory client certificate (optional).
|
41
|
+
# client_key_data - In-memory client private key to go with client_cert_data (optional).
|
42
|
+
def initialize(
|
43
|
+
registry: nil,
|
44
|
+
registry_url: nil,
|
45
|
+
schema_store: nil,
|
46
|
+
schemas_path: nil,
|
47
|
+
namespace: nil,
|
48
|
+
logger: nil,
|
49
|
+
proxy: nil,
|
50
|
+
client_cert: nil,
|
51
|
+
client_key: nil,
|
52
|
+
client_key_pass: nil,
|
53
|
+
client_cert_data: nil,
|
54
|
+
client_key_data: nil
|
55
|
+
)
|
35
56
|
@logger = logger || Logger.new($stderr)
|
36
57
|
@namespace = namespace
|
37
58
|
@schema_store = schema_store || SchemaStore.new(path: schemas_path || DEFAULT_SCHEMAS_PATH)
|
38
|
-
@registry = registry || CachedConfluentSchemaRegistry.new(
|
59
|
+
@registry = registry || CachedConfluentSchemaRegistry.new(
|
60
|
+
ConfluentSchemaRegistry.new(
|
61
|
+
registry_url,
|
62
|
+
logger: @logger,
|
63
|
+
proxy: proxy,
|
64
|
+
client_cert: client_cert,
|
65
|
+
client_key: client_key,
|
66
|
+
client_key_pass: client_key_pass,
|
67
|
+
client_cert_data: client_cert_data,
|
68
|
+
client_key_data: client_key_data
|
69
|
+
)
|
70
|
+
)
|
39
71
|
@schemas_by_id = {}
|
40
72
|
end
|
41
73
|
|
@@ -50,15 +82,26 @@ class AvroTurf
|
|
50
82
|
# the schema registry (optional).
|
51
83
|
# version - The integer version of the schema that should be used to decode
|
52
84
|
# the data. Must match the schema used when encoding (optional).
|
85
|
+
# schema_id - The integer id of the schema that should be used to encode
|
86
|
+
# the data.
|
87
|
+
# validate - The boolean for performing complete message validation before
|
88
|
+
# encoding it, Avro::SchemaValidator::ValidationError with
|
89
|
+
# a descriptive message will be raised in case of invalid message.
|
53
90
|
#
|
54
91
|
# Returns the encoded data as a String.
|
55
|
-
def encode(message, schema_name: nil, namespace: @namespace, subject: nil, version: nil)
|
56
|
-
|
57
|
-
|
92
|
+
def encode(message, schema_name: nil, namespace: @namespace, subject: nil, version: nil, schema_id: nil, validate: false)
|
93
|
+
schema, schema_id = if schema_id
|
94
|
+
fetch_schema_by_id(schema_id)
|
95
|
+
elsif subject && version
|
96
|
+
fetch_schema(subject: subject, version: version)
|
58
97
|
elsif schema_name
|
59
|
-
register_schema(subject, schema_name, namespace)
|
98
|
+
register_schema(subject: subject, schema_name: schema_name, namespace: namespace)
|
60
99
|
else
|
61
|
-
raise ArgumentError.new('Neither schema_name nor subject + version provided to determine the schema.')
|
100
|
+
raise ArgumentError.new('Neither schema_name nor schema_id nor subject + version provided to determine the schema.')
|
101
|
+
end
|
102
|
+
|
103
|
+
if validate
|
104
|
+
Avro::SchemaValidator.validate!(schema, message, recursive: true, encoded: false, fail_on_extra_fields: true)
|
62
105
|
end
|
63
106
|
|
64
107
|
stream = StringIO.new
|
@@ -76,7 +119,11 @@ class AvroTurf
|
|
76
119
|
|
77
120
|
stream.string
|
78
121
|
rescue Excon::Error::NotFound
|
79
|
-
|
122
|
+
if schema_id
|
123
|
+
raise SchemaNotFoundError.new("Schema with id: #{schema_id} is not found on registry")
|
124
|
+
else
|
125
|
+
raise SchemaNotFoundError.new("Schema with subject: `#{subject}` version: `#{version}` is not found on registry")
|
126
|
+
end
|
80
127
|
end
|
81
128
|
|
82
129
|
# Decodes data into the original message.
|
@@ -88,6 +135,20 @@ class AvroTurf
|
|
88
135
|
#
|
89
136
|
# Returns the decoded message.
|
90
137
|
def decode(data, schema_name: nil, namespace: @namespace)
|
138
|
+
decode_message(data, schema_name: schema_name, namespace: namespace).message
|
139
|
+
end
|
140
|
+
|
141
|
+
# Decodes data into the original message.
|
142
|
+
#
|
143
|
+
# data - A String containing encoded data.
|
144
|
+
# schema_name - The String name of the schema that should be used to decode
|
145
|
+
# the data. Must match the schema used when encoding (optional).
|
146
|
+
# namespace - The namespace of the schema (optional).
|
147
|
+
#
|
148
|
+
# Returns Struct with the next attributes:
|
149
|
+
# schema_id - The integer id of schema used to encode the message
|
150
|
+
# message - The decoded message
|
151
|
+
def decode_message(data, schema_name: nil, namespace: @namespace)
|
91
152
|
readers_schema = schema_name && @schema_store.find(schema_name, namespace)
|
92
153
|
stream = StringIO.new(data)
|
93
154
|
decoder = Avro::IO::BinaryDecoder.new(stream)
|
@@ -108,29 +169,36 @@ class AvroTurf
|
|
108
169
|
end
|
109
170
|
|
110
171
|
reader = Avro::IO::DatumReader.new(writers_schema, readers_schema)
|
111
|
-
reader.read(decoder)
|
172
|
+
message = reader.read(decoder)
|
173
|
+
|
174
|
+
DecodedMessage.new(schema_id, writers_schema, readers_schema, message)
|
112
175
|
rescue Excon::Error::NotFound
|
113
176
|
raise SchemaNotFoundError.new("Schema with id: #{schema_id} is not found on registry")
|
114
177
|
end
|
115
178
|
|
116
|
-
private
|
117
|
-
|
118
179
|
# Providing subject and version to determine the schema,
|
119
180
|
# which skips the auto registeration of schema on the schema registry.
|
120
181
|
# Fetch the schema from registry with the provided subject name and version.
|
121
|
-
def fetch_schema(subject
|
182
|
+
def fetch_schema(subject:, version: 'latest')
|
122
183
|
schema_data = @registry.subject_version(subject, version)
|
123
184
|
schema_id = schema_data.fetch('id')
|
124
185
|
schema = Avro::Schema.parse(schema_data.fetch('schema'))
|
125
|
-
[
|
186
|
+
[schema, schema_id]
|
187
|
+
end
|
188
|
+
|
189
|
+
# Fetch the schema from registry with the provided schema_id.
|
190
|
+
def fetch_schema_by_id(schema_id)
|
191
|
+
schema_json = @registry.fetch(schema_id)
|
192
|
+
schema = Avro::Schema.parse(schema_json)
|
193
|
+
[schema, schema_id]
|
126
194
|
end
|
127
195
|
|
128
196
|
# Schemas are registered under the full name of the top level Avro record
|
129
197
|
# type, or `subject` if it's provided.
|
130
|
-
def register_schema(subject
|
198
|
+
def register_schema(schema_name:, subject: nil, namespace: nil)
|
131
199
|
schema = @schema_store.find(schema_name, namespace)
|
132
200
|
schema_id = @registry.register(subject || schema.fullname, schema)
|
133
|
-
[
|
201
|
+
[schema, schema_id]
|
134
202
|
end
|
135
203
|
end
|
136
204
|
end
|
@@ -46,26 +46,45 @@ class AvroTurf::SchemaStore
|
|
46
46
|
|
47
47
|
# Loads single schema
|
48
48
|
# Such method is not thread-safe, do not call it of from mutex synchronization routine
|
49
|
-
def load_schema!(fullname, namespace = nil)
|
49
|
+
def load_schema!(fullname, namespace = nil, local_schemas_cache = {})
|
50
50
|
*namespace, schema_name = fullname.split(".")
|
51
51
|
schema_path = File.join(@path, *namespace, schema_name + ".avsc")
|
52
52
|
schema_json = JSON.parse(File.read(schema_path))
|
53
|
-
schema = Avro::Schema.real_parse(schema_json, @schemas)
|
54
53
|
|
54
|
+
schema = Avro::Schema.real_parse(schema_json, local_schemas_cache)
|
55
|
+
|
56
|
+
# Don't cache the parsed schema until after its fullname is validated
|
55
57
|
if schema.respond_to?(:fullname) && schema.fullname != fullname
|
56
58
|
raise AvroTurf::SchemaError, "expected schema `#{schema_path}' to define type `#{fullname}'"
|
57
59
|
end
|
58
60
|
|
61
|
+
# Cache only this new top-level schema by its fullname. It's critical
|
62
|
+
# not to make every sub-schema resolvable at the top level here because
|
63
|
+
# multiple different avsc files may define the same sub-schema, and
|
64
|
+
# if we share the @schemas cache across all parsing contexts, the Avro
|
65
|
+
# gem will raise an Avro::SchemaParseError when parsing another avsc
|
66
|
+
# file that contains a subschema with the same fullname as one
|
67
|
+
# encountered previously in a different file:
|
68
|
+
# <Avro::SchemaParseError: The name "foo.bar" is already in use.>
|
69
|
+
# Essentially, the only schemas that should be resolvable in @schemas
|
70
|
+
# are those that have their own .avsc files on disk.
|
71
|
+
@schemas[fullname] = schema
|
72
|
+
|
59
73
|
schema
|
60
74
|
rescue ::Avro::SchemaParseError => e
|
61
75
|
# This is a hack in order to figure out exactly which type was missing. The
|
62
76
|
# Avro gem ought to provide this data directly.
|
63
77
|
if e.to_s =~ /"([\w\.]+)" is not a schema we know about/
|
64
|
-
|
78
|
+
# Try to first resolve a referenced schema from disk.
|
79
|
+
# If this is successful, the Avro gem will have mutated the
|
80
|
+
# local_schemas_cache, adding all the new schemas it found.
|
81
|
+
load_schema!($1, nil, local_schemas_cache)
|
65
82
|
|
66
|
-
#
|
67
|
-
|
68
|
-
|
83
|
+
# Attempt to re-parse the original schema now that the dependency
|
84
|
+
# has been resolved and use the now-updated local_schemas_cache to
|
85
|
+
# pick up where we left off.
|
86
|
+
local_schemas_cache.delete(fullname)
|
87
|
+
load_schema!(fullname, nil, local_schemas_cache)
|
69
88
|
else
|
70
89
|
raise
|
71
90
|
end
|
data/lib/avro_turf/version.rb
CHANGED
@@ -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
|
@@ -24,6 +24,21 @@ describe AvroTurf::CachedConfluentSchemaRegistry do
|
|
24
24
|
}.to_json
|
25
25
|
end
|
26
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
|
+
|
27
42
|
before do
|
28
43
|
FileUtils.mkdir_p("spec/cache")
|
29
44
|
end
|
@@ -105,6 +120,38 @@ describe AvroTurf::CachedConfluentSchemaRegistry do
|
|
105
120
|
end
|
106
121
|
end
|
107
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
|
+
|
108
155
|
it_behaves_like "a confluent schema registry client" do
|
109
156
|
let(:upstream) { AvroTurf::ConfluentSchemaRegistry.new(registry_url, logger: logger) }
|
110
157
|
let(:registry) { described_class.new(upstream) }
|
data/spec/messaging_spec.rb
CHANGED
@@ -4,13 +4,19 @@ 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
|
|
@@ -29,6 +35,7 @@ describe AvroTurf::Messaging do
|
|
29
35
|
}
|
30
36
|
AVSC
|
31
37
|
end
|
38
|
+
let(:schema) { Avro::Schema.parse(schema_json) }
|
32
39
|
|
33
40
|
before do
|
34
41
|
FileUtils.mkdir_p("spec/schemas")
|
@@ -66,8 +73,8 @@ describe AvroTurf::Messaging do
|
|
66
73
|
shared_examples_for 'encoding and decoding with the schema from registry' do
|
67
74
|
before do
|
68
75
|
registry = AvroTurf::ConfluentSchemaRegistry.new(registry_url, logger: logger)
|
69
|
-
registry.register('person',
|
70
|
-
registry.register('people',
|
76
|
+
registry.register('person', schema)
|
77
|
+
registry.register('people', schema)
|
71
78
|
end
|
72
79
|
|
73
80
|
it 'encodes and decodes messages' do
|
@@ -93,10 +100,37 @@ describe AvroTurf::Messaging do
|
|
93
100
|
end
|
94
101
|
end
|
95
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
|
+
|
96
128
|
it_behaves_like "encoding and decoding with the schema from schema store"
|
97
129
|
|
98
130
|
it_behaves_like 'encoding and decoding with the schema from registry'
|
99
131
|
|
132
|
+
it_behaves_like 'encoding and decoding with the schema_id from registry'
|
133
|
+
|
100
134
|
context "with a provided registry" do
|
101
135
|
let(:registry) { AvroTurf::ConfluentSchemaRegistry.new(registry_url, logger: logger) }
|
102
136
|
|
@@ -112,6 +146,8 @@ describe AvroTurf::Messaging do
|
|
112
146
|
|
113
147
|
it_behaves_like 'encoding and decoding with the schema from registry'
|
114
148
|
|
149
|
+
it_behaves_like 'encoding and decoding with the schema_id from registry'
|
150
|
+
|
115
151
|
it "uses the provided registry" do
|
116
152
|
allow(registry).to receive(:register).and_call_original
|
117
153
|
message = { "full_name" => "John Doe" }
|
@@ -146,4 +182,221 @@ describe AvroTurf::Messaging do
|
|
146
182
|
expect(schema_store).to have_received(:find).with("person", nil)
|
147
183
|
end
|
148
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
|
300
|
+
|
301
|
+
context "validating" do
|
302
|
+
subject(:encode){ avro.encode(message, schema_name: "person", validate: true) }
|
303
|
+
|
304
|
+
context "for correct message" do
|
305
|
+
it { expect { encode }.not_to raise_error }
|
306
|
+
end
|
307
|
+
|
308
|
+
context "when message has wrong type" do
|
309
|
+
let(:message) { { "full_name" => 123 } }
|
310
|
+
|
311
|
+
it { expect { encode }.to raise_error(Avro::SchemaValidator::ValidationError, /\.full_name expected type string, got int/) }
|
312
|
+
end
|
313
|
+
|
314
|
+
context "when message contains extra fields (typo in key)" do
|
315
|
+
let(:message) { { "fulll_name" => "John Doe" } }
|
316
|
+
|
317
|
+
it { expect { encode }.to raise_error(Avro::SchemaValidator::ValidationError, /extra field 'fulll_name'/) }
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
context 'fetching and registering schema' do
|
322
|
+
let(:schema_store) { AvroTurf::SchemaStore.new(path: "spec/schemas") }
|
323
|
+
|
324
|
+
let(:registry) { AvroTurf::ConfluentSchemaRegistry.new(registry_url, logger: logger) }
|
325
|
+
|
326
|
+
let(:avro) do
|
327
|
+
AvroTurf::Messaging.new(
|
328
|
+
registry: registry,
|
329
|
+
schema_store: schema_store,
|
330
|
+
logger: logger
|
331
|
+
)
|
332
|
+
end
|
333
|
+
|
334
|
+
let(:schema_id) { 234 }
|
335
|
+
|
336
|
+
context 'using fetch_schema' do
|
337
|
+
subject { avro.fetch_schema(subject: subj, version: version) }
|
338
|
+
|
339
|
+
let(:subj) { 'subject' }
|
340
|
+
|
341
|
+
let(:version) { 'version' }
|
342
|
+
|
343
|
+
let(:response) { {'id' => schema_id, 'schema' => schema_json} }
|
344
|
+
|
345
|
+
before do
|
346
|
+
allow(registry).to receive(:subject_version).with(subj, version).and_return(response)
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'gets schema from registry' do
|
350
|
+
expect(subject).to eq([schema, schema_id])
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
context 'using fetch_schema_by_id' do
|
355
|
+
subject { avro.fetch_schema_by_id(schema_id) }
|
356
|
+
|
357
|
+
before do
|
358
|
+
allow(registry).to receive(:fetch).with(schema_id).and_return(schema_json)
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'gets schema from registry' do
|
362
|
+
expect(subject).to eq([schema, schema_id])
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
context 'using register_schema' do
|
367
|
+
let(:schema_name) { 'schema_name' }
|
368
|
+
|
369
|
+
let(:namespace) { 'namespace' }
|
370
|
+
|
371
|
+
before do
|
372
|
+
allow(schema_store).to receive(:find).with(schema_name, namespace).and_return(schema)
|
373
|
+
end
|
374
|
+
|
375
|
+
context 'when subject is not set' do
|
376
|
+
subject { avro.register_schema(schema_name: schema_name, namespace: namespace) }
|
377
|
+
|
378
|
+
before do
|
379
|
+
allow(registry).to receive(:register).with(schema.fullname, schema).and_return(schema_id)
|
380
|
+
end
|
381
|
+
|
382
|
+
it 'registers schema in registry' do
|
383
|
+
expect(subject).to eq([schema, schema_id])
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
context 'when subject is set' do
|
388
|
+
subject { avro.register_schema(schema_name: schema_name, namespace: namespace, subject: subj) }
|
389
|
+
|
390
|
+
let(:subj) { 'subject' }
|
391
|
+
|
392
|
+
before do
|
393
|
+
allow(registry).to receive(:register).with(subj, schema).and_return(schema_id)
|
394
|
+
end
|
395
|
+
|
396
|
+
it 'registers schema in registry' do
|
397
|
+
expect(subject).to eq([schema, schema_id])
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
149
402
|
end
|
data/spec/schema_store_spec.rb
CHANGED
@@ -198,6 +198,104 @@ describe AvroTurf::SchemaStore do
|
|
198
198
|
expect(schema.fullname).to eq "person"
|
199
199
|
end
|
200
200
|
|
201
|
+
# This test would fail under avro_turf <= v0.11.0
|
202
|
+
it "does NOT cache *nested* schemas in memory" do
|
203
|
+
FileUtils.mkdir_p("spec/schemas/test")
|
204
|
+
|
205
|
+
define_schema "test/person.avsc", <<-AVSC
|
206
|
+
{
|
207
|
+
"name": "person",
|
208
|
+
"namespace": "test",
|
209
|
+
"type": "record",
|
210
|
+
"fields": [
|
211
|
+
{
|
212
|
+
"name": "address",
|
213
|
+
"type": {
|
214
|
+
"name": "address",
|
215
|
+
"type": "record",
|
216
|
+
"fields": [
|
217
|
+
{ "name": "addr1", "type": "string" },
|
218
|
+
{ "name": "addr2", "type": "string" },
|
219
|
+
{ "name": "city", "type": "string" },
|
220
|
+
{ "name": "zip", "type": "string" }
|
221
|
+
]
|
222
|
+
}
|
223
|
+
}
|
224
|
+
]
|
225
|
+
}
|
226
|
+
AVSC
|
227
|
+
|
228
|
+
schema = store.find('person', 'test')
|
229
|
+
expect(schema.fullname).to eq "test.person"
|
230
|
+
|
231
|
+
expect { store.find('address', 'test') }.
|
232
|
+
to raise_error(AvroTurf::SchemaNotFoundError)
|
233
|
+
end
|
234
|
+
|
235
|
+
# This test would fail under avro_turf <= v0.11.0
|
236
|
+
it "allows two different avsc files to define nested sub-schemas with the same fullname" do
|
237
|
+
FileUtils.mkdir_p("spec/schemas/test")
|
238
|
+
|
239
|
+
define_schema "test/person.avsc", <<-AVSC
|
240
|
+
{
|
241
|
+
"name": "person",
|
242
|
+
"namespace": "test",
|
243
|
+
"type": "record",
|
244
|
+
"fields": [
|
245
|
+
{
|
246
|
+
"name": "location",
|
247
|
+
"type": {
|
248
|
+
"name": "location",
|
249
|
+
"type": "record",
|
250
|
+
"fields": [
|
251
|
+
{ "name": "city", "type": "string" },
|
252
|
+
{ "name": "zipcode", "type": "string" }
|
253
|
+
]
|
254
|
+
}
|
255
|
+
}
|
256
|
+
]
|
257
|
+
}
|
258
|
+
AVSC
|
259
|
+
|
260
|
+
define_schema "test/company.avsc", <<-AVSC
|
261
|
+
{
|
262
|
+
"name": "company",
|
263
|
+
"namespace": "test",
|
264
|
+
"type": "record",
|
265
|
+
"fields": [
|
266
|
+
{
|
267
|
+
"name": "headquarters",
|
268
|
+
"type": {
|
269
|
+
"name": "location",
|
270
|
+
"type": "record",
|
271
|
+
"fields": [
|
272
|
+
{ "name": "city", "type": "string" },
|
273
|
+
{ "name": "postcode", "type": "string" }
|
274
|
+
]
|
275
|
+
}
|
276
|
+
}
|
277
|
+
]
|
278
|
+
}
|
279
|
+
AVSC
|
280
|
+
|
281
|
+
company = nil
|
282
|
+
person = store.find('person', 'test')
|
283
|
+
|
284
|
+
# This should *NOT* raise the error:
|
285
|
+
# #<Avro::SchemaParseError: The name "test.location" is already in use.>
|
286
|
+
expect { company = store.find('company', 'test') }.not_to raise_error
|
287
|
+
|
288
|
+
person_location_field = person.fields_hash['location']
|
289
|
+
expect(person_location_field.type.name).to eq('location')
|
290
|
+
expect(person_location_field.type.fields_hash).to include('zipcode')
|
291
|
+
expect(person_location_field.type.fields_hash).not_to include('postcode')
|
292
|
+
|
293
|
+
company_headquarters_field = company.fields_hash['headquarters']
|
294
|
+
expect(company_headquarters_field.type.name).to eq('location')
|
295
|
+
expect(company_headquarters_field.type.fields_hash).to include('postcode')
|
296
|
+
expect(company_headquarters_field.type.fields_hash).not_to include('zipcode')
|
297
|
+
end
|
298
|
+
|
201
299
|
it "is thread safe" do
|
202
300
|
define_schema "address.avsc", <<-AVSC
|
203
301
|
{
|
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:
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Schierbeck
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: avro
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
version: 1.7.7
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '1.
|
22
|
+
version: '1.11'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,77 +29,77 @@ dependencies:
|
|
29
29
|
version: 1.7.7
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '1.
|
32
|
+
version: '1.11'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: excon
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '0.
|
39
|
+
version: '0.71'
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '0.
|
46
|
+
version: '0.71'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: bundler
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: '
|
53
|
+
version: '2.0'
|
54
54
|
type: :development
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version: '
|
60
|
+
version: '2.0'
|
61
61
|
- !ruby/object:Gem::Dependency
|
62
62
|
name: rake
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
65
|
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version: '
|
67
|
+
version: '13.0'
|
68
68
|
type: :development
|
69
69
|
prerelease: false
|
70
70
|
version_requirements: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
72
|
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: '
|
74
|
+
version: '13.0'
|
75
75
|
- !ruby/object:Gem::Dependency
|
76
76
|
name: rspec
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
78
78
|
requirements:
|
79
79
|
- - "~>"
|
80
80
|
- !ruby/object:Gem::Version
|
81
|
-
version: 3.2
|
81
|
+
version: '3.2'
|
82
82
|
type: :development
|
83
83
|
prerelease: false
|
84
84
|
version_requirements: !ruby/object:Gem::Requirement
|
85
85
|
requirements:
|
86
86
|
- - "~>"
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
version: 3.2
|
88
|
+
version: '3.2'
|
89
89
|
- !ruby/object:Gem::Dependency
|
90
90
|
name: fakefs
|
91
91
|
requirement: !ruby/object:Gem::Requirement
|
92
92
|
requirements:
|
93
93
|
- - "~>"
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version: 0.
|
95
|
+
version: 0.20.0
|
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: 0.
|
102
|
+
version: 0.20.0
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
104
|
name: webmock
|
105
105
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,6 +164,8 @@ extensions: []
|
|
164
164
|
extra_rdoc_files: []
|
165
165
|
files:
|
166
166
|
- ".circleci/config.yml"
|
167
|
+
- ".github/workflows/ruby.yml"
|
168
|
+
- ".github/workflows/stale.yml"
|
167
169
|
- ".gitignore"
|
168
170
|
- ".rspec"
|
169
171
|
- CHANGELOG.md
|
@@ -248,7 +250,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
248
250
|
- !ruby/object:Gem::Version
|
249
251
|
version: '0'
|
250
252
|
requirements: []
|
251
|
-
|
253
|
+
rubyforge_project:
|
254
|
+
rubygems_version: 2.7.6
|
252
255
|
signing_key:
|
253
256
|
specification_version: 4
|
254
257
|
summary: A library that makes it easier to use the Avro serialization format from
|