graphql 1.5.15 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +4 -19
  3. data/lib/graphql/analysis/analyze_query.rb +27 -2
  4. data/lib/graphql/analysis/query_complexity.rb +10 -11
  5. data/lib/graphql/argument.rb +7 -6
  6. data/lib/graphql/backwards_compatibility.rb +47 -0
  7. data/lib/graphql/compatibility/execution_specification.rb +14 -0
  8. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +6 -1
  9. data/lib/graphql/compatibility/lazy_execution_specification.rb +19 -0
  10. data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +15 -6
  11. data/lib/graphql/directive.rb +1 -6
  12. data/lib/graphql/execution.rb +1 -0
  13. data/lib/graphql/execution/execute.rb +174 -160
  14. data/lib/graphql/execution/field_result.rb +5 -1
  15. data/lib/graphql/execution/lazy.rb +2 -2
  16. data/lib/graphql/execution/lazy/resolve.rb +8 -11
  17. data/lib/graphql/execution/multiplex.rb +134 -0
  18. data/lib/graphql/execution/selection_result.rb +5 -0
  19. data/lib/graphql/field.rb +1 -8
  20. data/lib/graphql/filter.rb +53 -0
  21. data/lib/graphql/internal_representation/node.rb +11 -6
  22. data/lib/graphql/internal_representation/rewrite.rb +3 -3
  23. data/lib/graphql/query.rb +160 -78
  24. data/lib/graphql/query/arguments.rb +14 -25
  25. data/lib/graphql/query/arguments_cache.rb +6 -13
  26. data/lib/graphql/query/context.rb +28 -10
  27. data/lib/graphql/query/executor.rb +1 -0
  28. data/lib/graphql/query/literal_input.rb +10 -4
  29. data/lib/graphql/query/null_context.rb +1 -1
  30. data/lib/graphql/query/serial_execution/field_resolution.rb +5 -1
  31. data/lib/graphql/query/validation_pipeline.rb +12 -7
  32. data/lib/graphql/query/variables.rb +1 -1
  33. data/lib/graphql/rake_task.rb +140 -0
  34. data/lib/graphql/relay/array_connection.rb +29 -48
  35. data/lib/graphql/relay/base_connection.rb +9 -7
  36. data/lib/graphql/relay/mutation.rb +0 -11
  37. data/lib/graphql/relay/mutation/instrumentation.rb +2 -2
  38. data/lib/graphql/relay/mutation/resolve.rb +7 -10
  39. data/lib/graphql/relay/relation_connection.rb +98 -61
  40. data/lib/graphql/scalar_type.rb +1 -15
  41. data/lib/graphql/schema.rb +90 -25
  42. data/lib/graphql/schema/build_from_definition.rb +22 -23
  43. data/lib/graphql/schema/build_from_definition/resolve_map.rb +70 -0
  44. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +47 -0
  45. data/lib/graphql/schema/middleware_chain.rb +1 -1
  46. data/lib/graphql/schema/printer.rb +2 -1
  47. data/lib/graphql/schema/timeout_middleware.rb +6 -6
  48. data/lib/graphql/schema/type_map.rb +1 -1
  49. data/lib/graphql/schema/warden.rb +5 -9
  50. data/lib/graphql/static_validation/definition_dependencies.rb +1 -1
  51. data/lib/graphql/version.rb +1 -1
  52. data/spec/graphql/analysis/analyze_query_spec.rb +2 -2
  53. data/spec/graphql/analysis/max_query_complexity_spec.rb +28 -0
  54. data/spec/graphql/argument_spec.rb +3 -3
  55. data/spec/graphql/execution/lazy_spec.rb +8 -114
  56. data/spec/graphql/execution/multiplex_spec.rb +131 -0
  57. data/spec/graphql/internal_representation/rewrite_spec.rb +10 -0
  58. data/spec/graphql/query/arguments_spec.rb +14 -16
  59. data/spec/graphql/query/context_spec.rb +14 -1
  60. data/spec/graphql/query/literal_input_spec.rb +19 -13
  61. data/spec/graphql/query/variables_spec.rb +1 -1
  62. data/spec/graphql/query_spec.rb +12 -1
  63. data/spec/graphql/rake_task_spec.rb +57 -0
  64. data/spec/graphql/relay/array_connection_spec.rb +24 -3
  65. data/spec/graphql/relay/connection_instrumentation_spec.rb +23 -0
  66. data/spec/graphql/relay/mutation_spec.rb +2 -10
  67. data/spec/graphql/relay/page_info_spec.rb +2 -2
  68. data/spec/graphql/relay/relation_connection_spec.rb +167 -3
  69. data/spec/graphql/schema/build_from_definition_spec.rb +93 -19
  70. data/spec/graphql/schema/warden_spec.rb +80 -0
  71. data/spec/graphql/schema_spec.rb +26 -2
  72. data/spec/spec_helper.rb +4 -2
  73. data/spec/support/lazy_helpers.rb +152 -0
  74. data/spec/support/star_wars/schema.rb +23 -0
  75. metadata +28 -3
  76. data/lib/graphql/schema/mask.rb +0 -55
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 75e66219aaa22753f69fb7bef629535bd80e987e
4
- data.tar.gz: dac5274307d3d472f739daf07c9daca74f010af4
3
+ metadata.gz: 21a090385055caa75ff227d787c3a794c5134c11
4
+ data.tar.gz: f6e6e5b3eb47827061ecc6327d2c431d57558be0
5
5
  SHA512:
6
- metadata.gz: 5161c7c97fc9ffe14db133ad61099addfea4ccf94eba8bc0f2ff56e1e6cb541740018fed81644292915a76ccebd9640f2b22d7a81fb3a41d60c6a7a377b45a9a
7
- data.tar.gz: 4ec7454191f5c7bf67aef5459bcfaeae1184c5968265a5f15b3e453827586b9a61d9783059aefcfc3df6285dec3ca7eb2445399123f9c9fee26012fb5a9621f2
6
+ metadata.gz: 9049055982560c7eebe4b37843ce665d885c32ed61f6b97dcb2cbfdefcade29cb62f98914f9824d4e772d410494c75abf732583c7ed2c6ebd87875a26897f7d9
7
+ data.tar.gz: ab419676204940e098b0bd2e612ce8cf6aa9b1456b03d599481be19a4a8e9efa4fed16238702e6d8a5a5df2dd4a6af472975400280729074f34c9e202612f0ef
@@ -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::AnalysisQueryComplexity.new do |query, complexity|
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
- # - `query` is needed for variables, then passed to handler
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(query)
22
+ def initial_value(target)
23
23
  {
24
- query: query,
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, memo[:query], child_complexity)
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].pop.max_possible_complexity
48
- @complexity_handler.call(reduced_value[:query], total_complexity)
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, query, child_complexity)
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
- args = query.arguments_for(irep_node, field_defn)
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(0)
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
@@ -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 [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") && obj[args[:id]]
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
- pushes << @value
21
+ if !@skipped
22
+ pushes << @value
23
+ end
19
24
  end
20
25
 
21
26
  def push
22
- if @context[:lazy_pushes].include?(@value)
23
- @context[:lazy_instrumentation] && @context[:lazy_instrumentation] << "PUSH"
24
- @context[:pushes] << @context[:lazy_pushes]
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
 
@@ -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?, :default_arguments)
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
 
@@ -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
- PROPAGATE_NULL = :__graphql_propagate_null__
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
- def resolve_selection(object, current_type, selection, query_ctx, mutation: false )
26
- selection_result = SelectionResult.new
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
- selection.typed_children[current_type].each do |name, subselection|
29
- field_result = resolve_field(
30
- selection_result,
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
- if mutation
39
- GraphQL::Execution::Lazy.resolve(field_result)
40
- end
47
+ if mutation
48
+ GraphQL::Execution::Lazy.resolve(field_result)
49
+ end
41
50
 
42
- selection_result.set(name, field_result)
51
+ selection_result.set(name, field_result)
43
52
 
44
- # If the last subselection caused a null to propagate to _this_ selection,
45
- # then we may as well quit executing fields because they
46
- # won't be in the response
47
- if selection_result.invalid_null?
48
- break
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
- def resolve_field(owner, selection, parent_type, field, object, query_ctx)
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
- result = if query.schema.lazy?(raw_value)
72
- field.prepare_lazy(raw_value, arguments, field_ctx).then { |inner_value|
73
- continue_resolve_field(owner, selection, parent_type, field, inner_value, field_ctx)
74
- }
75
- elsif raw_value.is_a?(GraphQL::Execution::Lazy)
76
- # It came from a connection resolve, assume it was already instrumented
77
- raw_value.then { |inner_value|
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
- def continue_resolve_field(owner, selection, parent_type, field, raw_value, field_ctx)
97
- query = field_ctx.query
98
-
99
- case raw_value
100
- when GraphQL::ExecutionError
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
- def resolve_value(owner, parent_type, field_defn, field_type, value, selection, field_ctx)
127
- if value.nil?
128
- if field_type.kind.non_null?
129
- type_error = GraphQL::InvalidNullError.new(parent_type, field_defn, value)
130
- field_ctx.schema.type_error(type_error, field_ctx)
131
- PROPAGATE_NULL
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
- nil
90
+ continue_resolve_field(owner, selection, parent_type, field, raw_value, field_ctx)
134
91
  end
135
- elsif value.is_a?(GraphQL::ExecutionError)
136
- if field_type.kind.non_null?
137
- PROPAGATE_NULL
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
- nil
101
+ result
140
102
  end
141
- else
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
- result << GraphQL::Execution::FieldResult.new(type: inner_type, owner: owner, value: inner_result)
170
- i += 1
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
- result
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
- if !possible_types.include?(resolved_type)
197
- type_error = GraphQL::UnresolvedTypeError.new(value, field_defn, parent_type, resolved_type, possible_types)
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
- resolved_type,
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