graphql 2.4.13 → 2.5.11

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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/query_complexity.rb +87 -7
  3. data/lib/graphql/backtrace/table.rb +37 -14
  4. data/lib/graphql/current.rb +1 -1
  5. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  6. data/lib/graphql/dashboard/installable.rb +22 -0
  7. data/lib/graphql/dashboard/limiters.rb +93 -0
  8. data/lib/graphql/dashboard/operation_store.rb +199 -0
  9. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  10. data/lib/graphql/dashboard/statics/dashboard.css +27 -0
  11. data/lib/graphql/dashboard/statics/dashboard.js +74 -9
  12. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  13. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  14. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  15. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  16. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  17. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  18. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  19. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  20. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  21. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  22. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  23. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  24. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  25. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  26. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  27. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +49 -1
  28. data/lib/graphql/dashboard.rb +45 -29
  29. data/lib/graphql/dataloader/active_record_association_source.rb +28 -8
  30. data/lib/graphql/dataloader/active_record_source.rb +26 -5
  31. data/lib/graphql/dataloader/null_dataloader.rb +7 -0
  32. data/lib/graphql/dataloader/source.rb +16 -4
  33. data/lib/graphql/dig.rb +2 -1
  34. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  35. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -1
  36. data/lib/graphql/execution/interpreter/runtime.rb +163 -59
  37. data/lib/graphql/execution/interpreter.rb +5 -13
  38. data/lib/graphql/execution/multiplex.rb +6 -1
  39. data/lib/graphql/invalid_null_error.rb +15 -2
  40. data/lib/graphql/language/lexer.rb +9 -2
  41. data/lib/graphql/language/nodes.rb +5 -1
  42. data/lib/graphql/language/parser.rb +14 -6
  43. data/lib/graphql/query/context.rb +3 -8
  44. data/lib/graphql/query/partial.rb +179 -0
  45. data/lib/graphql/query.rb +59 -55
  46. data/lib/graphql/schema/addition.rb +3 -1
  47. data/lib/graphql/schema/always_visible.rb +1 -0
  48. data/lib/graphql/schema/argument.rb +9 -3
  49. data/lib/graphql/schema/build_from_definition.rb +96 -47
  50. data/lib/graphql/schema/directive/flagged.rb +2 -0
  51. data/lib/graphql/schema/directive.rb +33 -1
  52. data/lib/graphql/schema/field.rb +23 -1
  53. data/lib/graphql/schema/input_object.rb +38 -30
  54. data/lib/graphql/schema/list.rb +1 -1
  55. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  56. data/lib/graphql/schema/member/has_dataloader.rb +4 -2
  57. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  58. data/lib/graphql/schema/member/has_interfaces.rb +2 -2
  59. data/lib/graphql/schema/member/type_system_helpers.rb +16 -2
  60. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  61. data/lib/graphql/schema/resolver.rb +1 -0
  62. data/lib/graphql/schema/scalar.rb +1 -6
  63. data/lib/graphql/schema/timeout.rb +19 -2
  64. data/lib/graphql/schema/validator/required_validator.rb +15 -6
  65. data/lib/graphql/schema/visibility/migration.rb +2 -2
  66. data/lib/graphql/schema/visibility/profile.rb +107 -21
  67. data/lib/graphql/schema/visibility.rb +41 -29
  68. data/lib/graphql/schema/warden.rb +13 -5
  69. data/lib/graphql/schema.rb +228 -32
  70. data/lib/graphql/static_validation/all_rules.rb +2 -2
  71. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  72. data/lib/graphql/static_validation/rules/fields_will_merge.rb +78 -16
  73. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  74. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  75. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  76. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  77. data/lib/graphql/testing/helpers.rb +5 -2
  78. data/lib/graphql/tracing/active_support_notifications_trace.rb +7 -0
  79. data/lib/graphql/tracing/appoptics_tracing.rb +5 -0
  80. data/lib/graphql/tracing/appsignal_trace.rb +26 -61
  81. data/lib/graphql/tracing/data_dog_trace.rb +41 -164
  82. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  83. data/lib/graphql/tracing/new_relic_trace.rb +34 -164
  84. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  85. data/lib/graphql/tracing/null_trace.rb +1 -1
  86. data/lib/graphql/tracing/perfetto_trace.rb +16 -19
  87. data/lib/graphql/tracing/prometheus_trace.rb +47 -74
  88. data/lib/graphql/tracing/scout_trace.rb +25 -59
  89. data/lib/graphql/tracing/sentry_trace.rb +56 -99
  90. data/lib/graphql/tracing/statsd_trace.rb +24 -47
  91. data/lib/graphql/tracing/trace.rb +0 -17
  92. data/lib/graphql/tracing.rb +1 -0
  93. data/lib/graphql/type_kinds.rb +1 -0
  94. data/lib/graphql/version.rb +1 -1
  95. data/lib/graphql.rb +1 -1
  96. metadata +35 -26
  97. data/lib/graphql/dashboard/views/graphql/dashboard/traces/index.html.erb +0 -63
  98. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -12,6 +12,14 @@ module GraphQL
