graphql 1.8.7 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (368) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +2 -1
  3. data/lib/generators/graphql/scalar_generator.rb +20 -0
  4. data/lib/generators/graphql/templates/base_scalar.erb +4 -0
  5. data/lib/generators/graphql/templates/scalar.erb +13 -0
  6. data/lib/graphql/analysis/ast/analyzer.rb +62 -0
  7. data/lib/graphql/analysis/ast/field_usage.rb +28 -0
  8. data/lib/graphql/analysis/ast/max_query_complexity.rb +23 -0
  9. data/lib/graphql/analysis/ast/max_query_depth.rb +18 -0
  10. data/lib/graphql/analysis/ast/query_complexity.rb +114 -0
  11. data/lib/graphql/analysis/ast/query_depth.rb +66 -0
  12. data/lib/graphql/analysis/ast/visitor.rb +255 -0
  13. data/lib/graphql/analysis/ast.rb +82 -0
  14. data/lib/graphql/analysis.rb +1 -0
  15. data/lib/graphql/argument.rb +5 -0
  16. data/lib/graphql/authorization.rb +1 -0
  17. data/lib/graphql/backwards_compatibility.rb +1 -1
  18. data/lib/graphql/base_type.rb +1 -1
  19. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +1 -2
  20. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -6
  21. data/lib/graphql/dig.rb +19 -0
  22. data/lib/graphql/directive/include_directive.rb +1 -7
  23. data/lib/graphql/directive/skip_directive.rb +1 -8
  24. data/lib/graphql/directive.rb +13 -1
  25. data/lib/graphql/enum_type.rb +8 -0
  26. data/lib/graphql/execution/execute.rb +36 -17
  27. data/lib/graphql/execution/instrumentation.rb +2 -0
  28. data/lib/graphql/execution/interpreter/execution_errors.rb +29 -0
  29. data/lib/graphql/execution/interpreter/hash_response.rb +46 -0
  30. data/lib/graphql/execution/interpreter/resolve.rb +58 -0
  31. data/lib/graphql/execution/interpreter/runtime.rb +597 -0
  32. data/lib/graphql/execution/interpreter.rb +99 -0
  33. data/lib/graphql/execution/lazy.rb +8 -1
  34. data/lib/graphql/execution/lookahead.rb +351 -0
  35. data/lib/graphql/execution/multiplex.rb +37 -29
  36. data/lib/graphql/execution.rb +2 -0
  37. data/lib/graphql/execution_error.rb +1 -1
  38. data/lib/graphql/field.rb +1 -7
  39. data/lib/graphql/integer_encoding_error.rb +12 -0
  40. data/lib/graphql/internal_representation/rewrite.rb +127 -142
  41. data/lib/graphql/introspection/dynamic_fields.rb +8 -2
  42. data/lib/graphql/introspection/entry_points.rb +11 -6
  43. data/lib/graphql/introspection/enum_value_type.rb +4 -0
  44. data/lib/graphql/introspection/schema_type.rb +7 -2
  45. data/lib/graphql/introspection/type_type.rb +9 -5
  46. data/lib/graphql/invalid_null_error.rb +1 -1
  47. data/lib/graphql/language/block_string.rb +37 -0
  48. data/lib/graphql/language/document_from_schema_definition.rb +10 -7
  49. data/lib/graphql/language/lexer.rb +55 -36
  50. data/lib/graphql/language/lexer.rl +8 -3
  51. data/lib/graphql/language/nodes.rb +440 -362
  52. data/lib/graphql/language/parser.rb +56 -56
  53. data/lib/graphql/language/parser.y +12 -12
  54. data/lib/graphql/language/printer.rb +2 -2
  55. data/lib/graphql/language/visitor.rb +158 -15
  56. data/lib/graphql/language.rb +0 -1
  57. data/lib/graphql/literal_validation_error.rb +6 -0
  58. data/lib/graphql/query/arguments.rb +3 -2
  59. data/lib/graphql/query/arguments_cache.rb +1 -1
  60. data/lib/graphql/query/context.rb +14 -5
  61. data/lib/graphql/query/executor.rb +1 -1
  62. data/lib/graphql/query/result.rb +1 -1
  63. data/lib/graphql/query/validation_pipeline.rb +35 -11
  64. data/lib/graphql/query/variable_validation_error.rb +10 -1
  65. data/lib/graphql/query.rb +16 -2
  66. data/lib/graphql/relay/base_connection.rb +2 -0
  67. data/lib/graphql/relay/connection_instrumentation.rb +3 -1
  68. data/lib/graphql/relay/connection_resolve.rb +1 -1
  69. data/lib/graphql/relay/node.rb +2 -28
  70. data/lib/graphql/relay/relation_connection.rb +1 -1
  71. data/lib/graphql/schema/argument.rb +13 -5
  72. data/lib/graphql/schema/base_64_encoder.rb +4 -4
  73. data/lib/graphql/schema/build_from_definition.rb +2 -4
  74. data/lib/graphql/schema/default_type_error.rb +1 -1
  75. data/lib/graphql/schema/directive/feature.rb +66 -0
  76. data/lib/graphql/schema/directive/include.rb +25 -0
  77. data/lib/graphql/schema/directive/skip.rb +25 -0
  78. data/lib/graphql/schema/directive/transform.rb +48 -0
  79. data/lib/graphql/schema/directive.rb +103 -0
  80. data/lib/graphql/schema/enum_value.rb +3 -2
  81. data/lib/graphql/schema/field/connection_extension.rb +50 -0
  82. data/lib/graphql/schema/field/scope_extension.rb +18 -0
  83. data/lib/graphql/schema/field.rb +273 -64
  84. data/lib/graphql/schema/field_extension.rb +69 -0
  85. data/lib/graphql/schema/input_object.rb +16 -8
  86. data/lib/graphql/schema/interface.rb +1 -0
  87. data/lib/graphql/schema/loader.rb +22 -16
  88. data/lib/graphql/schema/member/base_dsl_methods.rb +8 -2
  89. data/lib/graphql/schema/member/build_type.rb +33 -1
  90. data/lib/graphql/schema/member/has_arguments.rb +6 -2
  91. data/lib/graphql/schema/member/has_fields.rb +18 -70
  92. data/lib/graphql/schema/member/has_path.rb +25 -0
  93. data/lib/graphql/schema/member/instrumentation.rb +10 -7
  94. data/lib/graphql/schema/member.rb +2 -0
  95. data/lib/graphql/schema/mutation.rb +6 -48
  96. data/lib/graphql/schema/non_null.rb +5 -1
  97. data/lib/graphql/schema/object.rb +18 -4
  98. data/lib/graphql/schema/printer.rb +1 -1
  99. data/lib/graphql/schema/relay_classic_mutation.rb +42 -9
  100. data/lib/graphql/schema/resolver/has_payload_type.rb +65 -0
  101. data/lib/graphql/schema/resolver.rb +45 -20
  102. data/lib/graphql/schema/subscription.rb +97 -0
  103. data/lib/graphql/schema/traversal.rb +11 -7
  104. data/lib/graphql/schema.rb +186 -38
  105. data/lib/graphql/static_validation/all_rules.rb +3 -2
  106. data/lib/graphql/static_validation/base_visitor.rb +199 -0
  107. data/lib/graphql/static_validation/default_visitor.rb +15 -0
  108. data/lib/graphql/static_validation/definition_dependencies.rb +62 -68
  109. data/lib/graphql/static_validation/{message.rb → error.rb} +11 -11
  110. data/lib/graphql/static_validation/interpreter_visitor.rb +14 -0
  111. data/lib/graphql/static_validation/literal_validator.rb +54 -11
  112. data/lib/graphql/static_validation/no_validate_visitor.rb +10 -0
  113. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +87 -16
  114. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +31 -0
  115. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +11 -11
  116. data/lib/graphql/static_validation/rules/argument_names_are_unique_error.rb +30 -0
  117. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +52 -8
  118. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +35 -0
  119. data/lib/graphql/static_validation/rules/directives_are_defined.rb +12 -15
  120. data/lib/graphql/static_validation/rules/directives_are_defined_error.rb +29 -0
  121. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +19 -14
  122. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations_error.rb +31 -0
  123. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +17 -19
  124. data/lib/graphql/static_validation/rules/fields_are_defined_on_type_error.rb +32 -0
  125. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +30 -14
  126. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections_error.rb +31 -0
  127. data/lib/graphql/static_validation/rules/fields_will_merge.rb +356 -29
  128. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +32 -0
  129. data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +20 -13
  130. data/lib/graphql/static_validation/rules/fragment_names_are_unique_error.rb +29 -0
  131. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +37 -29
  132. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible_error.rb +35 -0
  133. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +25 -17
  134. data/lib/graphql/static_validation/rules/fragment_types_exist_error.rb +29 -0
  135. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +12 -10
  136. data/lib/graphql/static_validation/rules/fragments_are_finite_error.rb +29 -0
  137. data/lib/graphql/static_validation/rules/fragments_are_named.rb +7 -11
  138. data/lib/graphql/static_validation/rules/fragments_are_named_error.rb +26 -0
  139. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +16 -16
  140. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb +30 -0
  141. data/lib/graphql/static_validation/rules/fragments_are_used.rb +21 -14
  142. data/lib/graphql/static_validation/rules/fragments_are_used_error.rb +29 -0
  143. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +10 -14
  144. data/lib/graphql/static_validation/rules/mutation_root_exists_error.rb +26 -0
  145. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +30 -30
  146. data/lib/graphql/static_validation/rules/no_definitions_are_present_error.rb +25 -0
  147. data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +25 -17
  148. data/lib/graphql/static_validation/rules/operation_names_are_valid_error.rb +28 -0
  149. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +17 -18
  150. data/lib/graphql/static_validation/rules/required_arguments_are_present_error.rb +35 -0
  151. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +47 -0
  152. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present_error.rb +35 -0
  153. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +10 -14
  154. data/lib/graphql/static_validation/rules/subscription_root_exists_error.rb +26 -0
  155. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +28 -17
  156. data/lib/graphql/static_validation/rules/unique_directives_per_location_error.rb +29 -0
  157. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +35 -27
  158. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed_error.rb +39 -0
  159. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +15 -14
  160. data/lib/graphql/static_validation/rules/variable_names_are_unique_error.rb +29 -0
  161. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +41 -30
  162. data/lib/graphql/static_validation/rules/variable_usages_are_allowed_error.rb +38 -0
  163. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +18 -14
  164. data/lib/graphql/static_validation/rules/variables_are_input_types_error.rb +32 -0
  165. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +73 -65
  166. data/lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb +37 -0
  167. data/lib/graphql/static_validation/validation_context.rb +8 -51
  168. data/lib/graphql/static_validation/validator.rb +23 -15
  169. data/lib/graphql/static_validation.rb +5 -3
  170. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -2
  171. data/lib/graphql/subscriptions/event.rb +28 -5
  172. data/lib/graphql/subscriptions/subscription_root.rb +66 -0
  173. data/lib/graphql/subscriptions.rb +16 -2
  174. data/lib/graphql/tracing/active_support_notifications_tracing.rb +0 -1
  175. data/lib/graphql/tracing/appsignal_tracing.rb +1 -1
  176. data/lib/graphql/tracing/data_dog_tracing.rb +1 -1
  177. data/lib/graphql/tracing/new_relic_tracing.rb +3 -3
  178. data/lib/graphql/tracing/platform_tracing.rb +17 -1
  179. data/lib/graphql/tracing/prometheus_tracing.rb +1 -1
  180. data/lib/graphql/tracing/scout_tracing.rb +1 -1
  181. data/lib/graphql/tracing/skylight_tracing.rb +3 -3
  182. data/lib/graphql/tracing.rb +8 -8
  183. data/lib/graphql/types/float.rb +1 -1
  184. data/lib/graphql/types/int.rb +11 -2
  185. data/lib/graphql/types/iso_8601_date_time.rb +15 -1
  186. data/lib/graphql/types/relay/base_connection.rb +15 -1
  187. data/lib/graphql/types/relay/node.rb +0 -1
  188. data/lib/graphql/types/relay/node_field.rb +43 -0
  189. data/lib/graphql/types/relay/nodes_field.rb +45 -0
  190. data/lib/graphql/types/relay.rb +2 -0
  191. data/lib/graphql/unauthorized_error.rb +4 -0
  192. data/lib/graphql/unauthorized_field_error.rb +23 -0
  193. data/lib/graphql/upgrader/member.rb +5 -0
  194. data/lib/graphql/version.rb +1 -1
  195. data/lib/graphql.rb +6 -1
  196. data/readme.md +7 -7
  197. data/spec/dummy/Gemfile +1 -1
  198. data/spec/dummy/Gemfile.lock +157 -0
  199. data/spec/dummy/app/channels/graphql_channel.rb +22 -11
  200. data/spec/dummy/config/locales/en.yml +1 -1
  201. data/spec/dummy/log/test.log +199 -0
  202. data/spec/dummy/test/test_helper.rb +1 -0
  203. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/4w/4wzXRZrAkwKdgYaSE0pid5eB-fer8vSfSku_NPg4rMA.cache +0 -0
  204. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/7I/7IHVBiJT06QSpgLpLoJIxboQ0B-D_tMTxsvoezBTV3Q.cache +1 -0
  205. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/8w/8wY_SKagj8wHuwGNAAf6JnQ8joMbC6cEYpHrTAI8Urc.cache +1 -0
  206. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/AK/AKzz1u6bGb4auXcrObA_g5LL-oV0ejNGa448AgAi_WQ.cache +1 -0
  207. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ET/ETW4uxvaYpruL8y6_ZptUH82ZowMaHIqvg5WexBFdEM.cache +3 -0
  208. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F1/F1TWpjjyA56k9Z90n5B3xRn7DUdGjX73QCkYC6k07JQ.cache +0 -0
  209. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F8/F8MUNRzORGFgr329fNM0xLaoWCXdv3BIalT7dsvLfjs.cache +2 -0
  210. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/KB/KB07ZaKNC5uXJ7TjLi-WqnY6g7dq8wWp_8N3HNjBNxg.cache +2 -0
  211. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Ms/MsKSimH_UCB-H1tLvDABDHuvGciuoW6kVqQWDrXU5FQ.cache +0 -0
  212. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Mt/Mtci-Kim50aPOmeClD4AIicKn1d1WJ0n454IjSd94sk.cache +0 -0
  213. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/QH/QHt3Tc1Y6M66Oo_pDuMyWrQNs4Pp3SMeZR5K1wJj2Ts.cache +1 -0
  214. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/XU/XU4k1OXnfMils5SrirorPvDSyDSqiOWLZNtmAH1HH8k.cache +0 -0
  215. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ZI/ZIof7mZxWWCnraIFOCuV6a8QRWzKJXJnx2Xd7C0ZyX0.cache +1 -0
  216. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/cG/cGc_puuPS5pZKgUcy1Y_i1L6jl5UtsiIrMH59rTzR6c.cache +3 -0
  217. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/df/dfro_B6bx3KP1Go-7jEOqqZ2j4hVRseXIc3es9PKQno.cache +1 -0
  218. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/jO/jO1DfbqnG0mTULsjJJANc3fefrG2zt7DIMmcptMT628.cache +1 -0
  219. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/pE/pE7gO6pQ-z187Swb4hT554wmqsq-cNzgPWLrCz-LQQQ.cache +0 -0
  220. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/r9/r9iU1l58a6rxkZSW5RSC52_tD-_UQuHxoMVnkfJ7Mhs.cache +1 -0
  221. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/xi/xitPPFfPIyDMpaznV0sBBcw8eSCV8PJcLLWin78sCgE.cache +0 -0
  222. data/spec/dummy/tmp/screenshots/failures_test_it_handles_subscriptions.png +0 -0
  223. data/spec/graphql/analysis/analyze_query_spec.rb +1 -1
  224. data/spec/graphql/analysis/ast/field_usage_spec.rb +51 -0
  225. data/spec/graphql/analysis/ast/max_query_complexity_spec.rb +120 -0
  226. data/spec/graphql/analysis/ast/max_query_depth_spec.rb +114 -0
  227. data/spec/graphql/analysis/ast/query_complexity_spec.rb +299 -0
  228. data/spec/graphql/analysis/ast/query_depth_spec.rb +108 -0
  229. data/spec/graphql/analysis/ast_spec.rb +269 -0
  230. data/spec/graphql/authorization_spec.rb +120 -23
  231. data/spec/graphql/base_type_spec.rb +6 -4
  232. data/spec/graphql/enum_type_spec.rb +6 -1
  233. data/spec/graphql/execution/execute_spec.rb +9 -9
  234. data/spec/graphql/execution/instrumentation_spec.rb +19 -0
  235. data/spec/graphql/execution/interpreter_spec.rb +485 -0
  236. data/spec/graphql/execution/lazy_spec.rb +67 -1
  237. data/spec/graphql/execution/lookahead_spec.rb +363 -0
  238. data/spec/graphql/execution/multiplex_spec.rb +31 -3
  239. data/spec/graphql/execution/typecast_spec.rb +20 -20
  240. data/spec/graphql/execution_error_spec.rb +110 -96
  241. data/spec/graphql/field_spec.rb +1 -1
  242. data/spec/graphql/input_object_type_spec.rb +13 -352
  243. data/spec/graphql/int_type_spec.rb +19 -0
  244. data/spec/graphql/interface_type_spec.rb +4 -4
  245. data/spec/graphql/internal_representation/rewrite_spec.rb +2 -0
  246. data/spec/graphql/introspection/input_value_type_spec.rb +1 -1
  247. data/spec/graphql/introspection/type_type_spec.rb +1 -2
  248. data/spec/graphql/language/document_from_schema_definition_spec.rb +2 -2
  249. data/spec/graphql/language/lexer_spec.rb +72 -3
  250. data/spec/graphql/language/nodes_spec.rb +20 -0
  251. data/spec/graphql/language/printer_spec.rb +18 -6
  252. data/spec/graphql/language/visitor_spec.rb +320 -14
  253. data/spec/graphql/non_null_type_spec.rb +1 -1
  254. data/spec/graphql/object_type_spec.rb +32 -27
  255. data/spec/graphql/query/arguments_spec.rb +21 -0
  256. data/spec/graphql/query/context_spec.rb +28 -0
  257. data/spec/graphql/query/executor_spec.rb +40 -36
  258. data/spec/graphql/query_spec.rb +12 -6
  259. data/spec/graphql/schema/argument_spec.rb +35 -1
  260. data/spec/graphql/schema/build_from_definition_spec.rb +144 -29
  261. data/spec/graphql/schema/catchall_middleware_spec.rb +16 -15
  262. data/spec/graphql/schema/directive/feature_spec.rb +81 -0
  263. data/spec/graphql/schema/directive/transform_spec.rb +39 -0
  264. data/spec/graphql/schema/enum_spec.rb +12 -3
  265. data/spec/graphql/schema/enum_value_spec.rb +11 -0
  266. data/spec/graphql/schema/field_extension_spec.rb +115 -0
  267. data/spec/graphql/schema/field_spec.rb +47 -7
  268. data/spec/graphql/schema/input_object_spec.rb +95 -0
  269. data/spec/graphql/schema/instrumentation_spec.rb +3 -0
  270. data/spec/graphql/schema/interface_spec.rb +8 -2
  271. data/spec/graphql/schema/introspection_system_spec.rb +9 -1
  272. data/spec/graphql/schema/loader_spec.rb +5 -0
  273. data/spec/graphql/schema/member/accepts_definition_spec.rb +4 -0
  274. data/spec/graphql/schema/member/build_type_spec.rb +46 -0
  275. data/spec/graphql/schema/member/scoped_spec.rb +19 -3
  276. data/spec/graphql/schema/mutation_spec.rb +5 -3
  277. data/spec/graphql/schema/object_spec.rb +9 -1
  278. data/spec/graphql/schema/printer_spec.rb +255 -93
  279. data/spec/graphql/schema/relay_classic_mutation_spec.rb +133 -0
  280. data/spec/graphql/schema/resolver_spec.rb +173 -9
  281. data/spec/graphql/schema/scalar_spec.rb +6 -0
  282. data/spec/graphql/schema/subscription_spec.rb +416 -0
  283. data/spec/graphql/schema/traversal_spec.rb +10 -10
  284. data/spec/graphql/schema/type_expression_spec.rb +2 -2
  285. data/spec/graphql/schema/union_spec.rb +7 -0
  286. data/spec/graphql/schema/validation_spec.rb +1 -1
  287. data/spec/graphql/schema/warden_spec.rb +145 -88
  288. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +213 -73
  289. data/spec/graphql/static_validation/rules/argument_names_are_unique_spec.rb +2 -2
  290. data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +72 -29
  291. data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
  292. data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
  293. data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +10 -5
  294. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +10 -5
  295. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +131 -5
  296. data/spec/graphql/static_validation/rules/fragment_names_are_unique_spec.rb +2 -1
  297. data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
  298. data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +4 -2
  299. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
  300. data/spec/graphql/static_validation/rules/fragments_are_named_spec.rb +2 -1
  301. data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +6 -3
  302. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +22 -2
  303. data/spec/graphql/static_validation/rules/mutation_root_exists_spec.rb +2 -1
  304. data/spec/graphql/static_validation/rules/operation_names_are_valid_spec.rb +6 -3
  305. data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +13 -4
  306. data/spec/graphql/static_validation/rules/required_input_object_attributes_are_present_spec.rb +58 -0
  307. data/spec/graphql/static_validation/rules/subscription_root_exists_spec.rb +2 -1
  308. data/spec/graphql/static_validation/rules/unique_directives_per_location_spec.rb +14 -7
  309. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +14 -7
  310. data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
  311. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +8 -4
  312. data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +23 -3
  313. data/spec/graphql/static_validation/type_stack_spec.rb +10 -19
  314. data/spec/graphql/static_validation/validator_spec.rb +50 -2
  315. data/spec/graphql/subscriptions_spec.rb +27 -16
  316. data/spec/graphql/tracing/new_relic_tracing_spec.rb +16 -0
  317. data/spec/graphql/tracing/platform_tracing_spec.rb +59 -37
  318. data/spec/graphql/tracing/prometheus_tracing_spec.rb +3 -0
  319. data/spec/graphql/tracing/skylight_tracing_spec.rb +16 -0
  320. data/spec/graphql/types/iso_8601_date_time_spec.rb +29 -2
  321. data/spec/graphql/union_type_spec.rb +2 -2
  322. data/spec/graphql/upgrader/member_spec.rb +67 -0
  323. data/spec/{graphql → integration/mongoid/graphql}/relay/mongo_relation_connection_spec.rb +11 -22
  324. data/spec/integration/mongoid/spec_helper.rb +2 -0
  325. data/spec/{support → integration/mongoid}/star_trek/data.rb +0 -0
  326. data/spec/{support → integration/mongoid}/star_trek/schema.rb +56 -34
  327. data/spec/{support/star_wars → integration/rails}/data.rb +1 -0
  328. data/spec/{support → integration/rails/generators}/base_generator_test.rb +0 -0
  329. data/spec/{generators → integration/rails/generators}/graphql/enum_generator_spec.rb +0 -0
  330. data/spec/{generators → integration/rails/generators}/graphql/install_generator_spec.rb +1 -1
  331. data/spec/{generators → integration/rails/generators}/graphql/interface_generator_spec.rb +0 -0
  332. data/spec/{generators → integration/rails/generators}/graphql/loader_generator_spec.rb +0 -0
  333. data/spec/{generators → integration/rails/generators}/graphql/mutation_generator_spec.rb +0 -0
  334. data/spec/{generators → integration/rails/generators}/graphql/object_generator_spec.rb +0 -0
  335. data/spec/integration/rails/generators/graphql/scalar_generator_spec.rb +28 -0
  336. data/spec/{generators → integration/rails/generators}/graphql/union_generator_spec.rb +0 -0
  337. data/spec/integration/rails/graphql/input_object_type_spec.rb +364 -0
  338. data/spec/{graphql → integration/rails/graphql}/query/variables_spec.rb +7 -7
  339. data/spec/{graphql → integration/rails/graphql}/relay/array_connection_spec.rb +9 -9
  340. data/spec/{graphql → integration/rails/graphql}/relay/base_connection_spec.rb +11 -3
  341. data/spec/{graphql → integration/rails/graphql}/relay/connection_instrumentation_spec.rb +19 -22
  342. data/spec/{graphql → integration/rails/graphql}/relay/connection_resolve_spec.rb +16 -0
  343. data/spec/{graphql → integration/rails/graphql}/relay/connection_type_spec.rb +0 -0
  344. data/spec/{graphql → integration/rails/graphql}/relay/edge_spec.rb +0 -0
  345. data/spec/{graphql → integration/rails/graphql}/relay/mutation_spec.rb +48 -0
  346. data/spec/{graphql → integration/rails/graphql}/relay/node_spec.rb +0 -0
  347. data/spec/{graphql → integration/rails/graphql}/relay/page_info_spec.rb +22 -22
  348. data/spec/{graphql → integration/rails/graphql}/relay/range_add_spec.rb +4 -4
  349. data/spec/{graphql → integration/rails/graphql}/relay/relation_connection_spec.rb +56 -27
  350. data/spec/{graphql → integration/rails/graphql}/schema_spec.rb +15 -11
  351. data/spec/{graphql → integration/rails/graphql}/tracing/active_support_notifications_tracing_spec.rb +16 -9
  352. data/spec/integration/rails/spec_helper.rb +25 -0
  353. data/spec/integration/tmp/app/graphql/types/family_type.rb +9 -0
  354. data/spec/spec_helper.rb +23 -39
  355. data/spec/support/dummy/data.rb +20 -17
  356. data/spec/support/dummy/schema.rb +315 -305
  357. data/spec/support/error_bubbling_helpers.rb +23 -0
  358. data/spec/support/jazz.rb +213 -46
  359. data/spec/support/lazy_helpers.rb +69 -27
  360. data/spec/support/new_relic.rb +3 -0
  361. data/spec/support/skylight.rb +3 -0
  362. data/spec/support/star_wars/schema.rb +131 -81
  363. data/spec/support/static_validation_helpers.rb +9 -5
  364. metadata +418 -261
  365. data/lib/graphql/language/comments.rb +0 -45
  366. data/lib/graphql/static_validation/arguments_validator.rb +0 -50
  367. data/spec/graphql/schema/member/has_fields_spec.rb +0 -129
  368. data/spec/rails_dependency_sanity_spec.rb +0 -14
