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,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Field
6
+ class ConnectionExtension < GraphQL::Schema::FieldExtension
7
+ def apply
8
+ field.argument :after, "String", "Returns the elements in the list that come after the specified cursor.", required: false
9
+ field.argument :before, "String", "Returns the elements in the list that come before the specified cursor.", required: false
10
+ field.argument :first, "Int", "Returns the first _n_ elements from the list.", required: false
11
+ field.argument :last, "Int", "Returns the last _n_ elements from the list.", required: false
12
+ end
13
+
14
+ # Remove pagination args before passing it to a user method
15
+ def resolve(object:, arguments:, context:)
16
+ next_args = arguments.dup
17
+ next_args.delete(:first)
18
+ next_args.delete(:last)
19
+ next_args.delete(:before)
20
+ next_args.delete(:after)
21
+ yield(object, next_args)
22
+ end
23
+
24
+ def after_resolve(value:, object:, arguments:, context:, memo:)
25
+ if value.is_a? GraphQL::ExecutionError
26
+ # This isn't even going to work because context doesn't have ast_node anymore
27
+ context.add_error(value)
28
+ nil
29
+ elsif value.nil?
30
+ nil
31
+ else
32
+ if object.is_a?(GraphQL::Schema::Object)
33
+ object = object.object
34
+ end
35
+ connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(value)
36
+ connection_class.new(
37
+ value,
38
+ arguments,
39
+ field: field,
40
+ max_page_size: field.max_page_size,
41
+ parent: object,
42
+ context: context,
43
+ )
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Field
6
+ class ScopeExtension < GraphQL::Schema::FieldExtension
7
+ def after_resolve(value:, context:, **rest)
8
+ ret_type = @field.type.unwrap
9
+ if ret_type.respond_to?(:scope_items)
10
+ ret_type.scope_items(value, context)
11
+ else
12
+ value
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
  # test_via: ../object.rb
3
+ require "graphql/schema/field/connection_extension"
4
+ require "graphql/schema/field/scope_extension"
5
+
3
6
  module GraphQL
4
7
  class Schema
5
8
  class Field
6
9
  include GraphQL::Schema::Member::CachedGraphQLDefinition
7
10
  include GraphQL::Schema::Member::AcceptsDefinition
8
11
  include GraphQL::Schema::Member::HasArguments
12
+ include GraphQL::Schema::Member::HasPath
9
13
 
10
14
  # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
11
15
  attr_reader :name
@@ -16,15 +20,20 @@ module GraphQL
16
20
  # @return [String, nil] If present, the field is marked as deprecated with this documentation
17
21
  attr_accessor :deprecation_reason
18
22
 
19
- # @return [Symbol] Method or hash key to look up
23
+ # @return [Symbol] Method or hash key on the underlying object to look up
20
24
  attr_reader :method_sym
21
25
 
22
- # @return [String] Method or hash key to look up
26
+ # @return [String] Method or hash key on the underlying object to look up
23
27
  attr_reader :method_str
24
28
 
29
+ # @return [Symbol] The method on the type to look up
30
+ attr_reader :resolver_method
31
+
25
32
  # @return [Class] The type that this field belongs to
26
33
  attr_reader :owner
27
34
 
35
+ # @return [Symobol] the original name of the field, passed in by the user
36
+ attr_reader :original_name
28
37
 
29
38
  # @return [Class, nil] The {Schema::Resolver} this field was derived from, if there is one
30
39
  def resolver
@@ -33,6 +42,15 @@ module GraphQL
33
42
 
34
43
  alias :mutation :resolver
35
44
 
45
+ # @return [Array<Symbol>]
46
+ attr_reader :extras
47
+
48
+ # @return [Boolean] Apply tracing to this field? (Default: skip scalars, this is the override value)
49
+ attr_reader :trace
50
+
51
+ # @return [String, nil]
52
+ attr_reader :subscription_scope
53
+
36
54
  # Create a field instance from a list of arguments, keyword arguments, and a block.