12
12
  @scope = scope
13
13
  end
14
14
 
15
+ def self.batch_key_for(association, scope = nil)
16
+ if scope
17
+ [association, scope.to_sql]
18
+ else
19
+ [association]
20
+ end
21
+ end
22
+
15
23
  def load(record)
16
24
  if (assoc = record.association(@association)).loaded?
17
25
  assoc.target
@@ -23,7 +31,12 @@ module GraphQL
23
31
  def fetch(records)
24
32
  record_classes = Set.new.compare_by_identity
25
33
  associated_classes = Set.new.compare_by_identity
34
+ scoped_fetch = !@scope.nil?
26
35
  records.each do |record|
36
+ if scoped_fetch
37
+ assoc = record.association(@association)
38
+ assoc.reset
39
+ end
27
40
  if record_classes.add?(record.class)
28
41
  reflection = record.class.reflect_on_association(@association)
29
42
  if !reflection.polymorphic? && reflection.klass
@@ -40,18 +53,25 @@ module GraphQL
40
53
 
41
54
  ::ActiveRecord::Associations::Preloader.new(records: records, associations: @association, available_records: available_records, scope: @scope).call
42
55
 
43
- loaded_associated_records = records.map { |r| r.public_send(@association) }
44
- records_by_model = {}
45
- loaded_associated_records.each do |record|
46
- if record
47
- updates = records_by_model[record.class] ||= {}
48
- updates[record.id] = record
56
+ loaded_associated_records = records.map { |r|
57
+ assoc = r.association(@association)
58
+ lar = assoc.target
59
+ if scoped_fetch
60
+ assoc.reset
49
61
  end
50
- end
62
+ lar
63
+ }
51
64
 
52
- if @scope.nil?
65
+ if !scoped_fetch
53
66
  # Don't cache records loaded via scope because they might have reduced `SELECT`s
54
67
  # Could check .select_values here?
68
+ records_by_model = {}
69
+ loaded_associated_records.flatten.each do |record|
70
+ if record
71
+ updates = records_by_model[record.class] ||= {}
72
+ updates[record.id] = record
73
+ end
74
+ end
55
75
  records_by_model.each do |model_class, updates|
56
76
  dataloader.with(RECORD_SOURCE_CLASS, model_class).merge(updates)
57
77
  end
@@ -7,18 +7,39 @@ module GraphQL
7
7
  def initialize(model_class, find_by: model_class.primary_key)
8
8
  @model_class = model_class
9
9
  @find_by = find_by
