graphql 1.5.15 → 1.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.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
|