graphql 1.9.17 → 1.11.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (230) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +18 -2
  3. data/lib/generators/graphql/install_generator.rb +27 -0
  4. data/lib/generators/graphql/object_generator.rb +52 -8
  5. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  6. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  7. data/lib/generators/graphql/templates/base_field.erb +2 -0
  8. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  9. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  10. data/lib/generators/graphql/templates/base_mutation.erb +2 -0
  11. data/lib/generators/graphql/templates/base_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  13. data/lib/generators/graphql/templates/base_union.erb +2 -0
  14. data/lib/generators/graphql/templates/enum.erb +2 -0
  15. data/lib/generators/graphql/templates/graphql_controller.erb +14 -10
  16. data/lib/generators/graphql/templates/interface.erb +2 -0
  17. data/lib/generators/graphql/templates/loader.erb +2 -0
  18. data/lib/generators/graphql/templates/mutation.erb +2 -0
  19. data/lib/generators/graphql/templates/mutation_type.erb +2 -0
  20. data/lib/generators/graphql/templates/object.erb +2 -0
  21. data/lib/generators/graphql/templates/query_type.erb +2 -0
  22. data/lib/generators/graphql/templates/scalar.erb +2 -0
  23. data/lib/generators/graphql/templates/schema.erb +10 -0
  24. data/lib/generators/graphql/templates/union.erb +3 -1
  25. data/lib/graphql/analysis/ast/field_usage.rb +1 -1
  26. data/lib/graphql/analysis/ast/query_complexity.rb +178 -67
  27. data/lib/graphql/analysis/ast/visitor.rb +3 -3
  28. data/lib/graphql/analysis/ast.rb +12 -11
  29. data/lib/graphql/argument.rb +10 -38
  30. data/lib/graphql/backtrace/table.rb +10 -2
  31. data/lib/graphql/backtrace/tracer.rb +2 -1
  32. data/lib/graphql/base_type.rb +4 -0
  33. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
  34. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +5 -9
  35. data/lib/graphql/define/assign_enum_value.rb +1 -1
  36. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  37. data/lib/graphql/define/assign_object_field.rb +3 -3
  38. data/lib/graphql/define/defined_object_proxy.rb +3 -0
  39. data/lib/graphql/define/instance_definable.rb +18 -108
  40. data/lib/graphql/directive/deprecated_directive.rb +1 -12
  41. data/lib/graphql/directive.rb +8 -1
  42. data/lib/graphql/enum_type.rb +5 -71
  43. data/lib/graphql/execution/directive_checks.rb +2 -2
  44. data/lib/graphql/execution/errors.rb +2 -3
  45. data/lib/graphql/execution/execute.rb +1 -1
  46. data/lib/graphql/execution/instrumentation.rb +1 -1
  47. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  48. data/lib/graphql/execution/interpreter/arguments.rb +51 -0
  49. data/lib/graphql/execution/interpreter/arguments_cache.rb +79 -0
  50. data/lib/graphql/execution/interpreter/handles_raw_value.rb +25 -0
  51. data/lib/graphql/execution/interpreter/runtime.rb +227 -254
  52. data/lib/graphql/execution/interpreter.rb +34 -11
  53. data/lib/graphql/execution/lazy/lazy_method_map.rb +4 -0
  54. data/lib/graphql/execution/lookahead.rb +39 -114
  55. data/lib/graphql/execution/multiplex.rb +14 -5
  56. data/lib/graphql/field.rb +14 -118
  57. data/lib/graphql/filter.rb +1 -1
  58. data/lib/graphql/function.rb +1 -30
  59. data/lib/graphql/input_object_type.rb +6 -24
  60. data/lib/graphql/integer_decoding_error.rb +17 -0
  61. data/lib/graphql/interface_type.rb +7 -23
  62. data/lib/graphql/internal_representation/scope.rb +2 -2
  63. data/lib/graphql/internal_representation/visit.rb +2 -2
  64. data/lib/graphql/introspection/base_object.rb +2 -5
  65. data/lib/graphql/introspection/directive_type.rb +1 -1
  66. data/lib/graphql/introspection/entry_points.rb +7 -7
  67. data/lib/graphql/introspection/field_type.rb +7 -3
  68. data/lib/graphql/introspection/input_value_type.rb +33 -9
  69. data/lib/graphql/introspection/introspection_query.rb +6 -92
  70. data/lib/graphql/introspection/schema_type.rb +4 -9
  71. data/lib/graphql/introspection/type_type.rb +11 -7
  72. data/lib/graphql/introspection.rb +96 -0
  73. data/lib/graphql/invalid_null_error.rb +18 -0
  74. data/lib/graphql/language/block_string.rb +24 -5
  75. data/lib/graphql/language/definition_slice.rb +21 -10
  76. data/lib/graphql/language/document_from_schema_definition.rb +89 -64
  77. data/lib/graphql/language/lexer.rb +7 -3
  78. data/lib/graphql/language/lexer.rl +7 -3
  79. data/lib/graphql/language/nodes.rb +52 -91
  80. data/lib/graphql/language/parser.rb +719 -717
  81. data/lib/graphql/language/parser.y +104 -98
  82. data/lib/graphql/language/printer.rb +1 -1
  83. data/lib/graphql/language/sanitized_printer.rb +222 -0
  84. data/lib/graphql/language/visitor.rb +2 -2
  85. data/lib/graphql/language.rb +2 -1
  86. data/lib/graphql/name_validator.rb +6 -7
  87. data/lib/graphql/non_null_type.rb +0 -10
  88. data/lib/graphql/object_type.rb +45 -56
  89. data/lib/graphql/pagination/active_record_relation_connection.rb +41 -0
  90. data/lib/graphql/pagination/array_connection.rb +77 -0
  91. data/lib/graphql/pagination/connection.rb +208 -0
  92. data/lib/graphql/pagination/connections.rb +145 -0
  93. data/lib/graphql/pagination/mongoid_relation_connection.rb +25 -0
  94. data/lib/graphql/pagination/relation_connection.rb +185 -0
  95. data/lib/graphql/pagination/sequel_dataset_connection.rb +28 -0
  96. data/lib/graphql/pagination.rb +6 -0
  97. data/lib/graphql/query/arguments.rb +4 -2
  98. data/lib/graphql/query/context.rb +36 -9
  99. data/lib/graphql/query/fingerprint.rb +26 -0
  100. data/lib/graphql/query/input_validation_result.rb +23 -6
  101. data/lib/graphql/query/literal_input.rb +30 -10
  102. data/lib/graphql/query/null_context.rb +5 -1
  103. data/lib/graphql/query/validation_pipeline.rb +4 -1
  104. data/lib/graphql/query/variable_validation_error.rb +1 -1
  105. data/lib/graphql/query/variables.rb +16 -7
  106. data/lib/graphql/query.rb +64 -15
  107. data/lib/graphql/rake_task/validate.rb +3 -0
  108. data/lib/graphql/rake_task.rb +9 -9
  109. data/lib/graphql/relay/array_connection.rb +10 -12
  110. data/lib/graphql/relay/base_connection.rb +23 -13
  111. data/lib/graphql/relay/connection_type.rb +2 -1
  112. data/lib/graphql/relay/edge_type.rb +1 -0
  113. data/lib/graphql/relay/edges_instrumentation.rb +1 -1
  114. data/lib/graphql/relay/mutation.rb +1 -86
  115. data/lib/graphql/relay/node.rb +2 -2
  116. data/lib/graphql/relay/range_add.rb +14 -5
  117. data/lib/graphql/relay/relation_connection.rb +8 -10
  118. data/lib/graphql/scalar_type.rb +15 -59
  119. data/lib/graphql/schema/argument.rb +113 -11
  120. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  121. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +1 -1
  122. data/lib/graphql/schema/build_from_definition/resolve_map.rb +13 -5
  123. data/lib/graphql/schema/build_from_definition.rb +212 -190
  124. data/lib/graphql/schema/built_in_types.rb +5 -5
  125. data/lib/graphql/schema/default_type_error.rb +2 -0
  126. data/lib/graphql/schema/directive/deprecated.rb +18 -0
  127. data/lib/graphql/schema/directive/include.rb +1 -1
  128. data/lib/graphql/schema/directive/skip.rb +1 -1
  129. data/lib/graphql/schema/directive.rb +34 -3
  130. data/lib/graphql/schema/enum.rb +52 -4
  131. data/lib/graphql/schema/enum_value.rb +6 -1
  132. data/lib/graphql/schema/field/connection_extension.rb +44 -20
  133. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  134. data/lib/graphql/schema/field.rb +200 -129
  135. data/lib/graphql/schema/find_inherited_value.rb +13 -0
  136. data/lib/graphql/schema/finder.rb +13 -11
  137. data/lib/graphql/schema/input_object.rb +131 -22
  138. data/lib/graphql/schema/interface.rb +26 -8
  139. data/lib/graphql/schema/introspection_system.rb +108 -37
  140. data/lib/graphql/schema/late_bound_type.rb +3 -2
  141. data/lib/graphql/schema/list.rb +47 -0
  142. data/lib/graphql/schema/loader.rb +134 -96
  143. data/lib/graphql/schema/member/base_dsl_methods.rb +29 -12
  144. data/lib/graphql/schema/member/build_type.rb +19 -5
  145. data/lib/graphql/schema/member/cached_graphql_definition.rb +5 -0
  146. data/lib/graphql/schema/member/has_arguments.rb +105 -5
  147. data/lib/graphql/schema/member/has_ast_node.rb +20 -0
  148. data/lib/graphql/schema/member/has_fields.rb +20 -10
  149. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  150. data/lib/graphql/schema/member/type_system_helpers.rb +2 -2
  151. data/lib/graphql/schema/member/validates_input.rb +33 -0
  152. data/lib/graphql/schema/member.rb +6 -0
  153. data/lib/graphql/schema/mutation.rb +5 -1
  154. data/lib/graphql/schema/non_null.rb +30 -0
  155. data/lib/graphql/schema/object.rb +65 -12
  156. data/lib/graphql/schema/possible_types.rb +9 -4
  157. data/lib/graphql/schema/printer.rb +0 -15
  158. data/lib/graphql/schema/relay_classic_mutation.rb +5 -3
  159. data/lib/graphql/schema/resolver/has_payload_type.rb +5 -2
  160. data/lib/graphql/schema/resolver.rb +26 -18
  161. data/lib/graphql/schema/scalar.rb +27 -3
  162. data/lib/graphql/schema/subscription.rb +8 -18
  163. data/lib/graphql/schema/timeout.rb +29 -15
  164. data/lib/graphql/schema/traversal.rb +1 -1
  165. data/lib/graphql/schema/type_expression.rb +21 -13
  166. data/lib/graphql/schema/type_membership.rb +2 -2
  167. data/lib/graphql/schema/union.rb +37 -3
  168. data/lib/graphql/schema/unique_within_type.rb +1 -2
  169. data/lib/graphql/schema/validation.rb +10 -2
  170. data/lib/graphql/schema/warden.rb +115 -29
  171. data/lib/graphql/schema.rb +903 -195
  172. data/lib/graphql/static_validation/all_rules.rb +1 -0
  173. data/lib/graphql/static_validation/base_visitor.rb +10 -6
  174. data/lib/graphql/static_validation/literal_validator.rb +52 -27
  175. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +43 -83
  176. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +17 -5
  177. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +33 -25
  178. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
  179. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  180. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
  181. data/lib/graphql/static_validation/rules/fields_will_merge.rb +29 -21
  182. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  183. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  184. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  185. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
  186. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -5
  187. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +12 -13
  188. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -6
  189. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  190. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +5 -3
  191. data/lib/graphql/static_validation/type_stack.rb +2 -2
  192. data/lib/graphql/static_validation/validation_context.rb +1 -1
  193. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  194. data/lib/graphql/static_validation/validator.rb +30 -8
  195. data/lib/graphql/static_validation.rb +1 -0
  196. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +89 -19
  197. data/lib/graphql/subscriptions/broadcast_analyzer.rb +84 -0
  198. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
  199. data/lib/graphql/subscriptions/event.rb +23 -5
  200. data/lib/graphql/subscriptions/instrumentation.rb +10 -5
  201. data/lib/graphql/subscriptions/serialize.rb +22 -4
  202. data/lib/graphql/subscriptions/subscription_root.rb +15 -5
  203. data/lib/graphql/subscriptions.rb +108 -35
  204. data/lib/graphql/tracing/active_support_notifications_tracing.rb +14 -10
  205. data/lib/graphql/tracing/appoptics_tracing.rb +171 -0
  206. data/lib/graphql/tracing/appsignal_tracing.rb +8 -0
  207. data/lib/graphql/tracing/data_dog_tracing.rb +8 -0
  208. data/lib/graphql/tracing/new_relic_tracing.rb +9 -12
  209. data/lib/graphql/tracing/platform_tracing.rb +53 -9
  210. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  211. data/lib/graphql/tracing/prometheus_tracing.rb +8 -0
  212. data/lib/graphql/tracing/scout_tracing.rb +19 -0
  213. data/lib/graphql/tracing/skylight_tracing.rb +8 -0
  214. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  215. data/lib/graphql/tracing.rb +14 -34
  216. data/lib/graphql/types/big_int.rb +1 -1
  217. data/lib/graphql/types/int.rb +9 -2
  218. data/lib/graphql/types/iso_8601_date.rb +3 -3
  219. data/lib/graphql/types/iso_8601_date_time.rb +25 -10
  220. data/lib/graphql/types/relay/base_connection.rb +11 -7
  221. data/lib/graphql/types/relay/base_edge.rb +2 -1
  222. data/lib/graphql/types/string.rb +7 -1
  223. data/lib/graphql/unauthorized_error.rb +1 -1
  224. data/lib/graphql/union_type.rb +13 -28
  225. data/lib/graphql/unresolved_type_error.rb +2 -2
  226. data/lib/graphql/version.rb +1 -1
  227. data/lib/graphql.rb +31 -6
  228. data/readme.md +1 -1
  229. metadata +34 -9
  230. data/lib/graphql/literal_validation_error.rb +0 -6