10
- @type_for_column = @model_class.type_for_attribute(@find_by)
10
+ @find_by_many = find_by.is_a?(Array)
11
+ if @find_by_many
12
+ @type_for_column = @find_by.map { |fb| @model_class.type_for_attribute(fb) }
13
+ else
14
+ @type_for_column = @model_class.type_for_attribute(@find_by)
15
+ end
11
16
  end
12
17
 
13
- def load(requested_key)
14
- casted_key = @type_for_column.cast(requested_key)
15
- super(casted_key)
18
+ def result_key_for(requested_key)
19
+ normalize_fetch_key(requested_key)
20
+ end
21
+
22
+ def normalize_fetch_key(requested_key)
23
+ if @find_by_many
24
+ requested_key.each_with_index.map do |k, idx|
25
+ @type_for_column[idx].cast(k)
26
+ end
27
+ else
28
+ @type_for_column.cast(requested_key)
29
+ end
16
30
  end
17
31
 
18
32
  def fetch(record_ids)
19
33
  records = @model_class.where(@find_by => record_ids)
20
34
  record_lookup = {}
21
- records.each { |r| record_lookup[r.public_send(@find_by)] = r }
35
+ if @find_by_many
36
+ records.each do |r|
37
+ key = @find_by.map { |fb| r.public_send(fb) }
38
+ record_lookup[key] = r
39
+ end
40
+ else
41
+ records.each { |r| record_lookup[r.public_send(@find_by)] = r }
42
+ end
22
43
  record_ids.map { |id| record_lookup[id] }
23
44
  end
24
45
  end
@@ -9,8 +9,11 @@ module GraphQL
9
9
  class NullDataloader < Dataloader
10
10
  # These are all no-ops because code was
11
11
  # executed synchronously.
12
+
13
+ def initialize(*); end
12
14
  def run; end
13
15
  def run_isolated; yield; end
16
+ def clear_cache; end
14
17
  def yield(_source)
15
18
  raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
16
19
  end
@@ -19,6 +22,10 @@ module GraphQL
19
22
  yield
20
23
  nil
21
24
  end
25
+
26
+ def with(*)
27
+ raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
28
+ end
22
29
  end
23
30
  end
24
31
  end
@@ -21,7 +21,7 @@ module GraphQL
21
21
  def request(value)
22
22
  res_key = result_key_for(value)
23
23
  if !@results.key?(res_key)
24
- @pending[res_key] ||= value
24
+ @pending[res_key] ||= normalize_fetch_key(value)
25
25
  end
26
26
  Dataloader::Request.new(self, value)
27
27
  end
@@ -35,12 +35,24 @@ module GraphQL
35
35
  value
36
36
  end
37
37
 
38
+ # Implement this method if varying values given to {load} (etc) should be consolidated
39
+ # or normalized before being handed off to your {fetch} implementation.
40
+ #
41
+ # This is different than {result_key_for} because _that_ method handles unification inside Dataloader's cache,
42
+ # but this method changes the value passed into {fetch}.
43
+ #
44
+ # @param value [Object] The value passed to {load}, {load_all}, {request}, or {request_all}
45
+ # @return [Object] The value given to {fetch}
46
+ def normalize_fetch_key(value)
47
+ value
48
+ end
49
+
38
50
  # @return [Dataloader::Request] a pending request for a values from `keys`. Call `.load` on that object to wait for the results.
39
51
  def request_all(values)
40
52
  values.each do |v|
41
53
  res_key = result_key_for(v)
42
54
  if !@results.key?(res_key)
43
- @pending[res_key] ||= v
55
+ @pending[res_key] ||= normalize_fetch_key(v)
44
56
  end
45
57
  end
46
58
  Dataloader::RequestAll.new(self, values)
@@ -53,7 +65,7 @@ module GraphQL
53
65
  if @results.key?(result_key)
54
66
  result_for(result_key)
55
67
  else
56
- @pending[result_key] ||= value
68
+ @pending[result_key] ||= normalize_fetch_key(value)
57
69
  sync([result_key])