@@ -1,47 +1,374 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # frozen_string_literal: true
2
4
  module GraphQL
3
5
  module StaticValidation
4
- class FieldsWillMerge
5
- # Special handling for fields without arguments
6
+ module FieldsWillMerge
7
+ # Validates that a selection set is valid if all fields (including spreading any
8
+ # fragments) either correspond to distinct response names or can be merged
9
+ # without ambiguity.
10
+ #
11
+ # Original Algorithm: https://github.com/graphql/graphql-js/blob/master/src/validation/rules/OverlappingFieldsCanBeMerged.js
6
12
  NO_ARGS = {}.freeze
13
+ Field = Struct.new(:node, :definition, :owner_type, :parents)
14
+ FragmentSpread = Struct.new(:name, :parents)
15
+
16
+ def initialize(*)
17
+ super
18
+ @visited_fragments = {}
19
+ @compared_fragments = {}
20
+ end
21
+
22
+ def on_operation_definition(node, _parent)
23
+ conflicts_within_selection_set(node, type_definition)
24
+ super
25
+ end
26
+
27
+ def on_field(node, _parent)
28
+ conflicts_within_selection_set(node, type_definition)
29
+ super
30
+ end
31
+
32
+ private
33
+
34
+ def conflicts_within_selection_set(node, parent_type)
35
+ return if parent_type.nil?
36
+
37
+ fields, fragment_spreads = fields_and_fragments_from_selection(node, owner_type: parent_type, parents: [])
38
+
39
+ # (A) Find find all conflicts "within" the fields of this selection set.
40
+ find_conflicts_within(fields)
41
+
42
+ fragment_spreads.each_with_index do |fragment_spread, i|
43
+ are_mutually_exclusive = mutually_exclusive?(
44
+ fragment_spread.parents,
45
+ [parent_type]
46
+ )
47
+
48
+ # (B) Then find conflicts between these fields and those represented by
49
+ # each spread fragment name found.
50
+ find_conflicts_between_fields_and_fragment(
51
+ fragment_spread,
52
+ fields,
53
+ mutually_exclusive: are_mutually_exclusive,
54
+ )
55
+
56
+ # (C) Then compare this fragment with all other fragments found in this
57
+ # selection set to collect conflicts between fragments spread together.
58
+ # This compares each item in the list of fragment names to every other
59
+ # item in that same list (except for itself).
60
+ fragment_spreads[i + 1..-1].each do |fragment_spread2|
61
+ are_mutually_exclusive = mutually_exclusive?(
62
+ fragment_spread.parents,
63
+ fragment_spread2.parents
64
+ )
65
+
66
+ find_conflicts_between_fragments(
67
+ fragment_spread,
68
+ fragment_spread2,
69
+ mutually_exclusive: are_mutually_exclusive,
70
+ )
71
+ end
72
+ end
73
+ end
74
+
75
+ def find_conflicts_between_fragments(fragment_spread1, fragment_spread2, mutually_exclusive:)
76
+ fragment_name1 = fragment_spread1.name
77
+ fragment_name2 = fragment_spread2.name
78
+ return if fragment_name1 == fragment_name2
79
+
80
+ cache_key = compared_fragments_key(
81
+ fragment_name1,
82
+ fragment_name2,
83
+ mutually_exclusive,
84
+ )
85
+ if @compared_fragments.key?(cache_key)
86
+ return
87
+ else
88
+ @compared_fragments[cache_key] = true
89
+ end
90
+
91
+ fragment1 = context.fragments[fragment_name1]
92
+ fragment2 = context.fragments[fragment_name2]
93
+
94
+ return if fragment1.nil? || fragment2.nil?
95
+
96
+ fragment_type1 = context.schema.types[fragment1.type.name]
97
+ fragment_type2 = context.schema.types[fragment2.type.name]
98
+
99
+ return if fragment_type1.nil? || fragment_type2.nil?
100
+
101
+ fragment_fields1, fragment_spreads1 = fields_and_fragments_from_selection(
102
+ fragment1,
103
+ owner_type: fragment_type1,
104
+ parents: [*fragment_spread1.parents, fragment_type1]
105
+ )
106
+ fragment_fields2, fragment_spreads2 = fields_and_fragments_from_selection(
107
+ fragment2,
108
+ owner_type: fragment_type1,
109
+ parents: [*fragment_spread2.parents, fragment_type2]
110
+ )
111
+
112
+ # (F) First, find all conflicts between these two collections of fields
113
+ # (not including any nested fragments).
114
+ find_conflicts_between(
115
+ fragment_fields1,
116
+ fragment_fields2,
117
+ mutually_exclusive: mutually_exclusive,
118
+ )
119
+
120
+ # (G) Then collect conflicts between the first fragment and any nested
121
+ # fragments spread in the second fragment.
122
+ fragment_spreads2.each do |fragment_spread|
123
+ find_conflicts_between_fragments(
124
+ fragment_spread1,
125
+ fragment_spread,
126
+ mutually_exclusive: mutually_exclusive,
127
+ )
128
+ end
129
+
130
+ # (G) Then collect conflicts between the first fragment and any nested
131
+ # fragments spread in the second fragment.
132
+ fragment_spreads1.each do |fragment_spread|
133
+ find_conflicts_between_fragments(
134
+ fragment_spread2,
135
+ fragment_spread,
136
+ mutually_exclusive: mutually_exclusive,
137
+ )
138
+ end
139
+ end
140
+
141
+ def find_conflicts_between_fields_and_fragment(fragment_spread, fields, mutually_exclusive:)
142
+ fragment_name = fragment_spread.name
143
+ return if @visited_fragments.key?(fragment_name)
144
+ @visited_fragments[fragment_name] = true
145
+
146
+ fragment = context.fragments[fragment_name]
147
+ return if fragment.nil?
148
+
149
+ fragment_type = context.schema.types[fragment.type.name]
150
+ return if fragment_type.nil?
151
+
152
+ fragment_fields, fragment_spreads = fields_and_fragments_from_selection(fragment, owner_type: fragment_type, parents: [*fragment_spread.parents, fragment_type])
153
+
154
+ # (D) First find any conflicts between the provided collection of fields
155
+ # and the collection of fields represented by the given fragment.
156
+ find_conflicts_between(
157
+ fields,
158
+ fragment_fields,
159
+ mutually_exclusive: mutually_exclusive,
160
+ )
161
+
162
+ # (E) Then collect any conflicts between the provided collection of fields
163
+ # and any fragment names found in the given fragment.
164
+ fragment_spreads.each do |fragment_spread|
165
+ find_conflicts_between_fields_and_fragment(
166
+ fragment_spread,
167
+ fields,
168
+ mutually_exclusive: mutually_exclusive,
169
+ )
170
+ end
171
+ end
172
+
173
+ def find_conflicts_within(response_keys)
174
+ response_keys.each do |key, fields|
175
+ next if fields.size < 2
176
+ # find conflicts within nodes
177
+ for i in 0..fields.size - 1
178
+ for j in i + 1..fields.size - 1
179
+ find_conflict(key, fields[i], fields[j])
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ def find_conflict(response_key, field1, field2, mutually_exclusive: false)
186
+ node1 = field1.node
187
+ node2 = field2.node
188
+
189
+ are_mutually_exclusive = mutually_exclusive ||
190
+ mutually_exclusive?(field1.parents, field2.parents)
191
+
192
+ if !are_mutually_exclusive
193
+ 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
+ )
203
+ end
204
+
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
+ )
215
+ end
216
+ end
217
+
218
+ find_conflicts_between_sub_selection_sets(
219
+ field1,
220
+ field2,
221
+ mutually_exclusive: are_mutually_exclusive,
222
+ )
223
+ end
224
+
225
+ def find_conflicts_between_sub_selection_sets(field1, field2, mutually_exclusive:)
226
+ return if field1.definition.nil? || field2.definition.nil?
227
+
228
+ return_type1 = field1.definition.type.unwrap
229
+ return_type2 = field2.definition.type.unwrap
230
+ parents1 = [return_type1]
231
+ parents2 = [return_type2]
232
+
233
+ fields, fragment_spreads = fields_and_fragments_from_selection(
234
+ field1.node,
235
+ owner_type: return_type1,
236
+ parents: parents1
237
+ )
238
+
239
+ fields2, fragment_spreads2 = fields_and_fragments_from_selection(
240
+ field2.node,
241
+ owner_type: return_type2,
242
+ parents: parents2
243
+ )
244
+
245
+ # (H) First, collect all conflicts between these two collections of field.
246
+ find_conflicts_between(fields, fields2, mutually_exclusive: mutually_exclusive)
247
+
248
+ # (I) Then collect conflicts between the first collection of fields and
249
+ # those referenced by each fragment name associated with the second.
250
+ fragment_spreads2.each do |fragment_spread|
251
+ find_conflicts_between_fields_and_fragment(
252
+ fragment_spread,
253
+ fields,
254
+ mutually_exclusive: mutually_exclusive,
255
+ )
256
+ end
257
+
258
+ # (I) Then collect conflicts between the second collection of fields and
259
+ # those referenced by each fragment name associated with the first.
260
+ fragment_spreads.each do |fragment_spread|
261
+ find_conflicts_between_fields_and_fragment(
262
+ fragment_spread,
263
+ fields2,
264
+ mutually_exclusive: mutually_exclusive,
265
+ )
266
+ end
7
267
 