@@ -43,24 +43,24 @@ module GraphQL
43
43
  # might be stored up in lazies.
44
44
  # @return [void]
45
45
  def run_eager
46
-
47
46
  root_operation = query.selected_operation
48
47
  root_op_type = root_operation.operation_type || "query"
49
- legacy_root_type = schema.root_type_for_operation(root_op_type)
50
- root_type = legacy_root_type.metadata[:type_class] || raise("Invariant: type must be class-based: #{legacy_root_type}")
48
+ root_type = schema.root_type_for_operation(root_op_type)
51
49
  path = []
52
- @interpreter_context[:current_object] = query.root_value
53
- @interpreter_context[:current_path] = path
54
- object_proxy = root_type.authorized_new(query.root_value, context)
50
+ set_all_interpreter_context(query.root_value, nil, nil, path)
51
+ object_proxy = authorized_new(root_type, query.root_value, context, path)
55
52
  object_proxy = schema.sync_lazy(object_proxy)
56
53
  if object_proxy.nil?
57
54
  # Root .authorized? returned false.
58
55
  write_in_response(path, nil)
59
- nil
60
56
  else
61
57
  evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
62
- nil
63
58
  end
59
+ delete_interpreter_context(:current_path)
60
+ delete_interpreter_context(:current_field)
61
+ delete_interpreter_context(:current_object)
62
+ delete_interpreter_context(:current_arguments)
63
+ nil
64
64
  end
