graphql 1.11.4 → 1.11.5
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.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/generators/graphql/templates/union.erb +1 -1
- data/lib/graphql.rb +16 -0
- data/lib/graphql/argument.rb +3 -3
- data/lib/graphql/backtrace/tracer.rb +2 -1
- data/lib/graphql/execution/interpreter.rb +10 -0
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/runtime.rb +32 -18
- data/lib/graphql/introspection.rb +96 -0
- data/lib/graphql/introspection/field_type.rb +7 -3
- data/lib/graphql/introspection/input_value_type.rb +6 -0
- data/lib/graphql/introspection/introspection_query.rb +6 -92
- data/lib/graphql/introspection/type_type.rb +7 -3
- data/lib/graphql/language/block_string.rb +24 -5
- data/lib/graphql/language/lexer.rb +7 -3
- data/lib/graphql/language/lexer.rl +7 -3
- data/lib/graphql/language/nodes.rb +1 -1
- data/lib/graphql/language/parser.rb +107 -103
- data/lib/graphql/language/parser.y +4 -0
- data/lib/graphql/language/sanitized_printer.rb +59 -26
- data/lib/graphql/name_validator.rb +6 -7
- data/lib/graphql/pagination/connections.rb +2 -0
- data/lib/graphql/query.rb +2 -2
- data/lib/graphql/query/context.rb +10 -2
- data/lib/graphql/schema.rb +30 -16
- data/lib/graphql/schema/argument.rb +56 -5
- data/lib/graphql/schema/build_from_definition.rb +60 -35
- data/lib/graphql/schema/directive/deprecated.rb +1 -1
- data/lib/graphql/schema/field.rb +10 -4
- data/lib/graphql/schema/input_object.rb +5 -3
- data/lib/graphql/schema/interface.rb +1 -1
- data/lib/graphql/schema/late_bound_type.rb +2 -2
- data/lib/graphql/schema/loader.rb +1 -0
- data/lib/graphql/schema/member/build_type.rb +14 -4
- data/lib/graphql/schema/member/has_arguments.rb +3 -1
- data/lib/graphql/schema/member/type_system_helpers.rb +2 -2
- data/lib/graphql/schema/relay_classic_mutation.rb +3 -1
- data/lib/graphql/schema/timeout.rb +29 -15
- data/lib/graphql/schema/validation.rb +8 -0
- data/lib/graphql/static_validation/validator.rb +7 -4
- data/lib/graphql/subscriptions.rb +1 -3
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -7
- data/lib/graphql/version.rb +1 -1
- metadata +2 -2
@@ -4,7 +4,7 @@ module GraphQL
|
|
4
4
|
class Directive < GraphQL::Schema::Member
|
5
5
|
class Deprecated < GraphQL::Schema::Directive
|
6
6
|
description "Marks an element of a GraphQL schema as no longer supported."
|
7
|
-
locations(GraphQL::Schema::Directive::FIELD_DEFINITION, GraphQL::Schema::Directive::ENUM_VALUE)
|
7
|
+
locations(GraphQL::Schema::Directive::FIELD_DEFINITION, GraphQL::Schema::Directive::ENUM_VALUE, GraphQL::Schema::Directive::ARGUMENT_DEFINITION, GraphQL::Schema::Directive::INPUT_FIELD_DEFINITION)
|
8
8
|
|
9
9
|
reason_description = "Explains why this element was deprecated, usually also including a "\
|
10
10
|
"suggestion for how to access supported similar data. Formatted "\
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -203,7 +203,7 @@ module GraphQL
|
|
203
203
|
# @param broadcastable [Boolean] Whether or not this field can be distributed in subscription broadcasts
|
204
204
|
# @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
|
205
205
|
# @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
|
206
|
-
def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras:
|
206
|
+
def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, &definition_block)
|
207
207
|
if name.nil?
|
208
208
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
209
209
|
end
|
@@ -250,7 +250,7 @@ module GraphQL
|
|
250
250
|
method_name = method || hash_key || name_s
|
251
251
|
resolver_method ||= name_s.to_sym
|
252
252
|
|
253
|
-
@method_str = method_name.to_s
|
253
|
+
@method_str = -method_name.to_s
|
254
254
|
@method_sym = method_name.to_sym
|
255
255
|
@resolver_method = resolver_method
|
256
256
|
@complexity = complexity
|
@@ -274,7 +274,7 @@ module GraphQL
|
|
274
274
|
if arg.is_a?(Hash)
|
275
275
|
argument(name: name, **arg)
|
276
276
|
else
|
277
|
-
|
277
|
+
add_argument(arg)
|
278
278
|
end
|
279
279
|
end
|
280
280
|
|
@@ -282,7 +282,7 @@ module GraphQL
|
|
282
282
|
@subscription_scope = subscription_scope
|
283
283
|
|
284
284
|
# Do this last so we have as much context as possible when initializing them:
|
285
|
-
@extensions =
|
285
|
+
@extensions = EMPTY_ARRAY
|
286
286
|
if extensions.any?
|
287
287
|
self.extensions(extensions)
|
288
288
|
end
|
@@ -343,6 +343,9 @@ module GraphQL
|
|
343
343
|
# Read the value
|
344
344
|
@extensions
|
345
345
|
else
|
346
|
+
if @extensions.frozen?
|
347
|
+
@extensions = @extensions.dup
|
348
|
+
end
|
346
349
|
new_extensions.each do |extension|
|
347
350
|
if extension.is_a?(Hash)
|
348
351
|
extension = extension.to_a[0]
|
@@ -380,6 +383,9 @@ module GraphQL
|
|
380
383
|
# Read the value
|
381
384
|
@extras
|
382
385
|
else
|
386
|
+
if @extras.frozen?
|
387
|
+
@extras = @extras.dup
|
388
|
+
end
|
383
389
|
# Append to the set of extras on this field
|
384
390
|
@extras.concat(new_extras)
|
385
391
|
end
|
@@ -126,9 +126,11 @@ module GraphQL
|
|
126
126
|
argument_defn = super(*args, **kwargs, &block)
|
127
127
|
# Add a method access
|
128
128
|
method_name = argument_defn.keyword
|
129
|
-
|
130
|
-
|
131
|
-
|
129
|
+
class_eval <<-RUBY, __FILE__, __LINE__
|
130
|
+
def #{method_name}
|
131
|
+
self[#{method_name.inspect}]
|
132
|
+
end
|
133
|
+
RUBY
|
132
134
|
end
|
133
135
|
|
134
136
|
def to_graphql
|
@@ -30,7 +30,7 @@ module GraphQL
|
|
30
30
|
|
31
31
|
# The interface is accessible if any of its possible types are accessible
|
32
32
|
def accessible?(context)
|
33
|
-
context.schema.possible_types(self).each do |type|
|
33
|
+
context.schema.possible_types(self, context).each do |type|
|
34
34
|
if context.schema.accessible?(type, context)
|
35
35
|
return true
|
36
36
|
end
|
@@ -16,11 +16,11 @@ module GraphQL
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def to_non_null_type
|
19
|
-
GraphQL::NonNullType.new(of_type: self)
|
19
|
+
@to_non_null_type ||= GraphQL::NonNullType.new(of_type: self)
|
20
20
|
end
|
21
21
|
|
22
22
|
def to_list_type
|
23
|
-
GraphQL::ListType.new(of_type: self)
|
23
|
+
@to_list_type ||= GraphQL::ListType.new(of_type: self)
|
24
24
|
end
|
25
25
|
|
26
26
|
def inspect
|
@@ -4,6 +4,10 @@ module GraphQL
|
|
4
4
|
class Member
|
5
5
|
# @api private
|
6
6
|
module BuildType
|
7
|
+
if !String.method_defined?(:match?)
|
8
|
+
using GraphQL::StringMatchBackport
|
9
|
+
end
|
10
|
+
|
7
11
|
LIST_TYPE_ERROR = "Use an array of [T] or [T, null: true] for list types; other arrays are not supported"
|
8
12
|
|
9
13
|
module_function
|
@@ -162,10 +166,16 @@ module GraphQL
|
|
162
166
|
end
|
163
167
|
|
164
168
|
def underscore(string)
|
165
|
-
string
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
+
if string.match?(/\A[a-z_]+\Z/)
|
170
|
+
return string
|
171
|
+
end
|
172
|
+
string2 = string.dup
|
173
|
+
|
174
|
+
string2.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder
|
175
|
+
string2.gsub!(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing
|
176
|
+
string2.downcase!
|
177
|
+
|
178
|
+
string2
|
169
179
|
end
|
170
180
|
end
|
171
181
|
end
|
@@ -43,6 +43,7 @@ module GraphQL
|
|
43
43
|
# @param arg_defn [GraphQL::Schema::Argument]
|
44
44
|
# @return [GraphQL::Schema::Argument]
|
45
45
|
def add_argument(arg_defn)
|
46
|
+
@own_arguments ||= {}
|
46
47
|
own_arguments[arg_defn.name] = arg_defn
|
47
48
|
arg_defn
|
48
49
|
end
|
@@ -229,8 +230,9 @@ module GraphQL
|
|
229
230
|
end
|
230
231
|
end
|
231
232
|
|
233
|
+
NO_ARGUMENTS = {}.freeze
|
232
234
|
def own_arguments
|
233
|
-
@own_arguments
|
235
|
+
@own_arguments || NO_ARGUMENTS
|
234
236
|
end
|
235
237
|
end
|
236
238
|
end
|
@@ -6,12 +6,12 @@ module GraphQL
|
|
6
6
|
module TypeSystemHelpers
|
7
7
|
# @return [Schema::NonNull] Make a non-null-type representation of this type
|
8
8
|
def to_non_null_type
|
9
|
-
GraphQL::Schema::NonNull.new(self)
|
9
|
+
@to_non_null_type ||= GraphQL::Schema::NonNull.new(self)
|
10
10
|
end
|
11
11
|
|
12
12
|
# @return [Schema::List] Make a list-type representation of this type
|
13
13
|
def to_list_type
|
14
|
-
GraphQL::Schema::List.new(self)
|
14
|
+
@to_list_type ||= GraphQL::Schema::List.new(self)
|
15
15
|
end
|
16
16
|
|
17
17
|
# @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable.
|
@@ -122,7 +122,9 @@ module GraphQL
|
|
122
122
|
graphql_name("#{mutation_name}Input")
|
123
123
|
description("Autogenerated input type of #{mutation_name}")
|
124
124
|
mutation(mutation_class)
|
125
|
-
|
125
|
+
mutation_args.each do |_name, arg|
|
126
|
+
add_argument(arg)
|
127
|
+
end
|
126
128
|
argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
|
127
129
|
end
|
128
130
|
end
|
@@ -7,7 +7,7 @@ module GraphQL
|
|
7
7
|
# to the `errors` key. Any already-resolved fields will be in the `data` key, so
|
8
8
|
# you'll get a partial response.
|
9
9
|
#
|
10
|
-
# You can subclass `GraphQL::Schema::Timeout` and override
|
10
|
+
# You can subclass `GraphQL::Schema::Timeout` and override `max_seconds` and/or `handle_timeout`
|
11
11
|
# to provide custom logic when a timeout error occurs.
|
12
12
|
#
|
13
13
|
# Note that this will stop a query _in between_ field resolutions, but
|
@@ -33,8 +33,6 @@ module GraphQL
|
|
33
33
|
# end
|
34
34
|
#
|
35
35
|
class Timeout
|
36
|
-
attr_reader :max_seconds
|
37
|
-
|
38
36
|
def self.use(schema, **options)
|
39
37
|
tracer = new(**options)
|
40
38
|
schema.tracer(tracer)
|
@@ -48,32 +46,39 @@ module GraphQL
|
|
48
46
|
def trace(key, data)
|
49
47
|
case key
|
50
48
|
when 'execute_multiplex'
|
51
|
-
timeout_state = {
|
52
|
-
timeout_at: Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) + max_seconds * 1000,
|
53
|
-
timed_out: false
|
54
|
-
}
|
55
|
-
|
56
49
|
data.fetch(:multiplex).queries.each do |query|
|
50
|
+
timeout_duration_s = max_seconds(query)
|
51
|
+
timeout_state = if timeout_duration_s == false
|
52
|
+
# if the method returns `false`, don't apply a timeout
|
53
|
+
false
|
54
|
+
else
|
55
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
56
|
+
timeout_at = now + (max_seconds(query) * 1000)
|
57
|
+
{
|
58
|
+
timeout_at: timeout_at,
|
59
|
+
timed_out: false
|
60
|
+
}
|
61
|
+
end
|
57
62
|
query.context.namespace(self.class)[:state] = timeout_state
|
58
63
|
end
|
59
64
|
|
60
65
|
yield
|
61
66
|
when 'execute_field', 'execute_field_lazy'
|
62
|
-
|
63
|
-
timeout_state =
|
64
|
-
|
67
|
+
query_context = data[:context] || data[:query].context
|
68
|
+
timeout_state = query_context.namespace(self.class).fetch(:state)
|
69
|
+
# If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query.
|
70
|
+
if timeout_state != false && Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
|
65
71
|
error = if data[:context]
|
66
|
-
|
67
|
-
GraphQL::Schema::Timeout::TimeoutError.new(context.parent_type, context.field)
|
72
|
+
GraphQL::Schema::Timeout::TimeoutError.new(query_context.parent_type, query_context.field)
|
68
73
|
else
|
69
74
|
field = data.fetch(:field)
|
70
75
|
GraphQL::Schema::Timeout::TimeoutError.new(field.owner, field)
|
71
76
|
end
|
72
77
|
|
73
78
|
# Only invoke the timeout callback for the first timeout
|
74
|
-
|
79
|
+
if !timeout_state[:timed_out]
|
75
80
|
timeout_state[:timed_out] = true
|
76
|
-
handle_timeout(error, query)
|
81
|
+
handle_timeout(error, query_context.query)
|
77
82
|
end
|
78
83
|
|
79
84
|
error
|
@@ -85,6 +90,15 @@ module GraphQL
|
|
85
90
|
end
|
86
91
|
end
|
87
92
|
|
93
|
+
# Called at the start of each query.
|
94
|
+
# The default implementation returns the `max_seconds:` value from installing this plugin.
|
95
|
+
#
|
96
|
+
# @param query [GraphQL::Query] The query that's about to run
|
97
|
+
# @return [Integer, false] The number of seconds after which to interrupt query execution and call {#handle_error}, or `false` to bypass the timeout.
|
98
|
+
def max_seconds(query)
|
99
|
+
@max_seconds
|
100
|
+
end
|
101
|
+
|
88
102
|
# Invoked when a query times out.
|
89
103
|
# @param error [GraphQL::Schema::Timeout::TimeoutError]
|
90
104
|
# @param query [GraphQL::Error]
|
@@ -133,6 +133,12 @@ module GraphQL
|
|
133
133
|
end
|
134
134
|
}
|
135
135
|
|
136
|
+
DEPRECATED_ARGUMENTS_ARE_OPTIONAL = ->(argument) {
|
137
|
+
if argument.deprecation_reason && argument.type.non_null?
|
138
|
+
"must be optional because it's deprecated"
|
139
|
+
end
|
140
|
+
}
|
141
|
+
|
136
142
|
TYPE_IS_VALID_INPUT_TYPE = ->(type) {
|
137
143
|
outer_type = type.type
|
138
144
|
inner_type = outer_type.respond_to?(:unwrap) ? outer_type.unwrap : nil
|
@@ -265,8 +271,10 @@ module GraphQL
|
|
265
271
|
Rules::NAME_IS_STRING,
|
266
272
|
Rules::RESERVED_NAME,
|
267
273
|
Rules::DESCRIPTION_IS_STRING_OR_NIL,
|
274
|
+
Rules.assert_property(:deprecation_reason, String, NilClass),
|
268
275
|
Rules::TYPE_IS_VALID_INPUT_TYPE,
|
269
276
|
Rules::DEFAULT_VALUE_IS_VALID_FOR_TYPE,
|
277
|
+
Rules::DEPRECATED_ARGUMENTS_ARE_OPTIONAL,
|
270
278
|
],
|
271
279
|
GraphQL::BaseType => [
|
272
280
|
Rules::NAME_IS_STRING,
|
@@ -32,10 +32,13 @@ module GraphQL
|
|
32
32
|
|
33
33
|
context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class)
|
34
34
|
|
35
|
-
# Attach legacy-style rules
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
# Attach legacy-style rules.
|
36
|
+
# Only loop through rules if it has legacy-style rules
|
37
|
+
unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
|
38
|
+
legacy_rules.each do |rule_class_or_module|
|
39
|
+
if rule_class_or_module.method_defined?(:validate)
|
40
|
+
rule_class_or_module.new.validate(context)
|
41
|
+
end
|
39
42
|
end
|
40
43
|
end
|
41
44
|
|
@@ -4,9 +4,7 @@ require "graphql/subscriptions/broadcast_analyzer"
|
|
4
4
|
require "graphql/subscriptions/event"
|
5
5
|
require "graphql/subscriptions/instrumentation"
|
6
6
|
require "graphql/subscriptions/serialize"
|
7
|
-
|
8
|
-
require "graphql/subscriptions/action_cable_subscriptions"
|
9
|
-
end
|
7
|
+
require "graphql/subscriptions/action_cable_subscriptions"
|
10
8
|
require "graphql/subscriptions/subscription_root"
|
11
9
|
require "graphql/subscriptions/default_subscription_resolve_extension"
|
12
10
|
|
@@ -4,7 +4,7 @@ module GraphQL
|
|
4
4
|
# A subscriptions implementation that sends data
|
5
5
|
# as ActionCable broadcastings.
|
6
6
|
#
|
7
|
-
#
|
7
|
+
# Some things to keep in mind:
|
8
8
|
#
|
9
9
|
# - No queueing system; ActiveJob should be added
|
10
10
|
# - Take care to reload context when re-delivering the subscription. (see {Query#subscription_update?})
|
@@ -86,28 +86,32 @@ module GraphQL
|
|
86
86
|
EVENT_PREFIX = "graphql-event:"
|
87
87
|
|
88
88
|
# @param serializer [<#dump(obj), #load(string)] Used for serializing messages before handing them to `.broadcast(msg)`
|
89
|
-
|
89
|
+
# @param namespace [string] Used to namespace events and subscriptions (default: '')
|
90
|
+
def initialize(serializer: Serialize, namespace: '', action_cable: ActionCable, action_cable_coder: ActiveSupport::JSON, **rest)
|
90
91
|
# A per-process map of subscriptions to deliver.
|
91
92
|
# This is provided by Rails, so let's use it
|
92
93
|
@subscriptions = Concurrent::Map.new
|
93
94
|
@events = Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new { |h2, k2| h2[k2] = Concurrent::Array.new } }
|
95
|
+
@action_cable = action_cable
|
96
|
+
@action_cable_coder = action_cable_coder
|
94
97
|
@serializer = serializer
|
98
|
+
@transmit_ns = namespace
|
95
99
|
super
|
96
100
|
end
|
97
101
|
|
98
102
|
# An event was triggered; Push the data over ActionCable.
|
99
103
|
# Subscribers will re-evaluate locally.
|
100
104
|
def execute_all(event, object)
|
101
|
-
stream =
|
105
|
+
stream = stream_event_name(event)
|
102
106
|
message = @serializer.dump(object)
|
103
|
-
|
107
|
+
@action_cable.server.broadcast(stream, message)
|
104
108
|
end
|
105
109
|
|
106
110
|
# This subscription was re-evaluated.
|
107
111
|
# Send it to the specific stream where this client was waiting.
|
108
112
|
def deliver(subscription_id, result)
|
109
113
|
payload = { result: result.to_h, more: true }
|
110
|
-
|
114
|
+
@action_cable.server.broadcast(stream_subscription_name(subscription_id), payload)
|
111
115
|
end
|
112
116
|
|
113
117
|
# A query was run where these events were subscribed to.
|
@@ -117,7 +121,7 @@ module GraphQL
|
|
117
121
|
def write_subscription(query, events)
|
118
122
|
channel = query.context.fetch(:channel)
|
119
123
|
subscription_id = query.context[:subscription_id] ||= build_id
|
120
|
-
stream =
|
124
|
+
stream = stream_subscription_name(subscription_id)
|
121
125
|
channel.stream_from(stream)
|
122
126
|
@subscriptions[subscription_id] = query
|
123
127
|
events.each do |event|
|
@@ -141,7 +145,7 @@ module GraphQL
|
|
141
145
|
#
|
142
146
|
def setup_stream(channel, initial_event)
|
143
147
|
topic = initial_event.topic
|
144
|
-
channel.stream_from(
|
148
|
+
channel.stream_from(stream_event_name(initial_event), coder: @action_cable_coder) do |message|
|
145
149
|
object = @serializer.load(message)
|
146
150
|
events_by_fingerprint = @events[topic]
|
147
151
|
events_by_fingerprint.each do |_fingerprint, events|
|
@@ -197,6 +201,16 @@ module GraphQL
|
|
197
201
|
end
|
198
202
|
end
|
199
203
|
end
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
def stream_subscription_name(subscription_id)
|
208
|
+
[SUBSCRIPTION_PREFIX, @transmit_ns, subscription_id].join
|
209
|
+
end
|
210
|
+
|
211
|
+
def stream_event_name(event)
|
212
|
+
[EVENT_PREFIX, @transmit_ns, event.topic].join
|
213
|
+
end
|
200
214
|
end
|
201
215
|
end
|
202
216
|
end
|
data/lib/graphql/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.11.
|
4
|
+
version: 1.11.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark-ips
|