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 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