graphql 1.9.3 → 1.9.4

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.
Files changed (67) hide show
  1. checksums.yaml +5 -5
  2. data/lib/graphql.rb +1 -0
  3. data/lib/graphql/analysis/ast/field_usage.rb +1 -1
  4. data/lib/graphql/analysis/ast/max_query_complexity.rb +1 -1
  5. data/lib/graphql/analysis/ast/query_complexity.rb +3 -2
  6. data/lib/graphql/execution/execute.rb +10 -6
  7. data/lib/graphql/execution/interpreter/runtime.rb +23 -12
  8. data/lib/graphql/execution/lookahead.rb +0 -4
  9. data/lib/graphql/integer_encoding_error.rb +9 -1
  10. data/lib/graphql/language/visitor.rb +6 -0
  11. data/lib/graphql/load_application_object_failed_error.rb +22 -0
  12. data/lib/graphql/query.rb +6 -1
  13. data/lib/graphql/query/arguments.rb +1 -1
  14. data/lib/graphql/query/variables.rb +14 -8
  15. data/lib/graphql/rake_task/validate.rb +1 -1
  16. data/lib/graphql/schema/field_extension.rb +2 -2
  17. data/lib/graphql/schema/input_object.rb +15 -6
  18. data/lib/graphql/schema/member/has_arguments.rb +80 -0
  19. data/lib/graphql/schema/member/instrumentation.rb +6 -0
  20. data/lib/graphql/schema/resolver.rb +8 -92
  21. data/lib/graphql/static_validation/base_visitor.rb +8 -0
  22. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  23. data/lib/graphql/tracing.rb +2 -2
  24. data/lib/graphql/tracing/data_dog_tracing.rb +19 -0
  25. data/lib/graphql/tracing/platform_tracing.rb +2 -1
  26. data/lib/graphql/types.rb +1 -0
  27. data/lib/graphql/types/big_int.rb +19 -0
  28. data/lib/graphql/types/int.rb +1 -0
  29. data/lib/graphql/version.rb +1 -1
  30. data/spec/graphql/authorization_spec.rb +26 -0
  31. data/spec/graphql/language/visitor_spec.rb +19 -0
  32. data/spec/graphql/schema/argument_spec.rb +26 -1
  33. data/spec/graphql/schema/field_extension_spec.rb +24 -2
  34. data/spec/graphql/schema/input_object_spec.rb +119 -10
  35. data/spec/graphql/schema/introspection_system_spec.rb +9 -0
  36. data/spec/graphql/schema/resolver_spec.rb +2 -2
  37. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +2 -2
  38. data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +1 -1
  39. data/spec/graphql/static_validation/rules/required_input_object_attributes_are_present_spec.rb +16 -2
  40. data/spec/graphql/subscriptions_spec.rb +38 -0
  41. data/spec/graphql/tracing/new_relic_tracing_spec.rb +21 -0
  42. data/spec/graphql/types/big_int_spec.rb +24 -0
  43. data/spec/support/jazz.rb +11 -0
  44. metadata +13 -51
  45. data/spec/dummy/Gemfile.lock +0 -157
  46. data/spec/dummy/log/test.log +0 -199
  47. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/4w/4wzXRZrAkwKdgYaSE0pid5eB-fer8vSfSku_NPg4rMA.cache +0 -0
  48. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/7I/7IHVBiJT06QSpgLpLoJIxboQ0B-D_tMTxsvoezBTV3Q.cache +0 -1
  49. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/8w/8wY_SKagj8wHuwGNAAf6JnQ8joMbC6cEYpHrTAI8Urc.cache +0 -1
  50. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/AK/AKzz1u6bGb4auXcrObA_g5LL-oV0ejNGa448AgAi_WQ.cache +0 -1
  51. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ET/ETW4uxvaYpruL8y6_ZptUH82ZowMaHIqvg5WexBFdEM.cache +0 -3
  52. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F1/F1TWpjjyA56k9Z90n5B3xRn7DUdGjX73QCkYC6k07JQ.cache +0 -0
  53. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F8/F8MUNRzORGFgr329fNM0xLaoWCXdv3BIalT7dsvLfjs.cache +0 -2
  54. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/KB/KB07ZaKNC5uXJ7TjLi-WqnY6g7dq8wWp_8N3HNjBNxg.cache +0 -2
  55. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Ms/MsKSimH_UCB-H1tLvDABDHuvGciuoW6kVqQWDrXU5FQ.cache +0 -0
  56. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Mt/Mtci-Kim50aPOmeClD4AIicKn1d1WJ0n454IjSd94sk.cache +0 -0
  57. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/QH/QHt3Tc1Y6M66Oo_pDuMyWrQNs4Pp3SMeZR5K1wJj2Ts.cache +0 -1
  58. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/XU/XU4k1OXnfMils5SrirorPvDSyDSqiOWLZNtmAH1HH8k.cache +0 -0
  59. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ZI/ZIof7mZxWWCnraIFOCuV6a8QRWzKJXJnx2Xd7C0ZyX0.cache +0 -1
  60. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/cG/cGc_puuPS5pZKgUcy1Y_i1L6jl5UtsiIrMH59rTzR6c.cache +0 -3
  61. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/df/dfro_B6bx3KP1Go-7jEOqqZ2j4hVRseXIc3es9PKQno.cache +0 -1
  62. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/jO/jO1DfbqnG0mTULsjJJANc3fefrG2zt7DIMmcptMT628.cache +0 -1
  63. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/pE/pE7gO6pQ-z187Swb4hT554wmqsq-cNzgPWLrCz-LQQQ.cache +0 -0
  64. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/r9/r9iU1l58a6rxkZSW5RSC52_tD-_UQuHxoMVnkfJ7Mhs.cache +0 -1
  65. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/xi/xitPPFfPIyDMpaznV0sBBcw8eSCV8PJcLLWin78sCgE.cache +0 -0
  66. data/spec/dummy/tmp/screenshots/failures_test_it_handles_subscriptions.png +0 -0
  67. data/spec/integration/tmp/app/graphql/types/family_type.rb +0 -9