8
- def validate(context)
9
- context.each_irep_node do |node|
10
- if node.ast_nodes.size > 1
11
- defn_names = Set.new(node.ast_nodes.map(&:name))
268
+ # (J) Also collect conflicts between any fragment names by the first and
269
+ # fragment names by the second. This compares each item in the first set of
270
+ # names to each item in the second set of names.
271
+ fragment_spreads.each do |frag1|
272
+ fragment_spreads2.each do |frag2|
273
+ find_conflicts_between_fragments(
274
+ frag1,
275
+ frag2,
276
+ mutually_exclusive: mutually_exclusive
277
+ )
278
+ end
279
+ end
280
+ end
12
281
 
13
- # Check for more than one GraphQL::Field backing this node:
14
- if defn_names.size > 1
15
- defn_names = defn_names.sort.join(" or ")
16
- msg = "Field '#{node.name}' has a field conflict: #{defn_names}?"
17
- context.errors << GraphQL::StaticValidation::Message.new(msg, nodes: node.ast_nodes.to_a)
282
+ def find_conflicts_between(response_keys, response_keys2, mutually_exclusive:)
283
+ response_keys.each do |key, fields|
284
+ fields2 = response_keys2[key]
285
+ if fields2
286
+ fields.each do |field|
287
+ fields2.each do |field2|
288
+ find_conflict(
289
+ key,
290
+ field,
291
+ field2,
292
+ mutually_exclusive: mutually_exclusive,
293
+ )
294
+ end
18
295
  end
