avro_turf 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +7 -4
- data/CHANGELOG.md +19 -0
- data/README.md +6 -2
- data/avro_turf.gemspec +3 -1
- data/lib/avro_turf/cached_confluent_schema_registry.rb +2 -0
- data/lib/avro_turf/confluent_schema_registry.rb +7 -8
- data/lib/avro_turf/disk_cache.rb +32 -9
- data/lib/avro_turf/in_memory_cache.rb +2 -2
- data/lib/avro_turf/messaging.rb +8 -3
- data/lib/avro_turf/schema_store.rb +16 -7
- data/lib/avro_turf/version.rb +1 -1
- data/lib/avro_turf.rb +13 -3
- data/spec/avro_turf_spec.rb +175 -28
- data/spec/disk_cached_confluent_schema_registry_spec.rb +71 -1
- metadata +9 -9
- data/.circleci/config.yml +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e110697cb451e5315414f8086d32aa6e6c43444a462fcdaa678d1311302fcab8
|
4
|
+
data.tar.gz: a54afac8d7e8cb4a599695825ccb3d666d08ad800ccfe63229f14b6b42b26f11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78ee7cc3c12975d15bc621d9b3a96ed014cc8265536650f365ed80a861a06d53f3e9db9f7d698f71c02485df6f9b371944102404e76cc05e13816e12d312d97c
|
7
|
+
data.tar.gz: ac02799c0f25b88f8c9bfbbd0de48a2b3c39a7744ee1b90c6c87439b5a547db9456a2b3f89a9730303c76ea302159fdce9189f417e6424ecbac45da14682dd0d
|
data/.github/workflows/ruby.yml
CHANGED
@@ -6,13 +6,16 @@ jobs:
|
|
6
6
|
build:
|
7
7
|
|
8
8
|
runs-on: ubuntu-latest
|
9
|
+
strategy:
|
10
|
+
matrix:
|
11
|
+
ruby: [2.3, 2.4, 2.5, 2.6, 2.7, 3.0]
|
9
12
|
|
10
13
|
steps:
|
11
|
-
- uses: actions/checkout@
|
12
|
-
- name: Set up Ruby
|
13
|
-
uses:
|
14
|
+
- uses: actions/checkout@v2
|
15
|
+
- name: Set up Ruby ${{ matrix.ruby }}
|
16
|
+
uses: ruby/setup-ruby@v1
|
14
17
|
with:
|
15
|
-
ruby-version:
|
18
|
+
ruby-version: ${{ matrix.ruby }}
|
16
19
|
- name: Build and test with RSpec
|
17
20
|
run: |
|
18
21
|
gem install bundler
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,25 @@
|
|
2
2
|
|
3
3
|
## Unreleased
|
4
4
|
|
5
|
+
## v1.5.0
|
6
|
+
|
7
|
+
- Add CA cert file option (#157)
|
8
|
+
- Add compatibility with Avro v1.11.x.
|
9
|
+
|
10
|
+
## v1.4.1
|
11
|
+
|
12
|
+
- Purge sub-schemas from cache before re-parsing schema (#151)
|
13
|
+
|
14
|
+
## v1.4.0
|
15
|
+
|
16
|
+
- Add support for Ruby 3 (#146)
|
17
|
+
- Add ability to validate message before encoding in `AvroTurf#encode` interface
|
18
|
+
|
19
|
+
## v1.3.1
|
20
|
+
|
21
|
+
- Prevent CachedConfluentSchemaRegistry from caching the 'latest' version (#140)
|
22
|
+
- Fix issue with zero length schema cache file (#138)
|
23
|
+
|
5
24
|
## v1.3.0
|
6
25
|
|
7
26
|
- Add support for plain user/password auth to ConfluentSchemaRegistry (#120)
|
data/README.md
CHANGED
@@ -18,7 +18,7 @@ The aliases for the original names will be removed in a future release.
|
|
18
18
|
|
19
19
|
## Note about finding nested schemas
|
20
20
|
|
21
|
-
As of AvroTurf version 0.
|
21
|
+
As of AvroTurf version 1.0.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 v1.0.0.
|
22
22
|
|
23
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
24
|
|
@@ -44,7 +44,7 @@ As an example, if you have a `person` schema (defined in `my/schemas/contacts/pe
|
|
44
44
|
]
|
45
45
|
}
|
46
46
|
```
|
47
|
-
...this will no longer work in
|
47
|
+
...this will no longer work in v1.0.0:
|
48
48
|
```ruby
|
49
49
|
store = AvroTurf::SchemaStore.new(path: 'my/schemas')
|
50
50
|
store.load_schemas!
|
@@ -88,6 +88,10 @@ avro.decode(encoded_data, schema_name: "person")
|
|
88
88
|
|
89
89
|
# Encode some data using the named schema.
|
90
90
|
avro.encode({ "name" => "Jane", "age" => 28 }, schema_name: "person")
|
91
|
+
|
92
|
+
# Data can be validated before encoding to get a description of problem through
|
93
|
+
# Avro::SchemaValidator::ValidationError exception
|
94
|
+
avro.encode({ "titl" => "hello, world" }, schema_name: "person", validate: true)
|
91
95
|
```
|
92
96
|
|
93
97
|
### Inter-schema references
|
data/avro_turf.gemspec
CHANGED
@@ -12,12 +12,14 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.homepage = "https://github.com/dasch/avro_turf"
|
13
13
|
spec.license = "MIT"
|
14
14
|
|
15
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
16
|
+
|
15
17
|
spec.files = `git ls-files -z`.split("\x0")
|
16
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
20
|
spec.require_paths = ["lib"]
|
19
21
|
|
20
|
-
spec.add_dependency "avro", ">= 1.7.7", "< 1.
|
22
|
+
spec.add_dependency "avro", ">= 1.7.7", "< 1.12"
|
21
23
|
spec.add_dependency "excon", "~> 0.71"
|
22
24
|
|
23
25
|
spec.add_development_dependency "bundler", "~> 2.0"
|
@@ -33,6 +33,8 @@ class AvroTurf::CachedConfluentSchemaRegistry
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def subject_version(subject, version = 'latest')
|
36
|
+
return @upstream.subject_version(subject, version) if version == 'latest'
|
37
|
+
|
36
38
|
@cache.lookup_by_version(subject, version) ||
|
37
39
|
@cache.store_by_version(subject, version, @upstream.subject_version(subject, version))
|
38
40
|
end
|
@@ -9,6 +9,7 @@ class AvroTurf::ConfluentSchemaRegistry
|
|
9
9
|
proxy: nil,
|
10
10
|
user: nil,
|
11
11
|
password: nil,
|
12
|
+
ssl_ca_file: nil,
|
12
13
|
client_cert: nil,
|
13
14
|
client_key: nil,
|
14
15
|
client_key_pass: nil,
|
@@ -19,12 +20,13 @@ class AvroTurf::ConfluentSchemaRegistry
|
|
19
20
|
headers = {
|
20
21
|
"Content-Type" => CONTENT_TYPE
|
21
22
|
}
|
22
|
-
headers[:proxy] = proxy
|
23
|
+
headers[:proxy] = proxy unless proxy.nil?
|
23
24
|
@connection = Excon.new(
|
24
25
|
url,
|
25
26
|
headers: headers,
|
26
27
|
user: user,
|
27
28
|
password: password,
|
29
|
+
ssl_ca_file: ssl_ca_file,
|
28
30
|
client_cert: client_cert,
|
29
31
|
client_key: client_key,
|
30
32
|
client_key_pass: client_key_pass,
|
@@ -40,9 +42,7 @@ class AvroTurf::ConfluentSchemaRegistry
|
|
40
42
|
end
|
41
43
|
|
42
44
|
def register(subject, schema)
|
43
|
-
data = post("/subjects/#{subject}/versions", body: {
|
44
|
-
schema: schema.to_s
|
45
|
-
}.to_json)
|
45
|
+
data = post("/subjects/#{subject}/versions", body: { schema: schema.to_s }.to_json)
|
46
46
|
|
47
47
|
id = data.fetch("id")
|
48
48
|
|
@@ -82,8 +82,7 @@ class AvroTurf::ConfluentSchemaRegistry
|
|
82
82
|
# http://docs.confluent.io/3.1.2/schema-registry/docs/api.html#compatibility
|
83
83
|
def compatible?(subject, schema, version = 'latest')
|
84
84
|
data = post("/compatibility/subjects/#{subject}/versions/#{version}",
|
85
|
-
expects: [200, 404],
|
86
|
-
body: { schema: schema.to_s }.to_json)
|
85
|
+
expects: [200, 404], body: { schema: schema.to_s }.to_json)
|
87
86
|
data.fetch('is_compatible', false) unless data.has_key?('error_code')
|
88
87
|
end
|
89
88
|
|
@@ -94,7 +93,7 @@ class AvroTurf::ConfluentSchemaRegistry
|
|
94
93
|
|
95
94
|
# Update global config
|
96
95
|
def update_global_config(config)
|
97
|
-
put("/config",
|
96
|
+
put("/config", body: config.to_json)
|
98
97
|
end
|
99
98
|
|
100
99
|
# Get config for subject
|
@@ -104,7 +103,7 @@ class AvroTurf::ConfluentSchemaRegistry
|
|
104
103
|
|
105
104
|
# Update config for subject
|
106
105
|
def update_subject_config(subject, config)
|
107
|
-
put("/config/#{subject}",
|
106
|
+
put("/config/#{subject}", body: config.to_json)
|
108
107
|
end
|
109
108
|
|
110
109
|
private
|
data/lib/avro_turf/disk_cache.rb
CHANGED
@@ -2,15 +2,19 @@
|
|
2
2
|
# Extends the InMemoryCache to provide a write-thru to disk for persistent cache.
|
3
3
|
class AvroTurf::DiskCache < AvroTurf::InMemoryCache
|
4
4
|
|
5
|
-
def initialize(disk_path)
|
5
|
+
def initialize(disk_path, logger: Logger.new($stdout))
|
6
6
|
super()
|
7
7
|
|
8
|
+
@logger = logger
|
9
|
+
|
8
10
|
# load the write-thru cache on startup, if it exists
|
9
11
|
@schemas_by_id_path = File.join(disk_path, 'schemas_by_id.json')
|
10
|
-
|
12
|
+
hash = read_from_disk_cache(@schemas_by_id_path)
|
13
|
+
@schemas_by_id = hash if hash
|
11
14
|
|
12
15
|
@ids_by_schema_path = File.join(disk_path, 'ids_by_schema.json')
|
13
|
-
|
16
|
+
hash = read_from_disk_cache(@ids_by_schema_path)
|
17
|
+
@ids_by_schema = hash if hash
|
14
18
|
|
15
19
|
@schemas_by_subject_version_path = File.join(disk_path, 'schemas_by_subject_version.json')
|
16
20
|
@schemas_by_subject_version = {}
|
@@ -31,12 +35,18 @@ class AvroTurf::DiskCache < AvroTurf::InMemoryCache
|
|
31
35
|
return value
|
32
36
|
end
|
33
37
|
|
34
|
-
# override to
|
38
|
+
# override to use a json serializable cache key
|
39
|
+
def lookup_by_schema(subject, schema)
|
40
|
+
key = "#{subject}#{schema}"
|
41
|
+
@ids_by_schema[key]
|
42
|
+
end
|
43
|
+
|
44
|
+
# override to use a json serializable cache key and update the file cache
|
35
45
|
def store_by_schema(subject, schema, id)
|
36
|
-
|
37
|
-
|
46
|
+
key = "#{subject}#{schema}"
|
47
|
+
@ids_by_schema[key] = id
|
38
48
|
File.write(@ids_by_schema_path, JSON.pretty_generate(@ids_by_schema))
|
39
|
-
|
49
|
+
id
|
40
50
|
end
|
41
51
|
|
42
52
|
# checks instance var (in-memory cache) for schema
|
@@ -49,7 +59,7 @@ class AvroTurf::DiskCache < AvroTurf::InMemoryCache
|
|
49
59
|
|
50
60
|
return schema unless schema.nil?
|
51
61
|
|
52
|
-
hash =
|
62
|
+
hash = read_from_disk_cache(@schemas_by_subject_version_path)
|
53
63
|
if hash
|
54
64
|
@schemas_by_subject_version = hash
|
55
65
|
@schemas_by_subject_version[key]
|
@@ -63,7 +73,7 @@ class AvroTurf::DiskCache < AvroTurf::InMemoryCache
|
|
63
73
|
# update instance var (in memory-cache) to match
|
64
74
|
def store_by_version(subject, version, schema)
|
65
75
|
key = "#{subject}#{version}"
|
66
|
-
hash =
|
76
|
+
hash = read_from_disk_cache(@schemas_by_subject_version_path)
|
67
77
|
hash = if hash
|
68
78
|
hash[key] = schema
|
69
79
|
hash
|
@@ -77,6 +87,19 @@ class AvroTurf::DiskCache < AvroTurf::InMemoryCache
|
|
77
87
|
@schemas_by_subject_version[key]
|
78
88
|
end
|
79
89
|
|
90
|
+
# Parse the file from disk, if it exists and is not zero length
|
91
|
+
private def read_from_disk_cache(path)
|
92
|
+
if File.exist?(path)
|
93
|
+
if File.size(path)!=0
|
94
|
+
return JSON.parse(File.read(path))
|
95
|
+
else
|
96
|
+
# just log a message if skipping zero length file
|
97
|
+
@logger.warn "skipping JSON.parse of zero length file at #{path}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
return nil
|
101
|
+
end
|
102
|
+
|
80
103
|
private def write_to_disk_cache(path, hash)
|
81
104
|
File.write(path, JSON.pretty_generate(hash))
|
82
105
|
end
|
@@ -17,12 +17,12 @@ class AvroTurf::InMemoryCache
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def lookup_by_schema(subject, schema)
|
20
|
-
key = subject
|
20
|
+
key = [subject, schema.to_s]
|
21
21
|
@ids_by_schema[key]
|
22
22
|
end
|
23
23
|
|
24
24
|
def store_by_schema(subject, schema, id)
|
25
|
-
key = subject
|
25
|
+
key = [subject, schema.to_s]
|
26
26
|
@ids_by_schema[key] = id
|
27
27
|
end
|
28
28
|
|
data/lib/avro_turf/messaging.rb
CHANGED
@@ -36,6 +36,7 @@ class AvroTurf
|
|
36
36
|
# proxy - Forward the request via proxy (optional).
|
37
37
|
# user - User for basic auth (optional).
|
38
38
|
# password - Password for basic auth (optional).
|
39
|
+
# ssl_ca_file - Name of file containing CA certificate (optional).
|
39
40
|
# client_cert - Name of file containing client certificate (optional).
|
40
41
|
# client_key - Name of file containing client private key to go with client_cert (optional).
|
41
42
|
# client_key_pass - Password to go with client_key (optional).
|
@@ -51,6 +52,7 @@ class AvroTurf
|
|
51
52
|
proxy: nil,
|
52
53
|
user: nil,
|
53
54
|
password: nil,
|
55
|
+
ssl_ca_file: nil,
|
54
56
|
client_cert: nil,
|
55
57
|
client_key: nil,
|
56
58
|
client_key_pass: nil,
|
@@ -67,6 +69,7 @@ class AvroTurf
|
|
67
69
|
proxy: proxy,
|
68
70
|
user: user,
|
69
71
|
password: password,
|
72
|
+
ssl_ca_file: ssl_ca_file,
|
70
73
|
client_cert: client_cert,
|
71
74
|
client_key: client_key,
|
72
75
|
client_key_pass: client_key_pass,
|
@@ -124,7 +127,7 @@ class AvroTurf
|
|
124
127
|
writer.write(message, encoder)
|
125
128
|
|
126
129
|
stream.string
|
127
|
-
rescue Excon::
|
130
|
+
rescue Excon::Errors::NotFound
|
128
131
|
if schema_id
|
129
132
|
raise SchemaNotFoundError.new("Schema with id: #{schema_id} is not found on registry")
|
130
133
|
else
|
@@ -194,8 +197,10 @@ class AvroTurf
|
|
194
197
|
|
195
198
|
# Fetch the schema from registry with the provided schema_id.
|
196
199
|
def fetch_schema_by_id(schema_id)
|
197
|
-
|
198
|
-
|
200
|
+
schema = @schemas_by_id.fetch(schema_id) do
|
201
|
+
schema_json = @registry.fetch(schema_id)
|
202
|
+
Avro::Schema.parse(schema_json)
|
203
|
+
end
|
199
204
|
[schema, schema_id]
|
200
205
|
end
|
201
206
|
|
@@ -22,7 +22,7 @@ class AvroTurf::SchemaStore
|
|
22
22
|
# Still need to check is the schema already loaded
|
23
23
|
return @schemas[fullname] if @schemas.key?(fullname)
|
24
24
|
|
25
|
-
load_schema!(fullname
|
25
|
+
load_schema!(fullname)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
@@ -42,13 +42,12 @@ class AvroTurf::SchemaStore
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
|
45
|
+
protected
|
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,
|
50
|
-
|
51
|
-
schema_path = File.join(@path, *namespace, schema_name + ".avsc")
|
49
|
+
def load_schema!(fullname, local_schemas_cache = {})
|
50
|
+
schema_path = build_schema_path(fullname)
|
52
51
|
schema_json = JSON.parse(File.read(schema_path))
|
53
52
|
|
54
53
|
schema = Avro::Schema.real_parse(schema_json, local_schemas_cache)
|
@@ -78,17 +77,27 @@ class AvroTurf::SchemaStore
|
|
78
77
|
# Try to first resolve a referenced schema from disk.
|
79
78
|
# If this is successful, the Avro gem will have mutated the
|
80
79
|
# local_schemas_cache, adding all the new schemas it found.
|
81
|
-
load_schema!($1,
|
80
|
+
load_schema!($1, local_schemas_cache)
|
82
81
|
|
83
82
|
# Attempt to re-parse the original schema now that the dependency
|
84
83
|
# has been resolved and use the now-updated local_schemas_cache to
|
85
84
|
# pick up where we left off.
|
86
85
|
local_schemas_cache.delete(fullname)
|
87
|
-
|
86
|
+
# Ensure all sub-schemas are cleaned up to avoid conflicts when re-parsing
|
87
|
+
# schema.
|
88
|
+
local_schemas_cache.each do |schema_name, schema|
|
89
|
+
local_schemas_cache.delete(schema_name) unless File.exist?(build_schema_path(schema_name))
|
90
|
+
end
|
91
|
+
load_schema!(fullname, local_schemas_cache)
|
88
92
|
else
|
89
93
|
raise
|
90
94
|
end
|
91
95
|
rescue Errno::ENOENT, Errno::ENAMETOOLONG
|
92
96
|
raise AvroTurf::SchemaNotFoundError, "could not find Avro schema at `#{schema_path}'"
|
93
97
|
end
|
98
|
+
|
99
|
+
def build_schema_path(fullname)
|
100
|
+
*namespace, schema_name = fullname.split(".")
|
101
|
+
schema_path = File.join(@path, *namespace, schema_name + ".avsc")
|
102
|
+
end
|
94
103
|
end
|
data/lib/avro_turf/version.rb
CHANGED
data/lib/avro_turf.rb
CHANGED
@@ -40,12 +40,15 @@ class AvroTurf
|
|
40
40
|
#
|
41
41
|
# data - The data that should be encoded.
|
42
42
|
# schema_name - The name of a schema in the `schemas_path`.
|
43
|
+
# validate - The boolean for performing complete data validation before
|
44
|
+
# encoding it, Avro::SchemaValidator::ValidationError with
|
45
|
+
# a descriptive message will be raised in case of invalid message.
|
43
46
|
#
|
44
47
|
# Returns a String containing the encoded data.
|
45
|
-
def encode(data, schema_name: nil, namespace: @namespace)
|
48
|
+
def encode(data, schema_name: nil, namespace: @namespace, validate: false)
|
46
49
|
stream = StringIO.new
|
47
50
|
|
48
|
-
encode_to_stream(data, stream: stream, schema_name: schema_name, namespace: namespace)
|
51
|
+
encode_to_stream(data, stream: stream, schema_name: schema_name, namespace: namespace, validate: validate)
|
49
52
|
|
50
53
|
stream.string
|
51
54
|
end
|
@@ -56,12 +59,19 @@ class AvroTurf
|
|
56
59
|
# data - The data that should be encoded.
|
57
60
|
# schema_name - The name of a schema in the `schemas_path`.
|
58
61
|
# stream - An IO object that the encoded data should be written to (optional).
|
62
|
+
# validate - The boolean for performing complete data validation before
|
63
|
+
# encoding it, Avro::SchemaValidator::ValidationError with
|
64
|
+
# a descriptive message will be raised in case of invalid message.
|
59
65
|
#
|
60
66
|
# Returns nothing.
|
61
|
-
def encode_to_stream(data, schema_name: nil, stream: nil, namespace: @namespace)
|
67
|
+
def encode_to_stream(data, schema_name: nil, stream: nil, namespace: @namespace, validate: false)
|
62
68
|
schema = @schema_store.find(schema_name, namespace)
|
63
69
|
writer = Avro::IO::DatumWriter.new(schema)
|
64
70
|
|
71
|
+
if validate
|
72
|
+
Avro::SchemaValidator.validate!(schema, data, recursive: true, encoded: false, fail_on_extra_fields: true)
|
73
|
+
end
|
74
|
+
|
65
75
|
dw = Avro::DataFile::Writer.new(stream, writer, schema, @codec)
|
66
76
|
dw << data.as_avro
|
67
77
|
dw.close
|
data/spec/avro_turf_spec.rb
CHANGED
@@ -6,44 +6,130 @@ describe AvroTurf do
|
|
6
6
|
end
|
7
7
|
|
8
8
|
describe "#encode" do
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
context "when using plain schema" do
|
10
|
+
before do
|
11
|
+
define_schema "person.avsc", <<-AVSC
|
12
|
+
{
|
13
|
+
"name": "person",
|
14
|
+
"type": "record",
|
15
|
+
"fields": [
|
16
|
+
{
|
17
|
+
"type": "string",
|
18
|
+
"name": "full_name"
|
19
|
+
}
|
20
|
+
]
|
21
|
+
}
|
22
|
+
AVSC
|
23
|
+
end
|
24
|
+
|
25
|
+
it "encodes data with Avro" do
|
26
|
+
data = {
|
27
|
+
"full_name" => "John Doe"
|
20
28
|
}
|
21
|
-
AVSC
|
22
|
-
end
|
23
29
|
|
24
|
-
|
25
|
-
data = {
|
26
|
-
"full_name" => "John Doe"
|
27
|
-
}
|
30
|
+
encoded_data = avro.encode(data, schema_name: "person")
|
28
31
|
|
29
|
-
|
32
|
+
expect(avro.decode(encoded_data)).to eq(data)
|
33
|
+
end
|
30
34
|
|
31
|
-
|
35
|
+
it "allows specifying a codec that should be used to compress messages" do
|
36
|
+
compressed_avro = AvroTurf.new(schemas_path: "spec/schemas/", codec: "deflate")
|
37
|
+
|
38
|
+
data = {
|
39
|
+
"full_name" => "John Doe" * 100
|
40
|
+
}
|
41
|
+
|
42
|
+
uncompressed_data = avro.encode(data, schema_name: "person")
|
43
|
+
compressed_data = compressed_avro.encode(data, schema_name: "person")
|
44
|
+
|
45
|
+
expect(compressed_data.bytesize).to be < uncompressed_data.bytesize
|
46
|
+
expect(compressed_avro.decode(compressed_data)).to eq(data)
|
47
|
+
end
|
32
48
|
end
|
33
49
|
|
34
|
-
|
35
|
-
|
50
|
+
context 'when using nested schemas' do
|
51
|
+
before do
|
52
|
+
define_schema "post.avsc", <<-AVSC
|
53
|
+
{
|
54
|
+
"name": "post",
|
55
|
+
"type": "record",
|
56
|
+
"fields": [
|
57
|
+
{
|
58
|
+
"name": "tag",
|
59
|
+
"type": {
|
60
|
+
"type": "enum",
|
61
|
+
"name": "tag",
|
62
|
+
"symbols": ["foo", "bar"]
|
63
|
+
}
|
64
|
+
},
|
65
|
+
{
|
66
|
+
"name": "messages",
|
67
|
+
"type": {
|
68
|
+
"type": "array",
|
69
|
+
"items": "message"
|
70
|
+
}
|
71
|
+
},
|
72
|
+
{
|
73
|
+
"name": "status",
|
74
|
+
"type": "publishing_status"
|
75
|
+
}
|
76
|
+
]
|
77
|
+
}
|
78
|
+
AVSC
|
79
|
+
|
80
|
+
define_schema "publishing_status.avsc", <<-AVSC
|
81
|
+
{
|
82
|
+
"name": "publishing_status",
|
83
|
+
"type": "enum",
|
84
|
+
"symbols": ["draft", "published", "archived"]
|
85
|
+
}
|
86
|
+
AVSC
|
36
87
|
|
37
|
-
|
38
|
-
|
39
|
-
|
88
|
+
define_schema "message.avsc", <<-AVSC
|
89
|
+
{
|
90
|
+
"name": "message",
|
91
|
+
"type": "record",
|
92
|
+
"fields": [
|
93
|
+
{
|
94
|
+
"type": "string",
|
95
|
+
"name": "content"
|
96
|
+
},
|
97
|
+
{
|
98
|
+
"name": "label",
|
99
|
+
"type": {
|
100
|
+
"type": "enum",
|
101
|
+
"name": "label",
|
102
|
+
"symbols": ["foo", "bar"]
|
103
|
+
}
|
104
|
+
},
|
105
|
+
{
|
106
|
+
"name": "status",
|
107
|
+
"type": "publishing_status"
|
108
|
+
}
|
109
|
+
]
|
110
|
+
}
|
111
|
+
AVSC
|
112
|
+
end
|
113
|
+
|
114
|
+
it "encodes data with Avro" do
|
115
|
+
data = {
|
116
|
+
"tag" => "foo",
|
117
|
+
"messages" => [
|
118
|
+
{
|
119
|
+
"content" => "hello",
|
120
|
+
"label" => "bar",
|
121
|
+
"status" => "draft"
|
122
|
+
}
|
123
|
+
],
|
124
|
+
"status" => "published"
|
125
|
+
}
|
40
126
|
|
41
|
-
|
42
|
-
compressed_data = compressed_avro.encode(data, schema_name: "person")
|
127
|
+
encoded_data = avro.encode(data, schema_name: "post")
|
43
128
|
|
44
|
-
|
45
|
-
|
129
|
+
expect(avro.decode(encoded_data)).to eq(data)
|
130
|
+
end
|
46
131
|
end
|
132
|
+
|
47
133
|
end
|
48
134
|
|
49
135
|
describe "#decode" do
|
@@ -105,6 +191,67 @@ describe AvroTurf do
|
|
105
191
|
|
106
192
|
expect(avro.decode(stream.string)).to eq "hello"
|
107
193
|
end
|
194
|
+
|
195
|
+
context "validating" do
|
196
|
+
subject(:encode_to_stream) do
|
197
|
+
stream = StringIO.new
|
198
|
+
avro.encode_to_stream(message, stream: stream, schema_name: "message", validate: true)
|
199
|
+
end
|
200
|
+
|
201
|
+
context "with a valid message" do
|
202
|
+
let(:message) { { "full_name" => "John Doe" } }
|
203
|
+
|
204
|
+
it "does not raise any error" do
|
205
|
+
define_schema "message.avsc", <<-AVSC
|
206
|
+
{
|
207
|
+
"name": "message",
|
208
|
+
"type": "record",
|
209
|
+
"fields": [
|
210
|
+
{ "name": "full_name", "type": "string" }
|
211
|
+
]
|
212
|
+
}
|
213
|
+
AVSC
|
214
|
+
|
215
|
+
expect { encode_to_stream }.not_to raise_error
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context "when message has wrong type" do
|
220
|
+
let(:message) { { "full_name" => 123 } }
|
221
|
+
|
222
|
+
it "raises Avro::SchemaValidator::ValidationError with a message about type mismatch" do
|
223
|
+
define_schema "message.avsc", <<-AVSC
|
224
|
+
{
|
225
|
+
"name": "message",
|
226
|
+
"type": "record",
|
227
|
+
"fields": [
|
228
|
+
{ "name": "full_name", "type": "string" }
|
229
|
+
]
|
230
|
+
}
|
231
|
+
AVSC
|
232
|
+
|
233
|
+
expect { encode_to_stream }.to raise_error(Avro::SchemaValidator::ValidationError, /\.full_name expected type string, got int/)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context "when message contains extra fields (typo in key)" do
|
238
|
+
let(:message) { { "fulll_name" => "John Doe" } }
|
239
|
+
|
240
|
+
it "raises Avro::SchemaValidator::ValidationError with a message about extra field" do
|
241
|
+
define_schema "message.avsc", <<-AVSC
|
242
|
+
{
|
243
|
+
"name": "message",
|
244
|
+
"type": "record",
|
245
|
+
"fields": [
|
246
|
+
{ "name": "full_name", "type": "string" }
|
247
|
+
]
|
248
|
+
}
|
249
|
+
AVSC
|
250
|
+
|
251
|
+
expect { encode_to_stream }.to raise_error(Avro::SchemaValidator::ValidationError, /extra field 'fulll_name'/)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
108
255
|
end
|
109
256
|
|
110
257
|
describe "#decode_stream" do
|
@@ -4,7 +4,8 @@ require 'avro_turf/test/fake_confluent_schema_registry_server'
|
|
4
4
|
|
5
5
|
describe AvroTurf::CachedConfluentSchemaRegistry do
|
6
6
|
let(:upstream) { instance_double(AvroTurf::ConfluentSchemaRegistry) }
|
7
|
-
let(:
|
7
|
+
let(:logger_io) { StringIO.new }
|
8
|
+
let(:cache) { AvroTurf::DiskCache.new("spec/cache", logger: Logger.new(logger_io))}
|
8
9
|
let(:registry) { described_class.new(upstream, cache: cache) }
|
9
10
|
let(:id) { rand(999) }
|
10
11
|
let(:schema) do
|
@@ -80,6 +81,40 @@ describe AvroTurf::CachedConfluentSchemaRegistry do
|
|
80
81
|
end
|
81
82
|
end
|
82
83
|
|
84
|
+
describe "#fetch (zero length cache file)" do
|
85
|
+
let(:cache_after) do
|
86
|
+
{
|
87
|
+
"#{id}" => "#{schema}"
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
before do
|
92
|
+
# setup the disk cache with a zero length file
|
93
|
+
File.write(File.join("spec/cache", "schemas_by_id.json"), '')
|
94
|
+
end
|
95
|
+
|
96
|
+
it "skips zero length disk cache" do
|
97
|
+
# multiple calls return same result, with only one upstream call
|
98
|
+
allow(upstream).to receive(:fetch).with(id).and_return(schema)
|
99
|
+
expect(registry.fetch(id)).to eq(schema)
|
100
|
+
expect(registry.fetch(id)).to eq(schema)
|
101
|
+
expect(upstream).to have_received(:fetch).exactly(1).times
|
102
|
+
expect(load_cache("schemas_by_id.json")).to eq cache_after
|
103
|
+
expect(logger_io.string).to include("zero length file at spec/cache/schemas_by_id.json")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#fetch (corrupt cache file)" do
|
108
|
+
before do
|
109
|
+
# setup the disk cache with a corrupt file (i.e. not json)
|
110
|
+
File.write(File.join("spec/cache", "schemas_by_id.json"), 'NOTJSON')
|
111
|
+
end
|
112
|
+
|
113
|
+
it "raises error on corrupt cache file" do
|
114
|
+
expect{registry.fetch(id)}.to raise_error(JSON::ParserError, /unexpected token/)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
83
118
|
describe "#register" do
|
84
119
|
let(:subject_name) { "a_subject" }
|
85
120
|
let(:cache_before) do
|
@@ -120,6 +155,41 @@ describe AvroTurf::CachedConfluentSchemaRegistry do
|
|
120
155
|
end
|
121
156
|
end
|
122
157
|
|
158
|
+
describe "#register (zero length cache file)" do
|
159
|
+
let(:subject_name) { "a_subject" }
|
160
|
+
let(:cache_after) do
|
161
|
+
{
|
162
|
+
"#{subject_name}#{schema}" => id
|
163
|
+
}
|
164
|
+
end
|
165
|
+
|
166
|
+
before do
|
167
|
+
# setup the disk cache with a zero length file
|
168
|
+
File.write(File.join("spec/cache", "ids_by_schema.json"), '')
|
169
|
+
end
|
170
|
+
|
171
|
+
it "skips zero length disk cache" do
|
172
|
+
# multiple calls return same result, with only one upstream call
|
173
|
+
allow(upstream).to receive(:register).with(subject_name, schema).and_return(id)
|
174
|
+
expect(registry.register(subject_name, schema)).to eq(id)
|
175
|
+
expect(registry.register(subject_name, schema)).to eq(id)
|
176
|
+
expect(upstream).to have_received(:register).exactly(1).times
|
177
|
+
expect(load_cache("ids_by_schema.json")).to eq cache_after
|
178
|
+
expect(logger_io.string).to include("zero length file at spec/cache/ids_by_schema.json")
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe "#register (corrupt cache file)" do
|
183
|
+
before do
|
184
|
+
# setup the disk cache with a corrupt file (i.e. not json)
|
185
|
+
File.write(File.join("spec/cache", "ids_by_schema.json"), 'NOTJSON')
|
186
|
+
end
|
187
|
+
|
188
|
+
it "raises error on corrupt cache file" do
|
189
|
+
expect{registry.register(subject_name, schema)}.to raise_error(JSON::ParserError, /unexpected token/)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
123
193
|
describe "#subject_version" do
|
124
194
|
it "writes thru to disk cache" do
|
125
195
|
# multiple calls return same result, with zero upstream calls
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: avro_turf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Schierbeck
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-12-14 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.12'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,7 +29,7 @@ dependencies:
|
|
29
29
|
version: 1.7.7
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '1.
|
32
|
+
version: '1.12'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: excon
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,14 +156,13 @@ dependencies:
|
|
156
156
|
- - ">="
|
157
157
|
- !ruby/object:Gem::Version
|
158
158
|
version: '0'
|
159
|
-
description:
|
159
|
+
description:
|
160
160
|
email:
|
161
161
|
- dasch@zendesk.com
|
162
162
|
executables: []
|
163
163
|
extensions: []
|
164
164
|
extra_rdoc_files: []
|
165
165
|
files:
|
166
|
-
- ".circleci/config.yml"
|
167
166
|
- ".github/workflows/ruby.yml"
|
168
167
|
- ".github/workflows/stale.yml"
|
169
168
|
- ".gitignore"
|
@@ -226,7 +225,8 @@ files:
|
|
226
225
|
homepage: https://github.com/dasch/avro_turf
|
227
226
|
licenses:
|
228
227
|
- MIT
|
229
|
-
metadata:
|
228
|
+
metadata:
|
229
|
+
rubygems_mfa_required: 'true'
|
230
230
|
post_install_message: |2
|
231
231
|
|
232
232
|
avro_turf v0.8.0 deprecates the names AvroTurf::SchemaRegistry,
|
@@ -251,7 +251,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
251
251
|
version: '0'
|
252
252
|
requirements: []
|
253
253
|
rubygems_version: 3.1.2
|
254
|
-
signing_key:
|
254
|
+
signing_key:
|
255
255
|
specification_version: 4
|
256
256
|
summary: A library that makes it easier to use the Avro serialization format from
|
257
257
|
Ruby
|
data/.circleci/config.yml
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
version: 2
|
2
|
-
jobs:
|
3
|
-
build:
|
4
|
-
environment:
|
5
|
-
CIRCLE_ARTIFACTS: /tmp/circleci-artifacts
|
6
|
-
CIRCLE_TEST_REPORTS: /tmp/circleci-test-results
|
7
|
-
docker:
|
8
|
-
- image: circleci/ruby:2.6.2
|
9
|
-
steps:
|
10
|
-
- checkout
|
11
|
-
- run: mkdir -p $CIRCLE_ARTIFACTS $CIRCLE_TEST_REPORTS
|
12
|
-
- restore_cache:
|
13
|
-
keys:
|
14
|
-
# This branch if available
|
15
|
-
- v1-dep-{{ .Branch }}-
|
16
|
-
# Default branch if not
|
17
|
-
- v1-dep-master-
|
18
|
-
# Any branch if there are none on the default branch - this should be unnecessary if you have your default branch configured correctly
|
19
|
-
- v1-dep-
|
20
|
-
- run: gem install bundler --no-document
|
21
|
-
- run: 'bundle check --path=vendor/bundle || bundle install --path=vendor/bundle --jobs=4 --retry=3'
|
22
|
-
# Save dependency cache
|
23
|
-
- save_cache:
|
24
|
-
key: v1-dep-{{ .Branch }}-{{ epoch }}
|
25
|
-
paths:
|
26
|
-
- vendor/bundle
|
27
|
-
- ~/.bundle
|
28
|
-
- run: mkdir -p $CIRCLE_TEST_REPORTS/rspec
|
29
|
-
- run:
|
30
|
-
command: bundle exec rspec --color --require spec_helper --format progress
|
31
|
-
- store_test_results:
|
32
|
-
path: /tmp/circleci-test-results
|
33
|
-
- store_artifacts:
|
34
|
-
path: /tmp/circleci-artifacts
|
35
|
-
- store_artifacts:
|
36
|
-
path: /tmp/circleci-test-results
|