58
70
  result_for(result_key)
59
71
  end
@@ -68,7 +80,7 @@ module GraphQL
68
80
  k = result_key_for(v)
69
81
  result_keys << k
70
82
  if !@results.key?(k)
71
- @pending[k] ||= v
83
+ @pending[k] ||= normalize_fetch_key(v)
72
84
  pending_keys << k
73
85
  end
74
86
  }
data/lib/graphql/dig.rb CHANGED
@@ -5,7 +5,8 @@ module GraphQL
5
5
  # so we can use some of the magic in Schema::InputObject and Interpreter::Arguments
6
6
  # to handle stringified/symbolized keys.
7
7
  #
8
- # @param args [Array<[String, Symbol>] Retrieves the value object corresponding to the each key objects repeatedly
8
+ # @param own_key [String, Symbol] A key to retrieve
9
+ # @param rest_keys [Array<[String, Symbol>] Retrieves the value object corresponding to the each key objects repeatedly
9
10
  # @return [Object]
10
11
  def dig(own_key, *rest_keys)
11
12
  val = self[own_key]
@@ -23,9 +23,9 @@ module GraphQL
23
23
  if smallest_depth
24
24
  lazies = lazies_at_depth.delete(smallest_depth)
25
25
  if !lazies.empty?
26
- dataloader.append_job {
27
- lazies.each(&:value) # resolve these Lazy instances
28
- }
26
+ lazies.each do |l|
27
+ dataloader.append_job { l.value }
28
+ end
29
29
  # Run lazies _and_ dataloader, see if more are enqueued
30
30
  dataloader.run
31
31
  resolve_each_depth(lazies_at_depth, dataloader)
@@ -21,15 +21,25 @@ module GraphQL
21
21
  @graphql_metadata = nil
22
22
  @graphql_selections = selections
23
23
  @graphql_is_eager = is_eager
24
+ @base_path = nil
24
25
  end
25
26
 
27
+ # TODO test full path in Partial
28
+ attr_writer :base_path
29
+
26
30
  def path
27
31
  @path ||= build_path([])
28
32
  end
29
33
 
30
34
  def build_path(path_array)
31
35
  graphql_result_name && path_array.unshift(graphql_result_name)
32
- @graphql_parent ? @graphql_parent.build_path(path_array) : path_array
36
+ if @graphql_parent
37
+ @graphql_parent.build_path(path_array)
38
+ elsif @base_path
39
+ @base_path + path_array
40
+ else
41
+ path_array
42
+ end
33
43
  end
34
44
 
35
45
  attr_accessor :graphql_dead
@@ -44,8 +54,11 @@ module GraphQL
44
54
  def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager, _ast_node, _graphql_arguments, graphql_field) # rubocop:disable Metrics/ParameterLists
45
55
  super
46
56
  @graphql_result_data = {}
57
+ @ordered_result_keys = nil
47
58
  end
48
59
 
60
+ attr_accessor :ordered_result_keys
61
+
49
62
  include GraphQLResult
50
63
 
51
64
  attr_accessor :graphql_merged_into
@@ -63,7 +76,13 @@ module GraphQL
63
76
  t.set_leaf(key, value)
64
77
  end
65
78
 
79
+ before_size = @graphql_result_data.size
66
80
  @graphql_result_data[key] = value
81
+ after_size = @graphql_result_data.size
82
+ if after_size > before_size && @ordered_result_keys[before_size] != key
83
+ fix_result_order
84
+ end
85
+
67
86
  # keep this up-to-date if it's been initialized
68
87
  @graphql_metadata && @graphql_metadata[key] = value
69
88
 
@@ -74,7 +93,13 @@ module GraphQL
74
93
  if (t = @graphql_merged_into)
75
94
  t.set_child_result(key, value)
76
95
  end
96
+ before_size = @graphql_result_data.size
77
97
  @graphql_result_data[key] = value.graphql_result_data
