graphql 1.10.1 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (292) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +18 -2
  3. data/lib/generators/graphql/install_generator.rb +36 -6
  4. data/lib/generators/graphql/loader_generator.rb +1 -0
  5. data/lib/generators/graphql/mutation_generator.rb +2 -1
  6. data/lib/generators/graphql/object_generator.rb +54 -9
  7. data/lib/generators/graphql/relay.rb +63 -0
  8. data/lib/generators/graphql/relay_generator.rb +21 -0
  9. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  10. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  11. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  12. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  13. data/lib/generators/graphql/templates/base_field.erb +2 -0
  14. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  15. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  16. data/lib/generators/graphql/templates/base_mutation.erb +2 -0
  17. data/lib/generators/graphql/templates/base_object.erb +2 -0
  18. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  19. data/lib/generators/graphql/templates/base_union.erb +2 -0
  20. data/lib/generators/graphql/templates/enum.erb +2 -0
  21. data/lib/generators/graphql/templates/graphql_controller.erb +16 -12
  22. data/lib/generators/graphql/templates/interface.erb +2 -0
  23. data/lib/generators/graphql/templates/loader.erb +2 -0
  24. data/lib/generators/graphql/templates/mutation.erb +2 -0
  25. data/lib/generators/graphql/templates/mutation_type.erb +2 -0
  26. data/lib/generators/graphql/templates/node_type.erb +9 -0
  27. data/lib/generators/graphql/templates/object.erb +3 -1
  28. data/lib/generators/graphql/templates/query_type.erb +3 -3
  29. data/lib/generators/graphql/templates/scalar.erb +2 -0
  30. data/lib/generators/graphql/templates/schema.erb +21 -33
  31. data/lib/generators/graphql/templates/union.erb +3 -1
  32. data/lib/generators/graphql/type_generator.rb +1 -1
  33. data/lib/graphql/analysis/analyze_query.rb +7 -0
  34. data/lib/graphql/analysis/ast/field_usage.rb +24 -1
  35. data/lib/graphql/analysis/ast/query_complexity.rb +126 -109
  36. data/lib/graphql/analysis/ast/visitor.rb +13 -5
  37. data/lib/graphql/analysis/ast.rb +11 -2
  38. data/lib/graphql/argument.rb +3 -3
  39. data/lib/graphql/backtrace/inspect_result.rb +0 -1
  40. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  41. data/lib/graphql/backtrace/table.rb +34 -3
  42. data/lib/graphql/backtrace/traced_error.rb +0 -1
  43. data/lib/graphql/backtrace/tracer.rb +40 -9
  44. data/lib/graphql/backtrace.rb +28 -19
  45. data/lib/graphql/backwards_compatibility.rb +2 -1
  46. data/lib/graphql/base_type.rb +1 -1
  47. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
  48. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  49. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  50. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  51. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  52. data/lib/graphql/dataloader/null_dataloader.rb +22 -0
  53. data/lib/graphql/dataloader/request.rb +19 -0
  54. data/lib/graphql/dataloader/request_all.rb +19 -0
  55. data/lib/graphql/dataloader/source.rb +155 -0
  56. data/lib/graphql/dataloader.rb +308 -0
  57. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  58. data/lib/graphql/define/defined_object_proxy.rb +1 -1
  59. data/lib/graphql/define/instance_definable.rb +34 -4
  60. data/lib/graphql/define/type_definer.rb +5 -5
  61. data/lib/graphql/deprecated_dsl.rb +18 -5
  62. data/lib/graphql/deprecation.rb +9 -0
  63. data/lib/graphql/directive.rb +4 -4
  64. data/lib/graphql/enum_type.rb +7 -1
  65. data/lib/graphql/execution/errors.rb +110 -7
  66. data/lib/graphql/execution/execute.rb +8 -1
  67. data/lib/graphql/execution/instrumentation.rb +1 -1
  68. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  69. data/lib/graphql/execution/interpreter/arguments.rb +88 -0
  70. data/lib/graphql/execution/interpreter/arguments_cache.rb +103 -0
  71. data/lib/graphql/execution/interpreter/handles_raw_value.rb +18 -0
  72. data/lib/graphql/execution/interpreter/resolve.rb +37 -25
  73. data/lib/graphql/execution/interpreter/runtime.rb +685 -421
  74. data/lib/graphql/execution/interpreter.rb +42 -13
  75. data/lib/graphql/execution/lazy.rb +5 -1
  76. data/lib/graphql/execution/lookahead.rb +25 -110
  77. data/lib/graphql/execution/multiplex.rb +37 -25
  78. data/lib/graphql/field.rb +5 -1
  79. data/lib/graphql/function.rb +4 -0
  80. data/lib/graphql/input_object_type.rb +6 -0
  81. data/lib/graphql/integer_decoding_error.rb +17 -0
  82. data/lib/graphql/integer_encoding_error.rb +18 -2
  83. data/lib/graphql/interface_type.rb +7 -0
  84. data/lib/graphql/internal_representation/document.rb +2 -2
  85. data/lib/graphql/internal_representation/rewrite.rb +1 -1
  86. data/lib/graphql/internal_representation/scope.rb +2 -2
  87. data/lib/graphql/internal_representation/visit.rb +2 -2
  88. data/lib/graphql/introspection/directive_type.rb +8 -4
  89. data/lib/graphql/introspection/entry_points.rb +2 -2
  90. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  91. data/lib/graphql/introspection/field_type.rb +9 -5
  92. data/lib/graphql/introspection/input_value_type.rb +15 -3
  93. data/lib/graphql/introspection/introspection_query.rb +6 -92
  94. data/lib/graphql/introspection/schema_type.rb +4 -4
  95. data/lib/graphql/introspection/type_type.rb +16 -12
  96. data/lib/graphql/introspection.rb +96 -0
  97. data/lib/graphql/invalid_null_error.rb +18 -0
  98. data/lib/graphql/language/block_string.rb +20 -5
  99. data/lib/graphql/language/cache.rb +37 -0
  100. data/lib/graphql/language/document_from_schema_definition.rb +73 -25
  101. data/lib/graphql/language/lexer.rb +4 -3
  102. data/lib/graphql/language/lexer.rl +3 -3
  103. data/lib/graphql/language/nodes.rb +51 -89
  104. data/lib/graphql/language/parser.rb +552 -530
  105. data/lib/graphql/language/parser.y +114 -99
  106. data/lib/graphql/language/printer.rb +7 -2
  107. data/lib/graphql/language/sanitized_printer.rb +222 -0
  108. data/lib/graphql/language/token.rb +0 -4
  109. data/lib/graphql/language/visitor.rb +2 -2
  110. data/lib/graphql/language.rb +2 -0
  111. data/lib/graphql/name_validator.rb +2 -7
  112. data/lib/graphql/object_type.rb +44 -35
  113. data/lib/graphql/pagination/active_record_relation_connection.rb +14 -1
  114. data/lib/graphql/pagination/array_connection.rb +2 -2
  115. data/lib/graphql/pagination/connection.rb +75 -20
  116. data/lib/graphql/pagination/connections.rb +83 -31
  117. data/lib/graphql/pagination/relation_connection.rb +34 -14
  118. data/lib/graphql/parse_error.rb +0 -1
  119. data/lib/graphql/query/arguments.rb +4 -3
  120. data/lib/graphql/query/arguments_cache.rb +1 -2
  121. data/lib/graphql/query/context.rb +42 -7
  122. data/lib/graphql/query/executor.rb +0 -1
  123. data/lib/graphql/query/fingerprint.rb +26 -0
  124. data/lib/graphql/query/input_validation_result.rb +23 -6
  125. data/lib/graphql/query/literal_input.rb +1 -1
  126. data/lib/graphql/query/null_context.rb +24 -8
  127. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  128. data/lib/graphql/query/serial_execution.rb +1 -0
  129. data/lib/graphql/query/validation_pipeline.rb +5 -2
  130. data/lib/graphql/query/variable_validation_error.rb +1 -1
  131. data/lib/graphql/query/variables.rb +14 -4
  132. data/lib/graphql/query.rb +68 -13
  133. data/lib/graphql/railtie.rb +9 -1
  134. data/lib/graphql/rake_task.rb +12 -9
  135. data/lib/graphql/relay/array_connection.rb +10 -12
  136. data/lib/graphql/relay/base_connection.rb +26 -13
  137. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  138. data/lib/graphql/relay/connection_type.rb +1 -1
  139. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  140. data/lib/graphql/relay/mutation.rb +1 -0
  141. data/lib/graphql/relay/node.rb +3 -0
  142. data/lib/graphql/relay/range_add.rb +23 -9
  143. data/lib/graphql/relay/relation_connection.rb +8 -10
  144. data/lib/graphql/relay/type_extensions.rb +2 -0
  145. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  146. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  147. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  148. data/lib/graphql/rubocop.rb +4 -0
  149. data/lib/graphql/scalar_type.rb +16 -1
  150. data/lib/graphql/schema/addition.rb +247 -0
  151. data/lib/graphql/schema/argument.rb +210 -12
  152. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  153. data/lib/graphql/schema/build_from_definition/resolve_map.rb +3 -1
  154. data/lib/graphql/schema/build_from_definition.rb +213 -86
  155. data/lib/graphql/schema/default_type_error.rb +2 -0
  156. data/lib/graphql/schema/directive/deprecated.rb +1 -1
  157. data/lib/graphql/schema/directive/feature.rb +1 -1
  158. data/lib/graphql/schema/directive/flagged.rb +57 -0
  159. data/lib/graphql/schema/directive/include.rb +1 -1
  160. data/lib/graphql/schema/directive/skip.rb +1 -1
  161. data/lib/graphql/schema/directive/transform.rb +14 -2
  162. data/lib/graphql/schema/directive.rb +78 -2
  163. data/lib/graphql/schema/enum.rb +80 -9
  164. data/lib/graphql/schema/enum_value.rb +17 -6
  165. data/lib/graphql/schema/field/connection_extension.rb +46 -30
  166. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  167. data/lib/graphql/schema/field.rb +285 -133
  168. data/lib/graphql/schema/find_inherited_value.rb +4 -1
  169. data/lib/graphql/schema/finder.rb +5 -5
  170. data/lib/graphql/schema/input_object.rb +97 -89
  171. data/lib/graphql/schema/interface.rb +24 -19
  172. data/lib/graphql/schema/late_bound_type.rb +2 -2
  173. data/lib/graphql/schema/list.rb +7 -1
  174. data/lib/graphql/schema/loader.rb +137 -103
  175. data/lib/graphql/schema/member/accepts_definition.rb +8 -1
  176. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -19
  177. data/lib/graphql/schema/member/build_type.rb +14 -7
  178. data/lib/graphql/schema/member/has_arguments.rb +205 -12
  179. data/lib/graphql/schema/member/has_ast_node.rb +4 -1
  180. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  181. data/lib/graphql/schema/member/has_directives.rb +98 -0
  182. data/lib/graphql/schema/member/has_fields.rb +95 -30
  183. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  184. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  185. data/lib/graphql/schema/member/has_validators.rb +31 -0
  186. data/lib/graphql/schema/member/instrumentation.rb +0 -1
  187. data/lib/graphql/schema/member/type_system_helpers.rb +3 -3
  188. data/lib/graphql/schema/member.rb +6 -0
  189. data/lib/graphql/schema/middleware_chain.rb +1 -1
  190. data/lib/graphql/schema/mutation.rb +4 -0
  191. data/lib/graphql/schema/non_null.rb +5 -0
  192. data/lib/graphql/schema/object.rb +47 -46
  193. data/lib/graphql/schema/possible_types.rb +9 -4
  194. data/lib/graphql/schema/printer.rb +16 -34
  195. data/lib/graphql/schema/relay_classic_mutation.rb +32 -4
  196. data/lib/graphql/schema/resolver/has_payload_type.rb +34 -4
  197. data/lib/graphql/schema/resolver.rb +123 -63
  198. data/lib/graphql/schema/scalar.rb +11 -1
  199. data/lib/graphql/schema/subscription.rb +57 -21
  200. data/lib/graphql/schema/timeout.rb +29 -15
  201. data/lib/graphql/schema/timeout_middleware.rb +3 -1
  202. data/lib/graphql/schema/type_expression.rb +1 -1
  203. data/lib/graphql/schema/type_membership.rb +18 -4
  204. data/lib/graphql/schema/union.rb +41 -1
  205. data/lib/graphql/schema/unique_within_type.rb +1 -2
  206. data/lib/graphql/schema/validation.rb +12 -2
  207. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  208. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  209. data/lib/graphql/schema/validator/exclusion_validator.rb +33 -0
  210. data/lib/graphql/schema/validator/format_validator.rb +48 -0
  211. data/lib/graphql/schema/validator/inclusion_validator.rb +35 -0
  212. data/lib/graphql/schema/validator/length_validator.rb +59 -0
  213. data/lib/graphql/schema/validator/numericality_validator.rb +82 -0
  214. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  215. data/lib/graphql/schema/validator.rb +174 -0
  216. data/lib/graphql/schema/warden.rb +153 -28
  217. data/lib/graphql/schema.rb +364 -330
  218. data/lib/graphql/static_validation/all_rules.rb +1 -0
  219. data/lib/graphql/static_validation/base_visitor.rb +8 -5
  220. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  221. data/lib/graphql/static_validation/error.rb +3 -1
  222. data/lib/graphql/static_validation/literal_validator.rb +51 -26
  223. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +44 -87
  224. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +22 -6
  225. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +28 -22
  226. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  227. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
  228. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -43
  229. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  230. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  231. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  232. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  233. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  234. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +6 -7
  235. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +9 -10
  236. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +8 -8
  237. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +4 -2
  238. data/lib/graphql/static_validation/validation_context.rb +9 -3
  239. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  240. data/lib/graphql/static_validation/validator.rb +42 -8
  241. data/lib/graphql/static_validation.rb +1 -0
  242. data/lib/graphql/string_encoding_error.rb +13 -3
  243. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +118 -19
  244. data/lib/graphql/subscriptions/broadcast_analyzer.rb +81 -0
  245. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
  246. data/lib/graphql/subscriptions/event.rb +81 -30
  247. data/lib/graphql/subscriptions/instrumentation.rb +0 -1
  248. data/lib/graphql/subscriptions/serialize.rb +33 -6
  249. data/lib/graphql/subscriptions/subscription_root.rb +15 -4
  250. data/lib/graphql/subscriptions.rb +88 -45
  251. data/lib/graphql/tracing/active_support_notifications_tracing.rb +2 -1
  252. data/lib/graphql/tracing/appoptics_tracing.rb +173 -0
  253. data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
  254. data/lib/graphql/tracing/new_relic_tracing.rb +1 -12
  255. data/lib/graphql/tracing/platform_tracing.rb +43 -17
  256. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  257. data/lib/graphql/tracing/scout_tracing.rb +11 -0
  258. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  259. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  260. data/lib/graphql/tracing.rb +9 -33
  261. data/lib/graphql/types/big_int.rb +5 -1
  262. data/lib/graphql/types/int.rb +10 -3
  263. data/lib/graphql/types/iso_8601_date.rb +3 -3
  264. data/lib/graphql/types/iso_8601_date_time.rb +25 -10
  265. data/lib/graphql/types/relay/base_connection.rb +6 -90
  266. data/lib/graphql/types/relay/base_edge.rb +2 -34
  267. data/lib/graphql/types/relay/connection_behaviors.rb +156 -0
  268. data/lib/graphql/types/relay/default_relay.rb +27 -0
  269. data/lib/graphql/types/relay/edge_behaviors.rb +53 -0
  270. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  271. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  272. data/lib/graphql/types/relay/node.rb +2 -4
  273. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  274. data/lib/graphql/types/relay/node_field.rb +2 -20
  275. data/lib/graphql/types/relay/nodes_field.rb +2 -20
  276. data/lib/graphql/types/relay/page_info.rb +2 -14
  277. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  278. data/lib/graphql/types/relay.rb +11 -3
  279. data/lib/graphql/types/string.rb +8 -2
  280. data/lib/graphql/unauthorized_error.rb +2 -2
  281. data/lib/graphql/union_type.rb +2 -0
  282. data/lib/graphql/upgrader/member.rb +1 -0
  283. data/lib/graphql/upgrader/schema.rb +1 -0
  284. data/lib/graphql/version.rb +1 -1
  285. data/lib/graphql.rb +65 -31
  286. data/readme.md +3 -6
  287. metadata +77 -112
  288. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
  289. data/lib/graphql/literal_validation_error.rb +0 -6
  290. data/lib/graphql/types/relay/base_field.rb +0 -22
  291. data/lib/graphql/types/relay/base_interface.rb +0 -29
  292. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -10,6 +10,7 @@ module GraphQL