@@ -28,6 +28,12 @@ module GraphQL
28
28
  wrapper_class = root_type.metadata[:type_class]
29
29
  if wrapper_class
30
30
  new_root_value = wrapper_class.authorized_new(query.root_value, query.context)
31
+ new_root_value = query.schema.sync_lazy(new_root_value)
32
+ if new_root_value.nil?
33
+ # This is definitely a hack,
34
+ # but we need some way to tell execute.rb not to run.
35
+ query.context[:__root_unauthorized] = true
36
+ end
31
37
  query.root_value = new_root_value
32
38
  end
33
39
  end
@@ -168,78 +168,6 @@ module GraphQL
168
168
  public_send("load_#{name}", value)
169
169
  end
170
170
 
171
- class LoadApplicationObjectFailedError < GraphQL::ExecutionError
172
- # @return [GraphQL::Schema::Argument] the argument definition for the argument that was looked up
173
- attr_reader :argument
174
- # @return [String] The ID provided by the client
175
- attr_reader :id
176
- # @return [Object] The value found with this ID
177
- attr_reader :object
178
- def initialize(argument:, id:, object:)
179
- @id = id
180
- @argument = argument
181
- @object = object
182
- super("No object found for `#{argument.graphql_name}: #{id.inspect}`")
183
- end
184
- end
185
-
186
- # Look up the corresponding object for a provided ID.
187
- # By default, it uses Relay-style {Schema.object_from_id},
188
- # override this to find objects another way.
189
- #
190
- # @param type [Class, Module] A GraphQL type definition
191
- # @param id [String] A client-provided to look up
192
- # @param context [GraphQL::Query::Context] the current context
193
- def object_from_id(type, id, context)
194
- context.schema.object_from_id(id, context)
195
- end
196
-
197
- def load_application_object(arg_kwarg, id)
198
- argument = @arguments_by_keyword[arg_kwarg]
199
- lookup_as_type = @arguments_loads_as_type[arg_kwarg]
200
- # See if any object can be found for this ID
201
- loaded_application_object = object_from_id(lookup_as_type, id, context)
202
- context.schema.after_lazy(loaded_application_object) do |application_object|
203
- if application_object.nil?
204
- err = LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
205
- load_application_object_failed(err)
206
- end
207
- # Double-check that the located object is actually of this type
208
- # (Don't want to allow arbitrary access to objects this way)
209
- resolved_application_object_type = context.schema.resolve_type(lookup_as_type, application_object, context)
210
- context.schema.after_lazy(resolved_application_object_type) do |application_object_type|
211
- possible_object_types = context.schema.possible_types(lookup_as_type)
212
- if !possible_object_types.include?(application_object_type)
213
- err = LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
214
- load_application_object_failed(err)
215
- else
216
- # This object was loaded successfully
217
- # and resolved to the right type,
218
- # now apply the `.authorized?` class method if there is one
219
- if (class_based_type = application_object_type.metadata[:type_class])
220
- context.schema.after_lazy(class_based_type.authorized?(application_object, context)) do |authed|
221
- if authed
222
- application_object
223
- else
224
- raise GraphQL::UnauthorizedError.new(
225
- object: application_object,
226
- type: class_based_type,
227
- context: context,
228
- )
229
- end
230
- end
231
- else
232
- application_object
233
- end
234
- end
235
- end
236
- end
237
- end
238
-
239
- def load_application_object_failed(err)
240
- raise err
241
- end
242
-
243
171
  class << self