98
+ after_size = @graphql_result_data.size
99
+ if after_size > before_size && @ordered_result_keys[before_size] != key
100
+ fix_result_order
101
+ end
102
+
78
103
  # If we encounter some part of this response that requires metadata tracking,
79
104
  # then create the metadata hash if necessary. It will be kept up-to-date after this.
80
105
  (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
@@ -124,6 +149,14 @@ module GraphQL
124
149
  end
125
150
  @graphql_merged_into = into_result
126
151
  end
152
+
153
+ def fix_result_order
154
+ @ordered_result_keys.each do |k|
155
+ if @graphql_result_data.key?(k)
156
+ @graphql_result_data[k] = @graphql_result_data.delete(k)
157
+ end
158
+ end
159
+ end
127
160
  end
128
161
 
129
162
  class GraphQLResultArray
@@ -57,67 +57,160 @@ module GraphQL
57
57
  end
58
58
 
59
59
  def final_result
60
- @response && @response.graphql_result_data
60
+ @response.respond_to?(:graphql_result_data) ? @response.graphql_result_data : @response
61
61
  end
62
62
 
63
63
  def inspect
64
64
  "#<#{self.class.name} response=#{@response.inspect}>"
65
65
  end
66
66
 
67
- # This _begins_ the execution. Some deferred work
68
- # might be stored up in lazies.
69
67
  # @return [void]
70
68
  def run_eager
71
- root_operation = query.selected_operation
72
- root_op_type = root_operation.operation_type || "query"
73
- root_type = schema.root_type_for_operation(root_op_type)
74
- runtime_object = root_type.wrap(query.root_value, context)
75
- runtime_object = schema.sync_lazy(runtime_object)
76
- is_eager = root_op_type == "mutation"
77
- @response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, root_operation.selections, is_eager, root_operation, nil, nil)
78
- st = get_current_runtime_state
79
- st.current_result = @response
80
-
81
- if runtime_object.nil?
82
- # Root .authorized? returned false.
83
- @response = nil
69
+ root_type = query.root_type
70
+ case query
71
+ when GraphQL::Query
72
+ ast_node = query.selected_operation
73
+ selections = ast_node.selections
74
+ object = query.root_value
75
+ is_eager = ast_node.operation_type == "mutation"
76
+ base_path = nil
77
+ when GraphQL::Query::Partial
78
+ ast_node = query.ast_nodes.first
79
+ selections = query.ast_nodes.map(&:selections).inject(&:+)
80
+ object = query.object
81
+ is_eager = false
82
+ base_path = query.path
84
83
  else
85
- call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
86
- each_gathered_selections(@response) do |selections, is_selection_array|
87
- if is_selection_array
88
- selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, selections, is_eager, root_operation, nil, nil)
89
- final_response = @response
90
- else
91
- selection_response = @response
92
- final_response = nil
93
- end
84
+ raise ArgumentError, "Unexpected Runnable, can't execute: #{query.class} (#{query.inspect})"
85
+ end
86
+ object = schema.sync_lazy(object) # TODO test query partial with lazy root object
87
+ runtime_state = get_current_runtime_state
88
+ case root_type.kind.name
89
+ when "OBJECT"
90
+ object_proxy = root_type.wrap(object, context)
91
+ object_proxy = schema.sync_lazy(object_proxy)
92
+ if object_proxy.nil?
93
+ @response = nil
94
+ else
95
+ @response = GraphQLResultHash.new(nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil)
96
+ @response.base_path = base_path
97
+ runtime_state.current_result = @response
98
+ call_method_on_directives(:resolve, object, ast_node.directives) do
99
+ each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
100
+ @response.ordered_result_keys ||= ordered_result_keys
101
+ if is_selection_array
102
+ selection_response = GraphQLResultHash.new(nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil)
103
+ selection_response.ordered_result_keys = ordered_result_keys
104
+ final_response = @response
105
+ else
106
+ selection_response = @response
107
+ final_response = nil
108
+ end
94
109
 