10
10
  #
11
11
  # Original Algorithm: https://github.com/graphql/graphql-js/blob/master/src/validation/rules/OverlappingFieldsCanBeMerged.js
12
12
  NO_ARGS = {}.freeze
13
+
13
14
  Field = Struct.new(:node, :definition, :owner_type, :parents)
14
15
  FragmentSpread = Struct.new(:name, :parents)
15
16
 
@@ -17,20 +18,43 @@ module GraphQL
17
18
  super
18
19
  @visited_fragments = {}
19
20
  @compared_fragments = {}
21
+ @conflict_count = 0
20
22
  end
21
23
 
22
24
  def on_operation_definition(node, _parent)
23
- conflicts_within_selection_set(node, type_definition)
25
+ setting_errors { conflicts_within_selection_set(node, type_definition) }
24
26
  super
25
27
  end
26
28
 
27
29
  def on_field(node, _parent)
28
- conflicts_within_selection_set(node, type_definition)
30
+ setting_errors { conflicts_within_selection_set(node, type_definition) }
29
31
  super
30
32
  end
31
33
 
32
34
  private
33
35
 
36
+ def field_conflicts
37
+ @field_conflicts ||= Hash.new do |errors, field|
38
+ errors[field] = GraphQL::StaticValidation::FieldsWillMergeError.new(kind: :field, field_name: field)
39
+ end
40
+ end
41
+
42
+ def arg_conflicts
43
+ @arg_conflicts ||= Hash.new do |errors, field|
44
+ errors[field] = GraphQL::StaticValidation::FieldsWillMergeError.new(kind: :argument, field_name: field)
45
+ end
46
+ end
47
+
48
+ def setting_errors
49
+ @field_conflicts = nil
50
+ @arg_conflicts = nil
51
+
52
+ yield
53
+
54
+ field_conflicts.each_value { |error| add_error(error) }
55
+ arg_conflicts.each_value { |error| add_error(error) }
56
+ end
57
+
34
58
  def conflicts_within_selection_set(node, parent_type)
