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.
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