95
- @dataloader.append_job {
96
- evaluate_selections(
97
- selections,
98
- selection_response,
99
- final_response,
100
- nil,
110
+ @dataloader.append_job {
111
+ evaluate_selections(
112
+ selections,
113
+ selection_response,
114
+ final_response,
115
+ nil,
116
+ )
117
+ }
118
+ end
119
+ end
120
+ end
121
+ when "LIST"
122
+ inner_type = root_type.unwrap
123
+ case inner_type.kind.name
124
+ when "SCALAR", "ENUM"
125
+ result_name = ast_node.alias || ast_node.name
126
+ field_defn = query.field_definition
127
+ owner_type = field_defn.owner
128
+ selection_result = GraphQLResultHash.new(nil, owner_type, nil, nil, false, EmptyObjects::EMPTY_ARRAY, false, ast_node, nil, nil)
129
+ selection_result.base_path = base_path
130
+ selection_result.ordered_result_keys = [result_name]
131
+ runtime_state = get_current_runtime_state
132
+ runtime_state.current_result = selection_result
133
+ runtime_state.current_result_name = result_name
134
+ continue_value = continue_value(object, field_defn, false, ast_node, result_name, selection_result)
135
+ if HALT != continue_value
136
+ continue_field(continue_value, owner_type, field_defn, root_type, ast_node, nil, false, nil, nil, result_name, selection_result, false, runtime_state) # rubocop:disable Metrics/ParameterLists
137
+ end
138
+ @response = selection_result[result_name]
139
+ else
140
+ @response = GraphQLResultArray.new(nil, root_type, nil, nil, false, selections, false, ast_node, nil, nil)
141
+ @response.base_path = base_path
142
+ idx = nil
143
+ object.each do |inner_value|
144
+ idx ||= 0
145
+ this_idx = idx
146
+ idx += 1
147
+ @dataloader.append_job do
148
+ runtime_state.current_result_name = this_idx
149
+ runtime_state.current_result = @response
150
+ continue_field(
151
+ inner_value, root_type, nil, inner_type, nil, @response.graphql_selections, false, object_proxy,
152
+ nil, this_idx, @response, false, runtime_state
101
153
  )
102
- }
154
+ end
103
155
  end
104
156
  end
157
+ when "SCALAR", "ENUM"
158
+ result_name = ast_node.alias || ast_node.name
159
+ field_defn = query.field_definition
160
+ owner_type = field_defn.owner
161
+ selection_result = GraphQLResultHash.new(nil, owner_type, nil, nil, false, EmptyObjects::EMPTY_ARRAY, false, ast_node, nil, nil)
162
+ selection_result.ordered_result_keys = [result_name]
163
+ selection_result.base_path = base_path
164
+ runtime_state = get_current_runtime_state
165
+ runtime_state.current_result = selection_result
166
+ runtime_state.current_result_name = result_name
167
+ continue_value = continue_value(object, field_defn, false, ast_node, result_name, selection_result)
168
+ if HALT != continue_value
169
+ continue_field(continue_value, owner_type, field_defn, query.root_type, ast_node, nil, false, nil, nil, result_name, selection_result, false, runtime_state) # rubocop:disable Metrics/ParameterLists
170
+ end
171
+ @response = selection_result[result_name]
172
+ when "UNION", "INTERFACE"
173
+ resolved_type, _resolved_obj = resolve_type(root_type, object)
174
+ resolved_type = schema.sync_lazy(resolved_type)
175
+ object_proxy = resolved_type.wrap(object, context)
176
+ object_proxy = schema.sync_lazy(object_proxy)
177
+ @response = GraphQLResultHash.new(nil, resolved_type, object_proxy, nil, false, selections, false, query.ast_nodes.first, nil, nil)
178
+ @response.base_path = base_path
179
+ each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
180
+ @response.ordered_result_keys ||= ordered_result_keys
181
+ if is_selection_array == true
182
+ raise "This isn't supported yet"
183
+ end
184
+
185
+ @dataloader.append_job {
186
+ evaluate_selections(
187
+ selections,
188
+ @response,
189
+ nil,
190
+ runtime_state,
191
+ )
192
+ }
193
+ end
194
+ else
195
+ raise "Invariant: unsupported type kind for partial execution: #{root_type.kind.inspect} (#{root_type})"
105
196
  end
106
197
  nil
107
198
  end
108
199
 
109
200
  def each_gathered_selections(response_hash)
110
- gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections)
201
+ ordered_result_keys = []
202
+ gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections, nil, {}, ordered_result_keys)
203
+ ordered_result_keys.uniq!
111
204
  if gathered_selections.is_a?(Array)