35
59
  return if parent_type.nil?
36
60
 
@@ -183,6 +207,8 @@ module GraphQL
183
207
  end
184
208
 
185
209
  def find_conflict(response_key, field1, field2, mutually_exclusive: false)
210
+ return if @conflict_count >= context.max_errors
211
+
186
212
  node1 = field1.node
187
213
  node2 = field2.node
188
214
 
@@ -191,27 +217,21 @@ module GraphQL
191
217
 
192
218
  if !are_mutually_exclusive
193
219
  if node1.name != node2.name
194
- errored_nodes = [node1.name, node2.name].sort.join(" or ")
195
- msg = "Field '#{response_key}' has a field conflict: #{errored_nodes}?"
196
- context.errors << GraphQL::StaticValidation::FieldsWillMergeError.new(
197
- msg,
198
- nodes: [node1, node2],
199
- path: [],
200
- field_name: response_key,
201
- conflicts: errored_nodes
202
- )
220
+ conflict = field_conflicts[response_key]
221
+
222
+ conflict.add_conflict(node1, node1.name)
223
+ conflict.add_conflict(node2, node2.name)
224
+
225
+ @conflict_count += 1
203
226
  end
204
227
 
205
- args = possible_arguments(node1, node2)
206
- if args.size > 1
207
- msg = "Field '#{response_key}' has an argument conflict: #{args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ")}?"
208
- context.errors << GraphQL::StaticValidation::FieldsWillMergeError.new(
209
- msg,
210
- nodes: [node1, node2],
211
- path: [],
212
- field_name: response_key,
213
- conflicts: args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ")
214
- )
228
+ if !same_arguments?(node1, node2)
229
+ conflict = arg_conflicts[response_key]
230
+
231
+ conflict.add_conflict(node1, GraphQL::Language.serialize(serialize_field_args(node1)))
232
+ conflict.add_conflict(node2, GraphQL::Language.serialize(serialize_field_args(node2)))
233
+
234
+ @conflict_count += 1
215
235
  end