244
172
  # Default `:resolve` set below.
245
173
  # @return [Symbol] The method to call on instances of this object to resolve the field
@@ -327,36 +255,24 @@ module GraphQL
327
255
  # also add some preparation hook methods which will be used for this argument
328
256
  # @see {GraphQL::Schema::Argument#initialize} for the signature
329
257
  def argument(name, type, *rest, loads: nil, **kwargs, &block)
330
- if loads
331
- name_as_string = name.to_s
332
-
333
- inferred_arg_name = case name_as_string
334
- when /_id$/
335
- name_as_string.sub(/_id$/, "").to_sym
336
- when /_ids$/
337
- name_as_string.sub(/_ids$/, "")
338
- .sub(/([^s])$/, "\\1s")
339
- .to_sym
340
- else
341
- name
342
- end
343
-
344
- kwargs[:as] ||= inferred_arg_name
345
- own_arguments_loads_as_type[kwargs[:as]] = loads
346
- end
258
+ arg_defn = super(*argument_with_loads(name, type, *rest, loads: loads, **kwargs, &block))
347
259
 
348
- arg_defn = super(name, type, *rest, **kwargs, &block)
260
+ own_arguments_loads_as_type[arg_defn.keyword] = loads if loads
349
261
 
350
262
  if loads && arg_defn.type.list?
351
263
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
352
264
  def load_#{arg_defn.keyword}(values)
