avro_turf 0.9.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 01a2bdf42a996a7a89bab1769672adc11888d973e5c0c70df42d07b63e87e2bf
4
- data.tar.gz: c80b96eace3ae4fdb254f046335e7aaf21939cde9da86b671d7450e3d1d2ceb4
3
+ metadata.gz: 8fc2d29f1112e649cc2cd5ec96f2126a0ea5fa8f46ebee01d908cdceeac8da0d
4
+ data.tar.gz: e1989abd9d9b0e5db24f360e96e7bce7ce18e5f488cc794d25a2b3fae6102e66
5
5
  SHA512:
6
- metadata.gz: 978d5323dd68d2a1518dadcaa90c1871b065e184ffd06ca7a9a86ad1402c16a46f557ba5847e0a841a269b38bfdcb0769a66ec961eddd6b40dd4f025c6eb2c9e
7
- data.tar.gz: c2abf819298e5c925f60e7d5e6536aced127e1bee0ef4a66893ea25c16f1e8ea9905de9e142e3f7e0a5989901e7708a9ce000cd85384ef2a703d6e77eb27ce7d
6
+ metadata.gz: f562a66ba746d2c0e4bee8b8f3a0e7fcf65900f261eca444986c8ff6c68231e67aa9c4dc659213721d0d09c65fd9273e12e158938c0773baa48cf86598d6bef9
7
+ data.tar.gz: 53e3640900a3b64038fe063f5dadab2049417490be1833bb5f17124307abe628af64ea0c68b5bdc2bd4ef2f6e58bbfac287696eaf5ece9a35ddb7c2ab8e5127e
@@ -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 RspecJunitFormatter --out $CIRCLE_TEST_REPORTS/rspec/rspec.xml --format progress spec
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'
@@ -1,7 +1,30 @@
1
- # avro_turf
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
@@ -2,6 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in avro_turf.gemspec
4
4
  gemspec
5
-
6
- # Used by CircleCI to format RSpec results.
7
- gem 'rspec_junit_formatter', :git => 'git@github.com:circleci/rspec_junit_formatter.git'
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
@@ -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.10"
21
- spec.add_dependency "excon", "~> 0.45"
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", "~> 1.7"
24
- spec.add_development_dependency "rake", "~> 10.0"
25
- spec.add_development_dependency "rspec", "~> 3.2.0"
26
- spec.add_development_dependency "fakefs", "~> 0.6.7"
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(url, logger: Logger.new($stdout))
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
- @connection = Excon.new(url, headers: {
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)
@@ -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
@@ -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 - A schema registry object that responds to all methods in the
28
- # AvroTurf::ConfluentSchemaRegistry interface.
29
- # registry_url - The String URL of the schema registry that should be used.
30
- # schema_store - A schema store object that responds to #find(schema_name, namespace).
31
- # schemas_path - The String file system path where local schemas are stored.
32
- # namespace - The String default schema namespace.
33
- # logger - The Logger that should be used to log information (optional).
34
- def initialize(registry: nil, registry_url: nil, schema_store: nil, schemas_path: nil, namespace: nil, logger: nil)
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(ConfluentSchemaRegistry.new(registry_url, logger: @logger))
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
- schema_id, schema = if subject && version
57
- fetch_schema(subject, version)
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
- raise SchemaNotFoundError.new("Schema with subject: `#{subject}` version: `#{version}` is not found on registry")
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, version)
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
- [schema_id, schema]
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, schema_name, namespace)
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
- [schema_id, schema]
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
- load_schema!($1)
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
- # Re-resolve the original schema now that the dependency has been resolved.
67
- @schemas.delete(fullname)
68
- load_schema!(fullname)
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
@@ -1,3 +1,3 @@
1
1
  class AvroTurf
2
- VERSION = "0.9.0"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -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) { described_class.new(registry_url, logger: logger) }
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) }
@@ -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', Avro::Schema.parse(schema_json))
70
- registry.register('people', Avro::Schema.parse(schema_json))
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
@@ -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: 0.9.0
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: 2019-07-15 00:00:00.000000000 Z
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.10'
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.10'
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.45'
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.45'
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: '1.7'
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: '1.7'
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: '10.0'
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: '10.0'
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.0
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.0
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.6.7
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.6.7
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
- rubygems_version: 3.0.3
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