216
236
  end
217
237
 
@@ -313,7 +333,7 @@ module GraphQL
313
333
  selections.each do |node|
314
334
  case node
315
335
  when GraphQL::Language::Nodes::Field
316
- definition = context.schema.get_field(owner_type, node.name)
336
+ definition = context.query.get_field(owner_type, node.name)
317
337
  fields << Field.new(node, definition, owner_type, parents)
318
338
  when GraphQL::Language::Nodes::InlineFragment
319
339
  fragment_type = node.type ? context.warden.get_type(node.type.name) : owner_type
@@ -326,20 +346,19 @@ module GraphQL
326
346
  [fields, fragment_spreads]
327
347
  end
328
348
 
329
- def possible_arguments(field1, field2)
349
+ def same_arguments?(field1, field2)
330
350
  # Check for incompatible / non-identical arguments on this node:
331
- [field1, field2].map do |n|
332
- if n.arguments.any?
333
- serialized_args = {}
334
- n.arguments.each do |a|
335
- arg_value = a.value
336
- serialized_args[a.name] = serialize_arg(arg_value)
337
- end
338
- serialized_args
339
- else
340
- NO_ARGS
341
- end
342
- end.uniq
351
+ arguments1 = field1.arguments
352
+ arguments2 = field2.arguments
353
+
354
+ return false if arguments1.length != arguments2.length
355
+
356
+ arguments1.all? do |argument1|
357
+ argument2 = arguments2.find { |argument| argument.name == argument1.name }
358
+ return false if argument2.nil?
359
+
360
+ serialize_arg(argument1.value) == serialize_arg(argument2.value)
361
+ end
343
362
  end