37
55
  #
38
56
  # This method implements prioritization between the `resolver` or `mutation` defaults
@@ -41,10 +59,21 @@ module GraphQL
41
59
  # It also normalizes positional arguments into keywords for {Schema::Field#initialize}.
42
60
  # @param resolver [Class] A {GraphQL::Schema::Resolver} class to use for field configuration
43
61
  # @param mutation [Class] A {GraphQL::Schema::Mutation} class to use for field configuration
62
+ # @param subscription [Class] A {GraphQL::Schema::Subscription} class to use for field configuration
44
63
  # @return [GraphQL::Schema:Field] an instance of `self
45
64
  # @see {.initialize} for other options
46
- def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, **kwargs, &block)
47
- if (parent_config = resolver || mutation)
65
+ def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
66
+ if kwargs[:field]
67
+ if kwargs[:field] == GraphQL::Relay::Node.field
68
+ warn("Legacy-style `GraphQL::Relay::Node.field` is being added to a class-based type. See `GraphQL::Types::Relay::NodeField` for a replacement.")
69
+ return GraphQL::Types::Relay::NodeField
70
+ elsif kwargs[:field] == GraphQL::Relay::Node.plural_field
71
+ warn("Legacy-style `GraphQL::Relay::Node.plural_field` is being added to a class-based type. See `GraphQL::Types::Relay::NodesField` for a replacement.")
72
+ return GraphQL::Types::Relay::NodesField
73
+ end
74
+ end
75
+
76
+ if (parent_config = resolver || mutation || subscription)
48
77
  # Get the parent config, merge in local overrides
49
78
  kwargs = parent_config.field_options.merge(kwargs)
50
79
  # Add a reference to that parent class
@@ -86,7 +115,8 @@ module GraphQL
86
115
  elsif @return_type_expr
87
116
  Member::BuildType.to_type_name(@return_type_expr)
88
117
  else
89
- raise "No connection info possible"
118
+ # As a last ditch, try to force loading the return type:
119
+ type.unwrap.name
90
120
  end
91
121
  @connection = return_type_name.end_with?("Connection")
92
122
  else
@@ -100,19 +130,19 @@ module GraphQL
100
130
  # The default was overridden
101
131
  @scope
102
132
  else
103
- @return_type_expr && type.unwrap.respond_to?(:scope_items) && (connection? || type.list?)
133
+ @return_type_expr.is_a?(Array) || (@return_type_expr.is_a?(String) && @return_type_expr.include?("[")) || connection?
104
134
  end
105
135
  end
106
136
 
107
137
  # @param name [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API)
108
- # @param return_type_expr [Class, GraphQL::BaseType, Array] The return type of this field
109
- # @param desc [String] Field description
138
+ # @param type [Class, GraphQL::BaseType, Array] The return type of this field
110
139
  # @param owner [Class] The type that this field belongs to
111
140
  # @param null [Boolean] `true` if this field may return `null`, `false` if it is never `null`
112
141
  # @param description [String] Field description
113
142
  # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
114
- # @param method [Symbol] The method to call to resolve this field (defaults to `name`)
115
- # @param hash_key [Object] The hash key to lookup to resolve this field (defaults to `name` or `name.to_s`)
143
+ # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
144
+ # @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
145
+ # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
116
146
  # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
117
147
  # @param max_page_size [Integer] For connections, the maximum number of items to return from this field
118
148
  # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
@@ -125,8 +155,9 @@ module GraphQL
125
155
  # @param complexity [Numeric] When provided, set the complexity for this field
126
156
  # @param scope [Boolean] If true, the return type's `.scope_items` method will be called on the return value
127
157
  # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads
128
- def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, scope: nil, resolve: nil, introspection: false, hash_key: nil, camelize: true, complexity: 1, extras: [], resolver_class: nil, subscription_scope: nil, arguments: {}, &definition_block)
129
-
158
+ # @param extensions [Array<Class>] Named extensions to apply to this field (see also {#extension})
159
+ # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
160
+ def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: nil, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, extras: [], extensions: [], resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, arguments: {}, &definition_block)
130
161
  if name.nil?
