graphql 2.5.11 → 2.5.23

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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  4. data/lib/graphql/dashboard/application_controller.rb +41 -0
  5. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  6. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  7. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  8. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  9. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  10. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  11. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  12. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  13. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  15. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  16. data/lib/graphql/dashboard.rb +11 -73
  17. data/lib/graphql/dataloader/async_dataloader.rb +22 -11
  18. data/lib/graphql/dataloader/null_dataloader.rb +48 -10
  19. data/lib/graphql/dataloader.rb +75 -23
  20. data/lib/graphql/date_encoding_error.rb +1 -1
  21. data/lib/graphql/execution/interpreter/resolve.rb +7 -13
  22. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  23. data/lib/graphql/execution/interpreter/runtime.rb +24 -18
  24. data/lib/graphql/execution/interpreter.rb +8 -22
  25. data/lib/graphql/execution/lazy.rb +1 -1
  26. data/lib/graphql/execution/multiplex.rb +1 -1
  27. data/lib/graphql/execution/next/field_resolve_step.rb +743 -0
  28. data/lib/graphql/execution/next/load_argument_step.rb +64 -0
  29. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  30. data/lib/graphql/execution/next/runner.rb +411 -0
  31. data/lib/graphql/execution/next/selections_step.rb +37 -0
  32. data/lib/graphql/execution/next.rb +72 -0
  33. data/lib/graphql/execution.rb +8 -4
  34. data/lib/graphql/execution_error.rb +17 -10
  35. data/lib/graphql/introspection/directive_type.rb +7 -3
  36. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  37. data/lib/graphql/introspection/entry_points.rb +11 -3
  38. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  39. data/lib/graphql/introspection/field_type.rb +13 -5
  40. data/lib/graphql/introspection/input_value_type.rb +21 -13
  41. data/lib/graphql/introspection/type_type.rb +64 -28
  42. data/lib/graphql/invalid_null_error.rb +11 -5
  43. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  44. data/lib/graphql/language.rb +21 -12
  45. data/lib/graphql/pagination/connection.rb +2 -0
  46. data/lib/graphql/pagination/connections.rb +32 -0
  47. data/lib/graphql/query/context.rb +4 -3
  48. data/lib/graphql/query/null_context.rb +9 -3
  49. data/lib/graphql/schema/argument.rb +12 -0
  50. data/lib/graphql/schema/build_from_definition.rb +10 -1
  51. data/lib/graphql/schema/directive.rb +22 -4
  52. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  53. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  54. data/lib/graphql/schema/field.rb +79 -48
  55. data/lib/graphql/schema/field_extension.rb +33 -0
  56. data/lib/graphql/schema/list.rb +1 -1
  57. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  58. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  59. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  60. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  61. data/lib/graphql/schema/member/has_fields.rb +86 -5
  62. data/lib/graphql/schema/member.rb +5 -0
  63. data/lib/graphql/schema/non_null.rb +1 -1
  64. data/lib/graphql/schema/object.rb +1 -0
  65. data/lib/graphql/schema/resolver.rb +60 -1
  66. data/lib/graphql/schema/subscription.rb +0 -2
  67. data/lib/graphql/schema/validator/required_validator.rb +33 -2
  68. data/lib/graphql/schema/visibility/profile.rb +68 -49
  69. data/lib/graphql/schema/visibility.rb +3 -3
  70. data/lib/graphql/schema/wrapper.rb +7 -1
  71. data/lib/graphql/schema.rb +53 -10
  72. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  73. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  74. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  75. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  76. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  77. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  78. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
  79. data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
  80. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  81. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  82. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  83. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  84. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  85. data/lib/graphql/static_validation/validation_context.rb +1 -1
  86. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  87. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +25 -1
  88. data/lib/graphql/subscriptions/event.rb +1 -0
  89. data/lib/graphql/subscriptions.rb +21 -1
  90. data/lib/graphql/testing/helpers.rb +12 -9
  91. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  92. data/lib/graphql/testing.rb +1 -0
  93. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  94. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  95. data/lib/graphql/tracing/perfetto_trace.rb +209 -79
  96. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  97. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  98. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  99. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  100. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  101. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  102. data/lib/graphql/unauthorized_error.rb +9 -1
  103. data/lib/graphql/version.rb +1 -1
  104. data/lib/graphql.rb +8 -2
  105. metadata +17 -3
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ module Next
5
+ class LoadArgumentStep
6
+ def initialize(field_resolve_step:, arguments:, load_receiver:, argument_value:, argument_definition:, argument_key:)
7
+ @field_resolve_step = field_resolve_step
8
+ @load_receiver = load_receiver
9
+ @arguments = arguments
10
+ @argument_value = argument_value
11
+ @argument_definition = argument_definition
12
+ @argument_key = argument_key
13
+ @loaded_value = nil
14
+ end
15
+
16
+ def value
17
+ @loaded_value = @field_resolve_step.sync(@loaded_value)
18
+ assign_value
19
+ end
20
+
21
+ def call
22
+ context = @field_resolve_step.selections_step.query.context
23
+ @loaded_value = begin
24
+ @load_receiver.load_and_authorize_application_object(@argument_definition, @argument_value, context)
25
+ rescue GraphQL::UnauthorizedError => auth_err
26
+ context.schema.unauthorized_object(auth_err)
27
+ end
28
+ if (runner = @field_resolve_step.runner).resolves_lazies && runner.lazy?(@loaded_value)
29
+ runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self)
30
+ else
31
+ assign_value
32
+ end
33
+ rescue GraphQL::RuntimeError => err
34
+ @loaded_value = err
35
+ assign_value
36
+ rescue StandardError => stderr
37
+ @loaded_value = begin
38
+ context.query.handle_or_reraise(stderr)
39
+ rescue GraphQL::ExecutionError => ex_err
40
+ ex_err
41
+ end
42
+ assign_value
43
+ end
44
+
45
+ private
46
+
47
+ def assign_value
48
+ if @loaded_value.is_a?(GraphQL::Error)
49
+ @loaded_value.path = @field_resolve_step.path
50
+ @field_resolve_step.arguments = @loaded_value
51
+ else
52
+ @arguments[@argument_key] = @loaded_value
53
+ end
54
+
55
+ field_pending_steps = @field_resolve_step.pending_steps
56
+ field_pending_steps.delete(self)
57
+ if @field_resolve_step.arguments && field_pending_steps.size == 0 # rubocop:disable Development/ContextIsPassedCop
58
+ @field_resolve_step.runner.add_step(@field_resolve_step)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ module Next
5
+ class PrepareObjectStep
6
+ def initialize(static_type:, object:, runner:, graphql_result:, key:, is_non_null:, field_resolve_step:, next_objects:, next_results:, is_from_array:)
7
+ @static_type = static_type
8
+ @object = object
9
+ @runner = runner
10
+ @field_resolve_step = field_resolve_step
11
+ @is_non_null = is_non_null
12
+ @next_objects = next_objects
13
+ @next_results = next_results
14
+ @graphql_result = graphql_result
15
+ @resolved_type = nil
16
+ @authorized_value = nil
17
+ @authorization_error = nil
18
+ @key = key
19
+ @next_step = :resolve_type
20
+ @is_from_array = is_from_array
21
+ end
22
+
23
+ def value
24
+ if @authorized_value
25
+ query = @field_resolve_step.selections_step.query
26
+ query.current_trace.begin_authorized(@resolved_type, @object, query.context)
27
+ @authorized_value = @field_resolve_step.sync(@authorized_value)
28
+ query.current_trace.end_authorized(@resolved_type, @object, query.context, @authorized_value)
29
+ elsif @resolved_type
30
+ ctx = @field_resolve_step.selections_step.query.context
31
+ ctx.query.current_trace.begin_resolve_type(@static_type, @object, ctx)
32
+ @resolved_type, _ignored_value = @field_resolve_step.sync(@resolved_type)
33
+ ctx.query.current_trace.end_resolve_type(@static_type, @object, ctx, @resolved_type)
34
+ end
35
+ @runner.add_step(self)
36
+ end
37
+
38
+ def call
39
+ case @next_step
40
+ when :resolve_type
41
+ if @static_type.kind.abstract?
42
+ ctx = @field_resolve_step.selections_step.query.context
43
+ ctx.query.current_trace.begin_resolve_type(@static_type, @object, ctx)
44
+ @resolved_type, _ignored_value = @runner.schema.resolve_type(@static_type, @object, ctx)
45
+ ctx.query.current_trace.end_resolve_type(@static_type, @object, ctx, @resolved_type)
46
+ else
47
+ @resolved_type = @static_type
48
+ end
49
+ if @runner.resolves_lazies && @runner.lazy?(@resolved_type)
50
+ @next_step = :authorize
51
+ @runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self)
52
+ else
53
+ authorize
54
+ end
55
+ when :authorize
56
+ authorize
57
+ when :create_result
58
+ create_result
59
+ else
60
+ raise ArgumentError, "This is a bug, unknown step: #{@next_step.inspect}"
61
+ end
62
+ end
63
+
64
+ def authorize
65
+ if @field_resolve_step.was_scoped && !@resolved_type.reauthorize_scoped_objects
66
+ @authorized_value = @object
67
+ create_result
68
+ return
69
+ end
70
+
71
+ query = @field_resolve_step.selections_step.query
72
+ begin
73
+ query.current_trace.begin_authorized(@resolved_type, @object, query.context)
74
+ @authorized_value = @resolved_type.authorized?(@object, query.context)
75
+ query.current_trace.end_authorized(@resolved_type, @object, query.context, @authorized_value)
76
+ rescue GraphQL::UnauthorizedError => auth_err
77
+ @authorization_error = auth_err
78
+ end
79
+
80
+ if @runner.resolves_lazies && @runner.lazy?(@authorized_value)
81
+ @runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self)
82
+ @next_step = :create_result
83
+ else
84
+ create_result
85
+ end
86
+ rescue GraphQL::RuntimeError => err
87
+ @graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
88
+ end
89
+
90
+ def create_result
91
+ if !@authorized_value
92
+ @authorization_error ||= GraphQL::UnauthorizedError.new(object: @object, type: @resolved_type, context: @field_resolve_step.selections_step.query.context)
93
+ end
94
+
95
+ if @authorization_error
96
+ begin
97
+ new_obj = @runner.schema.unauthorized_object(@authorization_error)
98
+ if new_obj
99
+ @authorized_value = true
100
+ @object = new_obj
101
+ elsif @is_non_null
102
+ @graphql_result[@key] = @field_resolve_step.add_non_null_error(@is_from_array)
103
+ else
104
+ @graphql_result[@key] = @field_resolve_step.add_graphql_error(@authorization_error)
105
+ end
106
+ rescue GraphQL::Error => err
107
+ if @is_non_null
108
+ @graphql_result[@key] = @field_resolve_step.add_non_null_error(@is_from_array)
109
+ else
110
+ @graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
111
+ end
112
+ end
113
+ end
114
+
115
+ if @authorized_value
116
+ next_result_h = {}
117
+ @next_results << next_result_h
118
+ @next_objects << @object
119
+ @graphql_result[@key] = next_result_h
120
+ @runner.runtime_type_at[next_result_h] = @resolved_type
121
+ @runner.static_type_at[next_result_h] = @static_type
122
+ end
123
+
124
+ @field_resolve_step.authorized_finished(self)
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,411 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ module Next
5
+ class Runner
6
+ def initialize(multiplex, authorization:)
7
+ @multiplex = multiplex
8
+ @schema = multiplex.schema
9
+ @steps_queue = []
10
+ @runtime_type_at = {}.compare_by_identity
11
+ @static_type_at = {}.compare_by_identity
12
+ @selected_operation = nil
13
+ @dataloader = multiplex.context[:dataloader] ||= @schema.dataloader_class.new
14
+ @resolves_lazies = @schema.resolves_lazies?
15
+ @lazy_cache = resolves_lazies ? {}.compare_by_identity : nil
16
+ @field_resolve_step_class = @schema.uses_raw_value? ? RawValueFieldResolveStep : FieldResolveStep
17
+ @authorization = authorization
18
+ if @authorization
19
+ @authorizes_cache = Hash.new do |h, query_context|
20
+ h[query_context] = {}.compare_by_identity
21
+ end.compare_by_identity
22
+ end
23
+ end
24
+
25
+ def resolve_type(type, object, query)
26
+ query.current_trace.begin_resolve_type(type, object, query.context)
27
+ resolved_type, _ignored_new_value = query.resolve_type(type, object)
28
+ query.current_trace.end_resolve_type(type, object, query.context, resolved_type)
29
+ resolved_type
30
+ end
31
+
32
+ def authorizes?(graphql_definition, query_context)
33
+ auth_cache = @authorizes_cache[query_context]
34
+ case (auth_res = auth_cache[graphql_definition])
35
+ when nil
36
+ auth_cache[graphql_definition] = graphql_definition.authorizes?(query_context)
37
+ else
38
+ auth_res
39
+ end
40
+ end
41
+
42
+ def add_step(step)
43
+ @dataloader.append_job(step)
44
+ end
45
+
46
+ attr_reader :authorization, :steps_queue, :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at
47
+
48
+ def execute
49
+ Fiber[:__graphql_current_multiplex] = @multiplex
50
+ isolated_steps = [[]]
51
+ trace = @multiplex.current_trace
52
+ queries = @multiplex.queries
53
+ multiplex_analyzers = @schema.multiplex_analyzers
54
+ if @multiplex.max_complexity
55
+ multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
56
+ end
57
+
58
+ trace.execute_multiplex(multiplex: @multiplex) do
59
+ trace.begin_analyze_multiplex(@multiplex, multiplex_analyzers)
60
+ @schema.analysis_engine.analyze_multiplex(@multiplex, multiplex_analyzers)
61
+ trace.end_analyze_multiplex(@multiplex, multiplex_analyzers)
62
+
63
+ results = []
64
+ queries.each do |query|
65
+ if query.validate && !query.valid?
66
+ results << {
67
+ "errors" => query.static_errors.map(&:to_h)
68
+ }
69
+ next
70
+ end
71
+
72
+ selected_operation = query.document.definitions.first # TODO select named operation
73
+ data = {}
74
+
75
+ root_type = case selected_operation.operation_type
76
+ when nil, "query"
77
+ @schema.query
78
+ when "mutation"
79
+ @schema.mutation
80
+ when "subscription"
81
+ @schema.subscription
82
+ else
83
+ raise ArgumentError, "Unknown operation type: #{selected_operation.operation_type.inspect}"
84
+ end
85
+
86
+ if self.authorization && authorizes?(root_type, query.context)
87
+ query.current_trace.begin_authorized(root_type, query.root_value, query.context)
88
+ auth_check = schema.sync_lazy(root_type.authorized?(query.root_value, query.context))
89
+ query.current_trace.end_authorized(root_type, query.root_value, query.context, auth_check)
90
+ root_value = if auth_check
91
+ query.root_value
92
+ else
93
+ begin
94
+ auth_err = GraphQL::UnauthorizedError.new(object: query.root_value, type: root_type, context: query.context)
95
+ new_val = schema.unauthorized_object(auth_err)
96
+ if new_val
97
+ auth_check = true
98
+ end
99
+ new_val
100
+ rescue GraphQL::ExecutionError => ex_err
101
+ # The old runtime didn't add path and ast_nodes to this
102
+ query.context.add_error(ex_err)
103
+ nil
104
+ end
105
+ end
106
+
107
+ if !auth_check
108
+ results << {}
109
+ next
110
+ end
111
+ else
112
+ root_value = query.root_value
113
+ end
114
+
115
+ results << { "data" => data }
116
+
117
+ case selected_operation.operation_type
118
+ when nil, "query"
119
+ isolated_steps[0] << SelectionsStep.new(
120
+ parent_type: root_type,
121
+ selections: selected_operation.selections,
122
+ objects: [root_value],
123
+ results: [data],
124
+ path: EmptyObjects::EMPTY_ARRAY,
125
+ runner: self,
126
+ query: query,
127
+ )
128
+ when "mutation"
129
+ fields = {}
130
+ gather_selections(root_type, selected_operation.selections, nil, query, {}, into: fields)
131
+ fields.each_value do |field_resolve_step|
132
+ isolated_steps << [SelectionsStep.new(
133
+ parent_type: root_type,
134
+ selections: field_resolve_step.ast_nodes || Array(field_resolve_step.ast_node),
135
+ objects: [root_value],
136
+ results: [data],
137
+ path: EmptyObjects::EMPTY_ARRAY,
138
+ runner: self,
139
+ query: query,
140
+ )]
141
+ end
142
+ when "subscription"
143
+ if !query.subscription_update?
144
+ schema.subscriptions.initialize_subscriptions(query)
145
+ end
146
+ isolated_steps[0] << SelectionsStep.new(
147
+ parent_type: root_type,
148
+ selections: selected_operation.selections,
149
+ objects: [root_value],
150
+ results: [data],
151
+ path: EmptyObjects::EMPTY_ARRAY,
152
+ runner: self,
153
+ query: query,
154
+ )
155
+ else
156
+ raise ArgumentError, "Unhandled operation type: #{operation.operation_type.inspect}"
157
+ end
158
+
159
+ @static_type_at[data] = root_type
160
+
161
+ # TODO This is stupid but makes multiplex_spec.rb pass
162
+ trace.execute_query(query: query) do
163
+ end
164
+ end
165
+
166
+ while (next_isolated_steps = isolated_steps.shift)
167
+ next_isolated_steps.each do |step|
168
+ add_step(step)
169
+ end
170
+ @dataloader.run
171
+ end
172
+
173
+ # TODO This is stupid but makes multiplex_spec.rb pass
174
+ trace.execute_query_lazy(query: nil, multiplex: @multiplex) do
175
+ end
176
+
177
+ queries.each_with_index.map do |query, idx|
178
+ result = results[idx]
179
+ if query.subscription?
180
+ @schema.subscriptions.finish_subscriptions(query)
181
+ end
182
+
183
+ fin_result = if query.context.errors.empty?
184
+ result
185
+ else
186
+ data = result["data"]
187
+ data = propagate_errors(data, query)
188
+ errors = []
189
+ query.context.errors.each do |err|
190
+ if err.respond_to?(:to_h)
191
+ errors << err.to_h
192
+ end
193
+ end
194
+ res_h = {}
195
+ if !errors.empty?
196
+ res_h["errors"] = errors
197
+ end
198
+ res_h["data"] = data
199
+ res_h
200
+ end
201
+
202
+ query.result_values = fin_result
203
+ query.result
204
+ end
205
+ end
206
+ ensure
207
+ Fiber[:__graphql_current_multiplex] = nil
208
+ end
209
+
210
+ def gather_selections(type_defn, ast_selections, selections_step, query, prototype_result, into:)
211
+ ast_selections.each do |ast_selection|
212
+ next if !directives_include?(query, ast_selection)
213
+ case ast_selection
214
+ when GraphQL::Language::Nodes::Field
215
+ key = ast_selection.alias || ast_selection.name
216
+ step = into[key] ||= begin
217
+ prototype_result[key] = nil
218
+
219
+ @field_resolve_step_class.new(
220
+ selections_step: selections_step,
221
+ key: key,
222
+ parent_type: type_defn,
223
+ runner: self,
224
+ )
225
+ end
226
+ step.append_selection(ast_selection)
227
+ when GraphQL::Language::Nodes::InlineFragment
228
+ type_condition = ast_selection.type&.name
229
+ if type_condition.nil? || type_condition_applies?(query.context, type_defn, type_condition)
230
+ gather_selections(type_defn, ast_selection.selections, selections_step, query, prototype_result, into: into)
231
+ end
232
+ when GraphQL::Language::Nodes::FragmentSpread
233
+ fragment_definition = query.document.definitions.find { |defn| defn.is_a?(GraphQL::Language::Nodes::FragmentDefinition) && defn.name == ast_selection.name }
234
+ type_condition = fragment_definition.type.name
235
+ if type_condition_applies?(query.context, type_defn, type_condition)
236
+ gather_selections(type_defn, fragment_definition.selections, selections_step, query, prototype_result, into: into)
237
+ end
238
+ else
239
+ raise ArgumentError, "Unsupported graphql selection node: #{ast_selection.class} (#{ast_selection.inspect})"
240
+ end
241
+ end
242
+ end
243
+
244
+ def lazy?(object)
245
+ obj_class = object.class
246
+ is_lazy = @lazy_cache[obj_class]
247
+ if is_lazy.nil?
248
+ is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object)
249
+ end
250
+ is_lazy
251
+ end
252
+
253
+ private
254
+
255
+ def propagate_errors(data, query)
256
+ paths_to_check = query.context.errors.map(&:path)
257
+ paths_to_check.compact! # root-level auth errors currently come without a path
258
+ # TODO dry with above?
259
+ # This is also where a query-level "Step" would be used?
260
+ if (selected_operation = query.selected_operation)
261
+ root_type = case selected_operation.operation_type
262
+ when nil, "query"
263
+ query.schema.query
264
+ when "mutation"
265
+ query.schema.mutation
266
+ when "subscription"
267
+ query.schema.subscription
268
+ end
269
+ check_object_result(query, data, root_type, selected_operation.selections, [], [], paths_to_check)
270
+ end
271
+ end
272
+
273
+ def check_object_result(query, result_h, static_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
274
+ current_path_len = current_exec_path.length
275
+ ast_selections.each do |ast_selection|
276
+ case ast_selection
277
+ when Language::Nodes::Field
278
+ begin
279
+ key = ast_selection.alias || ast_selection.name
280
+ current_exec_path << key
281
+ current_result_path << key
282
+ if paths_to_check.any? { |path_to_check| path_to_check[current_path_len] == key }
283
+ result_value = result_h[key]
284
+ field_defn = query.context.types.field(static_type, ast_selection.name)
285
+ result_type = field_defn.type
286
+ if (result_type_non_null = result_type.non_null?)
287
+ result_type = result_type.of_type
288
+ end
289
+
290
+ new_result_value = if result_value.is_a?(GraphQL::Error)
291
+ result_value.path = current_result_path.dup
292
+ result_value.assign_graphql_result(query, result_h, key)
293
+ result_h.key?(key) ? result_h[key] : :unassigned
294
+ else
295
+ if result_type.list?
296
+ check_list_result(query, result_value, result_type.of_type, ast_selection.selections, current_exec_path, current_result_path, paths_to_check)
297
+ elsif !result_type.kind.leaf?
298
+ check_object_result(query, result_value, result_type, ast_selection.selections, current_exec_path, current_result_path, paths_to_check)
299
+ else
300
+ result_value
301
+ end
302
+ end
303
+
304
+ if new_result_value.nil? && result_type_non_null
305
+ return nil
306
+ elsif :unassigned.equal?(new_result_value)
307
+ # Do nothing
308
+ elsif !new_result_value.equal?(result_value)
309
+ result_h[key] = new_result_value
310
+ end
311
+ end
312
+ ensure
313
+ current_exec_path.pop
314
+ current_result_path.pop
315
+ end
316
+ when Language::Nodes::InlineFragment
317
+ static_type_at_result = @static_type_at[result_h]
318
+ if static_type_at_result && type_condition_applies?(query.context, static_type_at_result, ast_selection.type.name)
319
+ result_h = check_object_result(query, result_h, static_type, ast_selection.selections, current_exec_path, current_result_path, paths_to_check)
320
+ end
321
+ when Language::Nodes::FragmentSpread
322
+ fragment_defn = query.document.definitions.find { |defn| defn.is_a?(Language::Nodes::FragmentDefinition) && defn.name == ast_selection.name }
323
+ static_type_at_result = @static_type_at[result_h]
324
+ if static_type_at_result && type_condition_applies?(query.context, static_type_at_result, fragment_defn.type.name)
325
+ result_h = check_object_result(query, result_h, static_type, fragment_defn.selections, current_exec_path, current_result_path, paths_to_check)
326
+ end
327
+ end
328
+ end
329
+
330
+ result_h
331
+ end
332
+
333
+ def check_list_result(query, result_arr, inner_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
334
+ inner_type_non_null = false
335
+ if inner_type.non_null?
336
+ inner_type_non_null = true
337
+ inner_type = inner_type.of_type
338
+ end
339
+
340
+ new_invalid_null = false
341
+ result_arr.each_with_index do |result_item, idx|
342
+ current_result_path << idx
343
+ new_result = if result_item.is_a?(GraphQL::Error)
344
+ result_item.path = current_result_path.dup
345
+ result_item.assign_graphql_result(query, result_arr, idx)
346
+ result_arr[idx]
347
+ elsif inner_type.list?
348
+ check_list_result(query, result_item, inner_type.of_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
349
+ elsif !inner_type.kind.leaf?
350
+ check_object_result(query, result_item, inner_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
351
+ else
352
+ result_item
353
+ end
354
+
355
+ if new_result.nil? && inner_type_non_null
356
+ new_invalid_null = true
357
+ result_arr[idx] = nil
358
+ elsif !new_result.equal?(result_item)
359
+ result_arr[idx] = new_result
360
+ end
361
+ ensure
362
+ current_result_path.pop
363
+ end
364
+
365
+ if new_invalid_null
366
+ nil
367
+ else
368
+ result_arr
369
+ end
370
+ end
371
+
372
+ def dir_arg_value(query, arg_node)
373
+ if arg_node.value.is_a?(Language::Nodes::VariableIdentifier)
374
+ var_key = arg_node.value.name
375
+ if query.variables.key?(var_key)
376
+ query.variables[var_key]
377
+ else
378
+ query.variables[var_key.to_sym]
379
+ end
380
+ else
381
+ arg_node.value
382
+ end
383
+ end
384
+ def directives_include?(query, ast_selection)
385
+ if ast_selection.directives.any? { |dir_node|
386
+ if dir_node.name == "skip"
387
+ dir_node.arguments.any? { |arg_node| arg_node.name == "if" && dir_arg_value(query, arg_node) == true } # rubocop:disable Development/ContextIsPassedCop
388
+ elsif dir_node.name == "include"
389
+ dir_node.arguments.any? { |arg_node| arg_node.name == "if" && dir_arg_value(query, arg_node) == false } # rubocop:disable Development/ContextIsPassedCop
390
+ end
391
+ }
392
+ false
393
+ else
394
+ true
395
+ end
396
+ end
397
+
398
+ def type_condition_applies?(context, concrete_type, type_name)
399
+ if type_name == concrete_type.graphql_name
400
+ true
401
+ else
402
+ abs_t = @schema.get_type(type_name, context)
403
+ p_types = @schema.possible_types(abs_t, context)
404
+ c_p_types = @schema.possible_types(concrete_type, context)
405
+ p_types.any? { |t| c_p_types.include?(t) }
406
+ end
407
+ end
408
+ end
409
+ end
410
+ end
411
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ module Next
5
+ class SelectionsStep
6
+ def initialize(parent_type:, selections:, objects:, results:, runner:, query:, path:)
7
+ @path = path
8
+ @parent_type = parent_type
9
+ @selections = selections
10
+ @runner = runner
11
+ @objects = objects
12
+ @results = results
13
+ @query = query
14
+ @graphql_objects = nil
15
+ end
16
+
17
+ attr_reader :path, :query, :objects, :results
18
+
19
+ def graphql_objects
20
+ @graphql_objects ||= @objects.map do |obj|
21
+ @parent_type.scoped_new(obj, @query.context)
22
+ end
23
+ end
24
+
25
+ def call
26
+ grouped_selections = {}
27
+ prototype_result = @results.first
28
+ @runner.gather_selections(@parent_type, @selections, self, self.query, prototype_result, into: grouped_selections)
29
+ @results.each { |r| r.replace(prototype_result) }
30
+ grouped_selections.each_value do |frs|
31
+ @runner.add_step(frs)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end