344
363
 
345
364
  def serialize_arg(arg_value)
@@ -353,6 +372,14 @@ module GraphQL
353
372
  end
354
373
  end
355
374
 
375
+ def serialize_field_args(field)
376
+ serialized_args = {}
377
+ field.arguments.each do |argument|
378
+ serialized_args[argument.name] = serialize_arg(argument.value)
379
+ end
380
+ serialized_args
381
+ end
382
+
356
383
  def compared_fragments_key(frag1, frag2, exclusive)
357
384
  # Cache key to not compare two fragments more than once.
358
385
  # The key includes both fragment names sorted (this way we
@@ -365,17 +392,26 @@ module GraphQL
365
392
  # In this context, `parents` represends the "self scope" of the field,
366
393
  # what types may be found at this point in the query.
367
394
  def mutually_exclusive?(parents1, parents2)
368
- parents1.each do |type1|
369
- parents2.each do |type2|
370
- # If the types we're comparing are both different object types,
371
- # they have to be mutually exclusive.
372
- if type1 != type2 && type1.kind.object? && type2.kind.object?
373
- return true
395
+ if parents1.empty? || parents2.empty?
396
+ false
397
+ elsif parents1.length == parents2.length
398
+ parents1.length.times.any? do |i|
399
+ type1 = parents1[i - 1]
400
+ type2 = parents2[i - 1]
401
+ if type1 == type2
402
+ # If the types we're comparing are the same type,
403
+ # then they aren't mutually exclusive
404
+ false
405
+ else
406
+ # Check if these two scopes have _any_ types in common.
407
+ possible_right_types = context.query.possible_types(type1)
408
+ possible_left_types = context.query.possible_types(type2)
409
+ (possible_right_types & possible_left_types).empty?
374
410
  end
375
411
  end
412
+ else
413
+ true
376
414
  end
377
-
378
- false
379
415
  end
380
416
  end
381
417
  end
@@ -3,12 +3,33 @@ module GraphQL
3
3
  module StaticValidation
4
4
  class FieldsWillMergeError < StaticValidation::Error
5
5
  attr_reader :field_name
6
- attr_reader :conflicts
6
+ attr_reader :kind
7
+
8
+ def initialize(kind:, field_name:)
9
+ super(nil)
7
10
 
8
- def initialize(message, path: nil, nodes: [], field_name:, conflicts:)
9
- super(message, path: path, nodes: nodes)
10
11
  @field_name = field_name
11
- @conflicts = conflicts
12
+ @kind = kind
13
+ @conflicts = []
14
+ end
15
+
16
+ def message
17
+ "Field '#{field_name}' has #{kind == :argument ? 'an' : 'a'} #{kind} conflict: #{conflicts}?"
18
+ end
19
+
20
+ def path
21
+ []
22
+ end
23
+
24
+ def conflicts
25
+ @conflicts.join(' or ')
26
+ end
27
+
28
+ def add_conflict(node, conflict_str)
29
+ return if nodes.include?(node)
30
+
31
+ @nodes << node
32
+ @conflicts << conflict_str
12
33
  end
13
34
 
14
35
  # A hash representation of this Message
@@ -7,12 +7,12 @@ module GraphQL
7
7
  dependency_map = context.dependencies