65
65
 
66
66
  def gather_selections(owner_object, owner_type, selections, selections_by_name)
@@ -89,11 +89,10 @@ module GraphQL
89
89
  end
90
90
  when GraphQL::Language::Nodes::InlineFragment
91
91
  if node.type
92
- type_defn = schema.types[node.type.name]
93
- type_defn = type_defn.metadata[:type_class]
92
+ type_defn = schema.get_type(node.type.name)
94
93
  # Faster than .map{}.include?()
95
94
  query.warden.possible_types(type_defn).each do |t|
96
- if t.metadata[:type_class] == owner_type
95
+ if t == owner_type
97
96
  gather_selections(owner_object, owner_type, node.selections, selections_by_name)
98
97
  break
99
98
  end
@@ -104,10 +103,10 @@ module GraphQL
104
103
  end
105
104
  when GraphQL::Language::Nodes::FragmentSpread
106
105
  fragment_def = query.fragments[node.name]
107
- type_defn = schema.types[fragment_def.type.name]
108
- type_defn = type_defn.metadata[:type_class]
109
- schema.possible_types(type_defn).each do |t|
110
- if t.metadata[:type_class] == owner_type
106
+ type_defn = schema.get_type(fragment_def.type.name)
107
+ possible_types = query.warden.possible_types(type_defn)
108
+ possible_types.each do |t|
109
+ if t == owner_type
111
110
  gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name)