112
205
  gathered_selections.each do |item|
113
- yield(item, true)
206
+ yield(item, true, ordered_result_keys)
114
207
  end
115
208
  else
116
- yield(gathered_selections, false)
209
+ yield(gathered_selections, false, ordered_result_keys)
117
210
  end
118
211
  end
119
212
 
120
- def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
213
+ def gather_selections(owner_object, owner_type, selections, selections_to_run, selections_by_name, ordered_result_keys)
121
214
  selections.each do |node|
122
215
  # Skip gathering this if the directive says so
123
216
  if !directives_include?(node, owner_object, owner_type)
@@ -126,6 +219,7 @@ module GraphQL
126
219
 
127
220
  if node.is_a?(GraphQL::Language::Nodes::Field)
128
221
  response_key = node.alias || node.name
222
+ ordered_result_keys << response_key
129
223
  selections = selections_by_name[response_key]
130
224
  # if there was already a selection of this field,
131
225
  # use an array to hold all selections,
@@ -162,14 +256,14 @@ module GraphQL
162
256
  type_defn = query.types.type(node.type.name)
163
257
 
164
258
  if query.types.possible_types(type_defn).include?(owner_type)
165
- result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
259
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
166
260
  if !result.equal?(next_selections)
167
261
  selections_to_run = result
168
262
  end
169
263
  end
170
264
  else
171
265
  # it's an untyped fragment, definitely continue
172
- result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
266
+ result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
173
267
  if !result.equal?(next_selections)
174
268
  selections_to_run = result
175
269
  end
@@ -178,7 +272,7 @@ module GraphQL
178
272
  fragment_def = query.fragments[node.name]
179
273
  type_defn = query.types.type(fragment_def.type.name)
180
274
  if query.types.possible_types(type_defn).include?(owner_type)
181
- result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
275
+ result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections, ordered_result_keys)
182
276
  if !result.equal?(next_selections)
183
277
  selections_to_run = result
184
278
  end
@@ -204,10 +298,7 @@ module GraphQL
204
298
  end
205
299
 
206
300
  call_method_on_directives(:resolve, selections_result.graphql_application_value, directives) do
207
- finished_jobs = 0
208
- enqueued_jobs = gathered_selections.size
209
301
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
210
-
211
302
  # Field resolution may pause the fiber,
212
303
  # so it wouldn't get to the `Resolve` call that happens below.
213
304
  # So instead trigger a run from this outer context.
@@ -217,12 +308,6 @@ module GraphQL
217
308
  evaluate_selection(
218
309
  result_name, field_ast_nodes_or_ast_node, selections_result
219
310
  )
220
- finished_jobs += 1
221
- if finished_jobs == enqueued_jobs
222
- if target_result
223
- selections_result.merge_into(target_result)
224
- end
225
- end
226
311
  @dataloader.clear_cache
227
312
  }
228
313
  else
@@ -230,15 +315,12 @@ module GraphQL
230
315
  evaluate_selection(
231
316
  result_name, field_ast_nodes_or_ast_node, selections_result
232
317
  )