131
162
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
132
163
  end
@@ -141,25 +172,40 @@ module GraphQL
141
172
  if (field || function || resolve) && extras.any?
142
173
  raise ArgumentError, "keyword `extras:` may only be used with method-based resolve and class-based field such as mutation class, please remove `field:`, `function:` or `resolve:`"
143
174
  end
175
+ @original_name = name
176
+ @underscored_name = Member::BuildType.underscore(name.to_s)
144
177
  @name = camelize ? Member::BuildType.camelize(name.to_s) : name.to_s
145
178
  @description = description
146
179
  if field.is_a?(GraphQL::Schema::Field)
147
- @field_instance = field
180
+ raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field."
148
181
  else
149
182
  @field = field
150
183
  end
151
184
  @function = function
152
185
  @resolve = resolve
153
186
  @deprecation_reason = deprecation_reason
187
+
154
188
  if method && hash_key
155
189
  raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)"
156
190
  end
157
191
 
192
+ if resolver_method
193
+ if method
194
+ raise ArgumentError, "Provide `method:` _or_ `resolver_method:`, not both. (called with: `method: #{method.inspect}, resolver_method: #{resolver_method.inspect}`)"
195
+ end
196
+
197
+ if hash_key
198
+ raise ArgumentError, "Provide `hash_key:` _or_ `resolver_method:`, not both. (called with: `hash_key: #{hash_key.inspect}, resolver_method: #{resolver_method.inspect}`)"
199
+ end
200
+ end
201
+
158
202
  # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
159
- method_name = method || hash_key || Member::BuildType.underscore(name.to_s)
203
+ method_name = method || hash_key || @underscored_name
204
+ resolver_method ||= @underscored_name
160
205
 
161
206
  @method_str = method_name.to_s
162
207
  @method_sym = method_name.to_sym
208
+ @resolver_method = resolver_method
163
209
  @complexity = complexity
164
210
  @return_type_expr = type
165
211
  @return_type_null = null
@@ -169,6 +215,9 @@ module GraphQL
169
215
  @extras = extras
170
216
  @resolver_class = resolver_class
171
217
  @scope = scope
218
+ @trace = trace
219
+ @relay_node_field = relay_node_field
220
+ @relay_nodes_field = relay_nodes_field
172
221
 
173
222
  # Override the default from HasArguments
174
223
  @own_arguments = {}
@@ -183,9 +232,25 @@ module GraphQL
183
232
  @owner = owner
184
233
  @subscription_scope = subscription_scope
185
234
 
235
+ # Do this last so we have as much context as possible when initializing them:
236
+ @extensions = []
237
+ if extensions.any?
238
+ self.extensions(extensions)
239
+ end
240
+ # This should run before connection extension,
241
+ # but should it run after the definition block?
242
+ if scoped?
243
+ self.extension(ScopeExtension)
244
+ end
245
+ # The problem with putting this after the definition_block
246
+ # is that it would override arguments
247
+ if connection?
248
+ self.extension(ConnectionExtension)
249
+ end
250
+
186
251
  if definition_block
187
252
  if definition_block.arity == 1
188
- instance_exec(self, &definition_block)
253
+ yield self
189
254
  else
190
255
  instance_eval(&definition_block)
191
256
  end
@@ -202,6 +267,49 @@ module GraphQL
202
267
  end
203
268
  end
204
269
 