112
111
  break
113
112
  end
@@ -118,9 +117,10 @@ module GraphQL
118
117
  end
119
118
  end
120
119
 
120
+ NO_ARGS = {}.freeze
121
+
121
122
  def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
122
- @interpreter_context[:current_object] = owner_object
123
- @interpreter_context[:current_path] = path
123
+ set_all_interpreter_context(owner_object, nil, nil, path)
124
124
  selections_by_name = {}
125
125
  gather_selections(owner_object, owner_type, selections, selections_by_name)
126
126
  selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
@@ -138,18 +138,17 @@ module GraphQL
138
138
  field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
139
139
  is_introspection = false
140
140
  if field_defn.nil?
141
- field_defn = if owner_type == schema.query.metadata[:type_class] && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
141
+ field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
142
142
  is_introspection = true
143
- entry_point_field.metadata[:type_class]
143
+ entry_point_field
144
144
  elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
145
145
  is_introspection = true
146
- dynamic_field.metadata[:type_class]
146
+ dynamic_field
147
147
  else
148
148
  raise "Invariant: no field for #{owner_type}.#{field_name}"
149
149
  end
150
150
  end
151
-
152
- return_type = resolve_if_late_bound_type(field_defn.type)
151
+ return_type = field_defn.type
153
152
 
154
153
  next_path = path.dup
155
154
  next_path << result_name
@@ -160,92 +159,109 @@ module GraphQL
160
159
  # to propagate `null`
161
160
  set_type_at_path(next_path, return_type)
162
161
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
163
- @interpreter_context[:current_path] = next_path
164
- @interpreter_context[:current_field] = field_defn
162
+ set_all_interpreter_context(nil, field_defn, nil, next_path)
165
163
 
166
164
  context.scoped_context = scoped_context
167
165
  object = owner_object
168
166
 
169
167
  if is_introspection
170
- object = field_defn.owner.authorized_new(object, context)
168
+ object = authorized_new(field_defn.owner, object, context, next_path)
171
169
  end
172
170
 
173
171
  begin
174
172
  kwarg_arguments = arguments(object, field_defn, ast_node)
175
- rescue GraphQL::ExecutionError => e
176
- continue_value(next_path, e, field_defn, return_type.non_null?, ast_node)
173
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => e
174
+ continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
177
175
  next
178
176
  end
179
177
 
180
- # It might turn out that making arguments for every field is slow.
181
- # If we have to cache them, we'll need a more subtle approach here.
182
- field_defn.extras.each do |extra|
183
- case extra
184
- when :ast_node
185
- kwarg_arguments[:ast_node] = ast_node
186
- when :execution_errors
187
- kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
188
- when :path
189
- kwarg_arguments[:path] = next_path
190
- when :lookahead
191
- if !field_ast_nodes
192
- field_ast_nodes = [ast_node]
193
- end
194
- kwarg_arguments[:lookahead] = Execution::Lookahead.new(
195
- query: query,
196
- ast_nodes: field_ast_nodes,
197
- field: field_defn,
198
- )
178
+ after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
179
+ case resolved_arguments
180
+ when GraphQL::ExecutionError, GraphQL::UnauthorizedError
181
+ continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
182
+ next
183
+ end
184
+
185
+ if resolved_arguments.empty? && field_defn.extras.empty?
186
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
187
+ kwarg_arguments = NO_ARGS
199
188
  else
