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
@@ -0,0 +1,597 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Execution
5
+ class Interpreter
6
+ # I think it would be even better if we could somehow make
7
+ # `continue_field` not recursive. "Trampolining" it somehow.
8
+ #
9
+ # @api private
10
+ class Runtime
11
+ # @return [GraphQL::Query]
12
+ attr_reader :query
13
+
14
+ # @return [Class]
15
+ attr_reader :schema
16
+
17
+ # @return [GraphQL::Query::Context]
18
+ attr_reader :context
19
+
20
+ def initialize(query:, response:)
21
+ @query = query
22
+ @schema = query.schema
23
+ @context = query.context
24
+ @interpreter_context = @context.namespace(:interpreter)
25
+ @response = response
26
+ @dead_paths = {}
27
+ @types_at_paths = {}
28
+ # A cache of { Class => { String => Schema::Field } }
29
+ # Which assumes that MyObject.get_field("myField") will return the same field
30
+ # during the lifetime of a query
31
+ @fields_cache = Hash.new { |h, k| h[k] = {} }
32
+ end
33
+
34
+ def final_value
35
+ @response.final_value
36
+ end
37
+
38
+ def inspect
39
+ "#<#{self.class.name} response=#{@response.inspect}>"
40
+ end
41
+
42
+ # This _begins_ the execution. Some deferred work
43
+ # might be stored up in lazies.
44
+ # @return [void]
45
+ def run_eager
46
+ root_operation = query.selected_operation
47
+ root_op_type = root_operation.operation_type || "query"
48
+ legacy_root_type = schema.root_type_for_operation(root_op_type)
49
+ root_type = legacy_root_type.metadata[:type_class] || raise("Invariant: type must be class-based: #{legacy_root_type}")
50
+ object_proxy = root_type.authorized_new(query.root_value, context)
51
+ object_proxy = schema.sync_lazy(object_proxy)
52
+ path = []
53
+ evaluate_selections(path, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
54
+ nil
55
+ end
56
+
57
+ def gather_selections(owner_object, owner_type, selections, selections_by_name)
58
+ selections.each do |node|
59
+ # Skip gathering this if the directive says so
60
+ if !directives_include?(node, owner_object, owner_type)
61
+ next
62
+ end
63
+
64
+ case node
65
+ when GraphQL::Language::Nodes::Field
66
+ response_key = node.alias || node.name
67
+ selections = selections_by_name[response_key]
68
+ # if there was already a selection of this field,
69
+ # use an array to hold all selections,
70
+ # otherise, use the single node to represent the selection
71
+ if selections
72
+ # This field was already selected at least once,
73
+ # add this node to the list of selections
74
+ s = Array(selections)
75
+ s << node
76
+ selections_by_name[response_key] = s
77
+ else
78
+ # No selection was found for this field yet
79
+ selections_by_name[response_key] = node
80
+ end
81
+ when GraphQL::Language::Nodes::InlineFragment
82
+ if node.type
83
+ type_defn = schema.types[node.type.name]
84
+ type_defn = type_defn.metadata[:type_class]
85
+ # Faster than .map{}.include?()
86
+ query.warden.possible_types(type_defn).each do |t|
87
+ if t.metadata[:type_class] == owner_type
88
+ gather_selections(owner_object, owner_type, node.selections, selections_by_name)
89
+ break
90
+ end
91
+ end
92
+ else
93
+ # it's an untyped fragment, definitely continue
94
+ gather_selections(owner_object, owner_type, node.selections, selections_by_name)
95
+ end
96
+ when GraphQL::Language::Nodes::FragmentSpread
97
+ fragment_def = query.fragments[node.name]
98
+ type_defn = schema.types[fragment_def.type.name]
99
+ type_defn = type_defn.metadata[:type_class]
100
+ schema.possible_types(type_defn).each do |t|
101
+ if t.metadata[:type_class] == owner_type
102
+ gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name)
103
+ break
104
+ end
105
+ end
106
+ else
107
+ raise "Invariant: unexpected selection class: #{node.class}"
108
+ end
109
+ end
110
+ end
111
+
112
+ def evaluate_selections(path, owner_object, owner_type, selections, root_operation_type: nil)
113
+ selections_by_name = {}
114
+ gather_selections(owner_object, owner_type, selections, selections_by_name)
115
+ selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
116
+ # As a performance optimization, the hash key will be a `Node` if
117
+ # there's only one selection of the field. But if there are multiple
118
+ # selections of the field, it will be an Array of nodes
119
+ if field_ast_nodes_or_ast_node.is_a?(Array)
120
+ field_ast_nodes = field_ast_nodes_or_ast_node
121
+ ast_node = field_ast_nodes.first
122
+ else
123
+ field_ast_nodes = nil
124
+ ast_node = field_ast_nodes_or_ast_node
125
+ end
126
+ field_name = ast_node.name
127
+ field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
128
+ is_introspection = false
129
+ if field_defn.nil?
130
+ field_defn = if owner_type == schema.query.metadata[:type_class] && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
131
+ is_introspection = true
132
+ entry_point_field.metadata[:type_class]
133
+ elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
134
+ is_introspection = true
135
+ dynamic_field.metadata[:type_class]
136
+ else
137
+ raise "Invariant: no field for #{owner_type}.#{field_name}"
138
+ end
139
+ end
140
+
141
+ return_type = resolve_if_late_bound_type(field_defn.type)
142
+
143
+ next_path = path.dup
144
+ next_path << result_name
145
+ next_path.freeze
146
+
147
+ # This seems janky, but we need to know
148
+ # the field's return type at this path in order
149
+ # to propagate `null`
150
+ set_type_at_path(next_path, return_type)
151
+ # Set this before calling `run_with_directives`, so that the directive can have the latest path
152
+ @interpreter_context[:current_path] = next_path
153
+ @interpreter_context[:current_field] = field_defn
154
+
155
+ object = owner_object
156
+
157
+ if is_introspection
158
+ object = field_defn.owner.authorized_new(object, context)
159
+ end
160
+
161
+
162
+ kwarg_arguments = arguments(object, field_defn, ast_node)
163
+ # It might turn out that making arguments for every field is slow.
164
+ # If we have to cache them, we'll need a more subtle approach here.
165
+ field_defn.extras.each do |extra|
166
+ case extra
167
+ when :ast_node
168
+ kwarg_arguments[:ast_node] = ast_node
169
+ when :execution_errors
170
+ kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
171
+ when :path
172
+ kwarg_arguments[:path] = next_path
173
+ when :lookahead
174
+ if !field_ast_nodes
175
+ field_ast_nodes = [ast_node]
176
+ end
177
+ kwarg_arguments[:lookahead] = Execution::Lookahead.new(
178
+ query: query,
179
+ ast_nodes: field_ast_nodes,
180
+ field: field_defn,
181
+ )
182
+ else
183
+ kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
184
+ end
185
+ end
186
+
187
+ # Optimize for the case that field is selected only once
188
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
189
+ next_selections = ast_node.selections
190
+ else
191
+ next_selections = []
192
+ field_ast_nodes.each { |f| next_selections.concat(f.selections) }
193
+ end
194
+
195
+ field_result = resolve_with_directives(object, ast_node) do
196
+ # Actually call the field resolver and capture the result
197
+ app_result = query.trace("execute_field", {field: field_defn, path: next_path}) do
198
+ field_defn.resolve(object, kwarg_arguments, context)
199
+ end
200
+ after_lazy(app_result, field: field_defn, path: next_path) do |inner_result|
201
+ continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node)
202
+ if HALT != continue_value
203
+ continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false)
204
+ end
205
+ end
206
+ end
207
+
208
+ # If this field is a root mutation field, immediately resolve
209
+ # all of its child fields before moving on to the next root mutation field.
210
+ # (Subselections of this mutation will still be resolved level-by-level.)
211
+ if root_operation_type == "mutation"
212
+ Interpreter::Resolve.resolve_all([field_result])
213
+ else
214
+ field_result
215
+ end
216
+ end
217
+ end
218
+
219
+ HALT = Object.new
220
+ def continue_value(path, value, field, is_non_null, ast_node)
221
+ if value.nil?
222
+ if is_non_null
223
+ err = GraphQL::InvalidNullError.new(field.owner, field, value)
224
+ write_invalid_null_in_response(path, err)
225
+ else
226
+ write_in_response(path, nil)
227
+ end
228
+ HALT
229
+ elsif value.is_a?(GraphQL::ExecutionError)
230
+ value.path ||= path
231
+ value.ast_node ||= ast_node
232
+ write_execution_errors_in_response(path, [value])
233
+ HALT
234
+ elsif value.is_a?(Array) && value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
235
+ value.each do |v|
236
+ v.path ||= path
237
+ v.ast_node ||= ast_node
238
+ end
239
+ write_execution_errors_in_response(path, value)
240
+ HALT
241
+ elsif value.is_a?(GraphQL::UnauthorizedError)
242
+ # this hook might raise & crash, or it might return
243
+ # a replacement value
244
+ next_value = begin
245
+ schema.unauthorized_object(value)
246
+ rescue GraphQL::ExecutionError => err
247
+ err
248
+ end
249
+
250
+ continue_value(path, next_value, field, is_non_null, ast_node)
251
+ elsif GraphQL::Execution::Execute::SKIP == value
252
+ HALT
253
+ else
254
+ value
255
+ end
256
+ end
257
+
258
+ # The resolver for `field` returned `value`. Continue to execute the query,
259
+ # treating `value` as `type` (probably the return type of the field).
260
+ #
261
+ # Use `next_selections` to resolve object fields, if there are any.
262
+ #
263
+ # Location information from `path` and `ast_node`.
264
+ #
265
+ # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
266
+ def continue_field(path, value, field, type, ast_node, next_selections, is_non_null)
267
+ case type.kind.name
268
+ when "SCALAR", "ENUM"
269
+ r = type.coerce_result(value, context)
270
+ write_in_response(path, r)
271
+ r
272
+ when "UNION", "INTERFACE"
273
+ resolved_type_or_lazy = query.resolve_type(type, value)
274
+ after_lazy(resolved_type_or_lazy, path: path, field: field) do |resolved_type|
275
+ possible_types = query.possible_types(type)
276
+
277
+ if !possible_types.include?(resolved_type)
278
+ parent_type = field.owner
279
+ type_error = GraphQL::UnresolvedTypeError.new(value, field, parent_type, resolved_type, possible_types)
280
+ schema.type_error(type_error, context)
281
+ write_in_response(path, nil)
282
+ nil
283
+ else
284
+ resolved_type = resolved_type.metadata[:type_class]
285
+ continue_field(path, value, field, resolved_type, ast_node, next_selections, is_non_null)
286
+ end
287
+ end
288
+ when "OBJECT"
289
+ object_proxy = begin
290
+ type.authorized_new(value, context)
291
+ rescue GraphQL::ExecutionError => err
292
+ err
293
+ end
294
+ after_lazy(object_proxy, path: path, field: field) do |inner_object|
295
+ continue_value = continue_value(path, inner_object, field, is_non_null, ast_node)
296
+ if HALT != continue_value
297
+ response_hash = {}
298
+ write_in_response(path, response_hash)
299
+ evaluate_selections(path, continue_value, type, next_selections)
300
+ response_hash
301
+ end
302
+ end
303
+ when "LIST"
304
+ response_list = []
305
+ write_in_response(path, response_list)
306
+ inner_type = type.of_type
307
+ idx = 0
308
+ value.each do |inner_value|
309
+ next_path = path.dup
310
+ next_path << idx
311
+ next_path.freeze
312
+ idx += 1
313
+ set_type_at_path(next_path, inner_type)
314
+ # This will update `response_list` with the lazy
315
+ after_lazy(inner_value, path: next_path, field: field) do |inner_inner_value|
316
+ # reset `is_non_null` here and below, because the inner type will have its own nullability constraint
317
+ continue_value = continue_value(next_path, inner_inner_value, field, false, ast_node)
318
+ if HALT != continue_value
319
+ continue_field(next_path, continue_value, field, inner_type, ast_node, next_selections, false)
320
+ end
321
+ end
322
+ end
323
+ response_list
324
+ when "NON_NULL"
325
+ inner_type = type.of_type
326
+ # For fields like `__schema: __Schema!`
327
+ inner_type = resolve_if_late_bound_type(inner_type)
328
+ # Don't `set_type_at_path` because we want the static type,
329
+ # we're going to use that to determine whether a `nil` should be propagated or not.
330
+ continue_field(path, value, field, inner_type, ast_node, next_selections, true)
331
+ else
332
+ raise "Invariant: Unhandled type kind #{type.kind} (#{type})"
333
+ end
334
+ end
335
+
336
+ def resolve_with_directives(object, ast_node)
337
+ run_directive(object, ast_node, 0) { yield }
338
+ end
339
+
340
+ def run_directive(object, ast_node, idx)
341
+ dir_node = ast_node.directives[idx]
342
+ if !dir_node
343
+ yield
344
+ else
345
+ dir_defn = schema.directives.fetch(dir_node.name)
346
+ if !dir_defn.is_a?(Class)
347
+ dir_defn = dir_defn.metadata[:type_class] || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
348
+ end
349
+ dir_args = arguments(nil, dir_defn, dir_node)
350
+ dir_defn.resolve(object, dir_args, context) do
351
+ run_directive(object, ast_node, idx + 1) { yield }
352
+ end
353
+ end
354
+ end
355
+
356
+ # Check {Schema::Directive.include?} for each directive that's present
357
+ def directives_include?(node, graphql_object, parent_type)
358
+ node.directives.each do |dir_node|
359
+ dir_defn = schema.directives.fetch(dir_node.name).metadata[:type_class] || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
360
+ args = arguments(graphql_object, dir_defn, dir_node)
361
+ if !dir_defn.include?(graphql_object, args, context)
362
+ return false
363
+ end
364
+ end
365
+ true
366
+ end
367
+
368
+ def resolve_if_late_bound_type(type)
369
+ if type.is_a?(GraphQL::Schema::LateBoundType)
370
+ query.warden.get_type(type.name).metadata[:type_class]
371
+ else
372
+ type
373
+ end
374
+ end
375
+
376
+ # @param obj [Object] Some user-returned value that may want to be batched
377
+ # @param path [Array<String>]
378
+ # @param field [GraphQL::Schema::Field]
379
+ # @param eager [Boolean] Set to `true` for mutation root fields only
380
+ # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
381
+ def after_lazy(obj, field:, path:, eager: false)
382
+ @interpreter_context[:current_path] = path
383
+ @interpreter_context[:current_field] = field
384
+ if schema.lazy?(obj)
385
+ lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
386
+ @interpreter_context[:current_path] = path
387
+ @interpreter_context[:current_field] = field
388
+ # Wrap the execution of _this_ method with tracing,
389
+ # but don't wrap the continuation below
390
+ inner_obj = query.trace("execute_field_lazy", {field: field, path: path}) do
391
+ begin
392
+ schema.sync_lazy(obj)
393
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
394
+ yield(err)
395
+ end
396
+ end
397
+ after_lazy(inner_obj, field: field, path: path, eager: eager) do |really_inner_obj|
398
+ yield(really_inner_obj)
399
+ end
400
+ end
401
+
402
+ if eager
403
+ lazy.value
404
+ else
405
+ write_in_response(path, lazy)
406
+ lazy
407
+ end
408
+ else
409
+ yield(obj)
410
+ end
411
+ end
412
+
413
+ def each_argument_pair(ast_args_or_hash)
414
+ case ast_args_or_hash
415
+ when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
416
+ ast_args_or_hash.arguments.each do |arg|
417
+ yield(arg.name, arg.value)
418
+ end
419
+ when Hash
420
+ ast_args_or_hash.each do |key, value|
421
+ normalized_name = GraphQL::Schema::Member::BuildType.camelize(key.to_s)
422
+ yield(normalized_name, value)
423
+ end
424
+ else
425
+ raise "Invariant, unexpected #{ast_args_or_hash.inspect}"
426
+ end
427
+ end
428
+
429
+ def arguments(graphql_object, arg_owner, ast_node_or_hash)
430
+ kwarg_arguments = {}
431
+ arg_defns = arg_owner.arguments
432
+ each_argument_pair(ast_node_or_hash) do |arg_name, arg_value|
433
+ arg_defn = arg_defns[arg_name]
434
+ # Need to distinguish between client-provided `nil`
435
+ # and nothing-at-all
436
+ is_present, value = arg_to_value(graphql_object, arg_defn.type, arg_value)
437
+ if is_present
438
+ # This doesn't apply to directives, which are legacy
439
+ # Can remove this when Skip and Include use classes or something.
440
+ if graphql_object
441
+ value = arg_defn.prepare_value(graphql_object, value)
442
+ end
443
+ kwarg_arguments[arg_defn.keyword] = value
444
+ end
445
+ end
446
+ arg_defns.each do |name, arg_defn|
447
+ if arg_defn.default_value? && !kwarg_arguments.key?(arg_defn.keyword)
448
+ _is_present, value = arg_to_value(graphql_object, arg_defn.type, arg_defn.default_value)
449
+ kwarg_arguments[arg_defn.keyword] = value
450
+ end
451
+ end
452
+ kwarg_arguments
453
+ end
454
+
455
+ # Get a Ruby-ready value from a client query.
456
+ # @param graphql_object [Object] The owner of the field whose argument this is
457
+ # @param arg_type [Class, GraphQL::Schema::NonNull, GraphQL::Schema::List]
458
+ # @param ast_value [GraphQL::Language::Nodes::VariableIdentifier, String, Integer, Float, Boolean]
459
+ # @return [Array(is_present, value)]
460
+ def arg_to_value(graphql_object, arg_type, ast_value)
461
+ if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
462
+ # If it's not here, it will get added later
463
+ if query.variables.key?(ast_value.name)
464
+ return true, query.variables[ast_value.name]
465
+ else
466
+ return false, nil
467
+ end
468
+ elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue)
469
+ return true, nil
470
+ elsif arg_type.is_a?(GraphQL::Schema::NonNull)
471
+ arg_to_value(graphql_object, arg_type.of_type, ast_value)
472
+ elsif arg_type.is_a?(GraphQL::Schema::List)
473
+ # Treat a single value like a list
474
+ arg_value = Array(ast_value)
475
+ list = []
476
+ arg_value.map do |inner_v|
477
+ _present, value = arg_to_value(graphql_object, arg_type.of_type, inner_v)
478
+ list << value
479
+ end
480
+ return true, list
481
+ elsif arg_type.is_a?(Class) && arg_type < GraphQL::Schema::InputObject
482
+ # For these, `prepare` is applied during `#initialize`.
483
+ # Pass `nil` so it will be skipped in `#arguments`.
484
+ # What a mess.
485
+ args = arguments(nil, arg_type, ast_value)
486
+ # We're not tracking defaults_used, but for our purposes
487
+ # we compare the value to the default value.
488
+ return true, arg_type.new(ruby_kwargs: args, context: context, defaults_used: nil)
489
+ else
490
+ flat_value = flatten_ast_value(ast_value)
491
+ return true, arg_type.coerce_input(flat_value, context)
492
+ end
493
+ end
494
+
495
+ def flatten_ast_value(v)
496
+ case v
497
+ when GraphQL::Language::Nodes::Enum
498
+ v.name
499
+ when GraphQL::Language::Nodes::InputObject
500
+ h = {}
501
+ v.arguments.each do |arg|
502
+ h[arg.name] = flatten_ast_value(arg.value)
503
+ end
504
+ h
505
+ when Array
506
+ v.map { |v2| flatten_ast_value(v2) }
507
+ when GraphQL::Language::Nodes::VariableIdentifier
508
+ flatten_ast_value(query.variables[v.name])
509
+ else
510
+ v
511
+ end
512
+ end
513
+
514
+ def write_invalid_null_in_response(path, invalid_null_error)
515
+ if !dead_path?(path)
516
+ schema.type_error(invalid_null_error, context)
517
+ write_in_response(path, nil)
518
+ add_dead_path(path)
519
+ end
520
+ end
521
+
522
+ def write_execution_errors_in_response(path, errors)
523
+ if !dead_path?(path)
524
+ errors.each do |v|
525
+ context.errors << v
526
+ end
527
+ write_in_response(path, nil)
528
+ add_dead_path(path)
529
+ end
530
+ end
531
+
532
+ def write_in_response(path, value)
533
+ if dead_path?(path)
534
+ return
535
+ else
536
+ if value.nil? && path.any? && type_at(path).non_null?
537
+ # This nil is invalid, try writing it at the previous spot
538
+ propagate_path = path[0..-2]
539
+ write_in_response(propagate_path, value)
540
+ add_dead_path(propagate_path)
541
+ else
542
+ @response.write(path, value)
543
+ end
544
+ end
545
+ end
546
+
547
+ # To propagate nulls, we have to know what the field type was
548
+ # at previous parts of the response.
549
+ # This hash matches the response
550
+ def type_at(path)
551
+ t = @types_at_paths
552
+ path.each do |part|
553
+ t = t[part] || (raise("Invariant: #{part.inspect} not found in #{t}"))
554
+ end
555
+ t = t[:__type]
556
+ t
557
+ end
558
+
559
+ def set_type_at_path(path, type)
560
+ types = @types_at_paths
561
+ path.each do |part|
562
+ types = types[part] ||= {}
563
+ end
564
+ # Use this magic key so that the hash contains:
565
+ # - string keys for nested fields
566
+ # - :__type for the object type of a selection
567
+ types[:__type] ||= type
568
+ nil
569
+ end
570
+
571
+ # Mark `path` as having been permanently nulled out.
572
+ # No values will be added beyond that path.
573
+ def add_dead_path(path)
574
+ dead = @dead_paths
575
+ path.each do |part|
576
+ dead = dead[part] ||= {}
577
+ end
578
+ dead[:__dead] = true
579
+ end
580
+
581
+ def dead_path?(path)
582
+ res = @dead_paths
583
+ path.each do |part|
584
+ if res
585
+ if res[:__dead]
586
+ break
587
+ else
588
+ res = res[part]
589
+ end
590
+ end
591
+ end
592
+ res && res[:__dead]
593
+ end
594
+ end
595
+ end
596
+ end
597
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/execution/interpreter/execution_errors"
3
+ require "graphql/execution/interpreter/hash_response"
4
+ require "graphql/execution/interpreter/runtime"
5
+ require "graphql/execution/interpreter/resolve"
6
+
7
+ module GraphQL
8
+ module Execution
9
+ class Interpreter
10
+ def initialize
11
+ end
12
+
13
+ # Support `Executor` :S
14
+ def execute(_operation, _root_type, query)
15
+ runtime = evaluate(query)
16
+ sync_lazies(query: query)
17
+ runtime.final_value
18
+ end
19
+
20
+ def self.use(schema_defn)
21
+ schema_defn.target.interpreter = true
22
+ # Reach through the legacy objects for the actual class defn
23
+ schema_class = schema_defn.target.class
24
+ # This is not good, since both of these are holding state now,
25
+ # we have to update both :(
26
+ [schema_class, schema_defn].each do |schema_config|
27
+ schema_config.query_execution_strategy(GraphQL::Execution::Interpreter)
28
+ schema_config.mutation_execution_strategy(GraphQL::Execution::Interpreter)
29
+ schema_config.subscription_execution_strategy(GraphQL::Execution::Interpreter)
30
+ end
31
+ end
32
+
33
+ def self.begin_multiplex(multiplex)
34
+ # Since this is basically the batching context,
35
+ # share it for a whole multiplex
36
+ multiplex.context[:interpreter_instance] ||= self.new
37
+ end
38
+
39
+ def self.begin_query(query, multiplex)
40
+ # The batching context is shared by the multiplex,
41
+ # so fetch it out and use that instance.
42
+ interpreter =
43
+ query.context.namespace(:interpreter)[:interpreter_instance] =
44
+ multiplex.context[:interpreter_instance]
45
+ interpreter.evaluate(query)
46
+ query
47
+ end
48
+
49
+ def self.finish_multiplex(_results, multiplex)
50
+ interpreter = multiplex.context[:interpreter_instance]
51
+ interpreter.sync_lazies(multiplex: multiplex)
52
+ end
53
+
54
+ def self.finish_query(query, _multiplex)
55
+ {
56
+ "data" => query.context.namespace(:interpreter)[:runtime].final_value
57
+ }
58
+ end
59
+
60
+ # Run the eager part of `query`
61
+ # @return {Interpreter::Runtime}
62
+ def evaluate(query)
63
+ # Although queries in a multiplex _share_ an Interpreter instance,
64
+ # they also have another item of state, which is private to that query
65
+ # in particular, assign it here:
66
+ runtime = Runtime.new(
67
+ query: query,
68
+ response: HashResponse.new,
69
+ )
70
+ query.context.namespace(:interpreter)[:runtime] = runtime
71
+
72
+ query.trace("execute_query", {query: query}) do
73
+ runtime.run_eager
74
+ end
75
+
76
+ runtime
77
+ end
78
+
79
+ # Run the lazy part of `query` or `multiplex`.
80
+ # @return [void]
81
+ def sync_lazies(query: nil, multiplex: nil)
82
+ tracer = query || multiplex
83
+ if query.nil? && multiplex.queries.length == 1
84
+ query = multiplex.queries[0]
85
+ end
86
+ queries = multiplex ? multiplex.queries : [query]
87
+ final_values = queries.map do |query|
88
+ runtime = query.context.namespace(:interpreter)[:runtime]
89
+ # it might not be present if the query has an error
90
+ runtime ? runtime.final_value : nil
91
+ end
92
+ final_values.compact!
93
+ tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
94
+ Interpreter::Resolve.resolve_all(final_values)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end