graphql 1.9.17 → 1.11.7

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

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