296
+ end
297
+ end
298
+ end
299
+
300
+ NO_SELECTIONS = [{}.freeze, [].freeze].freeze
301
+
302
+ def fields_and_fragments_from_selection(node, owner_type:, parents:)
303
+ if node.selections.empty?
304
+ NO_SELECTIONS
305
+ else
306
+ fields, fragment_spreads = find_fields_and_fragments(node.selections, owner_type: owner_type, parents: parents, fields: [], fragment_spreads: [])
307
+ response_keys = fields.group_by { |f| f.node.alias || f.node.name }
308
+ [response_keys, fragment_spreads]
309
+ end
310
+ end
19
311
 
20
- # Check for incompatible / non-identical arguments on this node:
21
- args = node.ast_nodes.map do |n|
22
- if n.arguments.any?
23
- n.arguments.reduce({}) do |memo, a|
24
- arg_value = a.value
25
- memo[a.name] = case arg_value
26
- when GraphQL::Language::Nodes::AbstractNode
27
- arg_value.to_query_string
28
- else
29
- GraphQL::Language.serialize(arg_value)
30
- end
31
- memo
32
- end
312
+ def find_fields_and_fragments(selections, owner_type:, parents:, fields:, fragment_spreads:)
313
+ selections.each do |node|
314
+ case node
315
+ when GraphQL::Language::Nodes::Field
316
+ definition = context.schema.get_field(owner_type, node.name)
317
+ fields << Field.new(node, definition, owner_type, parents)
318
+ when GraphQL::Language::Nodes::InlineFragment
319
+ fragment_type = node.type ? context.schema.types[node.type.name] : owner_type
320
+ find_fields_and_fragments(node.selections, parents: [*parents, fragment_type], owner_type: owner_type, fields: fields, fragment_spreads: fragment_spreads) if fragment_type
321
+ when GraphQL::Language::Nodes::FragmentSpread
322
+ fragment_spreads << FragmentSpread.new(node.name, parents)
323
+ end
324
+ end
325
+
326
+ [fields, fragment_spreads]
327
+ end
328
+
329
+ def possible_arguments(field1, field2)
330
+ # Check for incompatible / non-identical arguments on this node:
331
+ [field1, field2].map do |n|
332
+ if n.arguments.any?
333
+ n.arguments.reduce({}) do |memo, a|
334
+ arg_value = a.value
335
+ memo[a.name] = case arg_value
336
+ when GraphQL::Language::Nodes::AbstractNode
337
+ arg_value.to_query_string
33
338
  else