353
- GraphQL::Execution::Lazy.all(values.map { |value| load_application_object(:#{arg_defn.keyword}, value) })
265
+ argument = @arguments_by_keyword[:#{arg_defn.keyword}]
266
+ lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
267
+ GraphQL::Execution::Lazy.all(values.map { |value| load_application_object(argument, lookup_as_type, value) })
354
268
  end
355
269
  RUBY
356
270
  elsif loads
357
271
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
358
272
  def load_#{arg_defn.keyword}(value)
359
- load_application_object(:#{arg_defn.keyword}, value)
273
+ argument = @arguments_by_keyword[:#{arg_defn.keyword}]
274
+ lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
275
+ load_application_object(argument, lookup_as_type, value)
360
276
  end
361
277
  RUBY
362
278
  else
@@ -145,6 +145,14 @@ module GraphQL
145
145
  @path.pop
146
146
  end
147
147
 
148
+ def on_input_object(node, parent)
149
+ is_list = @argument_definitions.last&.type&.list? == true
150
+
151
+ @path.push(parent.children.index(node)) if is_list
152
+ super
153
+ @path.pop if is_list
154
+ end
155
+
148
156
  # @return [GraphQL::BaseType] The current object type
149
157
  def type_definition
150
158
  @object_types.last
@@ -46,7 +46,7 @@ module GraphQL
46
46
  # # Track the subscription here so we can remove it
47
47
  # # on unsubscribe.
48
48
  # if result.context[:subscription_id]
49
- # @subscription_ids << context[:subscription_id]
49
+ # @subscription_ids << result.context[:subscription_id]
50
50
  # end
51
51
  #
52
52
  # transmit(payload)
@@ -42,8 +42,8 @@ module GraphQL
42
42
  # execute_multiplex | `{ multiplex: GraphQL::Execution::Multiplex }`
43
43
  # execute_query | `{ query: GraphQL::Query }`
44
44
  # execute_query_lazy | `{ query: GraphQL::Query?, multiplex: GraphQL::Execution::Multiplex? }`
45
- # execute_field | `{ context: GraphQL::Query::Context::FieldResolutionContext?, field: GraphQL::Schema::Field?, path: Array<String, Integer>?}`
46
- # execute_field_lazy | `{ context: GraphQL::Query::Context::FieldResolutionContext?, field: GraphQL::Schema::Field?, path: Array<String, Integer>?}`
45
+ # execute_field | `{ context: GraphQL::Query::Context::FieldResolutionContext?, owner: Class?, field: GraphQL::Schema::Field?, path: Array<String, Integer>?}`
46
+ # execute_field_lazy | `{ context: GraphQL::Query::Context::FieldResolutionContext?, owner: Class?, field: GraphQL::Schema::Field?, path: Array<String, Integer>?}`
47
47
  #
48
48
  # Note that `execute_field` and `execute_field_lazy` receive different data in different settings:
49
49
  #
@@ -21,6 +21,11 @@ module GraphQL
21
21
  if key == 'execute_multiplex'
22
22
  operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
23
23
  span.resource = operations unless operations.empty?
24
+
25
+ # For top span of query, set the analytics sample rate tag, if available.
26
+ if analytics_enabled?
27
+ Datadog::Contrib::Analytics.set_sample_rate(span, analytics_sample_rate)
28
+ end
24
29
  end
25
30
 
26
31
  if key == 'execute_query'
@@ -41,6 +46,20 @@ module GraphQL
41
46
  options.fetch(:tracer, Datadog.tracer)
42
47
  end
43
48
 
49
+ def analytics_available?
50
+ defined?(Datadog::Contrib::Analytics) \
51
+ && Datadog::Contrib::Analytics.respond_to?(:enabled?) \
52
+ && Datadog::Contrib::Analytics.respond_to?(:set_sample_rate)
53
+ end
54
+
55
+ def analytics_enabled?
56
+ analytics_available? && Datadog::Contrib::Analytics.enabled?(options.fetch(:analytics_enabled, false))
57
+ end
58
+
59
+ def analytics_sample_rate
60
+ options.fetch(:analytics_sample_rate, 1.0)
61
+ end
62
+
44
63
  def platform_field_key(type, field)
45
64
  "#{type.graphql_name}.#{field.graphql_name}"
46
65
  end
@@ -32,8 +32,9 @@ module GraphQL
32
32
  trace_field = true # implemented with instrumenter
33
33
  else
34
34
  field = data[:field]
35
+ owner = data[:owner]
35
36
  # Lots of duplicated work here, can this be done ahead of time?
36
- platform_key = platform_field_key(field.owner, field)
37
+ platform_key = platform_field_key(owner, field)
37
38
  return_type = field.type.unwrap
38
39
  # Handle LateBoundTypes, which don't have `#kind`
39
40
  trace_field = if return_type.respond_to?(:kind) && (return_type.kind.scalar? || return_type.kind.enum?)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/types/boolean"
3
+ require "graphql/types/big_int"
3
4
  require "graphql/types/float"
4
5
  require "graphql/types/id"
5
6
  require "graphql/types/int"
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Types
5
+ class BigInt < GraphQL::Schema::Scalar
6
+ description "Represents non-fractional signed whole numeric values. Since the value may exceed the size of a 32-bit integer, it's encoded as a string."
7
+
8
+ def self.coerce_input(value, _ctx)
9
+ Integer(value)
10
+ rescue ArgumentError
11
+ nil
12
+ end
13
+
14
+ def self.coerce_result(value, _ctx)
15
+ value.to_i.to_s
16
+ end
17
+ end
18
+ end
19
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module GraphQL
4
4
  module Types
5
+ # @see {Types::BigInt} for handling integers outside 32-bit range.
5
6
  class Int < GraphQL::Schema::Scalar
6
7
  description "Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1."
7
8
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.9.3"
3
+ VERSION = "1.9.4"
4
4
  end
@@ -912,4 +912,30 @@ describe GraphQL::Authorization do
912
912
  assert_equal [{"message"=>"Unauthorized Query: nil"}], unauth_res["errors"]
913
913
  end
914
914
  end
915
+
916
+ describe "returning false" do
917
+ class FalseSchema < GraphQL::Schema
918
+ class Query < GraphQL::Schema::Object
919
+ def self.authorized?(obj, ctx)
920
+ false
921
+ end
922
+
923
+ field :int, Integer, null: false
924
+
925
+ def int
926
+ 1
927
+ end
928
+ end
929
+ query(Query)
930
+ if TESTING_INTERPRETER
931
+ use GraphQL::Execution::Interpreter
932
+ end
933
+ end
934
+
935
+ it "works out-of-the-box" do
936
+ res = FalseSchema.execute("{ int }")
937
+ assert_nil res.fetch("data")
938
+ refute res.key?("errors")
939
+ end
940
+ end
915
941
  end
@@ -161,8 +161,15 @@ describe GraphQL::Language::Visitor do
161
161
  end
162
162
 
163
163
  def on_argument(node, parent)
164
+ # https://github.com/rmosolgo/graphql-ruby/issues/2148
165
+ # Parent could become a random value, double check that it's a node
166
+ # to actually fail the test
167
+ raise RuntimeError, "Parent isn't a Node!" unless parent.class < GraphQL::Language::Nodes::AbstractNode
168
+
164
169
  if node.name == "deleteMe"
165
170
  super(DELETE_NODE, parent)
171
+ elsif node.name.include?("nope")
172
+ [1]
166
173
  else
167
174
  super
168
175
  end
@@ -351,6 +358,18 @@ GRAPHQL
351
358
  assert_equal after_query, new_document.to_query_string
352
359
  end
353
360
 
361
+ it "ignore non-Nodes::AbstractNode return values" do
362
+ query = <<-GRAPHQL.chop
363
+ query {
364
+ doesntDoAnything(stillNothing: {nope: 1, alsoNope: 2, stillNope: 3})
365
+ }
366
+ GRAPHQL
367
+
368
+ document, new_document = get_result(query)
369
+ assert_equal query, document.to_query_string
370
+ assert_equal query, new_document.to_query_string
371
+ end
372
+
354
373
  it "can modify inline fragments" do
355
374
  before_query = <<-GRAPHQL.chop
356
375
  query {
@@ -4,7 +4,7 @@ require "spec_helper"
4
4
  describe GraphQL::Schema::Argument do
5
5
  module SchemaArgumentTest
6
6
  class Query < GraphQL::Schema::Object
7
- field :field, String, null: false do
7
+ field :field, String, null: true do
8
8
  argument :arg, String, description: "test", required: false
9
9
 
10
10
  argument :arg_with_block, String, required: false do
@@ -14,6 +14,11 @@ describe GraphQL::Schema::Argument do
14
14
  argument :aliased_arg, String, required: false, as: :renamed
15
15
  argument :prepared_arg, Int, required: false, prepare: :multiply
16
16
  argument :prepared_by_proc_arg, Int, required: false, prepare: ->(val, context) { context[:multiply_by] * val }
17
+ argument :exploding_prepared_arg, Int, required: false, prepare: ->(val, context) do
18
+ raise GraphQL::ExecutionError.new('boom!')
19
+ end
20
+
21
+ argument :keys, [String], required: false
17
22
 
18
23
  class Multiply
19
24
  def call(val, context)
@@ -41,6 +46,13 @@ describe GraphQL::Schema::Argument do
41
46
  end
42
47
  end
43
48
 
49
+ describe "#keys" do
50
+ it "is not overwritten by the 'keys' argument" do
51
+ expected_keys = ["aliasedArg", "arg", "argWithBlock", "explodingPreparedArg", "keys", "preparedArg", "preparedByCallableArg", "preparedByProcArg"]
52
+ assert_equal expected_keys, SchemaArgumentTest::Query.fields["field"].arguments.keys.sort
53
+ end
54
+ end
55
+
44
56
  describe "#path" do
45
57
  it "includes type, field and argument names" do
46
58
  assert_equal "Query.field.argWithBlock", SchemaArgumentTest::Query.fields["field"].arguments["argWithBlock"].path
@@ -105,6 +117,7 @@ describe GraphQL::Schema::Argument do
105
117
  # Make sure it's getting the renamed symbol:
106
118
  assert_equal '{:prepared_arg=>15}', res["data"]["field"]
107
119
  end
120
+
108
121
  it "calls the method on the provided Proc" do
109
122
  query_str = <<-GRAPHQL
110
123
  { field(preparedByProcArg: 5) }
@@ -114,6 +127,7 @@ describe GraphQL::Schema::Argument do
114
127
  # Make sure it's getting the renamed symbol:
115
128
  assert_equal '{:prepared_by_proc_arg=>15}', res["data"]["field"]
116
129
  end
130
+
117
131
  it "calls the method on the provided callable object" do
118
132
  query_str = <<-GRAPHQL
119
133
  { field(preparedByCallableArg: 5) }
@@ -123,5 +137,16 @@ describe GraphQL::Schema::Argument do
123
137
  # Make sure it's getting the renamed symbol:
124
138
  assert_equal '{:prepared_by_callable_arg=>15}', res["data"]["field"]
125
139
  end
140
+
141
+ it "handles exceptions raised by prepare" do
142
+ query_str = <<-GRAPHQL
143
+ { f1: field(arg: "echo"), f2: field(explodingPreparedArg: 5) }
144
+ GRAPHQL
145
+
146
+ res = SchemaArgumentTest::Schema.execute(query_str, context: {multiply_by: 3})
147
+ assert_equal({ 'f1' => '{:arg=>"echo"}', 'f2' => nil }, res['data'])
148
+ assert_equal(res['errors'][0]['message'], 'boom!')
149
+ assert_equal(res['errors'][0]['path'], ['f2'])
150
+ end
126
151
  end
127
152
  end
@@ -9,6 +9,12 @@ describe GraphQL::Schema::FieldExtension do
9
9
  end
10
10
  end
11
11
 
12
+ class PowerOfFilter < GraphQL::Schema::FieldExtension
13
+ def after_resolve(object:, value:, arguments:, context:, memo:)
14
+ value**options.fetch(:power, 2)
15
+ end
16
+ end
17
+
12
18
  class MultiplyByOption < GraphQL::Schema::FieldExtension
13
19
  def after_resolve(object:, value:, arguments:, context:, memo:)
14
20
  value * options[:factor]
@@ -52,8 +58,13 @@ describe GraphQL::Schema::FieldExtension do
52
58
  argument :input, Integer, required: true
53
59
  end
54
60
 
55
- def pass_thru(input:)
56
- input # return it as-is, it will be modified by extensions
61
+ field :square, Integer, null: false, resolver_method: :pass_thru, extensions: [PowerOfFilter] do
62
+ argument :input, Integer, required: true
63
+ end
64
+
65
+ field :cube, Integer, null: false, resolver_method: :pass_thru do
66
+ extension(PowerOfFilter, power: 3)
67
+ argument :input, Integer, required: true
57
68
  end
58
69
 
59
70
  field :tripled_by_option, Integer, null: false, resolver_method: :pass_thru do
@@ -68,6 +79,10 @@ describe GraphQL::Schema::FieldExtension do
68
79
  field :multiply_input2, Integer, null: false, resolver_method: :pass_thru, extensions: [MultiplyByArgumentUsingResolve] do
69
80
  argument :input, Integer, required: true
70
81
  end
82
+
83
+ def pass_thru(input:, **args)
84
+ input # return it as-is, it will be modified by extensions
85
+ end
71
86
  end
72
87
 
73
88
  class Schema < GraphQL::Schema
@@ -107,6 +122,13 @@ describe GraphQL::Schema::FieldExtension do
107
122
  assert_equal 12, res["data"]["tripledByOption"]
108
123
  end
109
124
 
125
+ it "provides an empty hash as default options" do
126
+ res = exec_query("{ square(input: 4) }")
127
+ assert_equal 16, res["data"]["square"]
128
+ res = exec_query("{ cube(input: 4) }")
129
+ assert_equal 64, res["data"]["cube"]
130
+ end
131
+
110
132
  it "can hide arguments from resolve methods" do
111
133
  res = exec_query("{ multiplyInput(input: 3, factor: 5) }")
112
134
  assert_equal 15, res["data"]["multiplyInput"]