200
- kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
189
+ kwarg_arguments = resolved_arguments.keyword_arguments
190
+
191
+ field_defn.extras.each do |extra|
192
+ case extra
193
+ when :ast_node
194
+ kwarg_arguments[:ast_node] = ast_node
195
+ when :execution_errors
196
+ kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
197
+ when :path
198
+ kwarg_arguments[:path] = next_path
199
+ when :lookahead
200
+ if !field_ast_nodes
201
+ field_ast_nodes = [ast_node]
202
+ end
203
+ kwarg_arguments[:lookahead] = Execution::Lookahead.new(
204
+ query: query,
205
+ ast_nodes: field_ast_nodes,
206
+ field: field_defn,
207
+ )
208
+ when :argument_details
209
+ kwarg_arguments[:argument_details] = resolved_arguments
210
+ else
211
+ kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
212
+ end
213
+ end
201
214
  end
202
- end
203
215
 
204
- @interpreter_context[:current_arguments] = kwarg_arguments
216
+ set_all_interpreter_context(nil, nil, kwarg_arguments, nil)
205
217
 
206
- # Optimize for the case that field is selected only once
207
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
208
- next_selections = ast_node.selections
209
- else
210
- next_selections = []
211
- field_ast_nodes.each { |f| next_selections.concat(f.selections) }
212
- end
218
+ # Optimize for the case that field is selected only once
219
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
220
+ next_selections = ast_node.selections
221
+ else
222
+ next_selections = []
223
+ field_ast_nodes.each { |f| next_selections.concat(f.selections) }
224
+ end
213
225
 
214
- field_result = resolve_with_directives(object, ast_node) do
215
- # Actually call the field resolver and capture the result
216
- app_result = begin
217
- query.with_error_handling do
218
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
219
- field_defn.resolve(object, kwarg_arguments, context)
226
+ field_result = resolve_with_directives(object, ast_node) do
227
+ # Actually call the field resolver and capture the result
228
+ app_result = begin
229
+ query.with_error_handling do
230
+ query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
231
+ field_defn.resolve(object, kwarg_arguments, context)
232
+ end
220
233
  end
234
+ rescue GraphQL::ExecutionError => err
235
+ err
221
236
  end
222
- rescue GraphQL::ExecutionError => err
223
- err
224
- end
225
- after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
226
- continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node)
227
- if HALT != continue_value
228
- continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
237
+ after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
238
+ continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
239
+ if RawValue === continue_value
240
+ # Write raw value directly to the response without resolving nested objects
241
+ write_in_response(next_path, continue_value.resolve)
242
+ elsif HALT != continue_value
243
+ continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
244
+ end
229
245
  end
230
246
  end
231
- end
232
247
 
233
- # If this field is a root mutation field, immediately resolve
234
- # all of its child fields before moving on to the next root mutation field.
235
- # (Subselections of this mutation will still be resolved level-by-level.)
236
- if root_operation_type == "mutation"
237
- Interpreter::Resolve.resolve_all([field_result])
238
- else
239
- field_result
248
+ # If this field is a root mutation field, immediately resolve
249
+ # all of its child fields before moving on to the next root mutation field.
250
+ # (Subselections of this mutation will still be resolved level-by-level.)
251
+ if root_operation_type == "mutation"
252
+ Interpreter::Resolve.resolve_all([field_result])
253
+ else
254
+ field_result
255
+ end
240
256
  end
241
257
  end
242
258
  end
243
259
 
244
260
  HALT = Object.new
245
- def continue_value(path, value, field, is_non_null, ast_node)
261
+ def continue_value(path, value, parent_type, field, is_non_null, ast_node)
246
262
  if value.nil?
247
263
  if is_non_null
248
- err = GraphQL::InvalidNullError.new(field.owner, field, value)
264
+ err = parent_type::InvalidNullError.new(parent_type, field, value)
249
265
  write_invalid_null_in_response(path, err)
250
266
  else
251
267
  write_in_response(path, nil)
@@ -272,7 +288,7 @@ module GraphQL
272
288
  err
273
289
  end
274
290
 
275
- continue_value(path, next_value, field, is_non_null, ast_node)
291
+ continue_value(path, next_value, parent_type, field, is_non_null, ast_node)
276
292
  elsif GraphQL::Execution::Execute::SKIP == value
277
293
  HALT
278
294
  else
@@ -288,93 +304,105 @@ module GraphQL
288
304
  # Location information from `path` and `ast_node`.
289
305
  #