34
- NO_ARGS
339
+ GraphQL::Language.serialize(arg_value)
35
340
  end
341
+ memo
36
342
  end
37
- args.uniq!
343
+ else
344
+ NO_ARGS
345
+ end
346
+ end.uniq
347
+ end
38
348
 
39
- if args.length > 1
40
- msg = "Field '#{node.name}' has an argument conflict: #{args.map{ |arg| GraphQL::Language.serialize(arg) }.join(" or ")}?"
41
- context.errors << GraphQL::StaticValidation::Message.new(msg, nodes: node.ast_nodes.to_a)
349
+ def compared_fragments_key(frag1, frag2, exclusive)
350
+ # Cache key to not compare two fragments more than once.
351
+ # The key includes both fragment names sorted (this way we
352
+ # avoid computing "A vs B" and "B vs A"). It also includes
353
+ # "exclusive" since the result may change depending on the parent_type
354
+ "#{[frag1, frag2].sort.join('-')}-#{exclusive}"
355
+ end
356
+
357
+ # Given two list of parents, find out if they are mutually exclusive
358
+ # In this context, `parents` represends the "self scope" of the field,
359
+ # what types may be found at this point in the query.
360
+ def mutually_exclusive?(parents1, parents2)
361
+ parents1.each do |type1|
362
+ parents2.each do |type2|
363
+ # If the types we're comparing are both different object types,
364
+ # they have to be mutually exclusive.
365
+ if type1 != type2 && type1.kind.object? && type2.kind.object?
366
+ return true
42
367
  end