270
+ # Read extension instances from this field,
271
+ # or add new classes/options to be initialized on this field.
272
+ #
273
+ # @param extensions [Array<Class>, Hash<Class => Object>] Add extensions to this field
274
+ # @return [Array<GraphQL::Schema::FieldExtension>] extensions to apply to this field
275
+ def extensions(new_extensions = nil)
276
+ if new_extensions.nil?
277
+ # Read the value
278
+ @extensions
279
+ else
280
+ if @resolve || @function
281
+ raise ArgumentError, <<-MSG
282
+ Extensions are not supported with resolve procs or functions,
283
+ but #{owner.name}.#{name} has: #{@resolve || @function}
284
+ So, it can't have extensions: #{extensions}.
285
+ Use a method or a Schema::Resolver instead.
286
+ MSG
287
+ end
288
+
289
+ # Normalize to a Hash of {name => options}
290
+ extensions_with_options = if new_extensions.last.is_a?(Hash)
291
+ new_extensions.pop
292
+ else
293
+ {}
294
+ end
295
+ new_extensions.each do |f|
296
+ extensions_with_options[f] = nil
297
+ end
298
+
299
+ # Initialize each class and stash the instance
300
+ extensions_with_options.each do |extension_class, options|
301
+ @extensions << extension_class.new(field: self, options: options)
302
+ end
303
+ end
304
+ end
305
+
306
+ # Add `extension` to this field, initialized with `options` if provided.
307
+ # @param extension [Class] subclass of {Schema::Fieldextension}
308
+ # @param options [Object] if provided, given as `options:` when initializing `extension`.
309
+ def extension(extension, options = nil)
310
+ extensions([{extension => options}])
311
+ end
312
+
205
313
  def complexity(new_complexity)
206
314
  case new_complexity
207
315
  when Proc
@@ -218,17 +326,13 @@ module GraphQL
218
326
  else
219
327
  raise("Invalid complexity: #{new_complexity.inspect} on #{@name}")
220
328
  end
221
-
222
329
  end
223
330
 
331
+ # @return [Integer, nil] Applied to connections if present
332
+ attr_reader :max_page_size
333
+
224
334
  # @return [GraphQL::Field]
225
335
  def to_graphql
226
- # this field was previously defined and passed here, so delegate to it
227
- if @field_instance
228
- return @field_instance.to_graphql
229
- end
230
-
231
-
232
336
  field_defn = if @field
233
337
  @field.dup
234
338
  elsif @function
@@ -257,24 +361,25 @@ module GraphQL
257
361
  field_defn.metadata[:resolver] = @resolver_class
258
362
  end
259
363
 
364
+ if !@trace.nil?
365
+ field_defn.trace = @trace
366
+ end
367
+
368
+ if @relay_node_field
369
+ field_defn.relay_node_field = @relay_node_field
370
+ end
371
+
372
+ if @relay_nodes_field
373
+ field_defn.relay_nodes_field = @relay_nodes_field
374
+ end
375
+
260
376
  field_defn.resolve = self.method(:resolve_field)
261
377
  field_defn.connection = connection?
262
- field_defn.connection_max_page_size = @max_page_size
378
+ field_defn.connection_max_page_size = max_page_size
263
379
  field_defn.introspection = @introspection
264
380
  field_defn.complexity = @complexity
265
381
  field_defn.subscription_scope = @subscription_scope
266
382
 
267
- # apply this first, so it can be overriden below
268
- if connection?
269
- # TODO: this could be a bit weird, because these fields won't be present
270
- # after initialization, only in the `to_graphql` response.
271
- # This calculation _could_ be moved up if need be.
272
- argument :after, "String", "Returns the elements in the list that come after the specified cursor.", required: false
273
- argument :before, "String", "Returns the elements in the list that come before the specified cursor.", required: false
274
- argument :first, "Int", "Returns the first _n_ elements from the list.", required: false
275
- argument :last, "Int", "Returns the last _n_ elements from the list.", required: false
276
- end
277
-
278
383
  arguments.each do |name, defn|
279
384
  arg_graphql = defn.to_graphql
280
385
  field_defn.arguments[arg_graphql.name] = arg_graphql
@@ -318,39 +423,99 @@ module GraphQL
318
423
  end
319
424
 
320
425
  def authorized?(object, context)