8
8
  dependency_map.cyclical_definitions.each do |defn|
9
9
  if defn.node.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
10
- context.errors << GraphQL::StaticValidation::FragmentsAreFiniteError.new(
10
+ add_error(GraphQL::StaticValidation::FragmentsAreFiniteError.new(
11
11
  "Fragment #{defn.name} contains an infinite loop",
12
12
  nodes: defn.node,
13
13
  path: defn.path,
14
14
  name: defn.name
15
- )
15
+ ))
16
16
  end
17
17
  end
18
18
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ module InputObjectNamesAreUnique
5
+ def on_input_object(node, parent)
6
+ validate_input_fields(node)
7
+ super
8
+ end
9
+
10
+ private
11
+
12
+ def validate_input_fields(node)
13
+ input_field_defns = node.arguments
14
+ input_fields_by_name = Hash.new { |h, k| h[k] = [] }
15
+ input_field_defns.each { |a| input_fields_by_name[a.name] << a }
16
+
17
+ input_fields_by_name.each do |name, defns|
18
+ if defns.size > 1
19
+ error = GraphQL::StaticValidation::InputObjectNamesAreUniqueError.new(
20
+ "There can be only one input field named \"#{name}\"",
21
+ nodes: defns,
22
+ name: name
23
+ )
24
+ add_error(error)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class InputObjectNamesAreUniqueError < StaticValidation::Error
5
+ attr_reader :name
6
+
7
+ def initialize(message, path: nil, nodes: [], name:)
8
+ super(message, path: path, nodes: nodes)
9
+ @name = name
10
+ end
11
+
12
+ # A hash representation of this Message
13
+ def to_h
14
+ extensions = {
15
+ "code" => code,
16
+ "name" => name
17
+ }
18
+
19
+ super.merge({
20
+ "extensions" => extensions
21
+ })
22
+ end
23
+
24
+ def code
25
+ "inputFieldNotUnique"
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -18,7 +18,7 @@ module GraphQL
18
18
  def assert_required_args(ast_node, defn)
19
19
  present_argument_names = ast_node.arguments.map(&:name)
20
20
  required_argument_names = context.warden.arguments(defn)
21
- .select { |a| a.type.kind.non_null? && !a.default_value? }
21
+ .select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) }
22
22
  .map(&:name)
23
23
 
24
24
  missing_names = required_argument_names - present_argument_names
@@ -23,11 +23,10 @@ module GraphQL
23
23
  defn = if arg_defn && arg_defn.type.unwrap.kind.input_object?
24
24
  arg_defn.type.unwrap
25
25
  else
26
- context.field_definition
26
+ context.directive_definition || context.field_definition
27
27
  end
28
28
 
29
- parent_type = context.warden.arguments(defn)
30
- .find{|f| f.name == parent_name(parent, defn) }
29
+ parent_type = context.warden.get_argument(defn, parent_name(parent, defn))
31
30
  parent_type ? parent_type.type.unwrap : nil
32
31
  end
33
32
 
@@ -35,16 +34,16 @@ module GraphQL
35
34
  parent_type = get_parent_type(context, parent)
36
35
  return unless parent_type && parent_type.kind.input_object?
37
36
 
38
- required_fields = parent_type.arguments
39
- .select{|k,v| v.type.kind.non_null?}
40
- .keys
37
+ required_fields = context.warden.arguments(parent_type)
38
+ .select{|arg| arg.type.kind.non_null?}
39
+ .map(&:graphql_name)
41
40
 
42
41
  present_fields = ast_node.arguments.map(&:name)
43
42
  missing_fields = required_fields - present_fields
44
43
 
45
44
  missing_fields.each do |missing_field|
46
45
  path = [*context.path, missing_field]
47
- missing_field_type = parent_type.arguments[missing_field].type
46
+ missing_field_type = context.warden.get_argument(parent_type, missing_field).type
48
47
  add_error(RequiredInputObjectAttributesArePresentError.new(
49
48
  "Argument '#{missing_field}' on InputObject '#{parent_type.to_type_signature}' is required. Expected type #{missing_field_type.to_type_signature}",
50
49
  argument_name: missing_field,
@@ -17,23 +17,22 @@ module GraphQL
17
17
  if type.nil?
18
18
  # This is handled by another validator
19
19
  else
20
- begin
21
- valid = context.valid_literal?(value, type)
22
- rescue GraphQL::CoercionError => err
23
- error_message = err.message
24
- rescue GraphQL::LiteralValidationError
25
- # noop, we just want to stop any LiteralValidationError from propagating
26
- end
20
+ validation_result = context.validate_literal(value, type)
21
+
22
+ if !validation_result.valid?
23
+ problems = validation_result.problems
24
+ first_problem = problems && problems.first
25
+ if first_problem
26
+ error_message = first_problem["message"]
27
+ end
27
28
 
28
- if !valid
29
29
  error_message ||= "Default value for $#{node.name} doesn't match type #{type.to_type_signature}"
30
- VariableDefaultValuesAreCorrectlyTypedError
31
30
  add_error(GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError.new(
32
31
  error_message,
33
32
  nodes: node,
34
33
  name: node.name,
35
34
  type: type.to_type_signature,
36
- error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_TYPE]
35
+ error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_TYPE],
37
36
  ))
