graphql 1.5.15 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql.rb +4 -19
- data/lib/graphql/analysis/analyze_query.rb +27 -2
- data/lib/graphql/analysis/query_complexity.rb +10 -11
- data/lib/graphql/argument.rb +7 -6
- data/lib/graphql/backwards_compatibility.rb +47 -0
- data/lib/graphql/compatibility/execution_specification.rb +14 -0
- data/lib/graphql/compatibility/execution_specification/specification_schema.rb +6 -1
- data/lib/graphql/compatibility/lazy_execution_specification.rb +19 -0
- data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +15 -6
- data/lib/graphql/directive.rb +1 -6
- data/lib/graphql/execution.rb +1 -0
- data/lib/graphql/execution/execute.rb +174 -160
- data/lib/graphql/execution/field_result.rb +5 -1
- data/lib/graphql/execution/lazy.rb +2 -2
- data/lib/graphql/execution/lazy/resolve.rb +8 -11
- data/lib/graphql/execution/multiplex.rb +134 -0
- data/lib/graphql/execution/selection_result.rb +5 -0
- data/lib/graphql/field.rb +1 -8
- data/lib/graphql/filter.rb +53 -0
- data/lib/graphql/internal_representation/node.rb +11 -6
- data/lib/graphql/internal_representation/rewrite.rb +3 -3
- data/lib/graphql/query.rb +160 -78
- data/lib/graphql/query/arguments.rb +14 -25
- data/lib/graphql/query/arguments_cache.rb +6 -13
- data/lib/graphql/query/context.rb +28 -10
- data/lib/graphql/query/executor.rb +1 -0
- data/lib/graphql/query/literal_input.rb +10 -4
- data/lib/graphql/query/null_context.rb +1 -1
- data/lib/graphql/query/serial_execution/field_resolution.rb +5 -1
- data/lib/graphql/query/validation_pipeline.rb +12 -7
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/rake_task.rb +140 -0
- data/lib/graphql/relay/array_connection.rb +29 -48
- data/lib/graphql/relay/base_connection.rb +9 -7
- data/lib/graphql/relay/mutation.rb +0 -11
- data/lib/graphql/relay/mutation/instrumentation.rb +2 -2
- data/lib/graphql/relay/mutation/resolve.rb +7 -10
- data/lib/graphql/relay/relation_connection.rb +98 -61
- data/lib/graphql/scalar_type.rb +1 -15
- data/lib/graphql/schema.rb +90 -25
- data/lib/graphql/schema/build_from_definition.rb +22 -23
- data/lib/graphql/schema/build_from_definition/resolve_map.rb +70 -0
- data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +47 -0
- data/lib/graphql/schema/middleware_chain.rb +1 -1
- data/lib/graphql/schema/printer.rb +2 -1
- data/lib/graphql/schema/timeout_middleware.rb +6 -6
- data/lib/graphql/schema/type_map.rb +1 -1
- data/lib/graphql/schema/warden.rb +5 -9
- data/lib/graphql/static_validation/definition_dependencies.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/analysis/analyze_query_spec.rb +2 -2
- data/spec/graphql/analysis/max_query_complexity_spec.rb +28 -0
- data/spec/graphql/argument_spec.rb +3 -3
- data/spec/graphql/execution/lazy_spec.rb +8 -114
- data/spec/graphql/execution/multiplex_spec.rb +131 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +10 -0
- data/spec/graphql/query/arguments_spec.rb +14 -16
- data/spec/graphql/query/context_spec.rb +14 -1
- data/spec/graphql/query/literal_input_spec.rb +19 -13
- data/spec/graphql/query/variables_spec.rb +1 -1
- data/spec/graphql/query_spec.rb +12 -1
- data/spec/graphql/rake_task_spec.rb +57 -0
- data/spec/graphql/relay/array_connection_spec.rb +24 -3
- data/spec/graphql/relay/connection_instrumentation_spec.rb +23 -0
- data/spec/graphql/relay/mutation_spec.rb +2 -10
- data/spec/graphql/relay/page_info_spec.rb +2 -2
- data/spec/graphql/relay/relation_connection_spec.rb +167 -3
- data/spec/graphql/schema/build_from_definition_spec.rb +93 -19
- data/spec/graphql/schema/warden_spec.rb +80 -0
- data/spec/graphql/schema_spec.rb +26 -2
- data/spec/spec_helper.rb +4 -2
- data/spec/support/lazy_helpers.rb +152 -0
- data/spec/support/star_wars/schema.rb +23 -0
- metadata +28 -3
- data/lib/graphql/schema/mask.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21a090385055caa75ff227d787c3a794c5134c11
|
4
|
+
data.tar.gz: f6e6e5b3eb47827061ecc6327d2c431d57558be0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9049055982560c7eebe4b37843ce665d885c32ed61f6b97dcb2cbfdefcade29cb62f98914f9824d4e772d410494c75abf732583c7ed2c6ebd87875a26897f7d9
|
7
|
+
data.tar.gz: ab419676204940e098b0bd2e612ce8cf6aa9b1456b03d599481be19a4a8e9efa4fed16238702e6d8a5a5df2dd4a6af472975400280729074f34c9e202612f0ef
|
data/lib/graphql.rb
CHANGED
@@ -3,26 +3,9 @@ require "delegate"
|
|
3
3
|
require "json"
|
4
4
|
require "set"
|
5
5
|
require "singleton"
|
6
|
+
require "forwardable"
|
6
7
|
|
7
8
|
module GraphQL
|
8
|
-
# Ruby stdlib was pretty busted until this fix:
|
9
|
-
# https://github.com/ruby/ruby/commit/46c0e79bb5b96c45c166ef62f8e585f528862abb#diff-43adf0e587a50dbaf51764a262008d40
|
10
|
-
module Delegate
|
11
|
-
def def_delegators(accessor, *method_names)
|
12
|
-
method_names.each do |method_name|
|
13
|
-
class_eval <<-RUBY
|
14
|
-
def #{method_name}(*args)
|
15
|
-
if block_given?
|
16
|
-
#{accessor}.#{method_name}(*args, &Proc.new)
|
17
|
-
else
|
18
|
-
#{accessor}.#{method_name}(*args)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
RUBY
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
9
|
class Error < StandardError
|
27
10
|
end
|
28
11
|
|
@@ -83,6 +66,7 @@ require "graphql/argument"
|
|
83
66
|
require "graphql/field"
|
84
67
|
require "graphql/type_kinds"
|
85
68
|
|
69
|
+
require "graphql/backwards_compatibility"
|
86
70
|
require "graphql/scalar_type"
|
87
71
|
require "graphql/boolean_type"
|
88
72
|
require "graphql/float_type"
|
@@ -95,6 +79,7 @@ require "graphql/introspection"
|
|
95
79
|
require "graphql/language"
|
96
80
|
require "graphql/analysis"
|
97
81
|
require "graphql/execution"
|
82
|
+
require "graphql/relay"
|
98
83
|
require "graphql/schema"
|
99
84
|
require "graphql/schema/loader"
|
100
85
|
require "graphql/schema/printer"
|
@@ -108,6 +93,6 @@ require "graphql/query"
|
|
108
93
|
require "graphql/internal_representation"
|
109
94
|
require "graphql/static_validation"
|
110
95
|
require "graphql/version"
|
111
|
-
require "graphql/relay"
|
112
96
|
require "graphql/compatibility"
|
113
97
|
require "graphql/function"
|
98
|
+
require "graphql/filter"
|
@@ -2,6 +2,27 @@
|
|
2
2
|
module GraphQL
|
3
3
|
module Analysis
|
4
4
|
module_function
|
5
|
+
|
6
|
+
# @return [void]
|
7
|
+
def analyze_multiplex(multiplex, analyzers)
|
8
|
+
reducer_states = analyzers.map { |r| ReducerState.new(r, multiplex) }
|
9
|
+
query_results = multiplex.queries.map do |query|
|
10
|
+
if query.valid?
|
11
|
+
analyze_query(query, query.analyzers, multiplex_states: reducer_states)
|
12
|
+
else
|
13
|
+
[]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
multiplex_results = reducer_states.map(&:finalize_reducer)
|
18
|
+
multiplex_errors = analysis_errors(multiplex_results)
|
19
|
+
|
20
|
+
multiplex.queries.each_with_index do |query, idx|
|
21
|
+
query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx])
|
22
|
+
end
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
5
26
|
# Visit `query`'s internal representation, calling `analyzers` along the way.
|
6
27
|
#
|
7
28
|
# - First, query analyzers are initialized by calling `.initial_value(query)`, if they respond to that method.
|
@@ -13,8 +34,8 @@ module GraphQL
|
|
13
34
|
# @param query [GraphQL::Query]
|
14
35
|
# @param analyzers [Array<#call>] Objects that respond to `#call(memo, visit_type, irep_node)`
|
15
36
|
# @return [Array<Any>] Results from those analyzers
|
16
|
-
def analyze_query(query, analyzers)
|
17
|
-
reducer_states = analyzers.map { |r| ReducerState.new(r, query) }
|
37
|
+
def analyze_query(query, analyzers, multiplex_states: [])
|
38
|
+
reducer_states = analyzers.map { |r| ReducerState.new(r, query) } + multiplex_states
|
18
39
|
|
19
40
|
irep = query.internal_representation
|
20
41
|
|
@@ -49,5 +70,9 @@ module GraphQL
|
|
49
70
|
reducer_state.memo = next_memo
|
50
71
|
end
|
51
72
|
end
|
73
|
+
|
74
|
+
def analysis_errors(results)
|
75
|
+
results.flatten.select { |r| r.is_a?(GraphQL::AnalysisError) }
|
76
|
+
end
|
52
77
|
end
|
53
78
|
end
|
@@ -4,7 +4,7 @@ module GraphQL
|
|
4
4
|
# Calculate the complexity of a query, using {Field#complexity} values.
|
5
5
|
#
|
6
6
|
# @example Log the complexity of incoming queries
|
7
|
-
# MySchema.query_analyzers << GraphQL::
|
7
|
+
# MySchema.query_analyzers << GraphQL::Analysis::QueryComplexity.new do |query, complexity|
|
8
8
|
# Rails.logger.info("Complexity: #{complexity}")
|
9
9
|
# end
|
10
10
|
#
|
@@ -17,11 +17,11 @@ module GraphQL
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# State for the query complexity calcuation:
|
20
|
-
# - `
|
20
|
+
# - `target` is passed to handler
|
21
21
|
# - `complexities_on_type` holds complexity scores for each type in an IRep node
|
22
|
-
def initial_value(
|
22
|
+
def initial_value(target)
|
23
23
|
{
|
24
|
-
|
24
|
+
target: target,
|
25
25
|
complexities_on_type: [TypeComplexity.new],
|
26
26
|
}
|
27
27
|
end
|
@@ -34,7 +34,7 @@ module GraphQL
|
|
34
34
|
else
|
35
35
|
type_complexities = memo[:complexities_on_type].pop
|
36
36
|
child_complexity = type_complexities.max_possible_complexity
|
37
|
-
own_complexity = get_complexity(irep_node,
|
37
|
+
own_complexity = get_complexity(irep_node, child_complexity)
|
38
38
|
memo[:complexities_on_type].last.merge(irep_node.owner_type, own_complexity)
|
39
39
|
end
|
40
40
|
end
|
@@ -44,21 +44,20 @@ module GraphQL
|
|
44
44
|
# Send the query and complexity to the block
|
45
45
|
# @return [Object, GraphQL::AnalysisError] Whatever the handler returns
|
46
46
|
def final_value(reduced_value)
|
47
|
-
total_complexity = reduced_value[:complexities_on_type].
|
48
|
-
@complexity_handler.call(reduced_value[:
|
47
|
+
total_complexity = reduced_value[:complexities_on_type].last.max_possible_complexity
|
48
|
+
@complexity_handler.call(reduced_value[:target], total_complexity)
|
49
49
|
end
|
50
50
|
|
51
51
|
private
|
52
52
|
|
53
53
|
# Get a complexity value for a field,
|
54
54
|
# by getting the number or calling its proc
|
55
|
-
def get_complexity(irep_node,
|
55
|
+
def get_complexity(irep_node, child_complexity)
|
56
56
|
field_defn = irep_node.definition
|
57
57
|
defined_complexity = field_defn.complexity
|
58
58
|
case defined_complexity
|
59
59
|
when Proc
|
60
|
-
|
61
|
-
defined_complexity.call(query.context, args, child_complexity)
|
60
|
+
defined_complexity.call(irep_node.query.context, irep_node.arguments, child_complexity)
|
62
61
|
when Numeric
|
63
62
|
defined_complexity + (child_complexity || 0)
|
64
63
|
else
|
@@ -70,7 +69,7 @@ module GraphQL
|
|
70
69
|
# Find the maximum possible complexity among those combinations.
|
71
70
|
class TypeComplexity
|
72
71
|
def initialize
|
73
|
-
@types = Hash.new
|
72
|
+
@types = Hash.new { |h, k| h[k] = 0 }
|
74
73
|
end
|
75
74
|
|
76
75
|
# Return the max possible complexity for types in this selection
|
data/lib/graphql/argument.rb
CHANGED
@@ -42,7 +42,7 @@ module GraphQL
|
|
42
42
|
|
43
43
|
# @api private
|
44
44
|
module DefaultPrepare
|
45
|
-
def self.call(value); value; end
|
45
|
+
def self.call(value, ctx); value; end
|
46
46
|
end
|
47
47
|
|
48
48
|
def initialize
|
@@ -81,16 +81,17 @@ module GraphQL
|
|
81
81
|
@expose_as ||= (@as || @name).to_s
|
82
82
|
end
|
83
83
|
|
84
|
-
# @param value
|
84
|
+
# @param value [Object] The incoming value from variables or query string literal
|
85
|
+
# @param ctx [GraphQL::Query::Context]
|
85
86
|
# @return [Object] The prepared `value` for this argument or `value` itself if no `prepare` function exists.
|
86
|
-
def prepare(value)
|
87
|
-
@prepare_proc.call(value)
|
87
|
+
def prepare(value, ctx)
|
88
|
+
@prepare_proc.call(value, ctx)
|
88
89
|
end
|
89
90
|
|
90
91
|
# Assign a `prepare` function to prepare this argument's value before `resolve` functions are called.
|
91
|
-
# @param prepare_proc [
|
92
|
+
# @param prepare_proc [#<call(value, ctx)>]
|
92
93
|
def prepare=(prepare_proc)
|
93
|
-
@prepare_proc = prepare_proc
|
94
|
+
@prepare_proc = BackwardsCompatibility.wrap_arity(prepare_proc, from: 1, to: 2, name: "Argument#prepare(value, ctx)")
|
94
95
|
end
|
95
96
|
|
96
97
|
NO_DEFAULT_VALUE = Object.new
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
# Helpers for migrating in a backwards-compatibile way
|
4
|
+
# @api private
|
5
|
+
module BackwardsCompatibility
|
6
|
+
module_function
|
7
|
+
# Given a callable whose API used to take `from` arguments,
|
8
|
+
# check its arity, and if needed, apply a wrapper so that
|
9
|
+
# it can be called with `to` arguments.
|
10
|
+
# If a wrapper is applied, warn the application with `name`.
|
11
|
+
def wrap_arity(callable, from:, to:, name:)
|
12
|
+
arity = get_arity(callable)
|
13
|
+
case arity
|
14
|
+
when to
|
15
|
+
# It already matches, return it as is
|
16
|
+
callable
|
17
|
+
when from
|
18
|
+
# It has the old arity, so wrap it with an arity converter
|
19
|
+
warn("#{name} with #{from} arguments is deprecated, it now accepts #{to} arguments")
|
20
|
+
ArityWrapper.new(callable, from)
|
21
|
+
else
|
22
|
+
raise "Can't wrap #{callable} (arity: #{arity}) to have arity #{to}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_arity(callable)
|
27
|
+
case callable
|
28
|
+
when Proc
|
29
|
+
callable.arity
|
30
|
+
else
|
31
|
+
callable.method(:call).arity
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ArityWrapper
|
36
|
+
def initialize(callable, old_arity)
|
37
|
+
@callable = callable
|
38
|
+
@old_arity = old_arity
|
39
|
+
end
|
40
|
+
|
41
|
+
def call(*args)
|
42
|
+
backwards_compat_args = args.first(@old_arity)
|
43
|
+
@callable.call(*backwards_compat_args)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -15,6 +15,7 @@ module GraphQL
|
|
15
15
|
# - Typecasting
|
16
16
|
# - Error handling (raise / return GraphQL::ExecutionError)
|
17
17
|
# - Provides Irep & AST node to resolve fn
|
18
|
+
# - Skipping fields
|
18
19
|
#
|
19
20
|
# Some things are explicitly _not_ tested here, because they're handled
|
20
21
|
# by other parts of the system:
|
@@ -97,6 +98,19 @@ module GraphQL
|
|
97
98
|
assert_equal ["SNCC"], res["data"]["node"]["organizations"].map { |o| o["name"] }
|
98
99
|
end
|
99
100
|
|
101
|
+
def test_it_skips_skipped_fields
|
102
|
+
query_str = <<-GRAPHQL
|
103
|
+
{
|
104
|
+
o3001: organization(id: "3001") { name }
|
105
|
+
o2001: organization(id: "2001") { name }
|
106
|
+
}
|
107
|
+
GRAPHQL
|
108
|
+
|
109
|
+
res = execute_query(query_str)
|
110
|
+
assert_equal ["o2001"], res["data"].keys
|
111
|
+
assert_equal false, res.key?("errors")
|
112
|
+
end
|
113
|
+
|
100
114
|
def test_it_propagates_nulls_to_field
|
101
115
|
query_string = %|
|
102
116
|
query getOrg($id: ID = "2001"){
|
@@ -158,7 +158,12 @@ module GraphQL
|
|
158
158
|
field :organization, !organization_type do
|
159
159
|
argument :id, !types.ID
|
160
160
|
resolve ->(obj, args, ctx) {
|
161
|
-
args[:id].start_with?("2")
|
161
|
+
if args[:id].start_with?("2")
|
162
|
+
obj[args[:id]]
|
163
|
+
else
|
164
|
+
# test context.skip
|
165
|
+
ctx.skip
|
166
|
+
end
|
162
167
|
}
|
163
168
|
end
|
164
169
|
|
@@ -180,6 +180,25 @@ module GraphQL
|
|
180
180
|
]
|
181
181
|
assert_equal expected_log, log
|
182
182
|
end
|
183
|
+
|
184
|
+
def test_it_skips_ctx_skip
|
185
|
+
query_string = <<-GRAPHQL
|
186
|
+
{
|
187
|
+
p0: push(value: 15) { value }
|
188
|
+
p1: push(value: 1) { value }
|
189
|
+
p2: push(value: 2) {
|
190
|
+
value
|
191
|
+
p3: push(value: 15) {
|
192
|
+
value
|
193
|
+
}
|
194
|
+
}
|
195
|
+
}
|
196
|
+
GRAPHQL
|
197
|
+
pushes = []
|
198
|
+
res = self.class.lazy_schema.execute(query_string, context: {pushes: pushes})
|
199
|
+
assert_equal [[1,2]], pushes
|
200
|
+
assert_equal({"data"=>{"p1"=>{"value"=>1}, "p2"=>{"value"=>2}}}, res)
|
201
|
+
end
|
183
202
|
end
|
184
203
|
end
|
185
204
|
end
|
@@ -10,21 +10,30 @@ module GraphQL
|
|
10
10
|
@value = nil
|
11
11
|
elsif value == 14
|
12
12
|
@value = GraphQL::ExecutionError.new("oops!")
|
13
|
+
elsif value == 15
|
14
|
+
@skipped = true
|
15
|
+
@value = ctx.skip
|
13
16
|
else
|
14
17
|
@value = value
|
15
18
|
end
|
16
19
|
@context = ctx
|
17
20
|
pushes = @context[:lazy_pushes] ||= []
|
18
|
-
|
21
|
+
if !@skipped
|
22
|
+
pushes << @value
|
23
|
+
end
|
19
24
|
end
|
20
25
|
|
21
26
|
def push
|
22
|
-
if @
|
23
|
-
@
|
24
|
-
|
25
|
-
@context[:lazy_pushes]
|
27
|
+
if @skipped
|
28
|
+
@value
|
29
|
+
else
|
30
|
+
if @context[:lazy_pushes].include?(@value)
|
31
|
+
@context[:lazy_instrumentation] && @context[:lazy_instrumentation] << "PUSH"
|
32
|
+
@context[:pushes] << @context[:lazy_pushes]
|
33
|
+
@context[:lazy_pushes] = []
|
34
|
+
end
|
35
|
+
self
|
26
36
|
end
|
27
|
-
self
|
28
37
|
end
|
29
38
|
end
|
30
39
|
|
data/lib/graphql/directive.rb
CHANGED
@@ -13,7 +13,7 @@ module GraphQL
|
|
13
13
|
attr_accessor :locations, :arguments, :name, :description
|
14
14
|
# @api private
|
15
15
|
attr_writer :default_directive
|
16
|
-
ensure_defined(:locations, :arguments, :name, :description, :default_directive
|
16
|
+
ensure_defined(:locations, :arguments, :name, :description, :default_directive?)
|
17
17
|
|
18
18
|
LOCATIONS = [
|
19
19
|
QUERY = :QUERY,
|
@@ -83,11 +83,6 @@ module GraphQL
|
|
83
83
|
def default_directive?
|
84
84
|
@default_directive
|
85
85
|
end
|
86
|
-
|
87
|
-
# @return [GraphQL::Query::Arguments] Arguments to use when no args are provided in the query
|
88
|
-
def default_arguments
|
89
|
-
@default_arguments ||= GraphQL::Query::LiteralInput.defaults_for(self.arguments)
|
90
|
-
end
|
91
86
|
end
|
92
87
|
end
|
93
88
|
|
data/lib/graphql/execution.rb
CHANGED
@@ -3,5 +3,6 @@ require "graphql/execution/directive_checks"
|
|
3
3
|
require "graphql/execution/execute"
|
4
4
|
require "graphql/execution/field_result"
|
5
5
|
require "graphql/execution/lazy"
|
6
|
+
require "graphql/execution/multiplex"
|
6
7
|
require "graphql/execution/selection_result"
|
7
8
|
require "graphql/execution/typecast"
|
@@ -4,7 +4,10 @@ module GraphQL
|
|
4
4
|
# A valid execution strategy
|
5
5
|
# @api private
|
6
6
|
class Execute
|
7
|
-
|
7
|
+
# @api private
|
8
|
+
SKIP = Object.new
|
9
|
+
# @api private
|
10
|
+
PROPAGATE_NULL = Object.new
|
8
11
|
|
9
12
|
def execute(ast_operation, root_type, query)
|
10
13
|
result = resolve_selection(
|
@@ -20,200 +23,211 @@ module GraphQL
|
|
20
23
|
result.to_h
|
21
24
|
end
|
22
25
|
|
23
|
-
private
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
# @api private
|
27
|
+
module ExecutionFunctions
|
28
|
+
module_function
|
29
|
+
|
30
|
+
def resolve_selection(object, current_type, selection, query_ctx, mutation: false )
|
31
|
+
selection_result = SelectionResult.new
|
32
|
+
|
33
|
+
selection.typed_children[current_type].each do |name, subselection|
|
34
|
+
field_result = resolve_field(
|
35
|
+
selection_result,
|
36
|
+
subselection,
|
37
|
+
current_type,
|
38
|
+
subselection.definition,
|
39
|
+
object,
|
40
|
+
query_ctx
|
41
|
+
)
|
27
42
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
subselection,
|
32
|
-
current_type,
|
33
|
-
subselection.definition,
|
34
|
-
object,
|
35
|
-
query_ctx
|
36
|
-
)
|
43
|
+
if field_result == SKIP
|
44
|
+
next
|
45
|
+
end
|
37
46
|
|
38
|
-
|
39
|
-
|
40
|
-
|
47
|
+
if mutation
|
48
|
+
GraphQL::Execution::Lazy.resolve(field_result)
|
49
|
+
end
|
41
50
|
|
42
|
-
|
51
|
+
selection_result.set(name, field_result)
|
43
52
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
53
|
+
# If the last subselection caused a null to propagate to _this_ selection,
|
54
|
+
# then we may as well quit executing fields because they
|
55
|
+
# won't be in the response
|
56
|
+
if selection_result.invalid_null?
|
57
|
+
break
|
58
|
+
end
|
49
59
|
end
|
50
|
-
end
|
51
|
-
|
52
|
-
selection_result
|
53
|
-
end
|
54
60
|
|
55
|
-
|
56
|
-
query = query_ctx.query
|
57
|
-
field_ctx = query_ctx.spawn(
|
58
|
-
parent_type: parent_type,
|
59
|
-
field: field,
|
60
|
-
key: selection.name,
|
61
|
-
selection: selection,
|
62
|
-
)
|
63
|
-
|
64
|
-
arguments = query.arguments_for(selection, field)
|
65
|
-
raw_value = begin
|
66
|
-
query_ctx.schema.middleware.invoke([parent_type, object, field, arguments, field_ctx])
|
67
|
-
rescue GraphQL::ExecutionError => err
|
68
|
-
err
|
61
|
+
selection_result
|
69
62
|
end
|
70
63
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
continue_resolve_field(owner, selection, parent_type, field, inner_value, field_ctx)
|
79
|
-
}
|
80
|
-
else
|
81
|
-
continue_resolve_field(owner, selection, parent_type, field, raw_value, field_ctx)
|
82
|
-
end
|
83
|
-
|
84
|
-
case result
|
85
|
-
when PROPAGATE_NULL, GraphQL::Execution::Lazy, SelectionResult
|
86
|
-
FieldResult.new(
|
87
|
-
owner: owner,
|
88
|
-
type: field.type,
|
89
|
-
value: result,
|
64
|
+
def resolve_field(owner, selection, parent_type, field, object, query_ctx)
|
65
|
+
query = query_ctx.query
|
66
|
+
field_ctx = query_ctx.spawn(
|
67
|
+
parent_type: parent_type,
|
68
|
+
field: field,
|
69
|
+
key: selection.name,
|
70
|
+
selection: selection,
|
90
71
|
)
|
91
|
-
else
|
92
|
-
result
|
93
|
-
end
|
94
|
-
end
|
95
72
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
raw_value.ast_node = field_ctx.ast_node
|
102
|
-
raw_value.path = field_ctx.path
|
103
|
-
query.context.errors.push(raw_value)
|
104
|
-
when Array
|
105
|
-
list_errors = raw_value.each_with_index.select { |value, _| value.is_a?(GraphQL::ExecutionError) }
|
106
|
-
if list_errors.any?
|
107
|
-
list_errors.each do |error, index|
|
108
|
-
error.ast_node = field_ctx.ast_node
|
109
|
-
error.path = field_ctx.path + [index]
|
110
|
-
query.context.errors.push(error)
|
111
|
-
end
|
73
|
+
arguments = query.arguments_for(selection, field)
|
74
|
+
raw_value = begin
|
75
|
+
query_ctx.schema.middleware.invoke([parent_type, object, field, arguments, field_ctx])
|
76
|
+
rescue GraphQL::ExecutionError => err
|
77
|
+
err
|
112
78
|
end
|
113
|
-
end
|
114
|
-
|
115
|
-
resolve_value(
|
116
|
-
owner,
|
117
|
-
parent_type,
|
118
|
-
field,
|
119
|
-
field.type,
|
120
|
-
raw_value,
|
121
|
-
selection,
|
122
|
-
field_ctx,
|
123
|
-
)
|
124
|
-
end
|
125
79
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
80
|
+
result = if query.schema.lazy?(raw_value)
|
81
|
+
field.prepare_lazy(raw_value, arguments, field_ctx).then { |inner_value|
|
82
|
+
continue_resolve_field(owner, selection, parent_type, field, inner_value, field_ctx)
|
83
|
+
}
|
84
|
+
elsif raw_value.is_a?(GraphQL::Execution::Lazy)
|
85
|
+
# It came from a connection resolve, assume it was already instrumented
|
86
|
+
raw_value.then { |inner_value|
|
87
|
+
continue_resolve_field(owner, selection, parent_type, field, inner_value, field_ctx)
|
88
|
+
}
|
132
89
|
else
|
133
|
-
|
90
|
+
continue_resolve_field(owner, selection, parent_type, field, raw_value, field_ctx)
|
134
91
|
end
|
135
|
-
|
136
|
-
|
137
|
-
|
92
|
+
|
93
|
+
case result
|
94
|
+
when PROPAGATE_NULL, GraphQL::Execution::Lazy, SelectionResult
|
95
|
+
FieldResult.new(
|
96
|
+
owner: owner,
|
97
|
+
type: field.type,
|
98
|
+
value: result,
|
99
|
+
)
|
138
100
|
else
|
139
|
-
|
101
|
+
result
|
140
102
|
end
|
141
|
-
|
142
|
-
case field_type.kind
|
143
|
-
when GraphQL::TypeKinds::SCALAR
|
144
|
-
field_type.coerce_result(value, field_ctx)
|
145
|
-
when GraphQL::TypeKinds::ENUM
|
146
|
-
field_type.coerce_result(value, field_ctx)
|
147
|
-
when GraphQL::TypeKinds::LIST
|
148
|
-
inner_type = field_type.of_type
|
149
|
-
i = 0
|
150
|
-
result = []
|
151
|
-
value.each do |inner_value|
|
152
|
-
inner_ctx = field_ctx.spawn(
|
153
|
-
key: i,
|
154
|
-
selection: selection,
|
155
|
-
parent_type: parent_type,
|
156
|
-
field: field_defn,
|
157
|
-
)
|
158
|
-
|
159
|
-
inner_result = resolve_value(
|
160
|
-
owner,
|
161
|
-
parent_type,
|
162
|
-
field_defn,
|
163
|
-
inner_type,
|
164
|
-
inner_value,
|
165
|
-
selection,
|
166
|
-
inner_ctx,
|
167
|
-
)
|
103
|
+
end
|
168
104
|
|
169
|
-
|
170
|
-
|
105
|
+
def continue_resolve_field(owner, selection, parent_type, field, raw_value, field_ctx)
|
106
|
+
query = field_ctx.query
|
107
|
+
|
108
|
+
case raw_value
|
109
|
+
when GraphQL::ExecutionError
|
110
|
+
raw_value.ast_node = field_ctx.ast_node
|
111
|
+
raw_value.path = field_ctx.path
|
112
|
+
query.context.errors.push(raw_value)
|
113
|
+
when Array
|
114
|
+
list_errors = raw_value.each_with_index.select { |value, _| value.is_a?(GraphQL::ExecutionError) }
|
115
|
+
if list_errors.any?
|
116
|
+
list_errors.each do |error, index|
|
117
|
+
error.ast_node = field_ctx.ast_node
|
118
|
+
error.path = field_ctx.path + [index]
|
119
|
+
query.context.errors.push(error)
|
120
|
+
end
|
171
121
|
end
|
172
|
-
|
173
|
-
when GraphQL::TypeKinds::NON_NULL
|
174
|
-
wrapped_type = field_type.of_type
|
175
|
-
resolve_value(
|
176
|
-
owner,
|
177
|
-
parent_type,
|
178
|
-
field_defn,
|
179
|
-
wrapped_type,
|
180
|
-
value,
|
181
|
-
selection,
|
182
|
-
field_ctx,
|
183
|
-
)
|
184
|
-
when GraphQL::TypeKinds::OBJECT
|
185
|
-
resolve_selection(
|
186
|
-
value,
|
187
|
-
field_type,
|
188
|
-
selection,
|
189
|
-
field_ctx
|
190
|
-
)
|
191
|
-
when GraphQL::TypeKinds::UNION, GraphQL::TypeKinds::INTERFACE
|
192
|
-
query = field_ctx.query
|
193
|
-
resolved_type = query.resolve_type(value)
|
194
|
-
possible_types = query.possible_types(field_type)
|
122
|
+
end
|
195
123
|
|
196
|
-
|
197
|
-
|
124
|
+
resolve_value(
|
125
|
+
owner,
|
126
|
+
parent_type,
|
127
|
+
field,
|
128
|
+
field.type,
|
129
|
+
raw_value,
|
130
|
+
selection,
|
131
|
+
field_ctx,
|
132
|
+
)
|
133
|
+
end
|
134
|
+
|
135
|
+
def resolve_value(owner, parent_type, field_defn, field_type, value, selection, field_ctx)
|
136
|
+
if value.nil?
|
137
|
+
if field_type.kind.non_null?
|
138
|
+
type_error = GraphQL::InvalidNullError.new(parent_type, field_defn, value)
|
198
139
|
field_ctx.schema.type_error(type_error, field_ctx)
|
199
140
|
PROPAGATE_NULL
|
200
141
|
else
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
elsif value.is_a?(GraphQL::ExecutionError)
|
145
|
+
if field_type.kind.non_null?
|
146
|
+
PROPAGATE_NULL
|
147
|
+
else
|
148
|
+
nil
|
149
|
+
end
|
150
|
+
elsif value == SKIP
|
151
|
+
value
|
152
|
+
else
|
153
|
+
case field_type.kind
|
154
|
+
when GraphQL::TypeKinds::SCALAR
|
155
|
+
field_type.coerce_result(value, field_ctx)
|
156
|
+
when GraphQL::TypeKinds::ENUM
|
157
|
+
field_type.coerce_result(value, field_ctx)
|
158
|
+
when GraphQL::TypeKinds::LIST
|
159
|
+
inner_type = field_type.of_type
|
160
|
+
i = 0
|
161
|
+
result = []
|
162
|
+
value.each do |inner_value|
|
163
|
+
inner_ctx = field_ctx.spawn(
|
164
|
+
key: i,
|
165
|
+
selection: selection,
|
166
|
+
parent_type: parent_type,
|
167
|
+
field: field_defn,
|
168
|
+
)
|
169
|
+
|
170
|
+
inner_result = resolve_value(
|
171
|
+
owner,
|
172
|
+
parent_type,
|
173
|
+
field_defn,
|
174
|
+
inner_type,
|
175
|
+
inner_value,
|
176
|
+
selection,
|
177
|
+
inner_ctx,
|
178
|
+
)
|
179
|
+
|
180
|
+
result << GraphQL::Execution::FieldResult.new(type: inner_type, owner: owner, value: inner_result)
|
181
|
+
i += 1
|
182
|
+
end
|
183
|
+
result
|
184
|
+
when GraphQL::TypeKinds::NON_NULL
|
185
|
+
wrapped_type = field_type.of_type
|
201
186
|
resolve_value(
|
202
187
|
owner,
|
203
188
|
parent_type,
|
204
189
|
field_defn,
|
205
|
-
|
190
|
+
wrapped_type,
|
206
191
|
value,
|
207
192
|
selection,
|
208
193
|
field_ctx,
|
209
194
|
)
|
195
|
+
when GraphQL::TypeKinds::OBJECT
|
196
|
+
resolve_selection(
|
197
|
+
value,
|
198
|
+
field_type,
|
199
|
+
selection,
|
200
|
+
field_ctx
|
201
|
+
)
|
202
|
+
when GraphQL::TypeKinds::UNION, GraphQL::TypeKinds::INTERFACE
|
203
|
+
query = field_ctx.query
|
204
|
+
resolved_type = query.resolve_type(value)
|
205
|
+
possible_types = query.possible_types(field_type)
|
206
|
+
|
207
|
+
if !possible_types.include?(resolved_type)
|
208
|
+
type_error = GraphQL::UnresolvedTypeError.new(value, field_defn, parent_type, resolved_type, possible_types)
|
209
|
+
field_ctx.schema.type_error(type_error, field_ctx)
|
210
|
+
PROPAGATE_NULL
|
211
|
+
else
|
212
|
+
resolve_value(
|
213
|
+
owner,
|
214
|
+
parent_type,
|
215
|
+
field_defn,
|
216
|
+
resolved_type,
|
217
|
+
value,
|
218
|
+
selection,
|
219
|
+
field_ctx,
|
220
|
+
)
|
221
|
+
end
|
222
|
+
else
|
223
|
+
raise("Unknown type kind: #{field_type.kind}")
|
210
224
|
end
|
211
|
-
else
|
212
|
-
raise("Unknown type kind: #{field_type.kind}")
|
213
225
|
end
|
214
226
|
end
|
215
227
|
end
|
216
228
|
|
229
|
+
include ExecutionFunctions
|
230
|
+
|
217
231
|
# A `.call`-able suitable to be the last step in a middleware chain
|
218
232
|
module FieldResolveStep
|
219
233
|
# Execute the field's resolve method
|