graphql 1.9.3 → 1.9.4

Sign up to get free protection for your applications and to get access to all the features.
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"]