43
368
  end
44
369
  end
370
+
371
+ false
45
372
  end
46
373
  end
47
374
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class FieldsWillMergeError < StaticValidation::Error
5
+ attr_reader :field_name
6
+ attr_reader :conflicts
7
+
8
+ def initialize(message, path: nil, nodes: [], field_name:, conflicts:)
9
+ super(message, path: path, nodes: nodes)
10
+ @field_name = field_name
11
+ @conflicts = conflicts
12
+ end
13
+
14
+ # A hash representation of this Message
15
+ def to_h
16
+ extensions = {
17
+ "code" => code,
18
+ "fieldName" => field_name,
19
+ "conflicts" => conflicts
20
+ }
21
+
22
+ super.merge({
23
+ "extensions" => extensions
24
+ })
25
+ end
26
+
27
+ def code
28
+ "fieldConflict"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,22 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  module StaticValidation
4
- class FragmentNamesAreUnique
5
- include GraphQL::StaticValidation::Message::MessageHelper
4
+ module FragmentNamesAreUnique
6
5
 
7
- def validate(context)
8
- fragments_by_name = Hash.new { |h, k| h[k] = [] }
9
- context.visitor[GraphQL::Language::Nodes::FragmentDefinition] << ->(node, parent) {
10
- fragments_by_name[node.name] << node
11
- }
6
+ def initialize(*)
7
+ super
8
+ @fragments_by_name = Hash.new { |h, k| h[k] = [] }
9
+ end
10
+
11
+ def on_fragment_definition(node, parent)
12
+ @fragments_by_name[node.name] << node
13
+ super
14
+ end
12
15
 