321
- if @resolver_class
426
+ self_auth = if @resolver_class
322
427
  @resolver_class.authorized?(object, context)
323
428
  else
324
429
  true
325
430
  end
431
+
432
+ if self_auth
433
+ # Faster than `.any?`
434
+ arguments.each_value do |arg|
435
+ if !arg.authorized?(object, context)
436
+ return false
437
+ end
438
+ end
439
+ true
440
+ else
441
+ false
442
+ end
326
443
  end
327
444
 
328
445
  # Implement {GraphQL::Field}'s resolve API.
329
446
  #
330
447
  # Eventually, we might hook up field instances to execution in another way. TBD.
448
+ # @see #resolve for how the interpreter hooks up to it
331
449
  def resolve_field(obj, args, ctx)
332
450
  ctx.schema.after_lazy(obj) do |after_obj|
333
451
  # First, apply auth ...
334
452
  query_ctx = ctx.query.context
453
+ # Some legacy fields can have `nil` here, not exactly sure why.
454
+ # @see https://github.com/rmosolgo/graphql-ruby/issues/1990 before removing
335
455
  inner_obj = after_obj && after_obj.object
336
- if authorized?(inner_obj, query_ctx) && arguments.each_value.all? { |a| a.authorized?(inner_obj, query_ctx) }
456
+ if authorized?(inner_obj, query_ctx)
337
457
  # Then if it passed, resolve the field
338
- v = if @resolve_proc
458
+ if @resolve_proc
339
459
  # Might be nil, still want to call the func in that case
340
460
  @resolve_proc.call(inner_obj, args, ctx)
341
- elsif @resolver_class
342
- singleton_inst = @resolver_class.new(object: inner_obj, context: query_ctx)
343
- public_send_field(singleton_inst, args, ctx)
344
461
  else
345
462
  public_send_field(after_obj, args, ctx)
346
463
  end
347
- apply_scope(v, ctx)
348
464
  else
349
- nil
465
+ err = GraphQL::UnauthorizedFieldError.new(object: inner_obj, type: obj.class, context: ctx, field: self)
466
+ query_ctx.schema.unauthorized_field(err)
350
467
  end
351
468
  end
352
469
  end
353
470
 
471
+ # This method is called by the interpreter for each field.
472
+ # You can extend it in your base field classes.
473
+ # @param object [GraphQL::Schema::Object] An instance of some type class, wrapping an application object
474
+ # @param args [Hash] A symbol-keyed hash of Ruby keyword arguments. (Empty if no args)
475
+ # @param ctx [GraphQL::Query::Context]
476
+ def resolve(object, args, ctx)
477
+ if @resolve_proc
478
+ raise "Can't run resolve proc for #{path} when using GraphQL::Execution::Interpreter"
479
+ end
480
+ begin
481
+ # Unwrap the GraphQL object to get the application object.
482
+ application_object = object.object
483
+ if self.authorized?(application_object, ctx)
484
+ # Apply field extensions
485
+ with_extensions(object, args, ctx) do |extended_obj, extended_args|
486
+ field_receiver = if @resolver_class
487
+ resolver_obj = if extended_obj.is_a?(GraphQL::Schema::Object)
488
+ extended_obj.object
489
+ else
490
+ extended_obj
491
+ end
492
+ @resolver_class.new(object: resolver_obj, context: ctx)
493
+ else
494
+ extended_obj
495
+ end
496
+
497
+ if field_receiver.respond_to?(@resolver_method)
498
+ # Call the method with kwargs, if there are any
499
+ if extended_args.any?
500
+ field_receiver.public_send(@resolver_method, **extended_args)
501
+ else
502
+ field_receiver.public_send(@resolver_method)
503
+ end
504
+ else
505
+ resolve_field_method(field_receiver, extended_args, ctx)
506
+ end
507
+ end
508
+ else
509
+ err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
510
+ ctx.schema.unauthorized_field(err)
511
+ end
512
+ rescue GraphQL::UnauthorizedError => err
513
+ ctx.schema.unauthorized_object(err)
514
+ end
515
+ rescue GraphQL::ExecutionError => err
516
+ err
517
+ end
518
+
354
519
  # Find a way to resolve this field, checking:
355
520
  #
356
521
  # - Hash keys, if the wrapped object is a hash;
@@ -379,7 +544,7 @@ module GraphQL
379
544
  raise <<-ERR
380
545
  Failed to implement #{@owner.graphql_name}.#{@name}, tried:
381
546
 
382
- - `#{obj.class}##{@method_sym}`, which did not exist
547
+ - `#{obj.class}##{@resolver_method}`, which did not exist
383
548
  - `#{obj.object.class}##{@method_sym}`, which did not exist
384
549
  - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
385
550
 
@@ -388,18 +553,19 @@ module GraphQL
388
553
  end
389
554
  end
390
555
 
391
- private
392
-
393
- def apply_scope(value, ctx)
394
- if scoped?
395
- ctx.schema.after_lazy(value) do |inner_value|
396
- @type.unwrap.scope_items(inner_value, ctx)
397
- end
556
+ # @param ctx [GraphQL::Query::Context::FieldResolutionContext]
557
+ def fetch_extra(extra_name, ctx)
558
+ if extra_name != :path && respond_to?(extra_name)
559
+ self.public_send(extra_name)
560
+ elsif ctx.respond_to?(extra_name)
561
+ ctx.public_send(extra_name)
398
562
  else
399
- value
563
+ raise NotImplementedError, "Unknown field extra for #{self.path}: #{extra_name.inspect}"
400
564
  end
401
565
  end
402
566
 
567
+ private
568
+
403
569
  NO_ARGS = {}.freeze
404
570
 
405
571
  def public_send_field(obj, graphql_args, field_ctx)
@@ -414,27 +580,70 @@ module GraphQL
414
580
  end
415
581
  end
416
582
 
417
- if connection?
418
- # Remove pagination args before passing it to a user method
419
- ruby_kwargs.delete(:first)
420
- ruby_kwargs.delete(:last)
421
- ruby_kwargs.delete(:before)
422
- ruby_kwargs.delete(:after)
423
- end
424
-
425
583
  @extras.each do |extra_arg|
426
- # TODO: provide proper tests for `:ast_node`, `:irep_node`, `:parent`, others?
427
- ruby_kwargs[extra_arg] = field_ctx.public_send(extra_arg)
584
+ ruby_kwargs[extra_arg] = fetch_extra(extra_arg, field_ctx)
428
585
  end
429
586
  else
430
587
  ruby_kwargs = NO_ARGS
431
588
  end
432
589
 
590
+ query_ctx = field_ctx.query.context
591
+ with_extensions(obj, ruby_kwargs, query_ctx) do |extended_obj, extended_args|
592
+ if @resolver_class
593
+ if extended_obj.is_a?(GraphQL::Schema::Object)
594
+ extended_obj = extended_obj.object
595
+ end
596
+ extended_obj = @resolver_class.new(object: extended_obj, context: query_ctx)
597
+ end
598
+
599
+ if extended_obj.respond_to?(@resolver_method)
600
+ if extended_args.any?
601
+ extended_obj.public_send(@resolver_method, **extended_args)
602
+ else
603
+ extended_obj.public_send(@resolver_method)
604
+ end
605
+ else
606
+ resolve_field_method(extended_obj, extended_args, query_ctx)
607
+ end
608
+ end
609
+ end
610
+
611
+ # Wrap execution with hooks.
612
+ # Written iteratively to avoid big stack traces.
613
+ # @return [Object] Whatever the
614
+ def with_extensions(obj, args, ctx)
615
+ if @extensions.empty?
616
+ yield(obj, args)
617
+ else
618
+ # Save these so that the originals can be re-given to `after_resolve` handlers.
619
+ original_args = args
620
+ original_obj = obj
621
+
622
+ memos = []
623
+ value = run_extensions_before_resolve(memos, obj, args, ctx) do |extended_obj, extended_args|
624
+ yield(extended_obj, extended_args)
625
+ end
433
626
 