290
306
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
291
- def continue_field(path, value, field, type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists
292
- case type.kind.name
307
+ def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists
308
+ case current_type.kind.name
293
309
  when "SCALAR", "ENUM"
294
- r = type.coerce_result(value, context)
310
+ r = current_type.coerce_result(value, context)
295
311
  write_in_response(path, r)
296
312
  r
297
313
  when "UNION", "INTERFACE"
298
- resolved_type_or_lazy = query.resolve_type(type, value)
299
- after_lazy(resolved_type_or_lazy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |resolved_type|
300
- possible_types = query.possible_types(type)
314
+ resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
315
+ resolved_value ||= value
316
+
317
+ after_lazy(resolved_type_or_lazy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
318
+ possible_types = query.possible_types(current_type)
301
319
 
302
320
  if !possible_types.include?(resolved_type)
303
- parent_type = field.owner
304
- type_error = GraphQL::UnresolvedTypeError.new(value, field, parent_type, resolved_type, possible_types)
321
+ parent_type = field.owner_type
322
+ err_class = current_type::UnresolvedTypeError
323
+ type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
305
324
  schema.type_error(type_error, context)
306
325
  write_in_response(path, nil)
307
326
  nil
308
327
  else
309
- resolved_type = resolved_type.metadata[:type_class]
310
- continue_field(path, value, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
328
+ continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
311
329
  end
312
330
  end
313
331
  when "OBJECT"
314
332
  object_proxy = begin
315
- type.authorized_new(value, context)
333
+ authorized_new(current_type, value, context, path)
316
334
  rescue GraphQL::ExecutionError => err
317
335
  err
318
336
  end
319
- after_lazy(object_proxy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_object|
320
- continue_value = continue_value(path, inner_object, field, is_non_null, ast_node)
337
+ after_lazy(object_proxy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
338
+ continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node)
321
339
  if HALT != continue_value
322
340
  response_hash = {}
323
341
  write_in_response(path, response_hash)
324
- evaluate_selections(path, context.scoped_context, continue_value, type, next_selections)
342
+ evaluate_selections(path, context.scoped_context, continue_value, current_type, next_selections)
325
343
  response_hash
326
344
  end
327
345
  end
328
346
  when "LIST"
329
347
  response_list = []
330
348
  write_in_response(path, response_list)
331
- inner_type = type.of_type
349
+ inner_type = current_type.of_type
332
350
  idx = 0
333
351
  scoped_context = context.scoped_context
334
- value.each do |inner_value|
335
- next_path = path.dup
336
- next_path << idx
337
- next_path.freeze
338
- idx += 1
339
- set_type_at_path(next_path, inner_type)
340
- # This will update `response_list` with the lazy
341
- after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
342
- # reset `is_non_null` here and below, because the inner type will have its own nullability constraint
343
- continue_value = continue_value(next_path, inner_inner_value, field, false, ast_node)
344
- if HALT != continue_value
345
- continue_field(next_path, continue_value, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
352
+ begin
353
+ value.each do |inner_value|
354
+ next_path = path.dup
355
+ next_path << idx
356
+ next_path.freeze
357
+ idx += 1
358
+ set_type_at_path(next_path, inner_type)
359
+ # This will update `response_list` with the lazy
360
+ after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
361
+ continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node)
362
+ if HALT != continue_value
363
+ continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
364
+ end
346
365
  end
347
366
  end
367
+ rescue NoMethodError => err
368
+ # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
369
+ if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
370
+ # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
371
+ raise ListResultFailedError.new(value: value, field: field, path: path)
372
+ else
373
+ # This was some other NoMethodError -- let it bubble to reveal the real error.
374
+ raise
375
+ end
348
376
  end
377
+
349
378
  response_list
350
379
  when "NON_NULL"
351
- inner_type = type.of_type
352
- # For fields like `__schema: __Schema!`
353
- inner_type = resolve_if_late_bound_type(inner_type)
380
+ inner_type = current_type.of_type
354
381
  # Don't `set_type_at_path` because we want the static type,
355
382
  # we're going to use that to determine whether a `nil` should be propagated or not.
356
- continue_field(path, value, field, inner_type, ast_node, next_selections, true, owner_object, arguments)
383
+ continue_field(path, value, owner_type, field, inner_type, ast_node, next_selections, true, owner_object, arguments)
357
384
  else
358
- raise "Invariant: Unhandled type kind #{type.kind} (#{type})"
385
+ raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
359
386
  end
360
387
  end
361
388
 
362
- def resolve_with_directives(object, ast_node)
363
- run_directive(object, ast_node, 0) { yield }
389
+ def resolve_with_directives(object, ast_node, &block)
390
+ return yield if ast_node.directives.empty?
391
+ run_directive(object, ast_node, 0, &block)
364
392
  end
365
393
 
366
- def run_directive(object, ast_node, idx)
394
+ def run_directive(object, ast_node, idx, &block)
367
395
  dir_node = ast_node.directives[idx]
368
396
  if !dir_node
369
397
  yield
370
398
  else
371
399
  dir_defn = schema.directives.fetch(dir_node.name)
372
400
  if !dir_defn.is_a?(Class)
373
- dir_defn = dir_defn.metadata[:type_class] || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
401
+ dir_defn = dir_defn.type_class || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
374
402
  end
375
- dir_args = arguments(nil, dir_defn, dir_node)
403
+ dir_args = arguments(nil, dir_defn, dir_node).keyword_arguments
376
404
  dir_defn.resolve(object, dir_args, context) do
377
- run_directive(object, ast_node, idx + 1) { yield }
405
+ run_directive(object, ast_node, idx + 1, &block)
378
406
  end
379
407
  end
380
408
  end
@@ -382,8 +410,8 @@ module GraphQL
382
410
  # Check {Schema::Directive.include?} for each directive that's present
383
411
  def directives_include?(node, graphql_object, parent_type)
384
412
  node.directives.each do |dir_node|
385
- dir_defn = schema.directives.fetch(dir_node.name).metadata[:type_class] || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
386
- args = arguments(graphql_object, dir_defn, dir_node)
413
+ dir_defn = schema.directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
414
+ args = arguments(graphql_object, dir_defn, dir_node).keyword_arguments
387
415
  if !dir_defn.include?(graphql_object, args, context)
388
416
  return false
389
417
  end
@@ -391,11 +419,18 @@ module GraphQL
391
419
  true
392
420
  end
393
421
 
394
- def resolve_if_late_bound_type(type)
395
- if type.is_a?(GraphQL::Schema::LateBoundType)
396
- query.warden.get_type(type.name).metadata[:type_class]
397
- else
398
- type
422
+ def set_all_interpreter_context(object, field, arguments, path)
423
+ if object
424
+ @context[:current_object] = @interpreter_context[:current_object] = object
425
+ end
426
+ if field
427
+ @context[:current_field] = @interpreter_context[:current_field] = field
428
+ end
429
+ if arguments
430
+ @context[:current_arguments] = @interpreter_context[:current_arguments] = arguments
431
+ end
432
+ if path
433
+ @context[:current_path] = @interpreter_context[:current_path] = path
399
434
  end
400
435
  end
401
436
 
@@ -403,33 +438,30 @@ module GraphQL
403
438
  # @param path [Array<String>]
404
439
  # @param field [GraphQL::Schema::Field]
405
440
  # @param eager [Boolean] Set to `true` for mutation root fields only
441
+ # @param trace [Boolean] If `false`, don't wrap this with field tracing
406
442
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
407
- def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false)
408
- @interpreter_context[:current_object] = owner_object
409
- @interpreter_context[:current_arguments] = arguments
410
- @interpreter_context[:current_path] = path
411
- @interpreter_context[:current_field] = field
443
+ def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true, &block)
444
+ set_all_interpreter_context(owner_object, field, arguments, path)
412
445
  if schema.lazy?(lazy_obj)
413
446
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
414
- @interpreter_context[:current_path] = path
415
- @interpreter_context[:current_field] = field
416
- @interpreter_context[:current_object] = owner_object
417
- @interpreter_context[:current_arguments] = arguments
447
+ set_all_interpreter_context(owner_object, field, arguments, path)
418
448
  context.scoped_context = scoped_context
419
449
  # Wrap the execution of _this_ method with tracing,
420
450
  # but don't wrap the continuation below
421
451
  inner_obj = begin
422
452
  query.with_error_handling do
423
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
453
+ if trace
454
+ query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
455
+ schema.sync_lazy(lazy_obj)
456
+ end
457
+ else
424
458
  schema.sync_lazy(lazy_obj)
425
459
  end
426
460
  end
427
461
  rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
428
- yield(err)
429
- end
430
- after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager) do |really_inner_obj|
431
- yield(really_inner_obj)
462
+ err
432
463
  end
464
+ after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
433
465
  end
434
466
 
435
467
  if eager
@@ -443,108 +475,14 @@ module GraphQL
443
475
  end
444
476
  end
445
477
 
446
- def each_argument_pair(ast_args_or_hash)
447
- case ast_args_or_hash
448
- when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
449
- ast_args_or_hash.arguments.each do |arg|
450
- yield(arg.name, arg.value)
451
- end
452
- when Hash
453
- ast_args_or_hash.each do |key, value|
454
- normalized_name = GraphQL::Schema::Member::BuildType.camelize(key.to_s)
455
- yield(normalized_name, value)
456
- end
478
+ def arguments(graphql_object, arg_owner, ast_node)
479
+ # Don't cache arguments if field extras or extensions are requested since they can mutate the argument data structure
480
+ if arg_owner.arguments_statically_coercible? &&
481
+ (!arg_owner.is_a?(GraphQL::Schema::Field) || (arg_owner.extras.empty? && arg_owner.extensions.empty?))
482
+ query.arguments_for(ast_node, arg_owner)
457
483
  else
458
- raise "Invariant, unexpected #{ast_args_or_hash.inspect}"
459
- end
460
- end
461
-
462
- def arguments(graphql_object, arg_owner, ast_node_or_hash)
463
- kwarg_arguments = {}
464
- arg_defns = arg_owner.arguments
465
- each_argument_pair(ast_node_or_hash) do |arg_name, arg_value|
466
- arg_defn = arg_defns[arg_name]
467
- # Need to distinguish between client-provided `nil`
468
- # and nothing-at-all
469
- is_present, value = arg_to_value(graphql_object, arg_defn.type, arg_value)
470
- if is_present
471
- # This doesn't apply to directives, which are legacy
472
- # Can remove this when Skip and Include use classes or something.
473
- if graphql_object
474
- value = arg_defn.prepare_value(graphql_object, value)
475
- end
476
- kwarg_arguments[arg_defn.keyword] = value
477
- end
478
- end
479
- arg_defns.each do |name, arg_defn|
480
- if arg_defn.default_value? && !kwarg_arguments.key?(arg_defn.keyword)
481
- _is_present, value = arg_to_value(graphql_object, arg_defn.type, arg_defn.default_value)
482
- kwarg_arguments[arg_defn.keyword] = value
483
- end
484
- end
485
- kwarg_arguments
486
- end
487
-
488
- # Get a Ruby-ready value from a client query.
489
- # @param graphql_object [Object] The owner of the field whose argument this is
490
- # @param arg_type [Class, GraphQL::Schema::NonNull, GraphQL::Schema::List]
491
- # @param ast_value [GraphQL::Language::Nodes::VariableIdentifier, String, Integer, Float, Boolean]
492
- # @return [Array(is_present, value)]
493
- def arg_to_value(graphql_object, arg_type, ast_value)
494
- if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
495
- # If it's not here, it will get added later
496
- if query.variables.key?(ast_value.name)
497
- return true, query.variables[ast_value.name]
498
- else
499
- return false, nil
500
- end
501
- elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue)
502
- return true, nil
503
- elsif arg_type.is_a?(GraphQL::Schema::NonNull)
504
- arg_to_value(graphql_object, arg_type.of_type, ast_value)
505
- elsif arg_type.is_a?(GraphQL::Schema::List)
506
- # Treat a single value like a list
507
- arg_value = Array(ast_value)
508
- list = []
509
- arg_value.map do |inner_v|
510
- _present, value = arg_to_value(graphql_object, arg_type.of_type, inner_v)
511
- list << value
512
- end
513
- return true, list
514
- elsif arg_type.is_a?(Class) && arg_type < GraphQL::Schema::InputObject
515
- # For these, `prepare` is applied during `#initialize`.
516
- # Pass `nil` so it will be skipped in `#arguments`.
517
- # What a mess.
518
- args = arguments(nil, arg_type, ast_value)
519
- # We're not tracking defaults_used, but for our purposes
520
- # we compare the value to the default value.
521
-
522
- input_obj = query.with_error_handling do
523
- arg_type.new(ruby_kwargs: args, context: context, defaults_used: nil)
524
- end
525
- return true, input_obj
526
- else
527
- flat_value = flatten_ast_value(ast_value)
528
- return true, arg_type.coerce_input(flat_value, context)
529
- end
530
- end
531
-
532
- def flatten_ast_value(v)
533
- case v
534
- when GraphQL::Language::Nodes::Enum
535
- v.name
536
- when GraphQL::Language::Nodes::InputObject
537
- h = {}
538
- v.arguments.each do |arg|
539
- h[arg.name] = flatten_ast_value(arg.value)
540
- end
541
- h
542
- when Array
543
- v.map { |v2| flatten_ast_value(v2) }
544
- when GraphQL::Language::Nodes::VariableIdentifier
545
- flatten_ast_value(query.variables[v.name])
546
- else
547
- v
484
+ # The arguments must be prepared in the context of the given object
485
+ query.arguments_for(ast_node, arg_owner, parent_object: graphql_object)
548
486
  end
549
487
  end
550
488
 
@@ -585,23 +523,11 @@ module GraphQL
585
523
  # at previous parts of the response.
586
524
  # This hash matches the response
587
525
  def type_at(path)
588
- t = @types_at_paths
589
- path.each do |part|
590
- t = t[part] || (raise("Invariant: #{part.inspect} not found in #{t}"))
591
- end
592
- t = t[:__type]
593
- t
526
+ @types_at_paths.fetch(path)
594
527
  end
595
528
 
596
529
  def set_type_at_path(path, type)
597
- types = @types_at_paths
598
- path.each do |part|
599
- types = types[part] ||= {}
600
- end
601
- # Use this magic key so that the hash contains:
602
- # - string keys for nested fields
603
- # - :__type for the object type of a selection
604
- types[:__type] ||= type
530
+ @types_at_paths[path] = type
605
531
  nil
606
532
  end
607
533
 
@@ -628,6 +554,53 @@ module GraphQL
628
554
  end
629
555
  res && res[:__dead]
630
556
  end
557
+
558
+ # Set this pair in the Query context, but also in the interpeter namespace,
559
+ # for compatibility.
560
+ def set_interpreter_context(key, value)
561
+ @interpreter_context[key] = value
562
+ @context[key] = value
563
+ end
564
+
565
+ def delete_interpreter_context(key)
566
+ @interpreter_context.delete(key)
567
+ @context.delete(key)
568
+ end
569
+
570
+ def resolve_type(type, value, path)
571
+ trace_payload = { context: context, type: type, object: value, path: path }
572
+ resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do
573
+ query.resolve_type(type, value)
574
+ end
575
+
576
+ if schema.lazy?(resolved_type)
577
+ GraphQL::Execution::Lazy.new do
578
+ query.trace("resolve_type_lazy", trace_payload) do
579
+ schema.sync_lazy(resolved_type)
580
+ end
581
+ end
582
+ else
583
+ [resolved_type, resolved_value]
584
+ end
585
+ end
586
+
587
+ def authorized_new(type, value, context, path)
588
+ trace_payload = { context: context, type: type, object: value, path: path }
589
+
590
+ auth_val = context.query.trace("authorized", trace_payload) do
591
+ type.authorized_new(value, context)
592
+ end
593
+
594
+ if context.schema.lazy?(auth_val)
595
+ GraphQL::Execution::Lazy.new do
596
+ context.query.trace("authorized_lazy", trace_payload) do
597
+ context.schema.sync_lazy(auth_val)
598
+ end
599
+ end
600
+ else
601
+ auth_val
602
+ end
603
+ end
631
604
  end
632
605
  end
633
606
  end