13
- context.visitor[GraphQL::Language::Nodes::Document].leave << ->(node, parent) {
14
- fragments_by_name.each do |name, fragments|
15
- if fragments.length > 1
16
- context.errors << message(%|Fragment name "#{name}" must be unique|, fragments, context: context)
17
- end
16
+ def on_document(_n, _p)
17
+ super
18
+ @fragments_by_name.each do |name, fragments|
19
+ if fragments.length > 1
20
+ add_error(GraphQL::StaticValidation::FragmentNamesAreUniqueError.new(
21
+ %|Fragment name "#{name}" must be unique|,
22
+ nodes: fragments,
23
+ name: name
24
+ ))
18
25
  end
19
- }
26
+ end
20
27
  end
21
28
  end
22
29
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class FragmentNamesAreUniqueError < StaticValidation::Error
5
+ attr_reader :fragment_name
6
+
7
+ def initialize(message, path: nil, nodes: [], name:)
8
+ super(message, path: path, nodes: nodes)
9
+ @fragment_name = name
10
+ end
11
+
12
+ # A hash representation of this Message
13
+ def to_h
14
+ extensions = {
15
+ "code" => code,
16
+ "fragmentName" => fragment_name
17
+ }
18
+
19
+ super.merge({
20
+ "extensions" => extensions
21
+ })
22
+ end
23
+
24
+ def code
25
+ "fragmentNotUnique"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,39 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  module StaticValidation
4
- class FragmentSpreadsArePossible
5
- include GraphQL::StaticValidation::Message::MessageHelper
6
-
7
- def validate(context)
8
-
9
- context.visitor[GraphQL::Language::Nodes::InlineFragment] << ->(node, parent) {
10
- fragment_parent = context.object_types[-2]
11
- fragment_child = context.object_types.last
12
- if fragment_child
13
- validate_fragment_in_scope(fragment_parent, fragment_child, node, context, context.path)
14
- end
15
- }
4
+ module FragmentSpreadsArePossible
5
+ def initialize(*)
6
+ super
7
+ @spreads_to_validate = []
8
+ end
16
9
 