434
- if ruby_kwargs.any?
435
- obj.public_send(@method_sym, **ruby_kwargs)
627
+ ctx.schema.after_lazy(value) do |resolved_value|
628
+ @extensions.each_with_index do |ext, idx|
629
+ memo = memos[idx]
630
+ # TODO after_lazy?
631
+ resolved_value = ext.after_resolve(object: original_obj, arguments: original_args, context: ctx, value: resolved_value, memo: memo)
632
+ end
633
+ resolved_value
634
+ end
635
+ end
636
+ end
637
+
638
+ def run_extensions_before_resolve(memos, obj, args, ctx, idx: 0)
639
+ extension = @extensions[idx]
640
+ if extension
641
+ extension.resolve(object: obj, arguments: args, context: ctx) do |extended_obj, extended_args, memo|
642
+ memos << memo
643
+ run_extensions_before_resolve(memos, extended_obj, extended_args, ctx, idx: idx + 1) { |o, a| yield(o, a) }
644
+ end
436
645
  else
437
- obj.public_send(@method_sym)
646
+ yield(obj, args)
438
647
  end
439
648
  end
440
649
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ # Extend this class to make field-level customizations to resolve behavior.
5
+ #
6
+ # When a extension is added to a field with `extension(MyExtension)`, a `MyExtension` instance
7
+ # is created, and its hooks are applied whenever that field is called.
8
+ #
9
+ # The instance is frozen so that instance variables aren't modified during query execution,
10
+ # which could cause all kinds of issues due to race conditions.
11
+ class FieldExtension
12
+ # @return [GraphQL::Schema::Field]
13
+ attr_reader :field
14
+
15
+ # @return [Object]
16
+ attr_reader :options
17
+
18
+ # Called when the extension is mounted with `extension(name, options)`.
19
+ # The instance is frozen to avoid improper use of state during execution.
20
+ # @param field [GraphQL::Schema::Field] The field where this extension was mounted
21
+ # @param options [Object] The second argument to `extension`, or `nil` if nothing was passed.
22
+ def initialize(field:, options:)
23
+ @field = field
24
+ @options = options
25
+ apply
26
+ freeze
27
+ end
28
+
29
+ # Called when this extension is attached to a field.
30
+ # The field definition may be extended during this method.
31
+ # @return [void]
32
+ def apply
33
+ end
34
+
35
+ # Called before resolving {#field}. It should either:
36
+ #
37
+ # - `yield` values to continue execution; OR
38
+ # - return something else to shortcut field execution.
39
+ #
40
+ # Whatever this method returns will be used for execution.
41
+ #
42
+ # @param object [Object] The object the field is being resolved on
43
+ # @param arguments [Hash] Ruby keyword arguments for resolving this field
44
+ # @param context [Query::Context] the context for this query
45
+ # @yieldparam object [Object] The object to continue resolving the field on
46
+ # @yieldparam arguments [Hash] The keyword arguments to continue resolving with
47
+ # @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later
48
+ # @return [Object] The return value for this field.
49
+ def resolve(object:, arguments:, context:)
50
+ yield(object, arguments, nil)
51
+ end
52
+
53
+ # Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced,
54
+ # but before the value was added to the GraphQL response.
55
+ #
56
+ # Whatever this hook returns will be used as the return value.
57
+ #
58
+ # @param object [Object] The object the field is being resolved on
59
+ # @param arguments [Hash] Ruby keyword arguments for resolving this field
60
+ # @param context [Query::Context] the context for this query
61
+ # @param value [Object] Whatever the field previously returned
62
+ # @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one
63
+ # @return [Object] The return value for this field.
64
+ def after_resolve(object:, arguments:, context:, value:, memo:)
65
+ value
66
+ end
67
+ end
68
+ end
69
+ end