graphql 2.5.19 → 2.5.21
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/dashboard/application_controller.rb +41 -0
- data/lib/graphql/dashboard/landings_controller.rb +9 -0
- data/lib/graphql/dashboard/statics_controller.rb +31 -0
- data/lib/graphql/dashboard/subscriptions.rb +2 -1
- data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +3 -3
- data/lib/graphql/dashboard.rb +9 -74
- data/lib/graphql/dataloader/null_dataloader.rb +7 -3
- data/lib/graphql/execution/multiplex.rb +1 -1
- data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
- data/lib/graphql/execution/next/load_argument_step.rb +60 -0
- data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
- data/lib/graphql/execution/next/runner.rb +389 -0
- data/lib/graphql/execution/next/selections_step.rb +37 -0
- data/lib/graphql/execution/next.rb +69 -0
- data/lib/graphql/execution.rb +1 -0
- data/lib/graphql/execution_error.rb +13 -10
- data/lib/graphql/introspection/directive_type.rb +7 -3
- data/lib/graphql/introspection/dynamic_fields.rb +5 -1
- data/lib/graphql/introspection/entry_points.rb +11 -3
- data/lib/graphql/introspection/enum_value_type.rb +5 -5
- data/lib/graphql/introspection/field_type.rb +13 -5
- data/lib/graphql/introspection/input_value_type.rb +21 -13
- data/lib/graphql/introspection/type_type.rb +64 -28
- data/lib/graphql/invalid_null_error.rb +11 -5
- data/lib/graphql/pagination/connection.rb +2 -0
- data/lib/graphql/pagination/connections.rb +32 -0
- data/lib/graphql/query/context.rb +3 -2
- data/lib/graphql/query/null_context.rb +9 -3
- data/lib/graphql/schema/argument.rb +5 -0
- data/lib/graphql/schema/build_from_definition.rb +7 -0
- data/lib/graphql/schema/field/connection_extension.rb +15 -35
- data/lib/graphql/schema/field/scope_extension.rb +22 -13
- data/lib/graphql/schema/field.rb +70 -1
- data/lib/graphql/schema/field_extension.rb +33 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
- data/lib/graphql/schema/member/has_authorization.rb +35 -0
- data/lib/graphql/schema/member/has_dataloader.rb +17 -0
- data/lib/graphql/schema/member/has_fields.rb +5 -1
- data/lib/graphql/schema/member.rb +5 -0
- data/lib/graphql/schema/object.rb +1 -0
- data/lib/graphql/schema/resolver.rb +45 -1
- data/lib/graphql/schema/visibility.rb +1 -1
- data/lib/graphql/schema.rb +33 -7
- data/lib/graphql/subscriptions.rb +1 -1
- data/lib/graphql/tracing/perfetto_trace.rb +1 -1
- data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
- data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
- data/lib/graphql/types/relay/has_node_field.rb +13 -8
- data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
- data/lib/graphql/types/relay/node_behaviors.rb +13 -2
- data/lib/graphql/unauthorized_error.rb +5 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +3 -0
- metadata +12 -2
|
@@ -5,24 +5,33 @@ module GraphQL
|
|
|
5
5
|
class Field
|
|
6
6
|
class ScopeExtension < GraphQL::Schema::FieldExtension
|
|
7
7
|
def after_resolve(object:, arguments:, context:, value:, memo:)
|
|
8
|
-
if
|
|
9
|
-
value
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
if object.is_a?(GraphQL::Schema::Object)
|
|
9
|
+
if value.nil?
|
|
10
|
+
value
|
|
11
|
+
else
|
|
12
|
+
return_type = field.type.unwrap
|
|
13
|
+
if return_type.respond_to?(:scope_items)
|
|
14
|
+
scoped_items = return_type.scope_items(value, context)
|
|
15
|
+
if !scoped_items.equal?(value) && !return_type.reauthorize_scoped_objects
|
|
16
|
+
if (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
|
|
17
|
+
(query_runtime_state = current_runtime_state[context.query])
|
|
18
|
+
query_runtime_state.was_authorized_by_scope_items = true
|
|
19
|
+
end
|
|
18
20
|
end
|
|
21
|
+
scoped_items
|
|
22
|
+
else
|
|
23
|
+
value
|
|
19
24
|
end
|
|
20
|
-
scoped_items
|
|
21
|
-
else
|
|
22
|
-
value
|
|
23
25
|
end
|
|
26
|
+
else
|
|
27
|
+
# TODO skip this entirely?
|
|
28
|
+
value
|
|
24
29
|
end
|
|
25
30
|
end
|
|
31
|
+
|
|
32
|
+
def after_resolve_next(**kwargs)
|
|
33
|
+
raise "This should never be called -- it's hardcoded in execution instead."
|
|
34
|
+
end
|
|
26
35
|
end
|
|
27
36
|
end
|
|
28
37
|
end
|
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,6 +193,10 @@ 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.
|
|
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.
|
|
195
200
|
# @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
|
|
196
201
|
# @param default_page_size [Integer, nil] For connections, the default number of items to return from this field, or `nil` to return unlimited results.
|
|
197
202
|
# @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
|
|
@@ -214,7 +219,7 @@ module GraphQL
|
|
|
214
219
|
# @param relay_nodes_field [Boolean] (Private, used by GraphQL-Ruby)
|
|
215
220
|
# @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
|
|
216
221
|
# @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}.
|
|
217
|
-
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: 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)
|
|
222
|
+
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, 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)
|
|
218
223
|
if name.nil?
|
|
219
224
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
|
220
225
|
end
|
|
@@ -262,6 +267,33 @@ module GraphQL
|
|
|
262
267
|
@method_str = -method_name.to_s
|
|
263
268
|
@method_sym = method_name.to_sym
|
|
264
269
|
@resolver_method = (resolver_method || name_s).to_sym
|
|
270
|
+
|
|
271
|
+
if resolve_static
|
|
272
|
+
@execution_next_mode = :resolve_static
|
|
273
|
+
@execution_next_mode_key = resolve_static == true ? @method_sym : resolve_static
|
|
274
|
+
elsif resolve_batch
|
|
275
|
+
@execution_next_mode = :resolve_batch
|
|
276
|
+
@execution_next_mode_key = resolve_batch == true ? @method_sym : resolve_batch
|
|
277
|
+
elsif resolve_each
|
|
278
|
+
@execution_next_mode = :resolve_each
|
|
279
|
+
@execution_next_mode_key = resolve_each == true ? @method_sym : resolve_each
|
|
280
|
+
elsif hash_key
|
|
281
|
+
@execution_next_mode = :hash_key
|
|
282
|
+
@execution_next_mode_key = hash_key
|
|
283
|
+
elsif dig
|
|
284
|
+
@execution_next_mode = :dig
|
|
285
|
+
@execution_next_mode_key = dig
|
|
286
|
+
elsif resolver_class
|
|
287
|
+
@execution_next_mode = :resolver_class
|
|
288
|
+
@execution_next_mode_key = resolver_class
|
|
289
|
+
elsif resolve_legacy_instance_method
|
|
290
|
+
@execution_next_mode = :resolve_legacy_instance_method
|
|
291
|
+
@execution_next_mode_key = resolve_legacy_instance_method == true ? @method_sym : resolve_legacy_instance_method
|
|
292
|
+
else
|
|
293
|
+
@execution_next_mode = :direct_send
|
|
294
|
+
@execution_next_mode_key = @method_sym
|
|
295
|
+
end
|
|
296
|
+
|
|
265
297
|
@complexity = complexity
|
|
266
298
|
@dynamic_introspection = dynamic_introspection
|
|
267
299
|
@return_type_expr = type
|
|
@@ -332,6 +364,9 @@ module GraphQL
|
|
|
332
364
|
end
|
|
333
365
|
end
|
|
334
366
|
|
|
367
|
+
# @api private
|
|
368
|
+
attr_reader :execution_next_mode_key, :execution_next_mode
|
|
369
|
+
|
|
335
370
|
# Calls the definition block, if one was given.
|
|
336
371
|
# This is deferred so that references to the return type
|
|
337
372
|
# can be lazily evaluated, reducing Rails boot time.
|
|
@@ -625,6 +660,13 @@ module GraphQL
|
|
|
625
660
|
end
|
|
626
661
|
end
|
|
627
662
|
|
|
663
|
+
def authorizes?(context)
|
|
664
|
+
method(:authorized?).owner != GraphQL::Schema::Field || (
|
|
665
|
+
(args = context.types.arguments(self)) &&
|
|
666
|
+
(args.any? { |a| a.authorizes?(context) })
|
|
667
|
+
)
|
|
668
|
+
end
|
|
669
|
+
|
|
628
670
|
def authorized?(object, args, context)
|
|
629
671
|
if @resolver_class
|
|
630
672
|
# The resolver _instance_ will check itself during `resolve()`
|
|
@@ -879,6 +921,33 @@ ERR
|
|
|
879
921
|
end
|
|
880
922
|
end
|
|
881
923
|
|
|
924
|
+
public
|
|
925
|
+
|
|
926
|
+
def run_next_extensions_before_resolve(objs, args, ctx, extended, idx: 0, &block)
|
|
927
|
+
extension = @extensions[idx]
|
|
928
|
+
if extension
|
|
929
|
+
extension.resolve_next(objects: objs, arguments: args, context: ctx) do |extended_objs, extended_args, memo|
|
|
930
|
+
if memo
|
|
931
|
+
memos = extended.memos ||= {}
|
|
932
|
+
memos[idx] = memo
|
|
933
|
+
end
|
|
934
|
+
|
|
935
|
+
if (extras = extension.added_extras)
|
|
936
|
+
ae = extended.added_extras ||= []
|
|
937
|
+
ae.concat(extras)
|
|
938
|
+
end
|
|
939
|
+
|
|
940
|
+
extended.object = extended_objs
|
|
941
|
+
extended.arguments = extended_args
|
|
942
|
+
run_next_extensions_before_resolve(extended_objs, extended_args, ctx, extended, idx: idx + 1, &block)
|
|
943
|
+
end
|
|
944
|
+
else
|
|
945
|
+
yield(objs, args)
|
|
946
|
+
end
|
|
947
|
+
end
|
|
948
|
+
|
|
949
|
+
private
|
|
950
|
+
|
|
882
951
|
def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0)
|
|
883
952
|
extension = @extensions[idx]
|
|
884
953
|
if extension
|
|
@@ -134,6 +134,24 @@ module GraphQL
|
|
|
134
134
|
yield(object, arguments, nil)
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
+
# Called before batch-resolving {#field}. It should either:
|
|
138
|
+
#
|
|
139
|
+
# - `yield` values to continue execution; OR
|
|
140
|
+
# - return something else to shortcut field execution.
|
|
141
|
+
#
|
|
142
|
+
# Whatever this method returns will be used for execution.
|
|
143
|
+
#
|
|
144
|
+
# @param objects [Array<Object>] The objects the field is being resolved on
|
|
145
|
+
# @param arguments [Hash] Ruby keyword arguments for resolving this field
|
|
146
|
+
# @param context [Query::Context] the context for this query
|
|
147
|
+
# @yieldparam objects [Array<Object>] The objects to continue resolving the field on. Length must be the same as passed-in `objects:`
|
|
148
|
+
# @yieldparam arguments [Hash] The keyword arguments to continue resolving with
|
|
149
|
+
# @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later
|
|
150
|
+
# @return [Array<Object>] The return value for this field, length matching passed-in `objects:`.
|
|
151
|
+
def resolve_next(objects:, arguments:, context:)
|
|
152
|
+
yield(objects, arguments, nil)
|
|
153
|
+
end
|
|
154
|
+
|
|
137
155
|
# Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced,
|
|
138
156
|
# but before the value was added to the GraphQL response.
|
|
139
157
|
#
|
|
@@ -148,6 +166,21 @@ module GraphQL
|
|
|
148
166
|
def after_resolve(object:, arguments:, context:, value:, memo:)
|
|
149
167
|
value
|
|
150
168
|
end
|
|
169
|
+
|
|
170
|
+
# Called after {#field} was batch-resolved, and after any lazy values (like `Promise`s) were synced,
|
|
171
|
+
# but before the value was added to the GraphQL response.
|
|
172
|
+
#
|
|
173
|
+
# Whatever this hook returns will be used as the return value.
|
|
174
|
+
#
|
|
175
|
+
# @param objects [Array<Object>] The objects the field is being resolved on
|
|
176
|
+
# @param arguments [Hash] Ruby keyword arguments for resolving this field
|
|
177
|
+
# @param context [Query::Context] the context for this query
|
|
178
|
+
# @param values [Array<Object>] Whatever the field returned, one for each of `objects`
|
|
179
|
+
# @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one
|
|
180
|
+
# @return [Array<Object>] The return values for this field, length matching `objects:`.
|
|
181
|
+
def after_resolve_next(objects:, arguments:, context:, values:, memo:)
|
|
182
|
+
values
|
|
183
|
+
end
|
|
151
184
|
end
|
|
152
185
|
end
|
|
153
186
|
end
|
|
@@ -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
|
|
@@ -20,6 +20,23 @@ module GraphQL
|
|
|
20
20
|
dataloader.with(source_class, *source_args).load(load_key)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# A shortcut method for loading many keys from a source.
|
|
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
|
+
#
|
|
33
|
+
# @param source_class [Class<GraphQL::Dataloader::Source>]
|
|
34
|
+
# @param source_args [Array<Object>] Any extra parameters defined in `source_class`'s `initialize` method
|
|
35
|
+
# @param load_keys [Array<Object>] The keys to look up using `def fetch`
|
|
36
|
+
def dataload_all(source_class, *source_args, load_keys)
|
|
37
|
+
dataloader.with(source_class, *source_args).load_all(load_keys)
|
|
38
|
+
end
|
|
39
|
+
|
|
23
40
|
# Find an object with ActiveRecord via {Dataloader::ActiveRecordSource}.
|
|
24
41
|
# @param model [Class<ActiveRecord::Base>]
|
|
25
42
|
# @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided
|
|
@@ -19,7 +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`)
|
|
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.
|
|
23
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
|
|
24
28
|
# @option kwargs [Class] :connection_extension The extension to add, to implement connections. If `nil`, no extension is added.
|
|
25
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.
|
|
@@ -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,8 +46,10 @@ module GraphQL
|
|
|
45
46
|
@prepared_arguments = nil
|
|
46
47
|
end
|
|
47
48
|
|
|
49
|
+
attr_accessor :exec_result, :exec_index, :field_resolve_step
|
|
50
|
+
|
|
48
51
|
# @return [Object] The application object this field is being resolved on
|
|
49
|
-
|
|
52
|
+
attr_accessor :object
|
|
50
53
|
|
|
51
54
|
# @return [GraphQL::Query::Context]
|
|
52
55
|
attr_reader :context
|
|
@@ -54,6 +57,47 @@ module GraphQL
|
|
|
54
57
|
# @return [GraphQL::Schema::Field]
|
|
55
58
|
attr_reader :field
|
|
56
59
|
|
|
60
|
+
attr_writer :prepared_arguments
|
|
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_authed, new_return_value = authorized?(**@prepared_arguments)
|
|
70
|
+
|
|
71
|
+
if (runner = @field_resolve_step.runner).resolves_lazies && runner.schema.lazy?(is_authed)
|
|
72
|
+
is_authed, new_return_value = runner.schema.sync_lazy(is_authed)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
result = if is_authed
|
|
76
|
+
Schema::Validator.validate!(self.class.validators, object, context, @prepared_arguments, as: @field)
|
|
77
|
+
call_resolve(@prepared_arguments)
|
|
78
|
+
else
|
|
79
|
+
new_return_value
|
|
80
|
+
end
|
|
81
|
+
q = context.query
|
|
82
|
+
q.current_trace.end_execute_field(field, @prepared_arguments, trace_objs, q, [result])
|
|
83
|
+
|
|
84
|
+
exec_result[exec_index] = result
|
|
85
|
+
rescue RuntimeError => err
|
|
86
|
+
exec_result[exec_index] = err
|
|
87
|
+
rescue StandardError => stderr
|
|
88
|
+
exec_result[exec_index] = begin
|
|
89
|
+
context.query.handle_or_reraise(stderr)
|
|
90
|
+
rescue GraphQL::ExecutionError => ex_err
|
|
91
|
+
ex_err
|
|
92
|
+
end
|
|
93
|
+
ensure
|
|
94
|
+
field_pending_steps = field_resolve_step.pending_steps
|
|
95
|
+
field_pending_steps.delete(self)
|
|
96
|
+
if field_pending_steps.size == 0 && field_resolve_step.field_results
|
|
97
|
+
field_resolve_step.runner.add_step(field_resolve_step)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
57
101
|
def arguments
|
|
58
102
|
@prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
|
|
59
103
|
end
|
|
@@ -191,7 +191,7 @@ module GraphQL
|
|
|
191
191
|
if refresh
|
|
192
192
|
@top_level_profile = nil
|
|
193
193
|
end
|
|
194
|
-
@top_level_profile ||= @schema.visibility_profile_class.new(context:
|
|
194
|
+
@top_level_profile ||= @schema.visibility_profile_class.new(context: @schema.null_context, schema: @schema, visibility: self)
|
|
195
195
|
end
|
|
196
196
|
|
|
197
197
|
private
|
data/lib/graphql/schema.rb
CHANGED
|
@@ -330,10 +330,16 @@ module GraphQL
|
|
|
330
330
|
find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins
|
|
331
331
|
end
|
|
332
332
|
|
|
333
|
+
attr_writer :null_context
|
|
334
|
+
|
|
335
|
+
def null_context
|
|
336
|
+
@null_context || GraphQL::Query::NullContext.instance
|
|
337
|
+
end
|
|
338
|
+
|
|
333
339
|
# Build a map of `{ name => type }` and return it
|
|
334
340
|
# @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
|
|
335
341
|
# @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
|
|
336
|
-
def types(context =
|
|
342
|
+
def types(context = null_context)
|
|
337
343
|
if use_visibility_profile?
|
|
338
344
|
types = Visibility::Profile.from_context(context, self)
|
|
339
345
|
return types.all_types_h
|
|
@@ -366,7 +372,7 @@ module GraphQL
|
|
|
366
372
|
# @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
|
|
367
373
|
# @param use_visibility_profile Private, for migration to {Schema::Visibility}
|
|
368
374
|
# @return [Module, nil] A type, or nil if there's no type called `type_name`
|
|
369
|
-
def get_type(type_name, context =
|
|
375
|
+
def get_type(type_name, context = null_context, use_visibility_profile = use_visibility_profile?)
|
|
370
376
|
if use_visibility_profile
|
|
371
377
|
profile = Visibility::Profile.from_context(context, self)
|
|
372
378
|
return profile.type(type_name)
|
|
@@ -617,7 +623,7 @@ module GraphQL
|
|
|
617
623
|
# @param use_visibility_profile Private, for migration to {Schema::Visibility}
|
|
618
624
|
# @return [Hash<String, Module>] All possible types, if no `type` is given.
|
|
619
625
|
# @return [Array<Module>] Possible types for `type`, if it's given.
|
|
620
|
-
def possible_types(type = nil, context =
|
|
626
|
+
def possible_types(type = nil, context = null_context, use_visibility_profile = use_visibility_profile?)
|
|
621
627
|
if use_visibility_profile
|
|
622
628
|
if type
|
|
623
629
|
return Visibility::Profile.from_context(context, self).possible_types(type)
|
|
@@ -701,7 +707,7 @@ module GraphQL
|
|
|
701
707
|
GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
|
|
702
708
|
end
|
|
703
709
|
|
|
704
|
-
def get_field(type_or_name, field_name, context =
|
|
710
|
+
def get_field(type_or_name, field_name, context = null_context, use_visibility_profile = use_visibility_profile?)
|
|
705
711
|
if use_visibility_profile
|
|
706
712
|
profile = Visibility::Profile.from_context(context, self)
|
|
707
713
|
parent_type = case type_or_name
|
|
@@ -738,7 +744,7 @@ module GraphQL
|
|
|
738
744
|
end
|
|
739
745
|
end
|
|
740
746
|
|
|
741
|
-
def get_fields(type, context =
|
|
747
|
+
def get_fields(type, context = null_context)
|
|
742
748
|
type.fields(context)
|
|
743
749
|
end
|
|
744
750
|
|
|
@@ -1228,6 +1234,7 @@ module GraphQL
|
|
|
1228
1234
|
vis = self.visibility
|
|
1229
1235
|
child_class.visibility = vis.dup_for(child_class)
|
|
1230
1236
|
end
|
|
1237
|
+
child_class.null_context = Query::NullContext.new(schema: child_class)
|
|
1231
1238
|
super
|
|
1232
1239
|
end
|
|
1233
1240
|
|
|
@@ -1329,10 +1336,11 @@ module GraphQL
|
|
|
1329
1336
|
def type_error(type_error, context)
|
|
1330
1337
|
case type_error
|
|
1331
1338
|
when GraphQL::InvalidNullError
|
|
1332
|
-
execution_error = GraphQL::ExecutionError.new(type_error.message,
|
|
1333
|
-
execution_error.path = context[:current_path]
|
|
1339
|
+
execution_error = GraphQL::ExecutionError.new(type_error.message, ast_nodes: type_error.ast_nodes)
|
|
1340
|
+
execution_error.path = type_error.path || context[:current_path]
|
|
1334
1341
|
|
|
1335
1342
|
context.errors << execution_error
|
|
1343
|
+
execution_error
|
|
1336
1344
|
when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
|
|
1337
1345
|
raise type_error
|
|
1338
1346
|
when GraphQL::IntegerDecodingError
|
|
@@ -1354,6 +1362,24 @@ module GraphQL
|
|
|
1354
1362
|
lazy_methods.set(lazy_class, value_method)
|
|
1355
1363
|
end
|
|
1356
1364
|
|
|
1365
|
+
def uses_raw_value?
|
|
1366
|
+
!!@uses_raw_value
|
|
1367
|
+
end
|
|
1368
|
+
|
|
1369
|
+
def uses_raw_value(new_val)
|
|
1370
|
+
@uses_raw_value = new_val
|
|
1371
|
+
end
|
|
1372
|
+
|
|
1373
|
+
def resolves_lazies?
|
|
1374
|
+
lazy_method_count = 0
|
|
1375
|
+
lazy_methods.each do |k, v|
|
|
1376
|
+
if !v.nil?
|
|
1377
|
+
lazy_method_count += 1
|
|
1378
|
+
end
|
|
1379
|
+
end
|
|
1380
|
+
lazy_method_count > 2
|
|
1381
|
+
end
|
|
1382
|
+
|
|
1357
1383
|
def instrument(instrument_step, instrumenter, options = {})
|
|
1358
1384
|
warn <<~WARN
|
|
1359
1385
|
Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html"
|
|
@@ -80,7 +80,7 @@ module GraphQL
|
|
|
80
80
|
|
|
81
81
|
# Normalize symbol-keyed args to strings, try camelizing them
|
|
82
82
|
# Should this accept a real context somehow?
|
|
83
|
-
normalized_args = normalize_arguments(normalized_event_name, field, args,
|
|
83
|
+
normalized_args = normalize_arguments(normalized_event_name, field, args, @schema.null_context)
|
|
84
84
|
|
|
85
85
|
event = Subscriptions::Event.new(
|
|
86
86
|
name: normalized_event_name,
|
|
@@ -682,7 +682,7 @@ module GraphQL
|
|
|
682
682
|
when GraphQL::Schema::InputObject
|
|
683
683
|
payload_to_debug(k, v.to_h, iid: iid, intern_value: intern_value)
|
|
684
684
|
else
|
|
685
|
-
class_name_iid = @interned_da_string_values[v.class.name]
|
|
685
|
+
class_name_iid = @interned_da_string_values[(v.class.name || "(anonymous)")]
|
|
686
686
|
da = [
|
|
687
687
|
debug_annotation(DA_DEBUG_INSPECT_CLASS_IID, :string_value_iid, class_name_iid),
|
|
688
688
|
]
|
|
@@ -196,18 +196,20 @@ module GraphQL
|
|
|
196
196
|
def edges
|
|
197
197
|
# Assume that whatever authorization needed to happen
|
|
198
198
|
# already happened at the connection level.
|
|
199
|
-
current_runtime_state = Fiber[:__graphql_runtime_info]
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
if (current_runtime_state = Fiber[:__graphql_runtime_info])
|
|
200
|
+
query_runtime_state = current_runtime_state[context.query]
|
|
201
|
+
query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
|
|
202
|
+
end
|
|
202
203
|
@object.edges
|
|
203
204
|
end
|
|
204
205
|
|
|
205
206
|
def nodes
|
|
206
207
|
# Assume that whatever authorization needed to happen
|
|
207
208
|
# already happened at the connection level.
|
|
208
|
-
current_runtime_state = Fiber[:__graphql_runtime_info]
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
if (current_runtime_state = Fiber[:__graphql_runtime_info])
|
|
210
|
+
query_runtime_state = current_runtime_state[context.query]
|
|
211
|
+
query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
|
|
212
|
+
end
|
|
211
213
|
@object.nodes
|
|
212
214
|
end
|
|
213
215
|
end
|
|
@@ -14,9 +14,10 @@ module GraphQL
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def node
|
|
17
|
-
current_runtime_state = Fiber[:__graphql_runtime_info]
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
if (current_runtime_state = Fiber[:__graphql_runtime_info])
|
|
18
|
+
query_runtime_state = current_runtime_state[context.query]
|
|
19
|
+
query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
|
|
20
|
+
end
|
|
20
21
|
@object.node
|
|
21
22
|
end
|
|
22
23
|
|
|
@@ -7,6 +7,17 @@ module GraphQL
|
|
|
7
7
|
module HasNodeField
|
|
8
8
|
def self.included(child_class)
|
|
9
9
|
child_class.field(**field_options, &field_block)
|
|
10
|
+
child_class.extend(ExecutionMethods)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ExecutionMethods
|
|
14
|
+
def get_relay_node(context, id:)
|
|
15
|
+
context.schema.object_from_id(id, context)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_relay_node(id:)
|
|
20
|
+
self.class.get_relay_node(context, id: id)
|
|
10
21
|
end
|
|
11
22
|
|
|
12
23
|
class << self
|
|
@@ -17,6 +28,8 @@ module GraphQL
|
|
|
17
28
|
null: true,
|
|
18
29
|
description: "Fetches an object given its ID.",
|
|
19
30
|
relay_node_field: true,
|
|
31
|
+
resolver_method: :get_relay_node,
|
|
32
|
+
resolve_static: :get_relay_node,
|
|
20
33
|
}
|
|
21
34
|
end
|
|
22
35
|
|
|
@@ -24,14 +37,6 @@ module GraphQL
|
|
|
24
37
|
Proc.new {
|
|
25
38
|
argument :id, "ID!",
|
|
26
39
|
description: "ID of the object."
|
|
27
|
-
|
|
28
|
-
def resolve(obj, args, ctx)
|
|
29
|
-
ctx.schema.object_from_id(args[:id], ctx)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def resolve_field(obj, args, ctx)
|
|
33
|
-
resolve(obj, args, ctx)
|
|
34
|
-
end
|
|
35
40
|
}
|
|
36
41
|
end
|
|
37
42
|
end
|
|
@@ -7,6 +7,17 @@ module GraphQL
|
|
|
7
7
|
module HasNodesField
|
|
8
8
|
def self.included(child_class)
|
|
9
9
|
child_class.field(**field_options, &field_block)
|
|
10
|
+
child_class.extend(ExecutionMethods)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ExecutionMethods
|
|
14
|
+
def get_relay_nodes(context, ids:)
|
|
15
|
+
ids.map { |id| context.schema.object_from_id(id, context) }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_relay_nodes(ids:)
|
|
20
|
+
self.class.get_relay_nodes(context, ids: ids)
|
|
10
21
|
end
|
|
11
22
|
|
|
12
23
|
class << self
|
|
@@ -17,6 +28,8 @@ module GraphQL
|
|
|
17
28
|
null: false,
|
|
18
29
|
description: "Fetches a list of objects given a list of IDs.",
|
|
19
30
|
relay_nodes_field: true,
|
|
31
|
+
resolver_method: :get_relay_nodes,
|
|
32
|
+
resolve_static: :get_relay_nodes
|
|
20
33
|
}
|
|
21
34
|
end
|
|
22
35
|
|
|
@@ -24,14 +37,6 @@ module GraphQL
|
|
|
24
37
|
Proc.new {
|
|
25
38
|
argument :ids, "[ID!]!",
|
|
26
39
|
description: "IDs of the objects."
|
|
27
|
-
|
|
28
|
-
def resolve(obj, args, ctx)
|
|
29
|
-
args[:ids].map { |id| ctx.schema.object_from_id(id, ctx) }
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def resolve_field(obj, args, ctx)
|
|
33
|
-
resolve(obj, args, ctx)
|
|
34
|
-
end
|
|
35
40
|
}
|
|
36
41
|
end
|
|
37
42
|
end
|