17
- spreads_to_validate = []
10
+ def on_inline_fragment(node, parent)
11
+ fragment_parent = context.object_types[-2]
12
+ fragment_child = context.object_types.last
13
+ if fragment_child
14
+ validate_fragment_in_scope(fragment_parent, fragment_child, node, context, context.path)
15
+ end
16
+ super
17
+ end
18
18
 
19
- context.visitor[GraphQL::Language::Nodes::FragmentSpread] << ->(node, parent) {
20
- fragment_parent = context.object_types.last
21
- spreads_to_validate << FragmentSpread.new(node: node, parent_type: fragment_parent, path: context.path)
22
- }
19
+ def on_fragment_spread(node, parent)
20
+ fragment_parent = context.object_types.last
21
+ @spreads_to_validate << FragmentSpread.new(node: node, parent_type: fragment_parent, path: context.path)
22
+ super
23
+ end
23
24
 
24
- context.visitor[GraphQL::Language::Nodes::Document].leave << ->(doc_node, parent) {
25
- spreads_to_validate.each do |frag_spread|
26
- frag_node = context.fragments[frag_spread.node.name]
27
- if frag_node
28
- fragment_child_name = frag_node.type.name
29
- fragment_child = context.warden.get_type(fragment_child_name)
30
- # Might be non-existent type name
31
- if fragment_child
32
- validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path)
33
- end
25
+ def on_document(node, parent)
26
+ super
27
+ @spreads_to_validate.each do |frag_spread|
28
+ frag_node = context.fragments[frag_spread.node.name]
29
+ if frag_node
30
+ fragment_child_name = frag_node.type.name
31
+ fragment_child = context.warden.get_type(fragment_child_name)
32
+ # Might be non-existent type name
33
+ if fragment_child
34
+ validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path)
34
35
  end
35
36
  end
36
- }
37
+ end
37
38
  end
38
39
 
39
40
  private
@@ -48,7 +49,14 @@ module GraphQL
48
49
 
49
50
  if child_types.none? { |c| parent_types.include?(c) }
50
51
  name = node.respond_to?(:name) ? " #{node.name}" : ""
51
- context.errors << message("Fragment#{name} on #{child_type.name} can't be spread inside #{parent_type.name}", node, path: path)
52
+ add_error(GraphQL::StaticValidation::FragmentSpreadsArePossibleError.new(
53
+ "Fragment#{name} on #{child_type.name} can't be spread inside #{parent_type.name}",
54
+ nodes: node,
55
+ path: path,
56
+ fragment_name: name.empty? ? "unknown" : name,
57
+ type: child_type.name,
58
+ parent: parent_type.name
59
+ ))
52
60
  end
53
61
  end
54
62
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class FragmentSpreadsArePossibleError < StaticValidation::Error
5
+ attr_reader :type_name
6
+ attr_reader :fragment_name
7
+ attr_reader :parent_name
8
+
9
+ def initialize(message, path: nil, nodes: [], type:, fragment_name:, parent:)
10
+ super(message, path: path, nodes: nodes)
11
+ @type_name = type
12
+ @fragment_name = fragment_name
13
+ @parent_name = parent
14
+ end
15
+
16
+ # A hash representation of this Message
17
+ def to_h
18
+ extensions = {
19
+ "code" => code,
20
+ "typeName" => type_name,
21
+ "fragmentName" => fragment_name,
22
+ "parentName" => parent_name
23
+ }
24
+
25
+ super.merge({
26
+ "extensions" => extensions
27
+ })
28
+ end
29
+
30
+ def code
31
+ "cannotSpreadFragment"
32
+ end
33
+ end
34
+ end
35
+ end