graphql 2.5.20 → 2.6.0
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/lib/graphql/analysis.rb +20 -13
- data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +3 -3
- data/lib/graphql/execution/field_resolve_step.rb +631 -0
- data/lib/graphql/execution/finalize.rb +217 -0
- data/lib/graphql/execution/input_values.rb +261 -0
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
- data/lib/graphql/execution/interpreter/runtime.rb +3 -2
- data/lib/graphql/execution/interpreter.rb +6 -9
- data/lib/graphql/execution/lazy.rb +1 -1
- data/lib/graphql/execution/load_argument_step.rb +64 -0
- data/lib/graphql/execution/multiplex.rb +1 -1
- data/lib/graphql/execution/next.rb +90 -0
- data/lib/graphql/execution/prepare_object_step.rb +128 -0
- data/lib/graphql/execution/runner.rb +410 -0
- data/lib/graphql/execution/selections_step.rb +91 -0
- data/lib/graphql/execution.rb +8 -4
- data/lib/graphql/execution_error.rb +5 -1
- data/lib/graphql/introspection/dynamic_fields.rb +5 -1
- data/lib/graphql/introspection/entry_points.rb +5 -1
- data/lib/graphql/pagination/connection.rb +2 -0
- data/lib/graphql/pagination/connections.rb +32 -0
- data/lib/graphql/query/context.rb +7 -1
- data/lib/graphql/query/partial.rb +18 -3
- data/lib/graphql/query.rb +10 -1
- data/lib/graphql/runtime_error.rb +6 -0
- data/lib/graphql/schema/argument.rb +1 -0
- data/lib/graphql/schema/build_from_definition.rb +22 -25
- data/lib/graphql/schema/directive.rb +23 -9
- data/lib/graphql/schema/field/connection_extension.rb +4 -37
- data/lib/graphql/schema/field/scope_extension.rb +18 -13
- data/lib/graphql/schema/field.rb +59 -62
- data/lib/graphql/schema/field_extension.rb +11 -8
- data/lib/graphql/schema/interface.rb +26 -0
- data/lib/graphql/schema/list.rb +5 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
- data/lib/graphql/schema/member/has_authorization.rb +35 -0
- data/lib/graphql/schema/member/has_dataloader.rb +28 -0
- data/lib/graphql/schema/member/has_fields.rb +11 -5
- data/lib/graphql/schema/member.rb +5 -0
- data/lib/graphql/schema/non_null.rb +1 -1
- data/lib/graphql/schema/object.rb +1 -0
- data/lib/graphql/schema/resolver.rb +80 -0
- data/lib/graphql/schema/subscription.rb +0 -2
- data/lib/graphql/schema/visibility/profile.rb +68 -49
- data/lib/graphql/schema/wrapper.rb +7 -1
- data/lib/graphql/schema.rb +12 -10
- data/lib/graphql/static_validation/base_visitor.rb +90 -66
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -2
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
- data/lib/graphql/static_validation/validation_context.rb +1 -1
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +34 -10
- data/lib/graphql/subscriptions/event.rb +1 -0
- data/lib/graphql/subscriptions.rb +35 -0
- data/lib/graphql/tracing/perfetto_trace.rb +2 -2
- data/lib/graphql/tracing/trace.rb +6 -0
- data/lib/graphql/types/relay/has_node_field.rb +10 -2
- data/lib/graphql/types/relay/has_nodes_field.rb +10 -2
- data/lib/graphql/types/relay/node_behaviors.rb +13 -2
- data/lib/graphql/unauthorized_error.rb +4 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +1 -3
- metadata +13 -9
- data/lib/graphql/execution/batching/field_compatibility.rb +0 -150
- data/lib/graphql/execution/batching/field_resolve_step.rb +0 -408
- data/lib/graphql/execution/batching/prepare_object_step.rb +0 -112
- data/lib/graphql/execution/batching/runner.rb +0 -352
- data/lib/graphql/execution/batching/selections_step.rb +0 -37
- data/lib/graphql/execution/batching.rb +0 -62
data/lib/graphql/schema/field.rb
CHANGED
|
@@ -8,6 +8,7 @@ module GraphQL
|
|
|
8
8
|
include GraphQL::Schema::Member::HasArguments
|
|
9
9
|
include GraphQL::Schema::Member::HasArguments::FieldConfigured
|
|
10
10
|
include GraphQL::Schema::Member::HasAstNode
|
|
11
|
+
include GraphQL::Schema::Member::HasAuthorization
|
|
11
12
|
include GraphQL::Schema::Member::HasPath
|
|
12
13
|
include GraphQL::Schema::Member::HasValidators
|
|
13
14
|
extend GraphQL::Schema::FindInheritedValue
|
|
@@ -192,9 +193,11 @@ module GraphQL
|
|
|
192
193
|
# @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
|
|
193
194
|
# @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
|
|
194
195
|
# @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
|
|
195
|
-
# @param resolve_static [Symbol, nil] Used by {Schema.
|
|
196
|
-
# @param resolve_batch [Symbol, nil] Used by {Schema.
|
|
197
|
-
# @param resolve_each [Symbol, nil] Used by {Schema.
|
|
196
|
+
# @param resolve_static [Symbol, true, nil] Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
|
|
197
|
+
# @param resolve_batch [Symbol, true, nil] Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
|
|
198
|
+
# @param resolve_each [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
|
|
199
|
+
# @param resolve_legacy_instance_method [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class.
|
|
200
|
+
# @param dataload [Class, Hash] Shorthand for making dataloader calls
|
|
198
201
|
# @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
|
|
199
202
|
# @param default_page_size [Integer, nil] For connections, the default number of items to return from this field, or `nil` to return unlimited results.
|
|
200
203
|
# @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
|
|
@@ -217,7 +220,7 @@ module GraphQL
|
|
|
217
220
|
# @param relay_nodes_field [Boolean] (Private, used by GraphQL-Ruby)
|
|
218
221
|
# @param extras [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] Extra arguments to be injected into the resolver for this field
|
|
219
222
|
# @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}.
|
|
220
|
-
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, resolve_static: nil, resolve_each: nil, resolve_batch: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, 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: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
|
|
223
|
+
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, resolve_legacy_instance_method: nil, resolve_static: nil, resolve_each: nil, resolve_batch: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, dataload: nil, 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: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
|
|
221
224
|
if name.nil?
|
|
222
225
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
|
223
226
|
end
|
|
@@ -267,23 +270,32 @@ module GraphQL
|
|
|
267
270
|
@resolver_method = (resolver_method || name_s).to_sym
|
|
268
271
|
|
|
269
272
|
if resolve_static
|
|
270
|
-
@
|
|
271
|
-
@
|
|
273
|
+
@execution_mode = :resolve_static
|
|
274
|
+
@execution_mode_key = resolve_static == true ? @method_sym : resolve_static
|
|
272
275
|
elsif resolve_batch
|
|
273
|
-
@
|
|
274
|
-
@
|
|
276
|
+
@execution_mode = :resolve_batch
|
|
277
|
+
@execution_mode_key = resolve_batch == true ? @method_sym : resolve_batch
|
|
275
278
|
elsif resolve_each
|
|
276
|
-
@
|
|
277
|
-
@
|
|
279
|
+
@execution_mode = :resolve_each
|
|
280
|
+
@execution_mode_key = resolve_each == true ? @method_sym : resolve_each
|
|
278
281
|
elsif hash_key
|
|
279
|
-
@
|
|
280
|
-
@
|
|
282
|
+
@execution_mode = :hash_key
|
|
283
|
+
@execution_mode_key = hash_key
|
|
281
284
|
elsif dig
|
|
282
|
-
@
|
|
283
|
-
@
|
|
285
|
+
@execution_mode = :dig
|
|
286
|
+
@execution_mode_key = dig
|
|
287
|
+
elsif resolver_class
|
|
288
|
+
@execution_mode = :resolver_class
|
|
289
|
+
@execution_mode_key = resolver_class
|
|
290
|
+
elsif resolve_legacy_instance_method
|
|
291
|
+
@execution_mode = :resolve_legacy_instance_method
|
|
292
|
+
@execution_mode_key = resolve_legacy_instance_method == true ? @method_sym : resolve_legacy_instance_method
|
|
293
|
+
elsif dataload
|
|
294
|
+
@execution_mode = :dataload
|
|
295
|
+
@execution_mode_key = dataload
|
|
284
296
|
else
|
|
285
|
-
@
|
|
286
|
-
@
|
|
297
|
+
@execution_mode = :direct_send
|
|
298
|
+
@execution_mode_key = @method_sym
|
|
287
299
|
end
|
|
288
300
|
|
|
289
301
|
@complexity = complexity
|
|
@@ -356,49 +368,8 @@ module GraphQL
|
|
|
356
368
|
end
|
|
357
369
|
end
|
|
358
370
|
|
|
359
|
-
# Called by {Execution::Batching} to resolve this field for each of `objects`
|
|
360
|
-
# @param field_resolve_step [Execution::Batching::FieldResolveStep] an internal metadata object from execution code
|
|
361
|
-
# @param objects [Array<Object>] Objects returned from previously-executed fields
|
|
362
|
-
# @param context [GraphQL::Query::Context]
|
|
363
|
-
# @param args_hash [Hash<Symbol => Object>] Ruby-style arguments for this field
|
|
364
|
-
# @return [Array<Object>] One field result for each of `objects`; must have the same length as `objects`
|
|
365
|
-
# @see #initialize Use `resolve_static:`, `resolve_batch:`, `resolve_each:`, `hash_key:`, or `method:`
|
|
366
371
|
# @api private
|
|
367
|
-
|
|
368
|
-
case @batch_mode
|
|
369
|
-
when :resolve_batch
|
|
370
|
-
if args_hash.empty?
|
|
371
|
-
@owner.public_send(@batch_mode_key, objects, context)
|
|
372
|
-
else
|
|
373
|
-
@owner.public_send(@batch_mode_key, objects, context, **args_hash)
|
|
374
|
-
end
|
|
375
|
-
when :resolve_static
|
|
376
|
-
result = if args_hash.empty?
|
|
377
|
-
@owner.public_send(@batch_mode_key, context)
|
|
378
|
-
else
|
|
379
|
-
@owner.public_send(@batch_mode_key, context, **args_hash)
|
|
380
|
-
end
|
|
381
|
-
Array.new(objects.size, result)
|
|
382
|
-
when :resolve_each
|
|
383
|
-
if args_hash.empty?
|
|
384
|
-
objects.map { |o| @owner.public_send(@batch_mode_key, o, context) }
|
|
385
|
-
else
|
|
386
|
-
objects.map { |o| @owner.public_send(@batch_mode_key, o, context, **args_hash) }
|
|
387
|
-
end
|
|
388
|
-
when :hash_key
|
|
389
|
-
objects.map { |o| o[@batch_mode_key] }
|
|
390
|
-
when :direct_send
|
|
391
|
-
if args_hash.empty?
|
|
392
|
-
objects.map { |o| o.public_send(@batch_mode_key) }
|
|
393
|
-
else
|
|
394
|
-
objects.map { |o| o.public_send(@batch_mode_key, **args_hash) }
|
|
395
|
-
end
|
|
396
|
-
when :dig
|
|
397
|
-
objects.map { |o| o.dig(*@batch_mode_key) }
|
|
398
|
-
else
|
|
399
|
-
raise "Batching execution for #{path} not implemented; provide `resolve_static:`, `resolve_batch:`, `hash_key:`, `method:`, or use a compatibility plug-in"
|
|
400
|
-
end
|
|
401
|
-
end
|
|
372
|
+
attr_reader :execution_mode_key, :execution_mode
|
|
402
373
|
|
|
403
374
|
# Calls the definition block, if one was given.
|
|
404
375
|
# This is deferred so that references to the return type
|
|
@@ -694,10 +665,9 @@ module GraphQL
|
|
|
694
665
|
end
|
|
695
666
|
|
|
696
667
|
def authorizes?(context)
|
|
697
|
-
method(:authorized?).owner != GraphQL::Schema::Field ||
|
|
698
|
-
(args = context.types.arguments(self)) &&
|
|
699
|
-
|
|
700
|
-
)
|
|
668
|
+
method(:authorized?).owner != GraphQL::Schema::Field ||
|
|
669
|
+
((args = context.types.arguments(self)) && (args.any? { |a| a.authorizes?(context) })) ||
|
|
670
|
+
(@resolver_class&.authorizes?(context)) || false
|
|
701
671
|
end
|
|
702
672
|
|
|
703
673
|
def authorized?(object, args, context)
|
|
@@ -954,6 +924,33 @@ ERR
|
|
|
954
924
|
end
|
|
955
925
|
end
|
|
956
926
|
|
|
927
|
+
public
|
|
928
|
+
|
|
929
|
+
def run_next_extensions_before_resolve(objs, args, ctx, extended, idx: 0, &block)
|
|
930
|
+
extension = @extensions[idx]
|
|
931
|
+
if extension
|
|
932
|
+
extension.resolve(objects: objs, arguments: args, context: ctx) do |extended_objs, extended_args, memo|
|
|
933
|
+
if memo
|
|
934
|
+
memos = extended.memos ||= {}
|
|
935
|
+
memos[idx] = memo
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
if (extras = extension.added_extras)
|
|
939
|
+
ae = extended.added_extras ||= []
|
|
940
|
+
ae.concat(extras)
|
|
941
|
+
end
|
|
942
|
+
|
|
943
|
+
extended.object = extended_objs
|
|
944
|
+
extended.arguments = extended_args
|
|
945
|
+
run_next_extensions_before_resolve(extended_objs, extended_args, ctx, extended, idx: idx + 1, &block)
|
|
946
|
+
end
|
|
947
|
+
else
|
|
948
|
+
yield(objs, args)
|
|
949
|
+
end
|
|
950
|
+
end
|
|
951
|
+
|
|
952
|
+
private
|
|
953
|
+
|
|
957
954
|
def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0)
|
|
958
955
|
extension = @extensions[idx]
|
|
959
956
|
if extension
|
|
@@ -123,15 +123,16 @@ module GraphQL
|
|
|
123
123
|
#
|
|
124
124
|
# Whatever this method returns will be used for execution.
|
|
125
125
|
#
|
|
126
|
-
# @param object [Object] The object the field is being resolved on
|
|
126
|
+
# @param object [Object] The object the field is being resolved on (not passed by new execution)
|
|
127
|
+
# @param objects [Array<Object>] The objects the field is being resolved on (passed by new execution)
|
|
127
128
|
# @param arguments [Hash] Ruby keyword arguments for resolving this field
|
|
128
129
|
# @param context [Query::Context] the context for this query
|
|
129
|
-
# @yieldparam
|
|
130
|
+
# @yieldparam object_or_objects [Object, Array<Object>] The object or objects (new execution) to continue resolving the field on
|
|
130
131
|
# @yieldparam arguments [Hash] The keyword arguments to continue resolving with
|
|
131
132
|
# @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later
|
|
132
133
|
# @return [Object] The return value for this field.
|
|
133
|
-
def resolve(object
|
|
134
|
-
yield(object, arguments, nil)
|
|
134
|
+
def resolve(object: nil, objects: nil, arguments:, context:)
|
|
135
|
+
yield(object.nil? ? objects : object, arguments, nil)
|
|
135
136
|
end
|
|
136
137
|
|
|
137
138
|
# Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced,
|
|
@@ -139,14 +140,16 @@ module GraphQL
|
|
|
139
140
|
#
|
|
140
141
|
# Whatever this hook returns will be used as the return value.
|
|
141
142
|
#
|
|
142
|
-
# @param object [Object] The object the field is being resolved on
|
|
143
|
+
# @param object [Object] The object the field is being resolved on (not passed by new execution)
|
|
144
|
+
# @param objects [Array<Object>] The object the field is being resolved on (passed by new execution)
|
|
143
145
|
# @param arguments [Hash] Ruby keyword arguments for resolving this field
|
|
144
146
|
# @param context [Query::Context] the context for this query
|
|
145
|
-
# @param value [Object] Whatever the field previously returned
|
|
147
|
+
# @param value [Object] Whatever the field previously returned (not passed by new execution)
|
|
148
|
+
# @param values [Array<Object>] Whatever the field previously returned (passed by new execution)
|
|
146
149
|
# @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one
|
|
147
150
|
# @return [Object] The return value for this field.
|
|
148
|
-
def after_resolve(object
|
|
149
|
-
value
|
|
151
|
+
def after_resolve(object: nil, objects: nil, arguments:, context:, values: nil, value: nil, memo:)
|
|
152
|
+
value.nil? ? values : value
|
|
150
153
|
end
|
|
151
154
|
end
|
|
152
155
|
end
|
|
@@ -33,6 +33,26 @@ module GraphQL
|
|
|
33
33
|
self::DefinitionMethods.module_exec(&block)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
# Instance methods defined in this block will become class methods on objects that implement this interface.
|
|
37
|
+
# Use it to implement `resolve_each:`, `resolve_batch:`, and `resolve_static:` fields.
|
|
38
|
+
# @example
|
|
39
|
+
# field :thing, String, resolve_static: true
|
|
40
|
+
#
|
|
41
|
+
# resolver_methods do
|
|
42
|
+
# def thing
|
|
43
|
+
# Somehow.get.thing
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
def resolver_methods(&block)
|
|
47
|
+
if !defined?(@_resolver_methods)
|
|
48
|
+
resolver_methods_module = Module.new
|
|
49
|
+
@_resolver_methods = resolver_methods_module
|
|
50
|
+
const_set(:ResolverMethods, resolver_methods_module)
|
|
51
|
+
extend(self::ResolverMethods)
|
|
52
|
+
end
|
|
53
|
+
self::ResolverMethods.module_exec(&block)
|
|
54
|
+
end
|
|
55
|
+
|
|
36
56
|
# @see {Schema::Warden} hides interfaces without visible implementations
|
|
37
57
|
def visible?(context)
|
|
38
58
|
true
|
|
@@ -79,6 +99,12 @@ module GraphQL
|
|
|
79
99
|
if !backtrace_line
|
|
80
100
|
raise "Attach interfaces using `implements(#{self})`, not `include(#{self})`"
|
|
81
101
|
end
|
|
102
|
+
|
|
103
|
+
child_class.ancestors.reverse_each do |ancestor|
|
|
104
|
+
if ancestor.const_defined?(:ResolverMethods)
|
|
105
|
+
child_class.extend(ancestor::ResolverMethods)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
82
108
|
end
|
|
83
109
|
|
|
84
110
|
super
|
data/lib/graphql/schema/list.rb
CHANGED
|
@@ -19,7 +19,11 @@ module GraphQL
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def to_type_signature
|
|
22
|
-
"[#{@of_type.to_type_signature}]"
|
|
22
|
+
@type_signature ||= -"[#{@of_type.to_type_signature}]"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def authorizes?(ctx)
|
|
26
|
+
of_type.authorizes?(ctx)
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
# This is for introspection, where it's expected the name will be `null`
|
|
@@ -26,16 +26,6 @@ module GraphQL
|
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# Just a convenience method to point out that people should use graphql_name instead
|
|
30
|
-
def name(new_name = nil)
|
|
31
|
-
return super() if new_name.nil?
|
|
32
|
-
|
|
33
|
-
fail(
|
|
34
|
-
"The new name override method is `graphql_name`, not `name`. Usage: "\
|
|
35
|
-
"graphql_name \"#{new_name}\""
|
|
36
|
-
)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
29
|
# Call this method to provide a new description; OR
|
|
40
30
|
# call it without an argument to get the description
|
|
41
31
|
# @param new_description [String]
|
|
@@ -130,7 +120,7 @@ module GraphQL
|
|
|
130
120
|
true
|
|
131
121
|
end
|
|
132
122
|
|
|
133
|
-
def default_relay
|
|
123
|
+
def default_relay?
|
|
134
124
|
false
|
|
135
125
|
end
|
|
136
126
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
class Schema
|
|
4
|
+
class Member
|
|
5
|
+
module HasAuthorization
|
|
6
|
+
def self.included(child_class)
|
|
7
|
+
child_class.include(InstanceConfigured)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.extended(child_class)
|
|
11
|
+
child_class.extend(ClassConfigured)
|
|
12
|
+
child_class.class_exec do
|
|
13
|
+
@authorizes = false
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def authorized?(object, context)
|
|
18
|
+
true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module InstanceConfigured
|
|
22
|
+
def authorizes?(context)
|
|
23
|
+
raise RequiredImplementationMissingError, "#{self.class} must implement #authorizes?(context)"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module ClassConfigured
|
|
28
|
+
def authorizes?(context)
|
|
29
|
+
method(:authorized?).owner != GraphQL::Schema::Member::HasAuthorization
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -22,6 +22,14 @@ module GraphQL
|
|
|
22
22
|
|
|
23
23
|
# A shortcut method for loading many keys from a source.
|
|
24
24
|
# Identical to `dataloader.with(source_class, *source_args).load_all(load_keys)`
|
|
25
|
+
#
|
|
26
|
+
# @example
|
|
27
|
+
# field :score, Integer, resolve_batch: true
|
|
28
|
+
#
|
|
29
|
+
# def self.score(posts)
|
|
30
|
+
# dataload_all(PostScoreSource, posts.map(&:id))
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
25
33
|
# @param source_class [Class<GraphQL::Dataloader::Source>]
|
|
26
34
|
# @param source_args [Array<Object>] Any extra parameters defined in `source_class`'s `initialize` method
|
|
27
35
|
# @param load_keys [Array<Object>] The keys to look up using `def fetch`
|
|
@@ -48,6 +56,16 @@ module GraphQL
|
|
|
48
56
|
source.load(find_by_value)
|
|
49
57
|
end
|
|
50
58
|
|
|
59
|
+
# @see dataload_record Like `dataload_record`, but accepts an Array of `find_by_values`
|
|
60
|
+
def dataload_all_records(model, find_by_values, find_by: nil)
|
|
61
|
+
source = if find_by
|
|
62
|
+
dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by)
|
|
63
|
+
else
|
|
64
|
+
dataloader.with(Dataloader::ActiveRecordSource, model)
|
|
65
|
+
end
|
|
66
|
+
source.load_all(find_by_values)
|
|
67
|
+
end
|
|
68
|
+
|
|
51
69
|
# Look up an associated record using a Rails association (via {Dataloader::ActiveRecordAssociationSource})
|
|
52
70
|
# @param association_name [Symbol] A `belongs_to` or `has_one` association. (If a `has_many` association is named here, it will be selected without pagination.)
|
|
53
71
|
# @param record [ActiveRecord::Base] The object that the association belongs to.
|
|
@@ -65,6 +83,16 @@ module GraphQL
|
|
|
65
83
|
end
|
|
66
84
|
source.load(record)
|
|
67
85
|
end
|
|
86
|
+
|
|
87
|
+
# @see dataload_association Like `dataload_assocation` but accepts an Array of records (required param)
|
|
88
|
+
def dataload_all_associations(records, association_name, scope: nil)
|
|
89
|
+
source = if scope
|
|
90
|
+
dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope)
|
|
91
|
+
else
|
|
92
|
+
dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name)
|
|
93
|
+
end
|
|
94
|
+
source.load_all(records)
|
|
95
|
+
end
|
|
68
96
|
end
|
|
69
97
|
end
|
|
70
98
|
end
|
|
@@ -19,10 +19,11 @@ module GraphQL
|
|
|
19
19
|
# @option kwargs [Symbol] :method The method to call on the underlying object to resolve this field (defaults to `name`)
|
|
20
20
|
# @option kwargs [String, Symbol] :hash_key The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
|
|
21
21
|
# @option kwargs [Array<String, Symbol>] :dig The nested hash keys to lookup on the underlying hash to resolve this field using dig
|
|
22
|
-
# @option kwargs [Symbol] :resolver_method The method on the type to call to resolve this field (defaults to `name`)
|
|
23
|
-
# @option kwargs [Symbol] :resolve_static Used by {Schema.
|
|
24
|
-
# @option kwargs [Symbol] :resolve_batch Used by {Schema.
|
|
25
|
-
# @option kwargs [Symbol] :resolve_each Used by {Schema.
|
|
22
|
+
# @option kwargs [Symbol, true] :resolver_method The method on the type to call to resolve this field (defaults to `name`)
|
|
23
|
+
# @option kwargs [Symbol, true] :resolve_static Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
|
|
24
|
+
# @option kwargs [Symbol, true] :resolve_batch Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
|
|
25
|
+
# @option kwargs [Symbol, true] :resolve_each Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
|
|
26
|
+
# @option kwargs [Symbol, true] :resolve_legacy_instance_method Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class.
|
|
26
27
|
# @option kwargs [Boolean] :connection `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
|
|
27
28
|
# @option kwargs [Class] :connection_extension The extension to add, to implement connections. If `nil`, no extension is added.
|
|
28
29
|
# @option kwargs [Integer, nil] :max_page_size For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
|
|
@@ -47,6 +48,7 @@ module GraphQL
|
|
|
47
48
|
# @option kwargs [Boolean] :dynamic_introspection (Private, used by GraphQL-Ruby)
|
|
48
49
|
# @option kwargs [Boolean] :relay_node_field (Private, used by GraphQL-Ruby)
|
|
49
50
|
# @option kwargs [Boolean] :relay_nodes_field (Private, used by GraphQL-Ruby)
|
|
51
|
+
# @option kwargs [Class, Hash] :dataload Shorthand for dataloader lookups
|
|
50
52
|
# @option kwargs [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] :extras Extra arguments to be injected into the resolver for this field
|
|
51
53
|
# @param kwargs [Hash] Keywords for defining the field. Any not documented here will be passed to your base field class where they must be handled.
|
|
52
54
|
# @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}.
|
|
@@ -148,10 +150,14 @@ module GraphQL
|
|
|
148
150
|
|
|
149
151
|
def global_id_field(field_name, **kwargs)
|
|
150
152
|
type = self
|
|
151
|
-
field field_name, "ID", **kwargs, null: false
|
|
153
|
+
field field_name, "ID", **kwargs, null: false, resolve_each: true
|
|
152
154
|
define_method(field_name) do
|
|
153
155
|
context.schema.id_from_object(object, type, context)
|
|
154
156
|
end
|
|
157
|
+
|
|
158
|
+
define_singleton_method(field_name) do |object, context|
|
|
159
|
+
context.schema.id_from_object(object, type, context)
|
|
160
|
+
end
|
|
155
161
|
end
|
|
156
162
|
|
|
157
163
|
# @param new_has_no_fields [Boolean] Call with `true` to make this Object type ignore the requirement to have any defined fields.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
require 'graphql/schema/member/base_dsl_methods'
|
|
3
3
|
require 'graphql/schema/member/graphql_type_names'
|
|
4
4
|
require 'graphql/schema/member/has_ast_node'
|
|
5
|
+
require 'graphql/schema/member/has_authorization'
|
|
5
6
|
require 'graphql/schema/member/has_dataloader'
|
|
6
7
|
require 'graphql/schema/member/has_directives'
|
|
7
8
|
require 'graphql/schema/member/has_deprecation_reason'
|
|
@@ -31,6 +32,10 @@ module GraphQL
|
|
|
31
32
|
extend HasPath
|
|
32
33
|
extend HasAstNode
|
|
33
34
|
extend HasDirectives
|
|
35
|
+
|
|
36
|
+
def self.authorizes?(_ctx)
|
|
37
|
+
false
|
|
38
|
+
end
|
|
34
39
|
end
|
|
35
40
|
end
|
|
36
41
|
end
|
|
@@ -5,6 +5,7 @@ require "graphql/query/null_context"
|
|
|
5
5
|
module GraphQL
|
|
6
6
|
class Schema
|
|
7
7
|
class Object < GraphQL::Schema::Member
|
|
8
|
+
extend GraphQL::Schema::Member::HasAuthorization
|
|
8
9
|
extend GraphQL::Schema::Member::HasFields
|
|
9
10
|
extend GraphQL::Schema::Member::HasInterfaces
|
|
10
11
|
include Member::HasDataloader
|
|
@@ -23,6 +23,7 @@ module GraphQL
|
|
|
23
23
|
# Really we only need description & comment from here, but:
|
|
24
24
|
extend Schema::Member::BaseDSLMethods
|
|
25
25
|
extend GraphQL::Schema::Member::HasArguments
|
|
26
|
+
extend GraphQL::Schema::Member::HasAuthorization
|
|
26
27
|
extend GraphQL::Schema::Member::HasValidators
|
|
27
28
|
include Schema::Member::HasPath
|
|
28
29
|
extend Schema::Member::HasPath
|
|
@@ -45,6 +46,8 @@ module GraphQL
|
|
|
45
46
|
@prepared_arguments = nil
|
|
46
47
|
end
|
|
47
48
|
|
|
49
|
+
attr_accessor :exec_result, :exec_index, :field_resolve_step, :raw_arguments
|
|
50
|
+
|
|
48
51
|
# @return [Object] The application object this field is being resolved on
|
|
49
52
|
attr_accessor :object
|
|
50
53
|
|
|
@@ -56,6 +59,79 @@ module GraphQL
|
|
|
56
59
|
|
|
57
60
|
attr_writer :prepared_arguments
|
|
58
61
|
|
|
62
|
+
def call
|
|
63
|
+
if self.class < Schema::HasSingleInputArgument
|
|
64
|
+
@prepared_arguments = @prepared_arguments[:input]
|
|
65
|
+
end
|
|
66
|
+
q = context.query
|
|
67
|
+
trace_objs = [object]
|
|
68
|
+
q.current_trace.begin_execute_field(field, @prepared_arguments, trace_objs, q)
|
|
69
|
+
is_ready = ready?(**@prepared_arguments)
|
|
70
|
+
runner = @field_resolve_step.runner
|
|
71
|
+
if runner.resolves_lazies && runner.schema.lazy?(is_ready)
|
|
72
|
+
is_ready, new_return_value = runner.schema.sync_lazy(is_ready)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if is_ready.is_a?(Array)
|
|
76
|
+
is_ready, new_return_value = is_ready
|
|
77
|
+
if is_ready != false
|
|
78
|
+
raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{new_return_value.inspect}]"
|
|
79
|
+
else
|
|
80
|
+
new_return_value
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if is_ready
|
|
85
|
+
begin
|
|
86
|
+
is_authed, new_return_value = authorized?(**@prepared_arguments)
|
|
87
|
+
rescue GraphQL::UnauthorizedError => err
|
|
88
|
+
new_return_value = q.schema.unauthorized_object(err)
|
|
89
|
+
is_authed = true # the error was handled
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if runner.resolves_lazies && runner.schema.lazy?(is_authed)
|
|
94
|
+
is_authed, new_return_value = runner.schema.sync_lazy(is_authed)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
result = if is_authed
|
|
98
|
+
Schema::Validator.validate!(self.class.validators, object, context, @prepared_arguments, as: @field)
|
|
99
|
+
if q.subscription? && @field.owner == context.schema.subscription
|
|
100
|
+
# This needs to use arguments without `loads:`. TODO extract this into subscription-related code somehow?
|
|
101
|
+
@original_arguments = @field_resolve_step.runner.input_values[q].argument_values(@field, @field_resolve_step.ast_node.arguments, nil)
|
|
102
|
+
end
|
|
103
|
+
call_resolve(@prepared_arguments)
|
|
104
|
+
elsif new_return_value.nil?
|
|
105
|
+
err = UnauthorizedFieldError.new(object: object, type: @field_resolve_step.parent_type, context: context, field: @field)
|
|
106
|
+
context.schema.unauthorized_field(err)
|
|
107
|
+
else
|
|
108
|
+
new_return_value
|
|
109
|
+
end
|
|
110
|
+
q = context.query
|
|
111
|
+
q.current_trace.end_execute_field(field, @prepared_arguments, trace_objs, q, [result])
|
|
112
|
+
exec_result[exec_index] = result
|
|
113
|
+
rescue GraphQL::UnauthorizedError => auth_err
|
|
114
|
+
exec_result[exec_index] = begin
|
|
115
|
+
context.schema.unauthorized_object(auth_err)
|
|
116
|
+
rescue GraphQL::ExecutionError => exec_err
|
|
117
|
+
exec_err
|
|
118
|
+
end
|
|
119
|
+
rescue GraphQL::RuntimeError => err
|
|
120
|
+
exec_result[exec_index] = err
|
|
121
|
+
rescue StandardError => stderr
|
|
122
|
+
exec_result[exec_index] = begin
|
|
123
|
+
context.query.handle_or_reraise(stderr)
|
|
124
|
+
rescue GraphQL::ExecutionError => ex_err
|
|
125
|
+
ex_err
|
|
126
|
+
end
|
|
127
|
+
ensure
|
|
128
|
+
field_pending_steps = field_resolve_step.pending_steps
|
|
129
|
+
field_pending_steps.delete(self)
|
|
130
|
+
if field_pending_steps.size == 0 && field_resolve_step.field_results
|
|
131
|
+
field_resolve_step.runner.add_step(field_resolve_step)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
59
135
|
def arguments
|
|
60
136
|
@prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
|
|
61
137
|
end
|
|
@@ -156,6 +232,10 @@ module GraphQL
|
|
|
156
232
|
authorize_arguments(args, inputs)
|
|
157
233
|
end
|
|
158
234
|
|
|
235
|
+
def self.authorizes?(context)
|
|
236
|
+
self.instance_method(:authorized?).owner != GraphQL::Schema::Resolver
|
|
237
|
+
end
|
|
238
|
+
|
|
159
239
|
# Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
|
|
160
240
|
#
|
|
161
241
|
# By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}.
|
|
@@ -15,8 +15,6 @@ module GraphQL
|
|
|
15
15
|
extend GraphQL::Schema::Resolver::HasPayloadType
|
|
16
16
|
extend GraphQL::Schema::Member::HasFields
|
|
17
17
|
NO_UPDATE = :no_update
|
|
18
|
-
# The generated payload type is required; If there's no payload,
|
|
19
|
-
# propagate null.
|
|
20
18
|
null false
|
|
21
19
|
|
|
22
20
|
# @api private
|