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.
- checksums.yaml +5 -5
- data/lib/graphql.rb +1 -0
- data/lib/graphql/analysis/ast/field_usage.rb +1 -1
- data/lib/graphql/analysis/ast/max_query_complexity.rb +1 -1
- data/lib/graphql/analysis/ast/query_complexity.rb +3 -2
- data/lib/graphql/execution/execute.rb +10 -6
- data/lib/graphql/execution/interpreter/runtime.rb +23 -12
- data/lib/graphql/execution/lookahead.rb +0 -4
- data/lib/graphql/integer_encoding_error.rb +9 -1
- data/lib/graphql/language/visitor.rb +6 -0
- data/lib/graphql/load_application_object_failed_error.rb +22 -0
- data/lib/graphql/query.rb +6 -1
- data/lib/graphql/query/arguments.rb +1 -1
- data/lib/graphql/query/variables.rb +14 -8
- data/lib/graphql/rake_task/validate.rb +1 -1
- data/lib/graphql/schema/field_extension.rb +2 -2
- data/lib/graphql/schema/input_object.rb +15 -6
- data/lib/graphql/schema/member/has_arguments.rb +80 -0
- data/lib/graphql/schema/member/instrumentation.rb +6 -0
- data/lib/graphql/schema/resolver.rb +8 -92
- data/lib/graphql/static_validation/base_visitor.rb +8 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
- data/lib/graphql/tracing.rb +2 -2
- data/lib/graphql/tracing/data_dog_tracing.rb +19 -0
- data/lib/graphql/tracing/platform_tracing.rb +2 -1
- data/lib/graphql/types.rb +1 -0
- data/lib/graphql/types/big_int.rb +19 -0
- data/lib/graphql/types/int.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/authorization_spec.rb +26 -0
- data/spec/graphql/language/visitor_spec.rb +19 -0
- data/spec/graphql/schema/argument_spec.rb +26 -1
- data/spec/graphql/schema/field_extension_spec.rb +24 -2
- data/spec/graphql/schema/input_object_spec.rb +119 -10
- data/spec/graphql/schema/introspection_system_spec.rb +9 -0
- data/spec/graphql/schema/resolver_spec.rb +2 -2
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +2 -2
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/required_input_object_attributes_are_present_spec.rb +16 -2
- data/spec/graphql/subscriptions_spec.rb +38 -0
- data/spec/graphql/tracing/new_relic_tracing_spec.rb +21 -0
- data/spec/graphql/types/big_int_spec.rb +24 -0
- data/spec/support/jazz.rb +11 -0
- metadata +13 -51
- data/spec/dummy/Gemfile.lock +0 -157
- data/spec/dummy/log/test.log +0 -199
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/4w/4wzXRZrAkwKdgYaSE0pid5eB-fer8vSfSku_NPg4rMA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/7I/7IHVBiJT06QSpgLpLoJIxboQ0B-D_tMTxsvoezBTV3Q.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/8w/8wY_SKagj8wHuwGNAAf6JnQ8joMbC6cEYpHrTAI8Urc.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/AK/AKzz1u6bGb4auXcrObA_g5LL-oV0ejNGa448AgAi_WQ.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ET/ETW4uxvaYpruL8y6_ZptUH82ZowMaHIqvg5WexBFdEM.cache +0 -3
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F1/F1TWpjjyA56k9Z90n5B3xRn7DUdGjX73QCkYC6k07JQ.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F8/F8MUNRzORGFgr329fNM0xLaoWCXdv3BIalT7dsvLfjs.cache +0 -2
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/KB/KB07ZaKNC5uXJ7TjLi-WqnY6g7dq8wWp_8N3HNjBNxg.cache +0 -2
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Ms/MsKSimH_UCB-H1tLvDABDHuvGciuoW6kVqQWDrXU5FQ.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Mt/Mtci-Kim50aPOmeClD4AIicKn1d1WJ0n454IjSd94sk.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/QH/QHt3Tc1Y6M66Oo_pDuMyWrQNs4Pp3SMeZR5K1wJj2Ts.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/XU/XU4k1OXnfMils5SrirorPvDSyDSqiOWLZNtmAH1HH8k.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ZI/ZIof7mZxWWCnraIFOCuV6a8QRWzKJXJnx2Xd7C0ZyX0.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/cG/cGc_puuPS5pZKgUcy1Y_i1L6jl5UtsiIrMH59rTzR6c.cache +0 -3
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/df/dfro_B6bx3KP1Go-7jEOqqZ2j4hVRseXIc3es9PKQno.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/jO/jO1DfbqnG0mTULsjJJANc3fefrG2zt7DIMmcptMT628.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/pE/pE7gO6pQ-z187Swb4hT554wmqsq-cNzgPWLrCz-LQQQ.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/r9/r9iU1l58a6rxkZSW5RSC52_tD-_UQuHxoMVnkfJ7Mhs.cache +0 -1
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/xi/xitPPFfPIyDMpaznV0sBBcw8eSCV8PJcLLWin78sCgE.cache +0 -0
- data/spec/dummy/tmp/screenshots/failures_test_it_handles_subscriptions.png +0 -0
- 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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
data/lib/graphql/tracing.rb
CHANGED
|
@@ -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(
|
|
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?)
|
data/lib/graphql/types.rb
CHANGED
|
@@ -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
|
data/lib/graphql/types/int.rb
CHANGED
data/lib/graphql/version.rb
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
56
|
-
input
|
|
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"]
|