38
37
  end
39
38
  end
@@ -22,15 +22,15 @@ module GraphQL
22
22
  node_values = node_values.select { |value| value.is_a? GraphQL::Language::Nodes::VariableIdentifier }
23
23
 
24
24
  if node_values.any?
25
- arguments = case parent
25
+ argument_owner = case parent
26
26
  when GraphQL::Language::Nodes::Field
27
- context.field_definition.arguments
27
+ context.field_definition
28
28
  when GraphQL::Language::Nodes::Directive
29
- context.directive_definition.arguments
29
+ context.directive_definition
30
30
  when GraphQL::Language::Nodes::InputObject
31
31
  arg_type = context.argument_definition.type.unwrap
32
- if arg_type.is_a?(GraphQL::InputObjectType)
33
- arguments = arg_type.input_fields
32
+ if arg_type.kind.input_object?
33
+ arg_type
34
34
  else
35
35
  # This is some kind of error
36
36
  nil
@@ -43,7 +43,7 @@ module GraphQL
43
43
  var_defn_ast = @declared_variables[node_value.name]
44
44
  # Might be undefined :(
45
45
  # VariablesAreUsedAndDefined can't finalize its search until the end of the document.
46
- var_defn_ast && arguments && validate_usage(arguments, node, var_defn_ast)
46
+ var_defn_ast && argument_owner && validate_usage(argument_owner, node, var_defn_ast)
47
47
  end
48
48
  end
49
49
  super
@@ -51,7 +51,7 @@ module GraphQL
51
51
 
52
52
  private
53
53
 
54
- def validate_usage(arguments, arg_node, ast_var)
54
+ def validate_usage(argument_owner, arg_node, ast_var)
55
55
  var_type = context.schema.type_from_ast(ast_var.type, context: context)
56
56
  if var_type.nil?
57
57
  return
@@ -65,7 +65,7 @@ module GraphQL
65
65
  end
66
66
  end
67
67
 
68
- arg_defn = arguments[arg_node.name]
68
+ arg_defn = context.warden.get_argument(argument_owner, arg_node.name)
69
69
  arg_defn_type = arg_defn.type
70
70
 
71
71
  var_inner_type = var_type.unwrap
@@ -126,8 +126,9 @@ module GraphQL
126
126
  node_variables
127
127
  .select { |name, usage| usage.declared? && !usage.used? }