233
- finished_jobs += 1
234
- if finished_jobs == enqueued_jobs
235
- if target_result
236
- selections_result.merge_into(target_result)
237
- end
238
- end
239
318
  }
240
319
  end
241
320
  end
321
+ if target_result
322
+ selections_result.merge_into(target_result)
323
+ end
242
324
  selections_result
243
325
  end
244
326
  end
@@ -464,16 +546,17 @@ module GraphQL
464
546
  path
465
547
  end
466
548
 
467
- HALT = Object.new
549
+ HALT = Object.new.freeze
468
550
  def continue_value(value, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
469
551
  case value
470
552
  when nil
471
553
  if is_non_null
472
554
  set_result(selection_result, result_name, nil, false, is_non_null) do
473
555
  # When this comes from a list item, use the parent object:
474
- parent_type = selection_result.is_a?(GraphQLResultArray) ? selection_result.graphql_parent.graphql_result_type : selection_result.graphql_result_type
556
+ is_from_array = selection_result.is_a?(GraphQLResultArray)
557
+ parent_type = is_from_array ? selection_result.graphql_parent.graphql_result_type : selection_result.graphql_result_type
475
558
  # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
476
- err = parent_type::InvalidNullError.new(parent_type, field, ast_node)
559
+ err = parent_type::InvalidNullError.new(parent_type, field, ast_node, is_from_array: is_from_array)
477
560
  schema.type_error(err, context)
478
561
  end
479
562
  else
@@ -581,13 +664,25 @@ module GraphQL
581
664
  when "SCALAR", "ENUM"
582
665
  r = begin
583
666
  current_type.coerce_result(value, context)
667
+ rescue GraphQL::ExecutionError => ex_err
668
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
584
669
  rescue StandardError => err
585
670
  query.handle_or_reraise(err)
586
671
  end
587
672
  set_result(selection_result, result_name, r, false, is_non_null)
588
673
  r
589
674
  when "UNION", "INTERFACE"
590
- resolved_type_or_lazy = resolve_type(current_type, value)
675
+ resolved_type_or_lazy = begin
676
+ resolve_type(current_type, value)
677
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
678
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
679
+ rescue StandardError => err
680
+ begin
681
+ query.handle_or_reraise(err)
682
+ rescue GraphQL::ExecutionError => ex_err
683
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
684
+ end
685
+ end
591
686
  after_lazy(resolved_type_or_lazy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_type_result, runtime_state|
592
687
  if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
593
688
  resolved_type, resolved_value = resolved_type_result
@@ -619,9 +714,11 @@ module GraphQL
619
714
  if HALT != continue_value
620
715
  response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field)
621
716
  set_result(selection_result, result_name, response_hash, true, is_non_null)
622
- each_gathered_selections(response_hash) do |selections, is_selection_array|
717
+ each_gathered_selections(response_hash) do |selections, is_selection_array, ordered_result_keys|
718
+ response_hash.ordered_result_keys ||= ordered_result_keys
623
719
  if is_selection_array
624
720
  this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false, ast_node, arguments, field)
721
+ this_result.ordered_result_keys = ordered_result_keys
625
722
  final_result = response_hash
626
723
  else
627
724
  this_result = response_hash
@@ -720,6 +817,13 @@ module GraphQL
720
817
  else
721
818
  dir_defn = @schema_directives.fetch(dir_node.name)
722
819
  raw_dir_args = arguments(nil, dir_defn, dir_node)
820
+ if !raw_dir_args.is_a?(GraphQL::ExecutionError)
821
+ begin
822
+ dir_defn.validate!(raw_dir_args, context)
823
+ rescue GraphQL::ExecutionError => err
824
+ raw_dir_args = err
825
+ end
826
+ end
723
827
  dir_args = continue_value(
724
828
  raw_dir_args, # value
725
829
  nil, # field