schema_registry_client 0.0.6 → 0.0.7
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/CHANGELOG.md +5 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/Rakefile +1 -1
- data/lib/schema_registry_client/avro_schema_store.rb +10 -10
- data/lib/schema_registry_client/cached_confluent_schema_registry.rb +6 -6
- data/lib/schema_registry_client/confluent_schema_registry.rb +13 -13
- data/lib/schema_registry_client/output/json_schema.rb +13 -13
- data/lib/schema_registry_client/output/proto_text.rb +46 -46
- data/lib/schema_registry_client/schema/avro.rb +9 -9
- data/lib/schema_registry_client/schema/base.rb +5 -5
- data/lib/schema_registry_client/schema/proto_json_schema.rb +4 -4
- data/lib/schema_registry_client/schema/protobuf.rb +22 -22
- data/lib/schema_registry_client/version.rb +2 -2
- data/lib/schema_registry_client/wire.rb +1 -1
- data/lib/schema_registry_client.rb +138 -134
- data/schema_registry_client.gemspec +20 -20
- data/spec/decoding_spec.rb +54 -54
- data/spec/encoding_spec.rb +91 -91
- data/spec/gen/everything/everything_pb.rb +10 -10
- data/spec/gen/referenced/referer_pb.rb +8 -8
- data/spec/gen/simple/simple_pb.rb +3 -3
- data/spec/json_schema_spec.rb +2 -2
- data/spec/proto_text_spec.rb +1 -1
- data/spec/spec_helper.rb +5 -5
- metadata +1 -1
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
require "google/protobuf"
|
|
4
|
+
require "google/protobuf/well_known_types"
|
|
5
|
+
require "google/protobuf/descriptor_pb"
|
|
6
|
+
require "schema_registry_client/output/proto_text"
|
|
7
|
+
require "schema_registry_client/schema/base"
|
|
8
|
+
require "schema_registry_client/wire"
|
|
9
|
+
|
|
10
|
+
module SchemaRegistry
|
|
11
11
|
module Schema
|
|
12
12
|
class Protobuf < Base
|
|
13
13
|
class << self
|
|
14
14
|
def schema_type
|
|
15
|
-
|
|
15
|
+
"PROTOBUF"
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def schema_text(message, schema_name: nil)
|
|
19
19
|
file_descriptor = if message.is_a?(Google::Protobuf::FileDescriptor)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
message
|
|
21
|
+
else
|
|
22
|
+
message.class.descriptor.file_descriptor
|
|
23
|
+
end
|
|
24
24
|
SchemaRegistry::Output::ProtoText.output(file_descriptor.to_proto)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def encode(message, stream, schema_name: nil)
|
|
28
28
|
_, indexes = find_index(message.class.descriptor.to_proto,
|
|
29
|
-
|
|
29
|
+
message.class.descriptor.file_descriptor.to_proto.message_type)
|
|
30
30
|
|
|
31
31
|
if indexes == [0]
|
|
32
32
|
SchemaRegistry::Wire.write_int(stream, 0)
|
|
@@ -60,7 +60,7 @@ class SchemaRegistry
|
|
|
60
60
|
all_files = ObjectSpace.each_object(Google::Protobuf::FileDescriptor).to_a
|
|
61
61
|
all_files.each do |file_desc|
|
|
62
62
|
file_path = file_desc.name
|
|
63
|
-
next if file_path.start_with?(
|
|
63
|
+
next if file_path.start_with?("google/protobuf/") # skip built-in protos
|
|
64
64
|
|
|
65
65
|
@all_schemas[file_path] = file_desc
|
|
66
66
|
end
|
|
@@ -69,13 +69,13 @@ class SchemaRegistry
|
|
|
69
69
|
def dependencies(message)
|
|
70
70
|
load_schemas! unless @all_schemas&.any?
|
|
71
71
|
file_descriptor = if message.is_a?(Google::Protobuf::FileDescriptor)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
message
|
|
73
|
+
else
|
|
74
|
+
message.class.descriptor.file_descriptor
|
|
75
|
+
end
|
|
76
76
|
|
|
77
77
|
deps = file_descriptor.to_proto.dependency.to_a
|
|
78
|
-
|
|
78
|
+
.reject { |d| d.start_with?("google/protobuf/") }
|
|
79
79
|
deps.to_h do |dep|
|
|
80
80
|
dependency_schema = @all_schemas[dep]
|
|
81
81
|
[dependency_schema.name, dependency_schema]
|
|
@@ -117,12 +117,12 @@ class SchemaRegistry
|
|
|
117
117
|
descriptor = Google::Protobuf::DescriptorPool.generated_pool.lookup(full_name)
|
|
118
118
|
unless descriptor
|
|
119
119
|
msg = "Could not find schema for #{full_name}. " \
|
|
120
|
-
|
|
120
|
+
"Make sure the corresponding .proto file has been compiled and loaded."
|
|
121
121
|
raise msg
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
path = find_descriptor(indexes, descriptor.file_descriptor.to_proto.message_type)
|
|
125
|
-
correct_message = Google::Protobuf::DescriptorPool.generated_pool.lookup("#{package}.#{path.join(
|
|
125
|
+
correct_message = Google::Protobuf::DescriptorPool.generated_pool.lookup("#{package}.#{path.join(".")}")
|
|
126
126
|
correct_message.msgclass.decode(encoded)
|
|
127
127
|
end
|
|
128
128
|
end
|
|
@@ -1,156 +1,160 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
require "logger"
|
|
4
|
+
require "json"
|
|
5
|
+
require "schema_registry_client/confluent_schema_registry"
|
|
6
|
+
require "schema_registry_client/cached_confluent_schema_registry"
|
|
7
|
+
require "schema_registry_client/schema/protobuf"
|
|
8
|
+
require "schema_registry_client/schema/proto_json_schema"
|
|
9
|
+
require "schema_registry_client/schema/avro"
|
|
10
|
+
|
|
11
|
+
module SchemaRegistry
|
|
12
12
|
class SchemaNotFoundError < StandardError; end
|
|
13
13
|
class SchemaError < StandardError; end
|
|
14
14
|
|
|
15
|
-
# Provides a way to encode and decode messages without having to embed schemas
|
|
16
|
-
# in the encoded data. Confluent's Schema Registry[1] is used to register
|
|
17
|
-
# a schema when encoding a message -- the registry will issue a schema id that
|
|
18
|
-
# will be included in the encoded data alongside the actual message. When
|
|
19
|
-
# decoding the data, the schema id will be used to look up the writer's schema
|
|
20
|
-
# from the registry.
|
|
21
|
-
#
|
|
22
|
-
# 1: https://github.com/confluentinc/schema-registry
|
|
23
|
-
# https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/serdes-protobuf.html
|
|
24
|
-
# https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format
|
|
25
|
-
MAGIC_BYTE = [0].pack('C').freeze
|
|
26
|
-
|
|
27
|
-
# Instantiate a new SchemaRegistry instance with the given configuration.
|
|
28
|
-
#
|
|
29
|
-
# registry - A schema registry object that responds to all methods in the
|
|
30
|
-
# SchemaRegistry::ConfluentSchemaRegistry interface.
|
|
31
|
-
# registry_url - The String URL of the schema registry that should be used.
|
|
32
|
-
# schema_context - Schema registry context name (optional)
|
|
33
|
-
# registry_path_prefix - The String URL path prefix used to namespace schema registry requests (optional).
|
|
34
|
-
# logger - The Logger that should be used to log information (optional).
|
|
35
|
-
# proxy - Forward the request via proxy (optional).
|
|
36
|
-
# user - User for basic auth (optional).
|
|
37
|
-
# password - Password for basic auth (optional).
|
|
38
|
-
# ssl_ca_file - Name of file containing CA certificate (optional).
|
|
39
|
-
# client_cert - Name of file containing client certificate (optional).
|
|
40
|
-
# client_key - Name of file containing client private key to go with client_cert (optional).
|
|
41
|
-
# client_key_pass - Password to go with client_key (optional).
|
|
42
|
-
# client_cert_data - In-memory client certificate (optional).
|
|
43
|
-
# client_key_data - In-memory client private key to go with client_cert_data (optional).
|
|
44
|
-
# connect_timeout - Timeout to use in the connection with the schema registry (optional).
|
|
45
|
-
# resolv_resolver - Custom domain name resolver (optional).
|
|
46
|
-
# schema_type - A SchemaRegistry::Schema::Base subclass.
|
|
47
|
-
def initialize( # rubocop:disable Metrics/ParameterLists
|
|
48
|
-
registry: nil,
|
|
49
|
-
registry_url: nil,
|
|
50
|
-
schema_context: nil,
|
|
51
|
-
registry_path_prefix: nil,
|
|
52
|
-
logger: nil,
|
|
53
|
-
proxy: nil,
|
|
54
|
-
user: nil,
|
|
55
|
-
password: nil,
|
|
56
|
-
ssl_ca_file: nil,
|
|
57
|
-
client_cert: nil,
|
|
58
|
-
client_key: nil,
|
|
59
|
-
client_key_pass: nil,
|
|
60
|
-
client_cert_data: nil,
|
|
61
|
-
client_key_data: nil,
|
|
62
|
-
connect_timeout: nil,
|
|
63
|
-
resolv_resolver: nil,
|
|
64
|
-
schema_type: SchemaRegistry::Schema::Protobuf
|
|
65
|
-
)
|
|
66
|
-
@logger = logger || Logger.new($stderr)
|
|
67
|
-
@registry = registry || SchemaRegistry::CachedConfluentSchemaRegistry.new(
|
|
68
|
-
SchemaRegistry::ConfluentSchemaRegistry.new(
|
|
69
|
-
registry_url,
|
|
70
|
-
schema_context: schema_context,
|
|
71
|
-
logger: @logger,
|
|
72
|
-
proxy: proxy,
|
|
73
|
-
user: user,
|
|
74
|
-
password: password,
|
|
75
|
-
ssl_ca_file: ssl_ca_file,
|
|
76
|
-
client_cert: client_cert,
|
|
77
|
-
client_key: client_key,
|
|
78
|
-
client_key_pass: client_key_pass,
|
|
79
|
-
client_cert_data: client_cert_data,
|
|
80
|
-
client_key_data: client_key_data,
|
|
81
|
-
path_prefix: registry_path_prefix,
|
|
82
|
-
connect_timeout: connect_timeout,
|
|
83
|
-
resolv_resolver: resolv_resolver
|
|
84
|
-
)
|
|
85
|
-
)
|
|
86
|
-
@schema = schema_type
|
|
87
|
-
end
|
|
88
|
-
|
|
89
15
|
class << self
|
|
90
16
|
attr_accessor :avro_schema_path
|
|
91
17
|
end
|
|
92
18
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
#
|
|
103
|
-
|
|
19
|
+
class Client
|
|
20
|
+
# Provides a way to encode and decode messages without having to embed schemas
|
|
21
|
+
# in the encoded data. Confluent's Schema Registry[1] is used to register
|
|
22
|
+
# a schema when encoding a message -- the registry will issue a schema id that
|
|
23
|
+
# will be included in the encoded data alongside the actual message. When
|
|
24
|
+
# decoding the data, the schema id will be used to look up the writer's schema
|
|
25
|
+
# from the registry.
|
|
26
|
+
#
|
|
27
|
+
# 1: https://github.com/confluentinc/schema-registry
|
|
28
|
+
# https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/serdes-protobuf.html
|
|
29
|
+
# https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format
|
|
30
|
+
MAGIC_BYTE = [0].pack("C").freeze
|
|
31
|
+
|
|
32
|
+
# Instantiate a new SchemaRegistry instance with the given configuration.
|
|
33
|
+
#
|
|
34
|
+
# registry - A schema registry object that responds to all methods in the
|
|
35
|
+
# SchemaRegistry::ConfluentSchemaRegistry interface.
|
|
36
|
+
# registry_url - The String URL of the schema registry that should be used.
|
|
37
|
+
# schema_context - Schema registry context name (optional)
|
|
38
|
+
# registry_path_prefix - The String URL path prefix used to namespace schema registry requests (optional).
|
|
39
|
+
# logger - The Logger that should be used to log information (optional).
|
|
40
|
+
# proxy - Forward the request via proxy (optional).
|
|
41
|
+
# user - User for basic auth (optional).
|
|
42
|
+
# password - Password for basic auth (optional).
|
|
43
|
+
# ssl_ca_file - Name of file containing CA certificate (optional).
|
|
44
|
+
# client_cert - Name of file containing client certificate (optional).
|
|
45
|
+
# client_key - Name of file containing client private key to go with client_cert (optional).
|
|
46
|
+
# client_key_pass - Password to go with client_key (optional).
|
|
47
|
+
# client_cert_data - In-memory client certificate (optional).
|
|
48
|
+
# client_key_data - In-memory client private key to go with client_cert_data (optional).
|
|
49
|
+
# connect_timeout - Timeout to use in the connection with the schema registry (optional).
|
|
50
|
+
# resolv_resolver - Custom domain name resolver (optional).
|
|
51
|
+
# schema_type - A SchemaRegistry::Schema::Base subclass.
|
|
52
|
+
def initialize( # rubocop:disable Metrics/ParameterLists
|
|
53
|
+
registry: nil,
|
|
54
|
+
registry_url: nil,
|
|
55
|
+
schema_context: nil,
|
|
56
|
+
registry_path_prefix: nil,
|
|
57
|
+
logger: nil,
|
|
58
|
+
proxy: nil,
|
|
59
|
+
user: nil,
|
|
60
|
+
password: nil,
|
|
61
|
+
ssl_ca_file: nil,
|
|
62
|
+
client_cert: nil,
|
|
63
|
+
client_key: nil,
|
|
64
|
+
client_key_pass: nil,
|
|
65
|
+
client_cert_data: nil,
|
|
66
|
+
client_key_data: nil,
|
|
67
|
+
connect_timeout: nil,
|
|
68
|
+
resolv_resolver: nil,
|
|
69
|
+
schema_type: SchemaRegistry::Schema::Protobuf
|
|
70
|
+
)
|
|
71
|
+
@logger = logger || Logger.new($stderr)
|
|
72
|
+
@registry = registry || SchemaRegistry::CachedConfluentSchemaRegistry.new(
|
|
73
|
+
SchemaRegistry::ConfluentSchemaRegistry.new(
|
|
74
|
+
registry_url,
|
|
75
|
+
schema_context: schema_context,
|
|
76
|
+
logger: @logger,
|
|
77
|
+
proxy: proxy,
|
|
78
|
+
user: user,
|
|
79
|
+
password: password,
|
|
80
|
+
ssl_ca_file: ssl_ca_file,
|
|
81
|
+
client_cert: client_cert,
|
|
82
|
+
client_key: client_key,
|
|
83
|
+
client_key_pass: client_key_pass,
|
|
84
|
+
client_cert_data: client_cert_data,
|
|
85
|
+
client_key_data: client_key_data,
|
|
86
|
+
path_prefix: registry_path_prefix,
|
|
87
|
+
connect_timeout: connect_timeout,
|
|
88
|
+
resolv_resolver: resolv_resolver
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
@schema = schema_type
|
|
92
|
+
end
|
|
104
93
|
|
|
105
|
-
#
|
|
106
|
-
|
|
94
|
+
# Encodes a message using the specified schema.
|
|
95
|
+
# @param message [Object] The message that should be encoded. Must be compatible with the schema.
|
|
96
|
+
# @param subject [String] The subject name the schema should be registered under in the schema registry (optional).
|
|
97
|
+
# @param schema_name [String] the name of the schema to use for encoding (optional).
|
|
98
|
+
# @return [String] the encoded data.
|
|
99
|
+
def encode(message, subject: nil, schema_text: nil, schema_name: nil)
|
|
100
|
+
id = register_schema(message, subject, schema_text: schema_text, schema_name: schema_name)
|
|
107
101
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
102
|
+
stream = StringIO.new
|
|
103
|
+
# Always start with the magic byte.
|
|
104
|
+
stream.write(MAGIC_BYTE)
|
|
111
105
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
# @param data [String] a string containing encoded data.
|
|
115
|
-
# @return [Object] the decoded data.
|
|
116
|
-
def decode(data)
|
|
117
|
-
stream = StringIO.new(data)
|
|
106
|
+
# The schema id is encoded as a 4-byte big-endian integer.
|
|
107
|
+
stream.write([id].pack("N"))
|
|
118
108
|
|
|
119
|
-
|
|
120
|
-
|
|
109
|
+
@schema.encode(message, stream, schema_name: schema_name)
|
|
110
|
+
stream.string
|
|
111
|
+
end
|
|
121
112
|
|
|
122
|
-
|
|
113
|
+
# Decodes data into the original message.
|
|
114
|
+
#
|
|
115
|
+
# @param data [String] a string containing encoded data.
|
|
116
|
+
# @return [Object] the decoded data.
|
|
117
|
+
def decode(data)
|
|
118
|
+
stream = StringIO.new(data)
|
|
123
119
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
schema = @registry.fetch(schema_id)
|
|
127
|
-
@schema.decode(stream, schema)
|
|
128
|
-
rescue Excon::Error::NotFound
|
|
129
|
-
raise SchemaNotFoundError, "Schema with id: #{schema_id} is not found on registry"
|
|
130
|
-
end
|
|
120
|
+
# The first byte is MAGIC!!!
|
|
121
|
+
magic_byte = stream.read(1)
|
|
131
122
|
|
|
132
|
-
|
|
123
|
+
raise "Expected data to begin with a magic byte, got `#{magic_byte.inspect}`" if magic_byte != MAGIC_BYTE
|
|
133
124
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
125
|
+
# The schema id is a 4-byte big-endian integer.
|
|
126
|
+
schema_id = stream.read(4).unpack1("N")
|
|
127
|
+
schema = @registry.fetch(schema_id)
|
|
128
|
+
@schema.decode(stream, schema)
|
|
129
|
+
rescue Excon::Error::NotFound
|
|
130
|
+
raise SchemaNotFoundError, "Schema with id: #{schema_id} is not found on registry"
|
|
131
|
+
end
|
|
137
132
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
@registry.
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def register_schema(message, subject, schema_text: nil, schema_name: nil)
|
|
136
|
+
schema_text ||= @schema.schema_text(message, schema_name: schema_name)
|
|
137
|
+
return if @registry.registered?(schema_text, subject)
|
|
138
|
+
|
|
139
|
+
# register dependencies first
|
|
140
|
+
dependencies = @schema.dependencies(message)
|
|
141
|
+
versions = dependencies.map do |name, dependency|
|
|
142
|
+
result = register_schema(dependency, name)
|
|
143
|
+
@registry.fetch_version(result, name)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
@registry.register(subject,
|
|
147
|
+
schema_text,
|
|
148
|
+
references: dependencies.keys.map.with_index do |dependency, i|
|
|
149
|
+
{
|
|
150
|
+
name: dependency,
|
|
151
|
+
subject: dependency,
|
|
152
|
+
version: versions[i]
|
|
153
|
+
}
|
|
154
|
+
end,
|
|
155
|
+
schema_type: @schema.schema_type)
|
|
143
156
|
end
|
|
144
157
|
|
|
145
|
-
@registry.register(subject,
|
|
146
|
-
schema_text,
|
|
147
|
-
references: dependencies.keys.map.with_index do |dependency, i|
|
|
148
|
-
{
|
|
149
|
-
name: dependency,
|
|
150
|
-
subject: dependency,
|
|
151
|
-
version: versions[i]
|
|
152
|
-
}
|
|
153
|
-
end,
|
|
154
|
-
schema_type: @schema.schema_type)
|
|
155
158
|
end
|
|
159
|
+
|
|
156
160
|
end
|
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
lib = File.expand_path(
|
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
-
require
|
|
5
|
+
require "schema_registry_client/version"
|
|
6
6
|
|
|
7
7
|
Gem::Specification.new do |spec|
|
|
8
|
-
spec.name =
|
|
8
|
+
spec.name = "schema_registry_client"
|
|
9
9
|
spec.version = SchemaRegistry::VERSION
|
|
10
|
-
spec.authors = [
|
|
11
|
-
spec.email = [
|
|
12
|
-
spec.summary =
|
|
13
|
-
spec.homepage =
|
|
14
|
-
spec.license =
|
|
15
|
-
spec.required_ruby_version =
|
|
10
|
+
spec.authors = ["Daniel Orner"]
|
|
11
|
+
spec.email = ["daniel.orner@flipp.com"]
|
|
12
|
+
spec.summary = "Confluent Schema Registry client with support for Avro and Protobuf"
|
|
13
|
+
spec.homepage = "https://github.com/flipp-oss/schema_registry_client"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 3.0"
|
|
16
16
|
|
|
17
|
-
spec.metadata[
|
|
17
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
18
18
|
|
|
19
19
|
spec.files = `git ls-files -z`.split("\x0")
|
|
20
20
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
21
|
-
spec.require_paths = [
|
|
21
|
+
spec.require_paths = ["lib"]
|
|
22
22
|
|
|
23
|
-
spec.add_dependency
|
|
24
|
-
spec.add_dependency
|
|
25
|
-
spec.add_dependency
|
|
23
|
+
spec.add_dependency "avro"
|
|
24
|
+
spec.add_dependency "excon"
|
|
25
|
+
spec.add_dependency "google-protobuf"
|
|
26
26
|
|
|
27
|
-
spec.add_development_dependency
|
|
28
|
-
spec.add_development_dependency
|
|
29
|
-
spec.add_development_dependency
|
|
30
|
-
spec.add_development_dependency
|
|
31
|
-
spec.add_development_dependency
|
|
32
|
-
spec.add_development_dependency
|
|
27
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
|
28
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
29
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
|
30
|
+
spec.add_development_dependency "simplecov"
|
|
31
|
+
spec.add_development_dependency "standardrb"
|
|
32
|
+
spec.add_development_dependency "webmock"
|
|
33
33
|
end
|