128
128
  .each { |var_name, usage|
129
+ declared_by_error_name = usage.declared_by.name || "anonymous #{usage.declared_by.operation_type}"
129
130
  add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new(
130
- "Variable $#{var_name} is declared by #{usage.declared_by.name} but not used",
131
+ "Variable $#{var_name} is declared by #{declared_by_error_name} but not used",
131
132
  nodes: usage.declared_by,
132
133
  path: usage.path,
133
134
  name: var_name,
@@ -139,8 +140,9 @@ module GraphQL
139
140
  node_variables
140
141
  .select { |name, usage| usage.used? && !usage.declared? }
141
142
  .each { |var_name, usage|
143
+ used_by_error_name = usage.used_by.name || "anonymous #{usage.used_by.operation_type}"
142
144
  add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new(
143
- "Variable $#{var_name} is used by #{usage.used_by.name} but not declared",
145
+ "Variable $#{var_name} is used by #{used_by_error_name} but not declared",
144
146
  nodes: usage.ast_node,
145
147
  path: usage.path,
146
148
  name: var_name,
@@ -15,14 +15,16 @@ module GraphQL
15
15
  extend Forwardable
16
16
 
17
17
  attr_reader :query, :errors, :visitor,
18
- :on_dependency_resolve_handlers
18
+ :on_dependency_resolve_handlers,
19
+ :max_errors
19
20
 
20
21
  def_delegators :@query, :schema, :document, :fragments, :operations, :warden
21
22
 
22
- def initialize(query, visitor_class)
23
+ def initialize(query, visitor_class, max_errors)
23
24
  @query = query
24
25
  @literal_validator = LiteralValidator.new(context: query.context)
25
26
  @errors = []
27
+ @max_errors = max_errors || Float::INFINITY
26
28
  @on_dependency_resolve_handlers = []
27
29
  @visitor = visitor_class.new(document, self)
28
30
  end
@@ -35,9 +37,13 @@ module GraphQL
35
37
  @on_dependency_resolve_handlers << handler
36
38
  end
37
39
 
38
- def valid_literal?(ast_value, type)
40
+ def validate_literal(ast_value, type)
39
41
  @literal_validator.validate(ast_value, type)
40
42
  end
43
+
44
+ def too_many_errors?
45
+ @errors.length >= @max_errors
46
+ end
41
47
  end
42
48
  end
43
49
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class ValidationTimeoutError < StaticValidation::Error
5
+ def initialize(message, path: nil, nodes: [])
6
+ super(message, path: path, nodes: nodes)
7
+ end
8
+
9
+ # A hash representation of this Message
10
+ def to_h
11
+ extensions = {
12
+ "code" => code
13
+ }
14
+
15
+ super.merge({
16
+ "extensions" => extensions
17
+ })
18
+ end
19
+
20
+ def code
21
+ "validationTimeout"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require "timeout"
3
+
2
4
  module GraphQL
3
5
  module StaticValidation
4
6
  # Initialized with a {GraphQL::Schema}, then it can validate {GraphQL::Language::Nodes::Documents}s based on that schema.
@@ -20,8 +22,11 @@ module GraphQL
20
22
 
21
23
  # Validate `query` against the schema. Returns an array of message hashes.
22
24
  # @param query [GraphQL::Query]
25
+ # @param validate [Boolean]
26
+ # @param timeout [Float] Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds.
27
+ # @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit.
23
28
  # @return [Array<Hash>]
24
- def validate(query, validate: true)
29
+ def validate(query, validate: true, timeout: nil, max_errors: nil)
25
30
  query.trace("validate", { validate: validate, query: query }) do
26
31
  can_skip_rewrite = query.context.interpreter? && query.schema.using_ast_analysis? && query.schema.is_a?(Class)
27
32
  errors = if validate == false && can_skip_rewrite
@@ -30,20 +35,35 @@ module GraphQL
30
35
  rules_to_use = validate ? @rules : []
31
36
  visitor_class = BaseVisitor.including_rules(rules_to_use, rewrite: !can_skip_rewrite)
32
37
 
33
- context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class)
38
+ context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class, max_errors)
39
+
40
+ begin
41
+ # CAUTION: Usage of the timeout module makes the assumption that validation rules are stateless Ruby code that requires no cleanup if process was interrupted. This means no blocking IO calls, native gems, locks, or `rescue` clauses that must be reached.
42
+ # A timeout value of 0 or nil will execute the block without any timeout.
43
+ Timeout::timeout(timeout) do
44
+ catch(:too_many_validation_errors) do
45
+ # Attach legacy-style rules.
46
+ # Only loop through rules if it has legacy-style rules
47
+ unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
48
+ legacy_rules.each do |rule_class_or_module|
49
+ if rule_class_or_module.method_defined?(:validate)
50
+ GraphQL::Deprecation.warn "Legacy validator rules will be removed from GraphQL-Ruby 2.0, use a module instead (see the built-in rules: https://github.com/rmosolgo/graphql-ruby/tree/master/lib/graphql/static_validation/rules)"
51
+ GraphQL::Deprecation.warn " -> Legacy validator: #{rule_class_or_module}"
52
+ rule_class_or_module.new.validate(context)
53
+ end
54
+ end
55
+ end
34
56
 
35
- # Attach legacy-style rules
36
- rules_to_use.each do |rule_class_or_module|
37
- if rule_class_or_module.method_defined?(:validate)
38
- rule_class_or_module.new.validate(context)
57
+ context.visitor.visit
58
+ end
39
59
  end
60
+ rescue Timeout::Error
61
+ handle_timeout(query, context)
40
62
  end
41
63
 
42
- context.visitor.visit
43
64
  context.errors
44
65
  end
45
66
 
46
-
47
67
  irep = if errors.empty? && context
48
68
  # Only return this if there are no errors and validation was actually run
49
69
  context.visitor.rewrite_document
@@ -56,6 +76,20 @@ module GraphQL
56
76
  irep: irep,
57
77
  }
58
78
  end
79
+ rescue GraphQL::ExecutionError => e
80
+ {
81
+ errors: [e],
82
+ irep: nil,
83
+ }
84
+ end
85
+
86
+ # Invoked when static validation times out.
87
+ # @param query [GraphQL::Query]
88
+ # @param context [GraphQL::StaticValidation::ValidationContext]
89
+ def handle_timeout(query, context)
90
+ context.errors << GraphQL::StaticValidation::ValidationTimeoutError.new(
91
+ "Timeout on validation of query"
92
+ )
59
93
  end
60
94
  end
61
95
  end
@@ -4,6 +4,7 @@ require "graphql/static_validation/definition_dependencies"
4
4
  require "graphql/static_validation/type_stack"
5
5
  require "graphql/static_validation/validator"
6
6
  require "graphql/static_validation/validation_context"
7
+ require "graphql/static_validation/validation_timeout_error"
7
8
  require "graphql/static_validation/literal_validator"
8
9
  require "graphql/static_validation/base_visitor"
9
10
  require "graphql/static_validation/no_validate_visitor"
@@ -1,10 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  class StringEncodingError < GraphQL::RuntimeTypeError
4
- attr_reader :string
5
- def initialize(str)
4
+ attr_reader :string, :field, :path
5
+ def initialize(str, context:)
6
6
  @string = str
7
- super("String \"#{str}\" was encoded as #{str.encoding}! GraphQL requires an encoding compatible with UTF-8.")
7
+ @field = context[:current_field]
8
+ @path = context[:current_path]
9
+ message = "String #{str.inspect} was encoded as #{str.encoding}".dup
10
+ if @path
11
+ message << " @ #{@path.join(".")}"
12
+ end
13
+ if @field
14
+ message << " (#{@field.path})"
15
+ end
16
+ message << ". GraphQL requires an encoding compatible with UTF-8."
17
+ super(message)
8
18
  end
9
19
  end
10
20
  end