graphql 1.13.24 → 2.5.11

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 (427) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  3. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  4. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  5. data/lib/generators/graphql/install_generator.rb +50 -1
  6. data/lib/generators/graphql/mutation_delete_generator.rb +1 -1
  7. data/lib/generators/graphql/mutation_update_generator.rb +1 -1
  8. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  9. data/lib/generators/graphql/relay.rb +21 -18
  10. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  11. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  12. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  13. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  14. data/lib/generators/graphql/templates/base_field.erb +2 -0
  15. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  16. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  17. data/lib/generators/graphql/templates/base_object.erb +2 -0
  18. data/lib/generators/graphql/templates/base_resolver.erb +8 -0
  19. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  20. data/lib/generators/graphql/templates/base_union.erb +2 -0
  21. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  22. data/lib/generators/graphql/templates/loader.erb +2 -0
  23. data/lib/generators/graphql/templates/mutation.erb +2 -0
  24. data/lib/generators/graphql/templates/node_type.erb +2 -0
  25. data/lib/generators/graphql/templates/query_type.erb +2 -0
  26. data/lib/generators/graphql/templates/schema.erb +8 -0
  27. data/lib/generators/graphql/type_generator.rb +1 -1
  28. data/lib/graphql/analysis/analyzer.rb +90 -0
  29. data/lib/graphql/analysis/field_usage.rb +65 -28
  30. data/lib/graphql/analysis/max_query_complexity.rb +11 -17
  31. data/lib/graphql/analysis/max_query_depth.rb +13 -19
  32. data/lib/graphql/analysis/query_complexity.rb +236 -61
  33. data/lib/graphql/analysis/query_depth.rb +38 -23
  34. data/lib/graphql/analysis/visitor.rb +280 -0
  35. data/lib/graphql/analysis.rb +93 -6
  36. data/lib/graphql/autoload.rb +38 -0
  37. data/lib/graphql/backtrace/table.rb +118 -73
  38. data/lib/graphql/backtrace.rb +2 -25
  39. data/lib/graphql/coercion_error.rb +1 -9
  40. data/lib/graphql/current.rb +57 -0
  41. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  42. data/lib/graphql/dashboard/installable.rb +22 -0
  43. data/lib/graphql/dashboard/limiters.rb +93 -0
  44. data/lib/graphql/dashboard/operation_store.rb +199 -0
  45. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  46. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  47. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  48. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  49. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  50. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  51. data/lib/graphql/dashboard/statics/icon.png +0 -0
  52. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  53. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  54. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  55. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  56. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  57. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  58. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  59. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  60. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  61. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  62. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  63. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  64. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  65. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  66. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  67. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  68. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  69. data/lib/graphql/dashboard.rb +158 -0
  70. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  71. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  72. data/lib/graphql/dataloader/async_dataloader.rb +101 -0
  73. data/lib/graphql/dataloader/null_dataloader.rb +11 -2
  74. data/lib/graphql/dataloader/request.rb +5 -0
  75. data/lib/graphql/dataloader/source.rb +103 -47
  76. data/lib/graphql/dataloader.rb +174 -148
  77. data/lib/graphql/dig.rb +3 -2
  78. data/lib/graphql/duration_encoding_error.rb +16 -0
  79. data/lib/graphql/execution/errors.rb +12 -82
  80. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  81. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  82. data/lib/graphql/execution/interpreter/arguments_cache.rb +30 -35
  83. data/lib/graphql/execution/interpreter/resolve.rb +32 -2
  84. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +215 -0
  85. data/lib/graphql/execution/interpreter/runtime.rb +525 -502
  86. data/lib/graphql/execution/interpreter.rb +127 -81
  87. data/lib/graphql/execution/lazy.rb +7 -21
  88. data/lib/graphql/execution/lookahead.rb +133 -55
  89. data/lib/graphql/execution/multiplex.rb +6 -176
  90. data/lib/graphql/execution.rb +11 -4
  91. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  92. data/lib/graphql/introspection/directive_type.rb +1 -1
  93. data/lib/graphql/introspection/dynamic_fields.rb +3 -8
  94. data/lib/graphql/introspection/entry_points.rb +10 -17
  95. data/lib/graphql/introspection/field_type.rb +1 -1
  96. data/lib/graphql/introspection/schema_type.rb +8 -11
  97. data/lib/graphql/introspection/type_type.rb +13 -6
  98. data/lib/graphql/introspection.rb +4 -3
  99. data/lib/graphql/invalid_name_error.rb +1 -1
  100. data/lib/graphql/invalid_null_error.rb +20 -17
  101. data/lib/graphql/language/block_string.rb +34 -18
  102. data/lib/graphql/language/cache.rb +13 -0
  103. data/lib/graphql/language/comment.rb +18 -0
  104. data/lib/graphql/language/definition_slice.rb +1 -1
  105. data/lib/graphql/language/document_from_schema_definition.rb +114 -80
  106. data/lib/graphql/language/lexer.rb +375 -1489
  107. data/lib/graphql/language/nodes.rb +189 -104
  108. data/lib/graphql/language/parser.rb +807 -1941
  109. data/lib/graphql/language/printer.rb +366 -163
  110. data/lib/graphql/language/sanitized_printer.rb +21 -23
  111. data/lib/graphql/language/static_visitor.rb +171 -0
  112. data/lib/graphql/language/visitor.rb +189 -138
  113. data/lib/graphql/language.rb +62 -1
  114. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  115. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  116. data/lib/graphql/pagination/array_connection.rb +8 -6
  117. data/lib/graphql/pagination/connection.rb +61 -7
  118. data/lib/graphql/pagination/connections.rb +3 -28
  119. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  120. data/lib/graphql/pagination/relation_connection.rb +2 -0
  121. data/lib/graphql/query/context/scoped_context.rb +101 -0
  122. data/lib/graphql/query/context.rb +131 -225
  123. data/lib/graphql/query/input_validation_result.rb +1 -1
  124. data/lib/graphql/query/null_context.rb +11 -33
  125. data/lib/graphql/query/partial.rb +179 -0
  126. data/lib/graphql/query/validation_pipeline.rb +14 -37
  127. data/lib/graphql/query/variable_validation_error.rb +1 -1
  128. data/lib/graphql/query/variables.rb +6 -19
  129. data/lib/graphql/query.rb +162 -98
  130. data/lib/graphql/railtie.rb +15 -109
  131. data/lib/graphql/rake_task/validate.rb +1 -1
  132. data/lib/graphql/rake_task.rb +30 -11
  133. data/lib/graphql/relay/range_add.rb +9 -20
  134. data/lib/graphql/relay.rb +0 -15
  135. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  136. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  137. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  138. data/lib/graphql/rubocop.rb +2 -0
  139. data/lib/graphql/schema/addition.rb +70 -33
  140. data/lib/graphql/schema/always_visible.rb +15 -0
  141. data/lib/graphql/schema/argument.rb +104 -59
  142. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  143. data/lib/graphql/schema/build_from_definition.rb +154 -74
  144. data/lib/graphql/schema/directive/flagged.rb +4 -2
  145. data/lib/graphql/schema/directive/one_of.rb +24 -0
  146. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  147. data/lib/graphql/schema/directive/transform.rb +1 -1
  148. data/lib/graphql/schema/directive.rb +47 -24
  149. data/lib/graphql/schema/enum.rb +137 -65
  150. data/lib/graphql/schema/enum_value.rb +11 -26
  151. data/lib/graphql/schema/field/connection_extension.rb +6 -16
  152. data/lib/graphql/schema/field/scope_extension.rb +8 -1
  153. data/lib/graphql/schema/field.rb +399 -404
  154. data/lib/graphql/schema/field_extension.rb +2 -5
  155. data/lib/graphql/schema/find_inherited_value.rb +2 -7
  156. data/lib/graphql/schema/has_single_input_argument.rb +160 -0
  157. data/lib/graphql/schema/input_object.rb +144 -99
  158. data/lib/graphql/schema/interface.rb +34 -51
  159. data/lib/graphql/schema/introspection_system.rb +12 -26
  160. data/lib/graphql/schema/late_bound_type.rb +12 -2
  161. data/lib/graphql/schema/list.rb +3 -9
  162. data/lib/graphql/schema/loader.rb +4 -6
  163. data/lib/graphql/schema/member/base_dsl_methods.rb +32 -18
  164. data/lib/graphql/schema/member/build_type.rb +15 -9
  165. data/lib/graphql/schema/member/has_arguments.rb +192 -96
  166. data/lib/graphql/schema/member/has_ast_node.rb +12 -0
  167. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  168. data/lib/graphql/schema/member/has_deprecation_reason.rb +18 -4
  169. data/lib/graphql/schema/member/has_directives.rb +81 -61
  170. data/lib/graphql/schema/member/has_fields.rb +119 -39
  171. data/lib/graphql/schema/member/has_interfaces.rb +66 -23
  172. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  173. data/lib/graphql/schema/member/has_validators.rb +32 -6
  174. data/lib/graphql/schema/member/relay_shortcuts.rb +47 -2
  175. data/lib/graphql/schema/member/scoped.rb +19 -0
  176. data/lib/graphql/schema/member/type_system_helpers.rb +32 -2
  177. data/lib/graphql/schema/member/validates_input.rb +4 -4
  178. data/lib/graphql/schema/member.rb +1 -6
  179. data/lib/graphql/schema/mutation.rb +7 -9
  180. data/lib/graphql/schema/non_null.rb +1 -7
  181. data/lib/graphql/schema/object.rb +42 -49
  182. data/lib/graphql/schema/printer.rb +12 -8
  183. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  184. data/lib/graphql/schema/relay_classic_mutation.rb +12 -124
  185. data/lib/graphql/schema/resolver/has_payload_type.rb +20 -10
  186. data/lib/graphql/schema/resolver.rb +96 -81
  187. data/lib/graphql/schema/scalar.rb +10 -30
  188. data/lib/graphql/schema/subscription.rb +60 -14
  189. data/lib/graphql/schema/timeout.rb +44 -31
  190. data/lib/graphql/schema/type_expression.rb +2 -2
  191. data/lib/graphql/schema/type_membership.rb +3 -0
  192. data/lib/graphql/schema/union.rb +12 -19
  193. data/lib/graphql/schema/unique_within_type.rb +1 -1
  194. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  195. data/lib/graphql/schema/validator/required_validator.rb +60 -10
  196. data/lib/graphql/schema/validator.rb +5 -3
  197. data/lib/graphql/schema/visibility/migration.rb +188 -0
  198. data/lib/graphql/schema/visibility/profile.rb +445 -0
  199. data/lib/graphql/schema/visibility/visit.rb +190 -0
  200. data/lib/graphql/schema/visibility.rb +311 -0
  201. data/lib/graphql/schema/warden.rb +318 -94
  202. data/lib/graphql/schema/wrapper.rb +0 -5
  203. data/lib/graphql/schema.rb +1148 -1085
  204. data/lib/graphql/static_validation/all_rules.rb +4 -3
  205. data/lib/graphql/static_validation/base_visitor.rb +11 -27
  206. data/lib/graphql/static_validation/definition_dependencies.rb +7 -1
  207. data/lib/graphql/static_validation/error.rb +2 -2
  208. data/lib/graphql/static_validation/literal_validator.rb +24 -7
  209. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  210. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  211. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  212. data/lib/graphql/static_validation/rules/directives_are_defined.rb +13 -7
  213. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +14 -12
  214. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  215. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +48 -6
  216. data/lib/graphql/static_validation/rules/fields_will_merge.rb +90 -27
  217. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  218. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  219. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  220. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  221. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  222. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  223. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  224. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
  225. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
  226. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  227. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +5 -5
  228. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  229. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  230. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +19 -9
  231. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  232. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  233. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  234. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  235. data/lib/graphql/static_validation/validation_context.rb +21 -5
  236. data/lib/graphql/static_validation/validator.rb +12 -26
  237. data/lib/graphql/static_validation.rb +0 -3
  238. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +14 -6
  239. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  240. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +40 -1
  241. data/lib/graphql/subscriptions/event.rb +24 -12
  242. data/lib/graphql/subscriptions/serialize.rb +3 -1
  243. data/lib/graphql/subscriptions.rb +48 -32
  244. data/lib/graphql/testing/helpers.rb +158 -0
  245. data/lib/graphql/testing.rb +2 -0
  246. data/lib/graphql/tracing/active_support_notifications_trace.rb +27 -0
  247. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  248. data/lib/graphql/tracing/appoptics_trace.rb +259 -0
  249. data/lib/graphql/tracing/appoptics_tracing.rb +9 -2
  250. data/lib/graphql/tracing/appsignal_trace.rb +54 -0
  251. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  252. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  253. data/lib/graphql/tracing/data_dog_trace.rb +71 -0
  254. data/lib/graphql/tracing/data_dog_tracing.rb +3 -0
  255. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  256. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  257. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  258. data/lib/graphql/{execution/instrumentation.rb → tracing/legacy_hooks_trace.rb} +11 -28
  259. data/lib/graphql/tracing/legacy_trace.rb +12 -0
  260. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  261. data/lib/graphql/tracing/new_relic_trace.rb +68 -0
  262. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  263. data/lib/graphql/tracing/notifications_trace.rb +195 -0
  264. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  265. data/lib/graphql/tracing/null_trace.rb +9 -0
  266. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  267. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  268. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  269. data/lib/graphql/tracing/platform_trace.rb +123 -0
  270. data/lib/graphql/tracing/platform_tracing.rb +28 -41
  271. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +6 -2
  272. data/lib/graphql/tracing/prometheus_trace.rb +93 -0
  273. data/lib/graphql/tracing/prometheus_tracing.rb +5 -3
  274. data/lib/graphql/tracing/scout_trace.rb +49 -0
  275. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  276. data/lib/graphql/tracing/sentry_trace.rb +80 -0
  277. data/lib/graphql/tracing/statsd_trace.rb +48 -0
  278. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  279. data/lib/graphql/tracing/trace.rb +186 -0
  280. data/lib/graphql/tracing.rb +32 -52
  281. data/lib/graphql/type_kinds.rb +8 -4
  282. data/lib/graphql/types/iso_8601_date.rb +4 -1
  283. data/lib/graphql/types/iso_8601_date_time.rb +4 -0
  284. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  285. data/lib/graphql/types/relay/base_connection.rb +16 -6
  286. data/lib/graphql/types/relay/connection_behaviors.rb +65 -23
  287. data/lib/graphql/types/relay/edge_behaviors.rb +33 -5
  288. data/lib/graphql/types/relay/node_behaviors.rb +12 -2
  289. data/lib/graphql/types/relay/page_info_behaviors.rb +11 -2
  290. data/lib/graphql/types/relay.rb +0 -3
  291. data/lib/graphql/types/string.rb +1 -1
  292. data/lib/graphql/types.rb +18 -10
  293. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  294. data/lib/graphql/version.rb +1 -1
  295. data/lib/graphql.rb +76 -123
  296. data/readme.md +13 -3
  297. metadata +225 -142
  298. data/lib/graphql/analysis/analyze_query.rb +0 -98
  299. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  300. data/lib/graphql/analysis/ast/field_usage.rb +0 -55
  301. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -23
  302. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  303. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  304. data/lib/graphql/analysis/ast/query_depth.rb +0 -56
  305. data/lib/graphql/analysis/ast/visitor.rb +0 -269
  306. data/lib/graphql/analysis/ast.rb +0 -91
  307. data/lib/graphql/analysis/reducer_state.rb +0 -48
  308. data/lib/graphql/argument.rb +0 -131
  309. data/lib/graphql/authorization.rb +0 -82
  310. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  311. data/lib/graphql/backtrace/legacy_tracer.rb +0 -56
  312. data/lib/graphql/backtrace/tracer.rb +0 -81
  313. data/lib/graphql/backwards_compatibility.rb +0 -61
  314. data/lib/graphql/base_type.rb +0 -232
  315. data/lib/graphql/boolean_type.rb +0 -2
  316. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +0 -53
  317. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +0 -200
  318. data/lib/graphql/compatibility/execution_specification.rb +0 -436
  319. data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +0 -111
  320. data/lib/graphql/compatibility/lazy_execution_specification.rb +0 -215
  321. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +0 -87
  322. data/lib/graphql/compatibility/query_parser_specification/query_assertions.rb +0 -79
  323. data/lib/graphql/compatibility/query_parser_specification.rb +0 -266
  324. data/lib/graphql/compatibility/schema_parser_specification.rb +0 -682
  325. data/lib/graphql/compatibility.rb +0 -5
  326. data/lib/graphql/define/assign_argument.rb +0 -12
  327. data/lib/graphql/define/assign_connection.rb +0 -13
  328. data/lib/graphql/define/assign_enum_value.rb +0 -18
  329. data/lib/graphql/define/assign_global_id_field.rb +0 -11
  330. data/lib/graphql/define/assign_mutation_function.rb +0 -34
  331. data/lib/graphql/define/assign_object_field.rb +0 -42
  332. data/lib/graphql/define/defined_object_proxy.rb +0 -53
  333. data/lib/graphql/define/instance_definable.rb +0 -255
  334. data/lib/graphql/define/no_definition_error.rb +0 -7
  335. data/lib/graphql/define/non_null_with_bang.rb +0 -16
  336. data/lib/graphql/define/type_definer.rb +0 -31
  337. data/lib/graphql/define.rb +0 -31
  338. data/lib/graphql/deprecated_dsl.rb +0 -55
  339. data/lib/graphql/deprecation.rb +0 -9
  340. data/lib/graphql/directive/deprecated_directive.rb +0 -2
  341. data/lib/graphql/directive/include_directive.rb +0 -2
  342. data/lib/graphql/directive/skip_directive.rb +0 -2
  343. data/lib/graphql/directive.rb +0 -107
  344. data/lib/graphql/enum_type.rb +0 -133
  345. data/lib/graphql/execution/execute.rb +0 -333
  346. data/lib/graphql/execution/flatten.rb +0 -40
  347. data/lib/graphql/execution/lazy/resolve.rb +0 -91
  348. data/lib/graphql/execution/typecast.rb +0 -50
  349. data/lib/graphql/field/resolve.rb +0 -59
  350. data/lib/graphql/field.rb +0 -226
  351. data/lib/graphql/filter.rb +0 -53
  352. data/lib/graphql/float_type.rb +0 -2
  353. data/lib/graphql/function.rb +0 -128
  354. data/lib/graphql/id_type.rb +0 -2
  355. data/lib/graphql/input_object_type.rb +0 -138
  356. data/lib/graphql/int_type.rb +0 -2
  357. data/lib/graphql/interface_type.rb +0 -72
  358. data/lib/graphql/internal_representation/document.rb +0 -27
  359. data/lib/graphql/internal_representation/node.rb +0 -206
  360. data/lib/graphql/internal_representation/print.rb +0 -51
  361. data/lib/graphql/internal_representation/rewrite.rb +0 -184
  362. data/lib/graphql/internal_representation/scope.rb +0 -88
  363. data/lib/graphql/internal_representation/visit.rb +0 -36
  364. data/lib/graphql/internal_representation.rb +0 -7
  365. data/lib/graphql/language/lexer.rl +0 -260
  366. data/lib/graphql/language/parser.y +0 -550
  367. data/lib/graphql/language/token.rb +0 -34
  368. data/lib/graphql/list_type.rb +0 -80
  369. data/lib/graphql/non_null_type.rb +0 -71
  370. data/lib/graphql/object_type.rb +0 -130
  371. data/lib/graphql/query/arguments.rb +0 -189
  372. data/lib/graphql/query/arguments_cache.rb +0 -24
  373. data/lib/graphql/query/executor.rb +0 -52
  374. data/lib/graphql/query/literal_input.rb +0 -136
  375. data/lib/graphql/query/serial_execution/field_resolution.rb +0 -92
  376. data/lib/graphql/query/serial_execution/operation_resolution.rb +0 -19
  377. data/lib/graphql/query/serial_execution/selection_resolution.rb +0 -23
  378. data/lib/graphql/query/serial_execution/value_resolution.rb +0 -87
  379. data/lib/graphql/query/serial_execution.rb +0 -40
  380. data/lib/graphql/relay/array_connection.rb +0 -83
  381. data/lib/graphql/relay/base_connection.rb +0 -189
  382. data/lib/graphql/relay/connection_instrumentation.rb +0 -54
  383. data/lib/graphql/relay/connection_resolve.rb +0 -43
  384. data/lib/graphql/relay/connection_type.rb +0 -54
  385. data/lib/graphql/relay/edge.rb +0 -27
  386. data/lib/graphql/relay/edge_type.rb +0 -19
  387. data/lib/graphql/relay/edges_instrumentation.rb +0 -39
  388. data/lib/graphql/relay/global_id_resolve.rb +0 -17
  389. data/lib/graphql/relay/mongo_relation_connection.rb +0 -50
  390. data/lib/graphql/relay/mutation/instrumentation.rb +0 -23
  391. data/lib/graphql/relay/mutation/resolve.rb +0 -56
  392. data/lib/graphql/relay/mutation/result.rb +0 -38
  393. data/lib/graphql/relay/mutation.rb +0 -106
  394. data/lib/graphql/relay/node.rb +0 -39
  395. data/lib/graphql/relay/page_info.rb +0 -7
  396. data/lib/graphql/relay/relation_connection.rb +0 -188
  397. data/lib/graphql/relay/type_extensions.rb +0 -32
  398. data/lib/graphql/scalar_type.rb +0 -91
  399. data/lib/graphql/schema/base_64_bp.rb +0 -26
  400. data/lib/graphql/schema/catchall_middleware.rb +0 -35
  401. data/lib/graphql/schema/default_parse_error.rb +0 -10
  402. data/lib/graphql/schema/default_type_error.rb +0 -17
  403. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  404. data/lib/graphql/schema/member/accepts_definition.rb +0 -164
  405. data/lib/graphql/schema/member/cached_graphql_definition.rb +0 -58
  406. data/lib/graphql/schema/member/instrumentation.rb +0 -131
  407. data/lib/graphql/schema/middleware_chain.rb +0 -82
  408. data/lib/graphql/schema/null_mask.rb +0 -11
  409. data/lib/graphql/schema/possible_types.rb +0 -44
  410. data/lib/graphql/schema/rescue_middleware.rb +0 -60
  411. data/lib/graphql/schema/timeout_middleware.rb +0 -88
  412. data/lib/graphql/schema/traversal.rb +0 -228
  413. data/lib/graphql/schema/validation.rb +0 -313
  414. data/lib/graphql/static_validation/default_visitor.rb +0 -15
  415. data/lib/graphql/static_validation/no_validate_visitor.rb +0 -10
  416. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
  417. data/lib/graphql/static_validation/type_stack.rb +0 -216
  418. data/lib/graphql/string_type.rb +0 -2
  419. data/lib/graphql/subscriptions/instrumentation.rb +0 -79
  420. data/lib/graphql/subscriptions/subscription_root.rb +0 -76
  421. data/lib/graphql/tracing/skylight_tracing.rb +0 -70
  422. data/lib/graphql/types/relay/default_relay.rb +0 -31
  423. data/lib/graphql/types/relay/node_field.rb +0 -24
  424. data/lib/graphql/types/relay/nodes_field.rb +0 -43
  425. data/lib/graphql/union_type.rb +0 -115
  426. data/lib/graphql/upgrader/member.rb +0 -937
  427. data/lib/graphql/upgrader/schema.rb +0 -38
@@ -1,24 +1,16 @@
1
1
  # frozen_string_literal: true
2
+ require "logger"
2
3
  require "graphql/schema/addition"
4
+ require "graphql/schema/always_visible"
3
5
  require "graphql/schema/base_64_encoder"
4
- require "graphql/schema/catchall_middleware"
5
- require "graphql/schema/default_parse_error"
6
- require "graphql/schema/default_type_error"
7
6
  require "graphql/schema/find_inherited_value"
8
7
  require "graphql/schema/finder"
9
- require "graphql/schema/invalid_type_error"
10
8
  require "graphql/schema/introspection_system"
11
9
  require "graphql/schema/late_bound_type"
12
- require "graphql/schema/middleware_chain"
13
- require "graphql/schema/null_mask"
14
- require "graphql/schema/possible_types"
15
- require "graphql/schema/rescue_middleware"
10
+ require "graphql/schema/ractor_shareable"
16
11
  require "graphql/schema/timeout"
17
- require "graphql/schema/timeout_middleware"
18
- require "graphql/schema/traversal"
19
12
  require "graphql/schema/type_expression"
20
13
  require "graphql/schema/unique_within_type"
21
- require "graphql/schema/validation"
22
14
  require "graphql/schema/warden"
23
15
  require "graphql/schema/build_from_definition"
24
16
 
@@ -40,16 +32,20 @@ require "graphql/schema/union"
40
32
  require "graphql/schema/directive"
41
33
  require "graphql/schema/directive/deprecated"
42
34
  require "graphql/schema/directive/include"
35
+ require "graphql/schema/directive/one_of"
43
36
  require "graphql/schema/directive/skip"
44
37
  require "graphql/schema/directive/feature"
45
38
  require "graphql/schema/directive/flagged"
46
39
  require "graphql/schema/directive/transform"
40
+ require "graphql/schema/directive/specified_by"
47
41
  require "graphql/schema/type_membership"
48
42
 
49
43
  require "graphql/schema/resolver"
50
44
  require "graphql/schema/mutation"
45
+ require "graphql/schema/has_single_input_argument"
51
46
  require "graphql/schema/relay_classic_mutation"
52
47
  require "graphql/schema/subscription"
48
+ require "graphql/schema/visibility"
53
49
 
54
50
  module GraphQL
55
51
  # A GraphQL schema which may be queried with {GraphQL::Query}.
@@ -65,12 +61,8 @@ module GraphQL
65
61
  # Any undiscoverable types may be provided with the `types` configuration.
66
62
  #
67
63
  # Schemas can restrict large incoming queries with `max_depth` and `max_complexity` configurations.
68
- # (These configurations can be overridden by specific calls to {Schema#execute})
64
+ # (These configurations can be overridden by specific calls to {Schema.execute})
69
65
  #
70
- # Schemas can specify how queries should be executed against them.
71
- # `query_execution_strategy`, `mutation_execution_strategy` and `subscription_execution_strategy`
72
- # each apply to corresponding root types.
73
- # #
74
66
  # @example defining a schema
75
67
  # class MySchema < GraphQL::Schema
76
68
  # query QueryType
@@ -79,21 +71,22 @@ module GraphQL
79
71
  # end
80
72
  #
81
73
  class Schema
82
- extend Forwardable
83
- extend GraphQL::Schema::Member::AcceptsDefinition
84
74
  extend GraphQL::Schema::Member::HasAstNode
85
- include GraphQL::Define::InstanceDefinable
86
- extend GraphQL::Define::InstanceDefinable::DeprecatedDefine
87
75
  extend GraphQL::Schema::FindInheritedValue
76
+ extend Autoload
88
77
 
89
- class DuplicateTypeNamesError < GraphQL::Error
90
- def initialize(type_name:, first_definition:, second_definition:, path:)
91
- super("Multiple definitions for `#{type_name}`. Previously found #{first_definition.inspect} (#{first_definition.class}), then found #{second_definition.inspect} (#{second_definition.class}) at #{path.join(".")}")
78
+ autoload :BUILT_IN_TYPES, "graphql/schema/built_in_types"
79
+
80
+ class DuplicateNamesError < GraphQL::Error
81
+ attr_reader :duplicated_name
82
+ def initialize(duplicated_name:, duplicated_definition_1:, duplicated_definition_2:)
83
+ @duplicated_name = duplicated_name
84
+ super(
85
+ "Found two visible definitions for `#{duplicated_name}`: #{duplicated_definition_1}, #{duplicated_definition_2}"
86
+ )
92
87
  end
93
88
  end
94
89
 
95
- class DuplicateNamesError < GraphQL::Error; end
96
-
97
90
  class UnresolvedLateBoundTypeError < GraphQL::Error
98
91
  attr_reader :type
99
92
  def initialize(type:)
@@ -102,767 +95,164 @@ module GraphQL
102
95
  end
103
96
  end
104
97
 
105
- module LazyHandlingMethods
106
- # Call the given block at the right time, either:
107
- # - Right away, if `value` is not registered with `lazy_resolve`
108
- # - After resolving `value`, if it's registered with `lazy_resolve` (eg, `Promise`)
109
- # @api private
110
- def after_lazy(value, &block)
111
- if lazy?(value)
112
- GraphQL::Execution::Lazy.new do
113
- result = sync_lazy(value)
114
- # The returned result might also be lazy, so check it, too
115
- after_lazy(result, &block)
116
- end
98
+ # Error that is raised when [#Schema#from_definition] is passed an invalid schema definition string.
99
+ class InvalidDocumentError < Error; end;
100
+
101
+ class << self
102
+ # Create schema with the result of an introspection query.
103
+ # @param introspection_result [Hash] A response from {GraphQL::Introspection::INTROSPECTION_QUERY}
104
+ # @return [Class<GraphQL::Schema>] the schema described by `input`
105
+ def from_introspection(introspection_result)
106
+ GraphQL::Schema::Loader.load(introspection_result)
107
+ end
108
+
109
+ # Create schema from an IDL schema or file containing an IDL definition.
110
+ # @param definition_or_path [String] A schema definition string, or a path to a file containing the definition
111
+ # @param default_resolve [<#call(type, field, obj, args, ctx)>] A callable for handling field resolution
112
+ # @param parser [Object] An object for handling definition string parsing (must respond to `parse`)
113
+ # @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
114
+ # @return [Class] the schema described by `document`
115
+ def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {}, base_types: {})
116
+ # If the file ends in `.graphql` or `.graphqls`, treat it like a filepath
117
+ if definition_or_path.end_with?(".graphql") || definition_or_path.end_with?(".graphqls")
118
+ GraphQL::Schema::BuildFromDefinition.from_definition_path(
119
+ self,
120
+ definition_or_path,
121
+ default_resolve: default_resolve,
122
+ parser: parser,
123
+ using: using,
124
+ base_types: base_types,
125
+ )
117
126
  else
118
- yield(value) if block_given?
127
+ GraphQL::Schema::BuildFromDefinition.from_definition(
128
+ self,
129
+ definition_or_path,
130
+ default_resolve: default_resolve,
131
+ parser: parser,
132
+ using: using,
133
+ base_types: base_types,
134
+ )
119
135
  end
120
136
  end
121
137
 
122
- # Override this method to handle lazy objects in a custom way.
123
- # @param value [Object] an instance of a class registered with {.lazy_resolve}
124
- # @return [Object] A GraphQL-ready (non-lazy) object
125
- # @api private
126
- def sync_lazy(value)
127
- lazy_method = lazy_method_name(value)
128
- if lazy_method
129
- synced_value = value.public_send(lazy_method)
130
- sync_lazy(synced_value)
131
- else
132
- value
133
- end
138
+ def deprecated_graphql_definition
139
+ graphql_definition(silence_deprecation_warning: true)
134
140
  end
135
141
 
136
- # @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {#lazy_resolve}.
137
- def lazy_method_name(obj)
138
- lazy_methods.get(obj)
142
+ # @return [GraphQL::Subscriptions]
143
+ def subscriptions(inherited: true)
144
+ defined?(@subscriptions) ? @subscriptions : (inherited ? find_inherited_value(:subscriptions, nil) : nil)
139
145
  end
140
146
 
141
- # @return [Boolean] True if this object should be lazily resolved
142
- def lazy?(obj)
143
- !!lazy_method_name(obj)
147
+ def subscriptions=(new_implementation)
148
+ @subscriptions = new_implementation
144
149
  end
145
150
 
146
- # Return a lazy if any of `maybe_lazies` are lazy,
147
- # otherwise, call the block eagerly and return the result.
148
- # @param maybe_lazies [Array]
149
- # @api private
150
- def after_any_lazies(maybe_lazies)
151
- if maybe_lazies.any? { |l| lazy?(l) }
152
- GraphQL::Execution::Lazy.all(maybe_lazies).then do |result|
153
- yield result
154
- end
151
+ # @param new_mode [Symbol] If configured, this will be used when `context: { trace_mode: ... }` isn't set.
152
+ def default_trace_mode(new_mode = NOT_CONFIGURED)
153
+ if !NOT_CONFIGURED.equal?(new_mode)
154
+ @default_trace_mode = new_mode
155
+ elsif defined?(@default_trace_mode) &&
156
+ !@default_trace_mode.nil? # This `nil?` check seems necessary because of
157
+ # Ractors silently initializing @default_trace_mode somehow
158
+ @default_trace_mode
159
+ elsif superclass.respond_to?(:default_trace_mode)
160
+ superclass.default_trace_mode
155
161
  else
156
- yield maybe_lazies
162
+ :default
157
163
  end
158
164
  end
159
- end
160
-
161
- include LazyHandlingMethods
162
- extend LazyHandlingMethods
163
-
164
- deprecated_accepts_definitions \
165
- :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
166
- :validate_timeout, :validate_max_errors, :max_depth, :max_complexity, :default_max_page_size,
167
- :orphan_types, :resolve_type, :type_error, :parse_error,
168
- :error_bubbling,
169
- :raise_definition_error,
170
- :object_from_id, :id_from_object,
171
- :default_mask,
172
- :cursor_encoder,
173
- # If these are given as classes, normalize them. Accept `nil` when building from string.
174
- query: ->(schema, t) { schema.query = t.respond_to?(:graphql_definition) ? t.graphql_definition : t },
175
- mutation: ->(schema, t) { schema.mutation = t.respond_to?(:graphql_definition) ? t.graphql_definition : t },
176
- subscription: ->(schema, t) { schema.subscription = t.respond_to?(:graphql_definition) ? t.graphql_definition : t },
177
- disable_introspection_entry_points: ->(schema) { schema.disable_introspection_entry_points = true },
178
- disable_schema_introspection_entry_point: ->(schema) { schema.disable_schema_introspection_entry_point = true },
179
- disable_type_introspection_entry_point: ->(schema) { schema.disable_type_introspection_entry_point = true },
180
- directives: ->(schema, directives) { schema.directives = directives.reduce({}) { |m, d| m[d.graphql_name] = d; m } },
181
- directive: ->(schema, directive) { schema.directives[directive.graphql_name] = directive },
182
- instrument: ->(schema, type, instrumenter, after_built_ins: false) {
183
- if type == :field && after_built_ins
184
- type = :field_after_built_ins
185
- end
186
- schema.instrumenters[type] << instrumenter
187
- },
188
- query_analyzer: ->(schema, analyzer) {
189
- if analyzer == GraphQL::Authorization::Analyzer
190
- GraphQL::Deprecation.warn("The Authorization query analyzer is deprecated. Authorizing at query runtime is generally a better idea.")
191
- end
192
- schema.query_analyzers << analyzer
193
- },
194
- multiplex_analyzer: ->(schema, analyzer) { schema.multiplex_analyzers << analyzer },
195
- middleware: ->(schema, middleware) { schema.middleware << middleware },
196
- lazy_resolve: ->(schema, lazy_class, lazy_value_method) { schema.lazy_methods.set(lazy_class, lazy_value_method) },
197
- rescue_from: ->(schema, err_class, &block) { schema.rescue_from(err_class, &block) },
198
- tracer: ->(schema, tracer) { schema.tracers.push(tracer) }
199
-
200
- ensure_defined :introspection_system
201
-
202
- attr_accessor \
203
- :query, :mutation, :subscription,
204
- :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
205
- :validate_timeout, :validate_max_errors, :max_depth, :max_complexity, :default_max_page_size,
206
- :orphan_types, :directives,
207
- :query_analyzers, :multiplex_analyzers, :instrumenters, :lazy_methods,
208
- :cursor_encoder,
209
- :ast_node,
210
- :raise_definition_error,
211
- :introspection_namespace,
212
- :analysis_engine
213
-
214
- # [Boolean] True if this object bubbles validation errors up from a field into its parent InputObject, if there is one.
215
- attr_accessor :error_bubbling
216
-
217
- # Single, long-lived instance of the provided subscriptions class, if there is one.
218
- # @return [GraphQL::Subscriptions]
219
- attr_accessor :subscriptions
220
-
221
- # @return [MiddlewareChain] MiddlewareChain which is applied to fields during execution
222
- attr_accessor :middleware
223
-
224
- # @return [<#call(member, ctx)>] A callable for filtering members of the schema
225
- # @see {Query.new} for query-specific filters with `except:`
226
- attr_accessor :default_mask
227
-
228
- # @see {GraphQL::Query::Context} The parent class of these classes
229
- # @return [Class] Instantiated for each query
230
- attr_accessor :context_class
231
-
232
- # [Boolean] True if this object disables the introspection entry point fields
233
- attr_accessor :disable_introspection_entry_points
234
-
235
- def disable_introspection_entry_points?
236
- !!@disable_introspection_entry_points
237
- end
238
-
239
- # [Boolean] True if this object disables the __schema introspection entry point field
240
- attr_accessor :disable_schema_introspection_entry_point
241
-
242
- def disable_schema_introspection_entry_point?
243
- !!@disable_schema_introspection_entry_point
244
- end
245
-
246
- # [Boolean] True if this object disables the __type introspection entry point field
247
- attr_accessor :disable_type_introspection_entry_point
248
-
249
- def disable_type_introspection_entry_point?
250
- !!@disable_type_introspection_entry_point
251
- end
252
-
253
- class << self
254
- attr_writer :default_execution_strategy
255
- end
256
-
257
- def default_filter
258
- GraphQL::Filter.new(except: default_mask)
259
- end
260
-
261
- # @return [Array<#trace(key, data)>] Tracers applied to every query
262
- # @see {Query#tracers} for query-specific tracers
263
- attr_reader :tracers
264
-
265
- DYNAMIC_FIELDS = ["__type", "__typename", "__schema"].freeze
266
-
267
- attr_reader :static_validator, :object_from_id_proc, :id_from_object_proc, :resolve_type_proc
268
-
269
- def initialize
270
- @tracers = []
271
- @definition_error = nil
272
- @orphan_types = []
273
- @directives = {}
274
- self.class.default_directives.each do |name, dir|
275
- @directives[name] = dir.graphql_definition
276
- end
277
- @static_validator = GraphQL::StaticValidation::Validator.new(schema: self)
278
- @middleware = MiddlewareChain.new(final_step: GraphQL::Execution::Execute::FieldResolveStep)
279
- @query_analyzers = []
280
- @multiplex_analyzers = []
281
- @resolve_type_proc = nil
282
- @object_from_id_proc = nil
283
- @id_from_object_proc = nil
284
- @type_error_proc = DefaultTypeError
285
- @parse_error_proc = DefaultParseError
286
- @instrumenters = Hash.new { |h, k| h[k] = [] }
287
- @lazy_methods = GraphQL::Execution::Lazy::LazyMethodMap.new
288
- @lazy_methods.set(GraphQL::Execution::Lazy, :value)
289
- @cursor_encoder = Base64Encoder
290
- # For schema instances, default to legacy runtime modules
291
- @analysis_engine = GraphQL::Analysis
292
- @query_execution_strategy = GraphQL::Execution::Execute
293
- @mutation_execution_strategy = GraphQL::Execution::Execute
294
- @subscription_execution_strategy = GraphQL::Execution::Execute
295
- @default_mask = GraphQL::Schema::NullMask
296
- @rebuilding_artifacts = false
297
- @context_class = GraphQL::Query::Context
298
- @introspection_namespace = nil
299
- @introspection_system = nil
300
- @interpreter = false
301
- @error_bubbling = false
302
- @disable_introspection_entry_points = false
303
- @disable_schema_introspection_entry_point = false
304
- @disable_type_introspection_entry_point = false
305
- end
306
-
307
- # @return [Boolean] True if using the new {GraphQL::Execution::Interpreter}
308
- def interpreter?
309
- query_execution_strategy == GraphQL::Execution::Interpreter &&
310
- mutation_execution_strategy == GraphQL::Execution::Interpreter &&
311
- subscription_execution_strategy == GraphQL::Execution::Interpreter
312
- end
313
-
314
- def inspect
315
- "#<#{self.class.name} ...>"
316
- end
317
-
318
- def initialize_copy(other)
319
- super
320
- @orphan_types = other.orphan_types.dup
321
- @directives = other.directives.dup
322
- @static_validator = GraphQL::StaticValidation::Validator.new(schema: self)
323
- @middleware = other.middleware.dup
324
- @query_analyzers = other.query_analyzers.dup
325
- @multiplex_analyzers = other.multiplex_analyzers.dup
326
- @tracers = other.tracers.dup
327
- @possible_types = GraphQL::Schema::PossibleTypes.new(self)
328
-
329
- @lazy_methods = other.lazy_methods.dup
330
-
331
- @instrumenters = Hash.new { |h, k| h[k] = [] }
332
- other.instrumenters.each do |key, insts|
333
- @instrumenters[key].concat(insts)
334
- end
335
-
336
- if other.rescues?
337
- @rescue_middleware = other.rescue_middleware
338
- end
339
-
340
- # This will be rebuilt when it's requested
341
- # or during a later `define` call
342
- @types = nil
343
- @introspection_system = nil
344
- end
345
-
346
- def rescue_from(*args, &block)
347
- rescue_middleware.rescue_from(*args, &block)
348
- end
349
-
350
- def remove_handler(*args, &block)
351
- rescue_middleware.remove_handler(*args, &block)
352
- end
353
-
354
- def using_ast_analysis?
355
- @analysis_engine == GraphQL::Analysis::AST
356
- end
357
-
358
- # For forwards-compatibility with Schema classes
359
- alias :graphql_definition :itself
360
-
361
- def deprecated_define(**kwargs, &block)
362
- super
363
- ensure_defined
364
- # Assert that all necessary configs are present:
365
- validation_error = Validation.validate(self)
366
- validation_error && raise(GraphQL::RequiredImplementationMissingError, validation_error)
367
- rebuild_artifacts
368
-
369
- @definition_error = nil
370
- nil
371
- rescue StandardError => err
372
- if @raise_definition_error || err.is_a?(CyclicalDefinitionError) || err.is_a?(GraphQL::RequiredImplementationMissingError)
373
- raise
374
- else
375
- # Raise this error _later_ to avoid messing with Rails constant loading
376
- @definition_error = err
377
- end
378
- nil
379
- end
380
-
381
- # Attach `instrumenter` to this schema for instrumenting events of `instrumentation_type`.
382
- # @param instrumentation_type [Symbol]
383
- # @param instrumenter
384
- # @return [void]
385
- def instrument(instrumentation_type, instrumenter)
386
- @instrumenters[instrumentation_type] << instrumenter
387
- if instrumentation_type == :field
388
- rebuild_artifacts
389
- end
390
- end
391
-
392
- # @return [Array<GraphQL::BaseType>] The root types of this schema
393
- def root_types
394
- @root_types ||= begin
395
- rebuild_artifacts
396
- @root_types
397
- end
398
- end
399
-
400
- # @see [GraphQL::Schema::Warden] Restricted access to members of a schema
401
- # @return [GraphQL::Schema::TypeMap] `{ name => type }` pairs of types in this schema
402
- def types
403
- @types ||= begin
404
- rebuild_artifacts
405
- @types
406
- end
407
- end
408
-
409
- def get_type(type_name)
410
- @types[type_name]
411
- end
412
-
413
- # @api private
414
- def introspection_system
415
- @introspection_system ||= begin
416
- rebuild_artifacts
417
- @introspection_system
418
- end
419
- end
420
-
421
- # Returns a list of Arguments and Fields referencing a certain type
422
- # @param type_name [String]
423
- # @return [Hash]
424
- def references_to(type_name = nil)
425
- rebuild_artifacts unless defined?(@type_reference_map)
426
- if type_name
427
- @type_reference_map.fetch(type_name, [])
428
- else
429
- @type_reference_map
430
- end
431
- end
432
-
433
- # Returns a list of Union types in which a type is a member
434
- # @param type [GraphQL::ObjectType]
435
- # @return [Array<GraphQL::UnionType>] list of union types of which the type is a member
436
- def union_memberships(type)
437
- rebuild_artifacts unless defined?(@union_memberships)
438
- @union_memberships.fetch(type.name, [])
439
- end
440
-
441
- # Execute a query on itself. Raises an error if the schema definition is invalid.
442
- # @see {Query#initialize} for arguments.
443
- # @return [Hash] query result, ready to be serialized as JSON
444
- def execute(query_str = nil, **kwargs)
445
- if query_str
446
- kwargs[:query] = query_str
447
- end
448
- # Some of the query context _should_ be passed to the multiplex, too
449
- multiplex_context = if (ctx = kwargs[:context])
450
- {
451
- backtrace: ctx[:backtrace],
452
- tracers: ctx[:tracers],
453
- }
454
- else
455
- {}
456
- end
457
- # Since we're running one query, don't run a multiplex-level complexity analyzer
458
- all_results = multiplex([kwargs], max_complexity: nil, context: multiplex_context)
459
- all_results[0]
460
- end
461
-
462
- # Execute several queries on itself. Raises an error if the schema definition is invalid.
463
- # @example Run several queries at once
464
- # context = { ... }
465
- # queries = [
466
- # { query: params[:query_1], variables: params[:variables_1], context: context },
467
- # { query: params[:query_2], variables: params[:variables_2], context: context },
468
- # ]
469
- # results = MySchema.multiplex(queries)
470
- # render json: {
471
- # result_1: results[0],
472
- # result_2: results[1],
473
- # }
474
- #
475
- # @see {Query#initialize} for query keyword arguments
476
- # @see {Execution::Multiplex#run_queries} for multiplex keyword arguments
477
- # @param queries [Array<Hash>] Keyword arguments for each query
478
- # @param context [Hash] Multiplex-level context
479
- # @return [Array<Hash>] One result for each query in the input
480
- def multiplex(queries, **kwargs)
481
- with_definition_error_check {
482
- GraphQL::Execution::Multiplex.run_all(self, queries, **kwargs)
483
- }
484
- end
485
165
 
486
- # Search for a schema member using a string path
487
- # @example Finding a Field
488
- # Schema.find("Ensemble.musicians")
489
- #
490
- # @see {GraphQL::Schema::Finder} for more examples
491
- # @param path [String] A dot-separated path to the member
492
- # @raise [Schema::Finder::MemberNotFoundError] if path could not be found
493
- # @return [GraphQL::BaseType, GraphQL::Field, GraphQL::Argument, GraphQL::Directive] A GraphQL Schema Member
494
- def find(path)
495
- rebuild_artifacts unless defined?(@finder)
496
- @find_cache[path] ||= @finder.find(path)
497
- end
498
-
499
- # Resolve field named `field_name` for type `parent_type`.
500
- # Handles dynamic fields `__typename`, `__type` and `__schema`, too
501
- # @param parent_type [String, GraphQL::BaseType]
502
- # @param field_name [String]
503
- # @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`
504
- # @see [GraphQL::Schema::Warden] Restricted access to members of a schema
505
- def get_field(parent_type, field_name, _context = GraphQL::Query::NullContext)
506
- with_definition_error_check do
507
- parent_type_name = case parent_type
508
- when GraphQL::BaseType, Class, Module
509
- parent_type.graphql_name
510
- when String
511
- parent_type
512
- else
513
- raise "Unexpected parent_type: #{parent_type}"
166
+ def trace_class(new_class = nil)
167
+ if new_class
168
+ # If any modules were already added for `:default`,
169
+ # re-apply them here
170
+ mods = trace_modules_for(:default)
171
+ mods.each { |mod| new_class.include(mod) }
172
+ new_class.include(DefaultTraceClass)
173
+ trace_mode(:default, new_class)
514
174
  end
175
+ trace_class_for(:default, build: true)
176
+ end
515
177
 
516
- defined_field = @instrumented_field_map[parent_type_name][field_name]
517
- if defined_field
518
- defined_field
519
- elsif parent_type == query && (entry_point_field = introspection_system.entry_point(name: field_name))
520
- entry_point_field
521
- elsif (dynamic_field = introspection_system.dynamic_field(name: field_name))
522
- dynamic_field
178
+ # @return [Class] Return the trace class to use for this mode, looking one up on the superclass if this Schema doesn't have one defined.
179
+ def trace_class_for(mode, build: false)
180
+ if (trace_class = own_trace_modes[mode])
181
+ trace_class
182
+ elsif superclass.respond_to?(:trace_class_for) && (trace_class = superclass.trace_class_for(mode, build: false))
183
+ trace_class
184
+ elsif build
185
+ own_trace_modes[mode] = build_trace_mode(mode)
523
186
  else
524
187
  nil
525
188
  end
526
189
  end
527
- end
528
-
529
- # Fields for this type, after instrumentation is applied
530
- # @return [Hash<String, GraphQL::Field>]
531
- def get_fields(type)
532
- @instrumented_field_map[type.graphql_name]
533
- end
534
-
535
- def type_from_ast(ast_node, context:)
536
- GraphQL::Schema::TypeExpression.build_type(self, ast_node)
537
- end
538
190
 
539
- # @see [GraphQL::Schema::Warden] Restricted access to members of a schema
540
- # @param type_defn [GraphQL::InterfaceType, GraphQL::UnionType] the type whose members you want to retrieve
541
- # @param context [GraphQL::Query::Context] The context for the current query
542
- # @return [Array<GraphQL::ObjectType>] types which belong to `type_defn` in this schema
543
- def possible_types(type_defn, context = GraphQL::Query::NullContext)
544
- if context == GraphQL::Query::NullContext
545
- @possible_types ||= GraphQL::Schema::PossibleTypes.new(self)
546
- @possible_types.possible_types(type_defn, context)
547
- else
548
- # Use the incoming context to cache this instance --
549
- # if it were cached on the schema, we'd have a memory leak
550
- # https://github.com/rmosolgo/graphql-ruby/issues/2878
551
- ns = context.namespace(:possible_types)
552
- per_query_possible_types = ns[:possible_types] ||= GraphQL::Schema::PossibleTypes.new(self)
553
- per_query_possible_types.possible_types(type_defn, context)
554
- end
555
- end
556
-
557
- # @see [GraphQL::Schema::Warden] Restricted access to root types
558
- # @return [GraphQL::ObjectType, nil]
559
- def root_type_for_operation(operation)
560
- case operation
561
- when "query"
562
- query
563
- when "mutation"
564
- mutation
565
- when "subscription"
566
- subscription
567
- else
568
- raise ArgumentError, "unknown operation type: #{operation}"
569
- end
570
- end
571
-
572
- def execution_strategy_for_operation(operation)
573
- case operation
574
- when "query"
575
- query_execution_strategy
576
- when "mutation"
577
- mutation_execution_strategy
578
- when "subscription"
579
- subscription_execution_strategy
580
- else
581
- raise ArgumentError, "unknown operation type: #{operation}"
582
- end
583
- end
584
-
585
- # Determine the GraphQL type for a given object.
586
- # This is required for unions and interfaces (including Relay's `Node` interface)
587
- # @see [GraphQL::Schema::Warden] Restricted access to members of a schema
588
- # @param type [GraphQL::UnionType, GraphQL:InterfaceType] the abstract type which is being resolved
589
- # @param object [Any] An application object which GraphQL is currently resolving on
590
- # @param ctx [GraphQL::Query::Context] The context for the current query
591
- # @return [GraphQL::ObjectType] The type for exposing `object` in GraphQL
592
- def resolve_type(type, object, ctx = :__undefined__)
593
- check_resolved_type(type, object, ctx) do |ok_type, ok_object, ok_ctx|
594
- if @resolve_type_proc.nil?
595
- raise(GraphQL::RequiredImplementationMissingError, "Can't determine GraphQL type for: #{ok_object.inspect}, define `resolve_type (type, obj, ctx) -> { ... }` inside `Schema.define`.")
596
- end
597
- @resolve_type_proc.call(ok_type, ok_object, ok_ctx)
598
- end
599
- end
600
-
601
- # This is a compatibility hack so that instance-level and class-level
602
- # methods can get correctness checks without calling one another
603
- # @api private
604
- def check_resolved_type(type, object, ctx = :__undefined__)
605
- if ctx == :__undefined__
606
- # Old method signature
607
- ctx = object
608
- object = type
609
- type = nil
610
- end
611
-
612
- if object.is_a?(GraphQL::Schema::Object)
613
- object = object.object
614
- end
615
-
616
- if type.respond_to?(:graphql_definition)
617
- type = type.graphql_definition
191
+ # Configure `trace_class` to be used whenever `context: { trace_mode: mode_name }` is requested.
192
+ # {default_trace_mode} is used when no `trace_mode: ...` is requested.
193
+ #
194
+ # When a `trace_class` is added this way, it will _not_ receive other modules added with `trace_with(...)`
195
+ # unless `trace_mode` is explicitly given. (This class will not receive any default trace modules.)
196
+ #
197
+ # Subclasses of the schema will use `trace_class` as a base class for this mode and those
198
+ # subclass also will _not_ receive default tracing modules.
199
+ #
200
+ # @param mode_name [Symbol]
201
+ # @param trace_class [Class] subclass of GraphQL::Tracing::Trace
202
+ # @return void
203
+ def trace_mode(mode_name, trace_class)
204
+ own_trace_modes[mode_name] = trace_class
205
+ nil
618
206
  end
619
207
 
620
- # Prefer a type-local function; fall back to the schema-level function
621
- type_proc = type && type.resolve_type_proc
622
- type_result = if type_proc
623
- type_proc.call(object, ctx)
624
- else
625
- yield(type, object, ctx)
208
+ def own_trace_modes
209
+ @own_trace_modes ||= {}
626
210
  end
627
211
 
628
- if type_result.nil?
629
- nil
630
- else
631
- after_lazy(type_result) do |resolved_type_result|
632
- if resolved_type_result.respond_to?(:graphql_definition)
633
- resolved_type_result = resolved_type_result.graphql_definition
212
+ def build_trace_mode(mode)
213
+ case mode
214
+ when :default
215
+ # Use the superclass's default mode if it has one, or else start an inheritance chain at the built-in base class.
216
+ base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode, build: true)) || GraphQL::Tracing::Trace
217
+ const_set(:DefaultTrace, Class.new(base_class) do
218
+ include DefaultTraceClass
219
+ end)
220
+ else
221
+ # First, see if the superclass has a custom-defined class for this.
222
+ # Then, if it doesn't, use this class's default trace
223
+ base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || trace_class_for(:default, build: true)
224
+ # Prepare the default trace class if it hasn't been initialized yet
225
+ base_class ||= (own_trace_modes[:default] = build_trace_mode(:default))
226
+ mods = trace_modules_for(mode)
227
+ if base_class < DefaultTraceClass
228
+ mods = trace_modules_for(:default) + mods
634
229
  end
635
- if !resolved_type_result.is_a?(GraphQL::BaseType)
636
- type_str = "#{resolved_type_result} (#{resolved_type_result.class.name})"
637
- raise "resolve_type(#{object}) returned #{type_str}, but it should return a GraphQL type"
638
- else
639
- resolved_type_result
230
+ # Copy the existing default options into this mode's options
231
+ default_options = trace_options_for(:default)
232
+ add_trace_options_for(mode, default_options)
233
+
234
+ Class.new(base_class) do
235
+ !mods.empty? && include(*mods)
640
236
  end
641
237
  end
642
238
  end
643
- end
644
-
645
- def resolve_type=(new_resolve_type_proc)
646
- callable = GraphQL::BackwardsCompatibility.wrap_arity(new_resolve_type_proc, from: 2, to: 3, last: true, name: "Schema#resolve_type(type, obj, ctx)")
647
- @resolve_type_proc = callable
648
- end
649
-
650
- # Fetch an application object by its unique id
651
- # @param id [String] A unique identifier, provided previously by this GraphQL schema
652
- # @param ctx [GraphQL::Query::Context] The context for the current query
653
- # @return [Any] The application object identified by `id`
654
- def object_from_id(id, ctx)
655
- if @object_from_id_proc.nil?
656
- raise(GraphQL::RequiredImplementationMissingError, "Can't fetch an object for id \"#{id}\" because the schema's `object_from_id (id, ctx) -> { ... }` function is not defined")
657
- else
658
- @object_from_id_proc.call(id, ctx)
659
- end
660
- end
661
-
662
- # @param new_proc [#call] A new callable for fetching objects by ID
663
- def object_from_id=(new_proc)
664
- @object_from_id_proc = new_proc
665
- end
666
-
667
- # When we encounter a type error during query execution, we call this hook.
668
- #
669
- # You can use this hook to write a log entry,
670
- # add a {GraphQL::ExecutionError} to the response (with `ctx.add_error`)
671
- # or raise an exception and halt query execution.
672
- #
673
- # @example A `nil` is encountered by a non-null field
674
- # type_error ->(err, query_ctx) {
675
- # err.is_a?(GraphQL::InvalidNullError) # => true
676
- # }
677
- #
678
- # @example An object doesn't resolve to one of a {UnionType}'s members
679
- # type_error ->(err, query_ctx) {
680
- # err.is_a?(GraphQL::UnresolvedTypeError) # => true
681
- # }
682
- #
683
- # @see {DefaultTypeError} is the default behavior.
684
- # @param err [GraphQL::TypeError] The error encountered during execution
685
- # @param ctx [GraphQL::Query::Context] The context for the field where the error occurred
686
- # @return void
687
- def type_error(err, ctx)
688
- @type_error_proc.call(err, ctx)
689
- end
690
-
691
- # @param new_proc [#call] A new callable for handling type errors during execution
692
- def type_error=(new_proc)
693
- @type_error_proc = new_proc
694
- end
695
-
696
- # Can't delegate to `class`
697
- alias :_schema_class :class
698
- def_delegators :_schema_class, :unauthorized_object, :unauthorized_field, :inaccessible_fields
699
- def_delegators :_schema_class, :directive
700
- def_delegators :_schema_class, :error_handler
701
- def_delegators :_schema_class, :validate
702
-
703
-
704
- # Given this schema member, find the class-based definition object
705
- # whose `method_name` should be treated as an application hook
706
- # @see {.visible?}
707
- # @see {.accessible?}
708
- def call_on_type_class(member, method_name, context, default:)
709
- member = if member.respond_to?(:type_class)
710
- member.type_class
711
- else
712
- member
713
- end
714
-
715
- if member.respond_to?(:relay_node_type) && (t = member.relay_node_type)
716
- member = t
717
- end
718
-
719
- if member.respond_to?(method_name)
720
- member.public_send(method_name, context)
721
- else
722
- default
723
- end
724
- end
725
-
726
- def visible?(member, context)
727
- call_on_type_class(member, :visible?, context, default: true)
728
- end
729
-
730
- def accessible?(member, context)
731
- call_on_type_class(member, :accessible?, context, default: true)
732
- end
733
-
734
- # A function to call when {#execute} receives an invalid query string
735
- #
736
- # @see {DefaultParseError} is the default behavior.
737
- # @param err [GraphQL::ParseError] The error encountered during parsing
738
- # @param ctx [GraphQL::Query::Context] The context for the query where the error occurred
739
- # @return void
740
- def parse_error(err, ctx)
741
- @parse_error_proc.call(err, ctx)
742
- end
743
-
744
- # @param new_proc [#call] A new callable for handling parse errors during execution
745
- def parse_error=(new_proc)
746
- @parse_error_proc = new_proc
747
- end
748
-
749
- # Get a unique identifier from this object
750
- # @param object [Any] An application object
751
- # @param type [GraphQL::BaseType] The current type definition
752
- # @param ctx [GraphQL::Query::Context] the context for the current query
753
- # @return [String] a unique identifier for `object` which clients can use to refetch it
754
- def id_from_object(object, type, ctx)
755
- if @id_from_object_proc.nil?
756
- raise(GraphQL::RequiredImplementationMissingError, "Can't generate an ID for #{object.inspect} of type #{type}, schema's `id_from_object` must be defined")
757
- else
758
- @id_from_object_proc.call(object, type, ctx)
759
- end
760
- end
761
239
 
762
- # @param new_proc [#call] A new callable for generating unique IDs
763
- def id_from_object=(new_proc)
764
- @id_from_object_proc = new_proc
765
- end
766
-
767
- # Create schema with the result of an introspection query.
768
- # @param introspection_result [Hash] A response from {GraphQL::Introspection::INTROSPECTION_QUERY}
769
- # @return [GraphQL::Schema] the schema described by `input`
770
- def self.from_introspection(introspection_result)
771
- GraphQL::Schema::Loader.load(introspection_result)
772
- end
773
-
774
- # Create schema from an IDL schema or file containing an IDL definition.
775
- # @param definition_or_path [String] A schema definition string, or a path to a file containing the definition
776
- # @param default_resolve [<#call(type, field, obj, args, ctx)>] A callable for handling field resolution
777
- # @param parser [Object] An object for handling definition string parsing (must respond to `parse`)
778
- # @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
779
- # @return [Class] the schema described by `document`
780
- def self.from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {})
781
- # If the file ends in `.graphql`, treat it like a filepath
782
- if definition_or_path.end_with?(".graphql")
783
- GraphQL::Schema::BuildFromDefinition.from_definition_path(
784
- definition_or_path,
785
- default_resolve: default_resolve,
786
- parser: parser,
787
- using: using,
788
- )
789
- else
790
- GraphQL::Schema::BuildFromDefinition.from_definition(
791
- definition_or_path,
792
- default_resolve: default_resolve,
793
- parser: parser,
794
- using: using,
795
- )
240
+ def own_trace_modules
241
+ @own_trace_modules ||= Hash.new { |h, k| h[k] = [] }
796
242
  end
797
- end
798
-
799
- # Error that is raised when [#Schema#from_definition] is passed an invalid schema definition string.
800
- class InvalidDocumentError < Error; end;
801
-
802
- # Return the GraphQL IDL for the schema
803
- # @param context [Hash]
804
- # @param only [<#call(member, ctx)>]
805
- # @param except [<#call(member, ctx)>]
806
- # @return [String]
807
- def to_definition(only: nil, except: nil, context: {})
808
- GraphQL::Schema::Printer.print_schema(self, only: only, except: except, context: context)
809
- end
810
-
811
- # Return the GraphQL::Language::Document IDL AST for the schema
812
- # @param context [Hash]
813
- # @param only [<#call(member, ctx)>]
814
- # @param except [<#call(member, ctx)>]
815
- # @return [GraphQL::Language::Document]
816
- def to_document(only: nil, except: nil, context: {})
817
- GraphQL::Language::DocumentFromSchemaDefinition.new(self, only: only, except: except, context: context).document
818
- end
819
-
820
- # Return the Hash response of {Introspection::INTROSPECTION_QUERY}.
821
- # @param context [Hash]
822
- # @param only [<#call(member, ctx)>]
823
- # @param except [<#call(member, ctx)>]
824
- # @return [Hash] GraphQL result
825
- def as_json(only: nil, except: nil, context: {})
826
- execute(Introspection.query(include_deprecated_args: true), only: only, except: except, context: context).to_h
827
- end
828
-
829
- # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
830
- # @see {#as_json}
831
- # @return [String]
832
- def to_json(*args)
833
- JSON.pretty_generate(as_json(*args))
834
- end
835
-
836
- def new_connections?
837
- !!connections
838
- end
839
-
840
- attr_accessor :connections
841
-
842
- class << self
843
- extend Forwardable
844
- # For compatibility, these methods all:
845
- # - Cause the Schema instance to be created, if it hasn't been created yet
846
- # - Delegate to that instance
847
- # Eventually, the methods will be moved into this class, removing the need for the singleton.
848
- def_delegators :deprecated_graphql_definition,
849
- # Execution
850
- :execution_strategy_for_operation,
851
- # Configuration
852
- :metadata, :redefine,
853
- :id_from_object_proc, :object_from_id_proc,
854
- :id_from_object=, :object_from_id=,
855
- :remove_handler
856
243
 
857
- def deprecated_graphql_definition
858
- graphql_definition(silence_deprecation_warning: true)
244
+ # @return [Array<Module>] Modules added for tracing in `trace_mode`, including inherited ones
245
+ def trace_modules_for(trace_mode)
246
+ modules = own_trace_modules[trace_mode]
247
+ if superclass.respond_to?(:trace_modules_for)
248
+ modules += superclass.trace_modules_for(trace_mode)
249
+ end
250
+ modules
859
251
  end
860
252
 
861
- # @return [GraphQL::Subscriptions]
862
- attr_accessor :subscriptions
863
253
 
864
254
  # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
865
- # @see {#as_json}
255
+ # @see #as_json Return a Hash representation of the schema
866
256
  # @return [String]
867
257
  def to_json(**args)
868
258
  JSON.pretty_generate(as_json(**args))
@@ -870,20 +260,29 @@ module GraphQL
870
260
 
871
261
  # Return the Hash response of {Introspection::INTROSPECTION_QUERY}.
872
262
  # @param context [Hash]
873
- # @param only [<#call(member, ctx)>]
874
- # @param except [<#call(member, ctx)>]
263
+ # @param include_deprecated_args [Boolean] If true, deprecated arguments will be included in the JSON response
264
+ # @param include_schema_description [Boolean] If true, the schema's description will be queried and included in the response
265
+ # @param include_is_repeatable [Boolean] If true, `isRepeatable: true|false` will be included with the schema's directives
266
+ # @param include_specified_by_url [Boolean] If true, scalar types' `specifiedByUrl:` will be included in the response
267
+ # @param include_is_one_of [Boolean] If true, `isOneOf: true|false` will be included with input objects
875
268
  # @return [Hash] GraphQL result
876
- def as_json(only: nil, except: nil, context: {})
877
- execute(Introspection.query(include_deprecated_args: true), only: only, except: except, context: context).to_h
269
+ def as_json(context: {}, include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false)
270
+ introspection_query = Introspection.query(
271
+ include_deprecated_args: include_deprecated_args,
272
+ include_schema_description: include_schema_description,
273
+ include_is_repeatable: include_is_repeatable,
274
+ include_is_one_of: include_is_one_of,
275
+ include_specified_by_url: include_specified_by_url,
276
+ )
277
+
278
+ execute(introspection_query, context: context).to_h
878
279
  end
879
280
 
880
281
  # Return the GraphQL IDL for the schema
881
282
  # @param context [Hash]
882
- # @param only [<#call(member, ctx)>]
883
- # @param except [<#call(member, ctx)>]
884
283
  # @return [String]
885
- def to_definition(only: nil, except: nil, context: {})
886
- GraphQL::Schema::Printer.print_schema(self, only: only, except: except, context: context)
284
+ def to_definition(context: {})
285
+ GraphQL::Schema::Printer.print_schema(self, context: context)
887
286
  end
888
287
 
889
288
  # Return the GraphQL::Language::Document IDL AST for the schema
@@ -911,35 +310,15 @@ module GraphQL
911
310
  @find_cache[path] ||= @finder.find(path)
912
311
  end
913
312
 
914
- def graphql_definition(silence_deprecation_warning: false)
915
- @graphql_definition ||= begin
916
- unless silence_deprecation_warning
917
- message = "Legacy `.graphql_definition` objects are deprecated and will be removed in GraphQL-Ruby 2.0. Use a class-based definition instead."
918
- caller_message = "\n\nCalled on #{self.inspect} from:\n #{caller(1, 25).map { |l| " #{l}" }.join("\n")}"
919
- GraphQL::Deprecation.warn(message + caller_message)
920
- end
921
- to_graphql(silence_deprecation_warning: silence_deprecation_warning)
922
- end
923
- end
924
-
925
- def default_filter
926
- GraphQL::Filter.new(except: default_mask)
927
- end
928
-
929
- def default_mask(new_mask = nil)
930
- if new_mask
931
- @own_default_mask = new_mask
932
- else
933
- @own_default_mask || find_inherited_value(:default_mask, Schema::NullMask)
934
- end
935
- end
936
-
937
313
  def static_validator
938
314
  GraphQL::StaticValidation::Validator.new(schema: self)
939
315
  end
940
316
 
317
+ # Add `plugin` to this schema
318
+ # @param plugin [#use] A Schema plugin
319
+ # @return void
941
320
  def use(plugin, **kwargs)
942
- if kwargs.any?
321
+ if !kwargs.empty?
943
322
  plugin.use(self, **kwargs)
944
323
  else
945
324
  plugin.use(self)
@@ -951,77 +330,14 @@ module GraphQL
951
330
  find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins
952
331
  end
953
332
 
954
- prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
955
- def to_graphql
956
- schema_defn = self.new
957
- schema_defn.raise_definition_error = true
958
- schema_defn.query = query && query.graphql_definition(silence_deprecation_warning: true)
959
- schema_defn.mutation = mutation && mutation.graphql_definition(silence_deprecation_warning: true)
960
- schema_defn.subscription = subscription && subscription.graphql_definition(silence_deprecation_warning: true)
961
- schema_defn.validate_timeout = validate_timeout
962
- schema_defn.validate_max_errors = validate_max_errors
963
- schema_defn.max_complexity = max_complexity
964
- schema_defn.error_bubbling = error_bubbling
965
- schema_defn.max_depth = max_depth
966
- schema_defn.default_max_page_size = default_max_page_size
967
- schema_defn.orphan_types = orphan_types.map { |t| t.graphql_definition(silence_deprecation_warning: true) }
968
- schema_defn.disable_introspection_entry_points = disable_introspection_entry_points?
969
- schema_defn.disable_schema_introspection_entry_point = disable_schema_introspection_entry_point?
970
- schema_defn.disable_type_introspection_entry_point = disable_type_introspection_entry_point?
971
-
972
- prepped_dirs = {}
973
- directives.each { |k, v| prepped_dirs[k] = v.graphql_definition}
974
- schema_defn.directives = prepped_dirs
975
- schema_defn.introspection_namespace = introspection
976
- schema_defn.resolve_type = method(:resolve_type)
977
- schema_defn.object_from_id = method(:object_from_id)
978
- schema_defn.id_from_object = method(:id_from_object)
979
- schema_defn.type_error = method(:type_error)
980
- schema_defn.context_class = context_class
981
- schema_defn.cursor_encoder = cursor_encoder
982
- schema_defn.tracers.concat(tracers)
983
- schema_defn.query_analyzers.concat(query_analyzers)
984
- schema_defn.analysis_engine = analysis_engine
985
-
986
- schema_defn.middleware.concat(all_middleware)
987
- schema_defn.multiplex_analyzers.concat(multiplex_analyzers)
988
- schema_defn.query_execution_strategy = query_execution_strategy
989
- schema_defn.mutation_execution_strategy = mutation_execution_strategy
990
- schema_defn.subscription_execution_strategy = subscription_execution_strategy
991
- schema_defn.default_mask = default_mask
992
- instrumenters.each do |step, insts|
993
- insts.each do |inst|
994
- schema_defn.instrumenters[step] << inst
995
- end
996
- end
997
-
998
- lazy_methods.each do |lazy_class, value_method|
999
- schema_defn.lazy_methods.set(lazy_class, value_method)
1000
- end
1001
-
1002
- error_handler.each_rescue do |err_class, handler|
1003
- schema_defn.rescue_from(err_class, &handler)
1004
- end
1005
-
1006
- schema_defn.subscriptions ||= self.subscriptions
1007
-
1008
- if !schema_defn.interpreter?
1009
- schema_defn.instrumenters[:query] << GraphQL::Schema::Member::Instrumentation
1010
- end
1011
-
1012
- if new_connections?
1013
- schema_defn.connections = self.connections
1014
- end
1015
-
1016
- schema_defn.send(:rebuild_artifacts)
1017
-
1018
- schema_defn
1019
- end
1020
-
1021
333
  # Build a map of `{ name => type }` and return it
1022
334
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
1023
335
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
1024
- def types(context = GraphQL::Query::NullContext)
336
+ def types(context = GraphQL::Query::NullContext.instance)
337
+ if use_visibility_profile?
338
+ types = Visibility::Profile.from_context(context, self)
339
+ return types.all_types_h
340
+ end
1025
341
  all_types = non_introspection_types.merge(introspection_system.types)
1026
342
  visible_types = {}
1027
343
  all_types.each do |k, v|
@@ -1032,7 +348,9 @@ module GraphQL
1032
348
  if visible_t.nil?
1033
349
  visible_t = t
1034
350
  else
1035
- raise DuplicateNamesError, "Found two visible type definitions for `#{k}`: #{visible_t.inspect}, #{t.inspect}"
351
+ raise DuplicateNamesError.new(
352
+ duplicated_name: k, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
353
+ )
1036
354
  end
1037
355
  end
1038
356
  end
@@ -1045,25 +363,37 @@ module GraphQL
1045
363
  end
1046
364
 
1047
365
  # @param type_name [String]
366
+ # @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
367
+ # @param use_visibility_profile Private, for migration to {Schema::Visibility}
1048
368
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
1049
- def get_type(type_name, context = GraphQL::Query::NullContext)
369
+ def get_type(type_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
370
+ if use_visibility_profile
371
+ profile = Visibility::Profile.from_context(context, self)
372
+ return profile.type(type_name)
373
+ end
1050
374
  local_entry = own_types[type_name]
1051
375
  type_defn = case local_entry
1052
376
  when nil
1053
377
  nil
1054
378
  when Array
1055
- visible_t = nil
1056
- warden = Warden.from_context(context)
1057
- local_entry.each do |t|
1058
- if warden.visible_type?(t, context)
1059
- if visible_t.nil?
1060
- visible_t = t
1061
- else
1062
- raise DuplicateNamesError, "Found two visible type definitions for `#{type_name}`: #{visible_t.inspect}, #{t.inspect}"
379
+ if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
380
+ local_entry
381
+ else
382
+ visible_t = nil
383
+ warden = Warden.from_context(context)
384
+ local_entry.each do |t|
385
+ if warden.visible_type?(t, context)
386
+ if visible_t.nil?
387
+ visible_t = t
388
+ else
389
+ raise DuplicateNamesError.new(
390
+ duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
391
+ )
392
+ end
1063
393
  end
1064
394
  end
395
+ visible_t
1065
396
  end
1066
- visible_t
1067
397
  when Module
1068
398
  local_entry
1069
399
  else
@@ -1072,7 +402,12 @@ module GraphQL
1072
402
 
1073
403
  type_defn ||
1074
404
  introspection_system.types[type_name] || # todo context-specific introspection?
1075
- (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context) : nil)
405
+ (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context, use_visibility_profile) : nil)
406
+ end
407
+
408
+ # @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility?
409
+ def has_defined_type?(type_name)
410
+ own_types.key?(type_name) || introspection_system.types.key?(type_name) || (superclass.respond_to?(:has_defined_type?) ? superclass.has_defined_type?(type_name) : false)
1076
411
  end
1077
412
 
1078
413
  # @api private
@@ -1094,55 +429,127 @@ module GraphQL
1094
429
  end
1095
430
  end
1096
431
 
1097
- def new_connections?
1098
- !!connections
1099
- end
1100
-
1101
- def query(new_query_object = nil)
1102
- if new_query_object
432
+ # Get or set the root `query { ... }` object for this schema.
433
+ #
434
+ # @example Using `Types::Query` as the entry-point
435
+ # query { Types::Query }
436
+ #
437
+ # @param new_query_object [Class<GraphQL::Schema::Object>] The root type to use for queries
438
+ # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root query type.
439
+ # @return [Class<GraphQL::Schema::Object>, nil] The configured query root type, if there is one.
440
+ def query(new_query_object = nil, &lazy_load_block)
441
+ if new_query_object || block_given?
1103
442
  if @query_object
1104
- raise GraphQL::Error, "Second definition of `query(...)` (#{new_query_object.inspect}) is invalid, already configured with #{@query_object.inspect}"
443
+ dup_defn = new_query_object || yield
444
+ raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
445
+ elsif use_visibility_profile?
446
+ if block_given?
447
+ if visibility.preload?
448
+ @query_object = lazy_load_block.call
449
+ self.visibility.query_configured(@query_object)
450
+ else
451
+ @query_object = lazy_load_block
452
+ end
453
+ else
454
+ @query_object = new_query_object
455
+ self.visibility.query_configured(@query_object)
456
+ end
1105
457
  else
1106
- @query_object = new_query_object
1107
- add_type_and_traverse(new_query_object, root: true)
1108
- nil
458
+ @query_object = new_query_object || lazy_load_block.call
459
+ add_type_and_traverse(@query_object, root: true)
1109
460
  end
461
+ nil
462
+ elsif @query_object.is_a?(Proc)
463
+ @query_object = @query_object.call
464
+ self.visibility&.query_configured(@query_object)
465
+ @query_object
1110
466
  else
1111
467
  @query_object || find_inherited_value(:query)
1112
468
  end
1113
469
  end
1114
470
 
1115
- def mutation(new_mutation_object = nil)
1116
- if new_mutation_object
471
+ # Get or set the root `mutation { ... }` object for this schema.
472
+ #
473
+ # @example Using `Types::Mutation` as the entry-point
474
+ # mutation { Types::Mutation }
475
+ #
476
+ # @param new_mutation_object [Class<GraphQL::Schema::Object>] The root type to use for mutations
477
+ # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root mutation type.
478
+ # @return [Class<GraphQL::Schema::Object>, nil] The configured mutation root type, if there is one.
479
+ def mutation(new_mutation_object = nil, &lazy_load_block)
480
+ if new_mutation_object || block_given?
1117
481
  if @mutation_object
1118
- raise GraphQL::Error, "Second definition of `mutation(...)` (#{new_mutation_object.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
482
+ dup_defn = new_mutation_object || yield
483
+ raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
484
+ elsif use_visibility_profile?
485
+ if block_given?
486
+ if visibility.preload?
487
+ @mutation_object = lazy_load_block.call
488
+ self.visibility.mutation_configured(@mutation_object)
489
+ else
490
+ @mutation_object = lazy_load_block
491
+ end
492
+ else
493
+ @mutation_object = new_mutation_object
494
+ self.visibility.mutation_configured(@mutation_object)
495
+ end
1119
496
  else
1120
- @mutation_object = new_mutation_object
1121
- add_type_and_traverse(new_mutation_object, root: true)
1122
- nil
497
+ @mutation_object = new_mutation_object || lazy_load_block.call
498
+ add_type_and_traverse(@mutation_object, root: true)
1123
499
  end
500
+ nil
501
+ elsif @mutation_object.is_a?(Proc)
502
+ @mutation_object = @mutation_object.call
503
+ self.visibility&.mutation_configured(@mutation_object)
504
+ @mutation_object
1124
505
  else
1125
506
  @mutation_object || find_inherited_value(:mutation)
1126
507
  end
1127
508
  end
1128
509
 
1129
- def subscription(new_subscription_object = nil)
1130
- if new_subscription_object
510
+ # Get or set the root `subscription { ... }` object for this schema.
511
+ #
512
+ # @example Using `Types::Subscription` as the entry-point
513
+ # subscription { Types::Subscription }
514
+ #
515
+ # @param new_subscription_object [Class<GraphQL::Schema::Object>] The root type to use for subscriptions
516
+ # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root subscription type.
517
+ # @return [Class<GraphQL::Schema::Object>, nil] The configured subscription root type, if there is one.
518
+ def subscription(new_subscription_object = nil, &lazy_load_block)
519
+ if new_subscription_object || block_given?
1131
520
  if @subscription_object
1132
- raise GraphQL::Error, "Second definition of `subscription(...)` (#{new_subscription_object.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
521
+ dup_defn = new_subscription_object || yield
522
+ raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
523
+ elsif use_visibility_profile?
524
+ if block_given?
525
+ if visibility.preload?
526
+ @subscription_object = lazy_load_block.call
527
+ visibility.subscription_configured(@subscription_object)
528
+ else
529
+ @subscription_object = lazy_load_block
530
+ end
531
+ else
532
+ @subscription_object = new_subscription_object
533
+ self.visibility.subscription_configured(@subscription_object)
534
+ end
535
+ add_subscription_extension_if_necessary
1133
536
  else
1134
- @subscription_object = new_subscription_object
537
+ @subscription_object = new_subscription_object || lazy_load_block.call
1135
538
  add_subscription_extension_if_necessary
1136
- add_type_and_traverse(new_subscription_object, root: true)
1137
- nil
539
+ add_type_and_traverse(@subscription_object, root: true)
1138
540
  end
541
+ nil
542
+ elsif @subscription_object.is_a?(Proc)
543
+ @subscription_object = @subscription_object.call
544
+ add_subscription_extension_if_necessary
545
+ self.visibility.subscription_configured(@subscription_object)
546
+ @subscription_object
1139
547
  else
1140
548
  @subscription_object || find_inherited_value(:subscription)
1141
549
  end
1142
550
  end
1143
551
 
1144
- # @see [GraphQL::Schema::Warden] Restricted access to root types
1145
- # @return [GraphQL::ObjectType, nil]
552
+ # @api private
1146
553
  def root_type_for_operation(operation)
1147
554
  case operation
1148
555
  when "query"
@@ -1156,34 +563,86 @@ module GraphQL
1156
563
  end
1157
564
  end
1158
565
 
566
+ # @return [Array<Class>] The root types (query, mutation, subscription) defined for this schema
1159
567
  def root_types
1160
- @root_types
568
+ if use_visibility_profile?
569
+ [query, mutation, subscription].compact
570
+ else
571
+ @root_types
572
+ end
573
+ end
574
+
575
+ # @api private
576
+ def warden_class
577
+ if defined?(@warden_class)
578
+ @warden_class
579
+ elsif superclass.respond_to?(:warden_class)
580
+ superclass.warden_class
581
+ else
582
+ GraphQL::Schema::Warden
583
+ end
584
+ end
585
+
586
+ # @api private
587
+ attr_writer :warden_class
588
+
589
+ # @api private
590
+ def visibility_profile_class
591
+ if defined?(@visibility_profile_class)
592
+ @visibility_profile_class
593
+ elsif superclass.respond_to?(:visibility_profile_class)
594
+ superclass.visibility_profile_class
595
+ else
596
+ GraphQL::Schema::Visibility::Profile
597
+ end
598
+ end
599
+
600
+ # @api private
601
+ attr_writer :visibility_profile_class, :use_visibility_profile
602
+ # @api private
603
+ attr_accessor :visibility
604
+ # @api private
605
+ def use_visibility_profile?
606
+ if defined?(@use_visibility_profile)
607
+ @use_visibility_profile
608
+ elsif superclass.respond_to?(:use_visibility_profile?)
609
+ superclass.use_visibility_profile?
610
+ else
611
+ false
612
+ end
1161
613
  end
1162
614
 
1163
615
  # @param type [Module] The type definition whose possible types you want to see
616
+ # @param context [GraphQL::Query::Context] used for filtering visible possible types at runtime
617
+ # @param use_visibility_profile Private, for migration to {Schema::Visibility}
1164
618
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
1165
619
  # @return [Array<Module>] Possible types for `type`, if it's given.
1166
- def possible_types(type = nil, context = GraphQL::Query::NullContext)
620
+ def possible_types(type = nil, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
621
+ if use_visibility_profile
622
+ if type
623
+ return Visibility::Profile.from_context(context, self).possible_types(type)
624
+ else
625
+ raise "Schema.possible_types is not implemented for `use_visibility_profile?`"
626
+ end
627
+ end
1167
628
  if type
1168
629
  # TODO duck-typing `.possible_types` would probably be nicer here
1169
630
  if type.kind.union?
1170
631
  type.possible_types(context: context)
1171
632
  else
1172
- stored_possible_types = own_possible_types[type.graphql_name]
633
+ stored_possible_types = own_possible_types[type]
1173
634
  visible_possible_types = if stored_possible_types && type.kind.interface?
1174
635
  stored_possible_types.select do |possible_type|
1175
- # Use `.graphql_name` comparison to match legacy vs class-based types.
1176
- # When we don't need to support legacy `.define` types, use `.include?(type)` instead.
1177
- possible_type.interfaces(context).any? { |interface| interface.graphql_name == type.graphql_name }
636
+ possible_type.interfaces(context).include?(type)
1178
637
  end
1179
638
  else
1180
639
  stored_possible_types
1181
640
  end
1182
641
  visible_possible_types ||
1183
- introspection_system.possible_types[type.graphql_name] ||
642
+ introspection_system.possible_types[type] ||
1184
643
  (
1185
644
  superclass.respond_to?(:possible_types) ?
1186
- superclass.possible_types(type, context) :
645
+ superclass.possible_types(type, context, use_visibility_profile) :
1187
646
  EMPTY_ARRAY
1188
647
  )
1189
648
  end
@@ -1218,38 +677,45 @@ module GraphQL
1218
677
  attr_writer :dataloader_class
1219
678
 
1220
679
  def references_to(to_type = nil, from: nil)
1221
- @own_references_to ||= Hash.new { |h, k| h[k] = [] }
1222
680
  if to_type
1223
- if !to_type.is_a?(String)
1224
- to_type = to_type.graphql_name
1225
- end
1226
-
1227
681
  if from
1228
- @own_references_to[to_type] << from
682
+ refs = own_references_to[to_type] ||= []
683
+ refs << from
1229
684
  else
1230
- own_refs = @own_references_to[to_type]
1231
- inherited_refs = find_inherited_value(:references_to, EMPTY_HASH)[to_type] || EMPTY_ARRAY
1232
- own_refs + inherited_refs
685
+ get_references_to(to_type) || EMPTY_ARRAY
1233
686
  end
1234
687
  else
1235
688
  # `@own_references_to` can be quite large for big schemas,
1236
689
  # and generally speaking, we won't inherit any values.
1237
690
  # So optimize the most common case -- don't create a duplicate Hash.
1238
691
  inherited_value = find_inherited_value(:references_to, EMPTY_HASH)
1239
- if inherited_value.any?
1240
- inherited_value.merge(@own_references_to)
692
+ if !inherited_value.empty?
693
+ inherited_value.merge(own_references_to)
1241
694
  else
1242
- @own_references_to
695
+ own_references_to
1243
696
  end
1244
697
  end
1245
698
  end
1246
699
 
1247
- def type_from_ast(ast_node, context: nil)
1248
- type_owner = context ? context.warden : self
1249
- GraphQL::Schema::TypeExpression.build_type(type_owner, ast_node)
700
+ def type_from_ast(ast_node, context: self.query_class.new(self, "{ __typename }").context)
701
+ GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
1250
702
  end
1251
703
 
1252
- def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext)
704
+ def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
705
+ if use_visibility_profile
706
+ profile = Visibility::Profile.from_context(context, self)
707
+ parent_type = case type_or_name
708
+ when String
709
+ profile.type(type_or_name)
710
+ when Module
711
+ type_or_name
712
+ when LateBoundType
713
+ profile.type(type_or_name.name)
714
+ else
715
+ raise GraphQL::InvariantError, "Unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
716
+ end
717
+ return profile.field(parent_type, field_name)
718
+ end
1253
719
  parent_type = case type_or_name
1254
720
  when LateBoundType
1255
721
  get_type(type_or_name.name, context)
@@ -1272,24 +738,31 @@ module GraphQL
1272
738
  end
1273
739
  end
1274
740
 
1275
- def get_fields(type, context = GraphQL::Query::NullContext)
741
+ def get_fields(type, context = GraphQL::Query::NullContext.instance)
1276
742
  type.fields(context)
1277
743
  end
1278
744
 
745
+ # Pass a custom introspection module here to use it for this schema.
746
+ # @param new_introspection_namespace [Module] If given, use this module for custom introspection on the schema
747
+ # @return [Module, nil] The configured namespace, if there is one
1279
748
  def introspection(new_introspection_namespace = nil)
1280
749
  if new_introspection_namespace
1281
750
  @introspection = new_introspection_namespace
1282
751
  # reset this cached value:
1283
752
  @introspection_system = nil
753
+ introspection_system
754
+ @introspection
1284
755
  else
1285
756
  @introspection || find_inherited_value(:introspection)
1286
757
  end
1287
758
  end
1288
759
 
760
+ # @return [Schema::IntrospectionSystem] Based on {introspection}
1289
761
  def introspection_system
1290
762
  if !@introspection_system
1291
763
  @introspection_system = Schema::IntrospectionSystem.new(self)
1292
764
  @introspection_system.resolve_late_bindings
765
+ self.visibility&.introspection_system_configured(@introspection_system)
1293
766
  end
1294
767
  @introspection_system
1295
768
  end
@@ -1309,39 +782,70 @@ module GraphQL
1309
782
  end
1310
783
  end
1311
784
 
1312
- def query_execution_strategy(new_query_execution_strategy = nil)
785
+ # A limit on the number of tokens to accept on incoming query strings.
786
+ # Use this to prevent parsing maliciously-large query strings.
787
+ # @return [nil, Integer]
788
+ def max_query_string_tokens(new_max_tokens = NOT_CONFIGURED)
789
+ if NOT_CONFIGURED.equal?(new_max_tokens)
790
+ defined?(@max_query_string_tokens) ? @max_query_string_tokens : find_inherited_value(:max_query_string_tokens)
791
+ else
792
+ @max_query_string_tokens = new_max_tokens
793
+ end
794
+ end
795
+
796
+ def default_page_size(new_default_page_size = nil)
797
+ if new_default_page_size
798
+ @default_page_size = new_default_page_size
799
+ else
800
+ @default_page_size || find_inherited_value(:default_page_size)
801
+ end
802
+ end
803
+
804
+ def query_execution_strategy(new_query_execution_strategy = nil, deprecation_warning: true)
805
+ if deprecation_warning
806
+ warn "GraphQL::Schema.query_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead."
807
+ warn " #{caller(1, 1).first}"
808
+ end
1313
809
  if new_query_execution_strategy
1314
810
  @query_execution_strategy = new_query_execution_strategy
1315
811
  else
1316
- @query_execution_strategy || find_inherited_value(:query_execution_strategy, self.default_execution_strategy)
812
+ @query_execution_strategy || (superclass.respond_to?(:query_execution_strategy) ? superclass.query_execution_strategy(deprecation_warning: false) : self.default_execution_strategy)
1317
813
  end
1318
814
  end
1319
815
 
1320
- def mutation_execution_strategy(new_mutation_execution_strategy = nil)
816
+ def mutation_execution_strategy(new_mutation_execution_strategy = nil, deprecation_warning: true)
817
+ if deprecation_warning
818
+ warn "GraphQL::Schema.mutation_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead."
819
+ warn " #{caller(1, 1).first}"
820
+ end
1321
821
  if new_mutation_execution_strategy
1322
822
  @mutation_execution_strategy = new_mutation_execution_strategy
1323
823
  else
1324
- @mutation_execution_strategy || find_inherited_value(:mutation_execution_strategy, self.default_execution_strategy)
824
+ @mutation_execution_strategy || (superclass.respond_to?(:mutation_execution_strategy) ? superclass.mutation_execution_strategy(deprecation_warning: false) : self.default_execution_strategy)
1325
825
  end
1326
826
  end
1327
827
 
1328
- def subscription_execution_strategy(new_subscription_execution_strategy = nil)
828
+ def subscription_execution_strategy(new_subscription_execution_strategy = nil, deprecation_warning: true)
829
+ if deprecation_warning
830
+ warn "GraphQL::Schema.subscription_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead."
831
+ warn " #{caller(1, 1).first}"
832
+ end
1329
833
  if new_subscription_execution_strategy
1330
834
  @subscription_execution_strategy = new_subscription_execution_strategy
1331
835
  else
1332
- @subscription_execution_strategy || find_inherited_value(:subscription_execution_strategy, self.default_execution_strategy)
836
+ @subscription_execution_strategy || (superclass.respond_to?(:subscription_execution_strategy) ? superclass.subscription_execution_strategy(deprecation_warning: false) : self.default_execution_strategy)
1333
837
  end
1334
838
  end
1335
839
 
1336
840
  attr_writer :validate_timeout
1337
841
 
1338
- def validate_timeout(new_validate_timeout = nil)
1339
- if new_validate_timeout
842
+ def validate_timeout(new_validate_timeout = NOT_CONFIGURED)
843
+ if !NOT_CONFIGURED.equal?(new_validate_timeout)
1340
844
  @validate_timeout = new_validate_timeout
1341
845
  elsif defined?(@validate_timeout)
1342
846
  @validate_timeout
1343
847
  else
1344
- find_inherited_value(:validate_timeout)
848
+ find_inherited_value(:validate_timeout) || 3
1345
849
  end
1346
850
  end
1347
851
 
@@ -1354,7 +858,7 @@ module GraphQL
1354
858
  else
1355
859
  string_or_document
1356
860
  end
1357
- query = GraphQL::Query.new(self, document: doc, context: context)
861
+ query = query_class.new(self, document: doc, context: context)
1358
862
  validator_opts = { schema: self }
1359
863
  rules && (validator_opts[:rules] = rules)
1360
864
  validator = GraphQL::StaticValidation::Validator.new(**validator_opts)
@@ -1362,23 +866,31 @@ module GraphQL
1362
866
  res[:errors]
1363
867
  end
1364
868
 
869
+ # @param new_query_class [Class<GraphQL::Query>] A subclass to use when executing queries
870
+ def query_class(new_query_class = NOT_CONFIGURED)
871
+ if NOT_CONFIGURED.equal?(new_query_class)
872
+ @query_class || (superclass.respond_to?(:query_class) ? superclass.query_class : GraphQL::Query)
873
+ else
874
+ @query_class = new_query_class
875
+ end
876
+ end
877
+
1365
878
  attr_writer :validate_max_errors
1366
879
 
1367
- def validate_max_errors(new_validate_max_errors = nil)
1368
- if new_validate_max_errors
1369
- @validate_max_errors = new_validate_max_errors
1370
- elsif defined?(@validate_max_errors)
1371
- @validate_max_errors
880
+ def validate_max_errors(new_validate_max_errors = NOT_CONFIGURED)
881
+ if NOT_CONFIGURED.equal?(new_validate_max_errors)
882
+ defined?(@validate_max_errors) ? @validate_max_errors : find_inherited_value(:validate_max_errors)
1372
883
  else
1373
- find_inherited_value(:validate_max_errors)
884
+ @validate_max_errors = new_validate_max_errors
1374
885
  end
1375
886
  end
1376
887
 
1377
888
  attr_writer :max_complexity
1378
889
 
1379
- def max_complexity(max_complexity = nil)
890
+ def max_complexity(max_complexity = nil, count_introspection_fields: true)
1380
891
  if max_complexity
1381
892
  @max_complexity = max_complexity
893
+ @max_complexity_count_introspection_fields = count_introspection_fields
1382
894
  elsif defined?(@max_complexity)
1383
895
  @max_complexity
1384
896
  else
@@ -1386,26 +898,23 @@ module GraphQL
1386
898
  end
1387
899
  end
1388
900
 
901
+ def max_complexity_count_introspection_fields
902
+ if defined?(@max_complexity_count_introspection_fields)
903
+ @max_complexity_count_introspection_fields
904
+ else
905
+ find_inherited_value(:max_complexity_count_introspection_fields, true)
906
+ end
907
+ end
908
+
1389
909
  attr_writer :analysis_engine
1390
910
 
1391
911
  def analysis_engine
1392
912
  @analysis_engine || find_inherited_value(:analysis_engine, self.default_analysis_engine)
1393
913
  end
1394
914
 
1395
- def using_ast_analysis?
1396
- analysis_engine == GraphQL::Analysis::AST
1397
- end
1398
-
1399
- def interpreter?
1400
- query_execution_strategy == GraphQL::Execution::Interpreter &&
1401
- mutation_execution_strategy == GraphQL::Execution::Interpreter &&
1402
- subscription_execution_strategy == GraphQL::Execution::Interpreter
1403
- end
1404
-
1405
- attr_writer :interpreter
1406
-
1407
915
  def error_bubbling(new_error_bubbling = nil)
1408
916
  if !new_error_bubbling.nil?
917
+ warn("error_bubbling(#{new_error_bubbling.inspect}) is deprecated; the default value of `false` will be the only option in GraphQL-Ruby 3.0")
1409
918
  @error_bubbling = new_error_bubbling
1410
919
  else
1411
920
  @error_bubbling.nil? ? find_inherited_value(:error_bubbling) : @error_bubbling
@@ -1416,9 +925,10 @@ module GraphQL
1416
925
 
1417
926
  attr_writer :max_depth
1418
927
 
1419
- def max_depth(new_max_depth = nil)
928
+ def max_depth(new_max_depth = nil, count_introspection_fields: true)
1420
929
  if new_max_depth
1421
930
  @max_depth = new_max_depth
931
+ @count_introspection_fields = count_introspection_fields
1422
932
  elsif defined?(@max_depth)
1423
933
  @max_depth
1424
934
  else
@@ -1426,6 +936,14 @@ module GraphQL
1426
936
  end
1427
937
  end
1428
938
 
939
+ def count_introspection_fields
940
+ if defined?(@count_introspection_fields)
941
+ @count_introspection_fields
942
+ else
943
+ find_inherited_value(:count_introspection_fields, true)
944
+ end
945
+ end
946
+
1429
947
  def disable_introspection_entry_points
1430
948
  @disable_introspection_entry_points = true
1431
949
  # TODO: this clears the cache made in `def types`. But this is not a great solution.
@@ -1464,18 +982,66 @@ module GraphQL
1464
982
  if instance_variable_defined?(:@disable_type_introspection_entry_point)
1465
983
  @disable_type_introspection_entry_point
1466
984
  else
1467
- find_inherited_value(:disable_type_introspection_entry_point?, false)
985
+ find_inherited_value(:disable_type_introspection_entry_point?, false)
986
+ end
987
+ end
988
+
989
+ # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema
990
+ # @return [Array<Module>] Type definitions added to this schema
991
+ def extra_types(*new_extra_types)
992
+ if !new_extra_types.empty?
993
+ new_extra_types = new_extra_types.flatten
994
+ @own_extra_types ||= []
995
+ @own_extra_types.concat(new_extra_types)
996
+ end
997
+ inherited_et = find_inherited_value(:extra_types, nil)
998
+ if inherited_et
999
+ if @own_extra_types
1000
+ inherited_et + @own_extra_types
1001
+ else
1002
+ inherited_et
1003
+ end
1004
+ else
1005
+ @own_extra_types || EMPTY_ARRAY
1468
1006
  end
1469
1007
  end
1470
1008
 
1009
+ # Tell the schema about these types so that they can be registered as implementations of interfaces in the schema.
1010
+ #
1011
+ # This method must be used when an object type is connected to the schema as an interface implementor but
1012
+ # not as a return type of a field. In that case, if the object type isn't registered here, GraphQL-Ruby won't be able to find it.
1013
+ #
1014
+ # @param new_orphan_types [Array<Class<GraphQL::Schema::Object>>] Object types to register as implementations of interfaces in the schema.
1015
+ # @return [Array<Class<GraphQL::Schema::Object>>] All previously-registered orphan types for this schema
1471
1016
  def orphan_types(*new_orphan_types)
1472
- if new_orphan_types.any?
1017
+ if !new_orphan_types.empty?
1473
1018
  new_orphan_types = new_orphan_types.flatten
1474
- add_type_and_traverse(new_orphan_types, root: false)
1019
+ non_object_types = new_orphan_types.reject { |ot| ot.is_a?(Class) && ot < GraphQL::Schema::Object }
1020
+ if !non_object_types.empty?
1021
+ raise ArgumentError, <<~ERR
1022
+ Only object type classes should be added as `orphan_types(...)`.
1023
+
1024
+ - Remove these no-op types from `orphan_types`: #{non_object_types.map { |t| "#{t.inspect} (#{t.kind.name})"}.join(", ")}
1025
+ - See https://graphql-ruby.org/type_definitions/interfaces.html#orphan-types
1026
+
1027
+ To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
1028
+ ERR
1029
+ end
1030
+ add_type_and_traverse(new_orphan_types, root: false) unless use_visibility_profile?
1475
1031
  own_orphan_types.concat(new_orphan_types.flatten)
1032
+ self.visibility&.orphan_types_configured(new_orphan_types)
1476
1033
  end
1477
1034
 
1478
- find_inherited_value(:orphan_types, EMPTY_ARRAY) + own_orphan_types
1035
+ inherited_ot = find_inherited_value(:orphan_types, nil)
1036
+ if inherited_ot
1037
+ if !own_orphan_types.empty?
1038
+ inherited_ot + own_orphan_types
1039
+ else
1040
+ inherited_ot
1041
+ end
1042
+ else
1043
+ own_orphan_types
1044
+ end
1479
1045
  end
1480
1046
 
1481
1047
  def default_execution_strategy
@@ -1494,6 +1060,41 @@ module GraphQL
1494
1060
  end
1495
1061
  end
1496
1062
 
1063
+
1064
+ # @param new_default_logger [#log] Something to use for logging messages
1065
+ def default_logger(new_default_logger = NOT_CONFIGURED)
1066
+ if NOT_CONFIGURED.equal?(new_default_logger)
1067
+ if defined?(@default_logger)
1068
+ @default_logger
1069
+ elsif superclass.respond_to?(:default_logger)
1070
+ superclass.default_logger
1071
+ elsif defined?(Rails) && Rails.respond_to?(:logger) && (rails_logger = Rails.logger)
1072
+ rails_logger
1073
+ else
1074
+ def_logger = Logger.new($stdout)
1075
+ def_logger.info! # It doesn't output debug info by default
1076
+ def_logger
1077
+ end
1078
+ elsif new_default_logger == nil
1079
+ @default_logger = Logger.new(IO::NULL)
1080
+ else
1081
+ @default_logger = new_default_logger
1082
+ end
1083
+ end
1084
+
1085
+ # @param context [GraphQL::Query::Context, nil]
1086
+ # @return [Logger] A logger to use for this context configuration, falling back to {.default_logger}
1087
+ def logger_for(context)
1088
+ if context && context[:logger] == false
1089
+ Logger.new(IO::NULL)
1090
+ elsif context && (l = context[:logger])
1091
+ l
1092
+ else
1093
+ default_logger
1094
+ end
1095
+ end
1096
+
1097
+ # @param new_context_class [Class<GraphQL::Query::Context>] A subclass to use when executing queries
1497
1098
  def context_class(new_context_class = nil)
1498
1099
  if new_context_class
1499
1100
  @context_class = new_context_class
@@ -1502,28 +1103,87 @@ module GraphQL
1502
1103
  end
1503
1104
  end
1504
1105
 
1106
+ # Register a handler for errors raised during execution. The handlers can return a new value or raise a new error.
1107
+ #
1108
+ # @example Handling "not found" with a client-facing error
1109
+ # rescue_from(ActiveRecord::NotFound) { raise GraphQL::ExecutionError, "An object could not be found" }
1110
+ #
1111
+ # @param err_classes [Array<StandardError>] Classes which should be rescued by `handler_block`
1112
+ # @param handler_block The code to run when one of those errors is raised during execution
1113
+ # @yieldparam error [StandardError] An instance of one of the configured `err_classes`
1114
+ # @yieldparam object [Object] The current application object in the query when the error was raised
1115
+ # @yieldparam arguments [GraphQL::Query::Arguments] The current field arguments when the error was raised
1116
+ # @yieldparam context [GraphQL::Query::Context] The context for the currently-running operation
1117
+ # @yieldreturn [Object] Some object to use in the place where this error was raised
1118
+ # @raise [GraphQL::ExecutionError] In the handler, raise to add a client-facing error to the response
1119
+ # @raise [StandardError] In the handler, raise to crash the query with a developer-facing error
1505
1120
  def rescue_from(*err_classes, &handler_block)
1506
1121
  err_classes.each do |err_class|
1507
- error_handler.rescue_from(err_class, handler_block)
1122
+ Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block)
1123
+ end
1124
+ end
1125
+
1126
+ def error_handlers
1127
+ @error_handlers ||= begin
1128
+ new_handler_hash = ->(h, k) {
1129
+ h[k] = {
1130
+ class: k,
1131
+ handler: nil,
1132
+ subclass_handlers: Hash.new(&new_handler_hash),
1133
+ }
1134
+ }
1135
+ {
1136
+ class: nil,
1137
+ handler: nil,
1138
+ subclass_handlers: Hash.new(&new_handler_hash),
1139
+ }
1140
+ end
1141
+ end
1142
+
1143
+ # @api private
1144
+ attr_accessor :using_backtrace
1145
+
1146
+ # @api private
1147
+ def handle_or_reraise(context, err)
1148
+ handler = Execution::Errors.find_handler_for(self, err.class)
1149
+ if handler
1150
+ obj = context[:current_object]
1151
+ args = context[:current_arguments]
1152
+ args = args && args.respond_to?(:keyword_arguments) ? args.keyword_arguments : nil
1153
+ field = context[:current_field]
1154
+ if obj.is_a?(GraphQL::Schema::Object)
1155
+ obj = obj.object
1156
+ end
1157
+ handler[:handler].call(err, obj, args, context, field)
1158
+ else
1159
+ if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError)
1160
+ err = GraphQL::Backtrace::TracedError.new(err, context)
1161
+ end
1162
+
1163
+ raise err
1508
1164
  end
1509
1165
  end
1510
1166
 
1511
1167
  # rubocop:disable Lint/DuplicateMethods
1512
1168
  module ResolveTypeWithType
1513
1169
  def resolve_type(type, obj, ctx)
1514
- first_resolved_type, resolved_value = if type.is_a?(Module) && type.respond_to?(:resolve_type)
1170
+ maybe_lazy_resolve_type_result = if type.is_a?(Module) && type.respond_to?(:resolve_type)
1515
1171
  type.resolve_type(obj, ctx)
1516
1172
  else
1517
1173
  super
1518
1174
  end
1519
1175
 
1520
- after_lazy(first_resolved_type) do |resolved_type|
1521
- if resolved_type.nil? || (resolved_type.is_a?(Module) && resolved_type.respond_to?(:kind)) || resolved_type.is_a?(GraphQL::BaseType)
1522
- if resolved_value
1523
- [resolved_type, resolved_value]
1524
- else
1525
- resolved_type
1526
- end
1176
+ after_lazy(maybe_lazy_resolve_type_result) do |resolve_type_result|
1177
+ if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2
1178
+ resolved_type = resolve_type_result[0]
1179
+ resolved_value = resolve_type_result[1]
1180
+ else
1181
+ resolved_type = resolve_type_result
1182
+ resolved_value = obj
1183
+ end
1184
+
1185
+ if resolved_type.nil? || (resolved_type.is_a?(Module) && resolved_type.respond_to?(:kind))
1186
+ [resolved_type, resolved_value]
1527
1187
  else
1528
1188
  raise ".resolve_type should return a type definition, but got #{resolved_type.inspect} (#{resolved_type.class}) from `resolve_type(#{type}, #{obj}, #{ctx})`"
1529
1189
  end
@@ -1531,51 +1191,96 @@ module GraphQL
1531
1191
  end
1532
1192
  end
1533
1193
 
1534
- def resolve_type(type, obj, ctx)
1535
- if type.kind.object?
1536
- type
1537
- else
1538
- raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(type, obj, ctx) must be implemented to use Union types or Interface types (tried to resolve: #{type.name})"
1539
- end
1194
+ # GraphQL-Ruby calls this method during execution when it needs the application to determine the type to use for an object.
1195
+ #
1196
+ # Usually, this object was returned from a field whose return type is an {GraphQL::Schema::Interface} or a {GraphQL::Schema::Union}.
1197
+ # But this method is called in other cases, too -- for example, when {GraphQL::Schema::Argument#loads} cases an object to be directly loaded from the database.
1198
+ #
1199
+ # @example Returning a GraphQL type based on the object's class name
1200
+ # class MySchema < GraphQL::Schema
1201
+ # def resolve_type(_abs_type, object, _context)
1202
+ # graphql_type_name = "Types::#{object.class.name}Type"
1203
+ # graphql_type_name.constantize # If this raises a NameError, then come implement special cases in this method
1204
+ # end
1205
+ # end
1206
+ # @param abstract_type [Class, Module, nil] The Interface or Union type which is being resolved, if there is one
1207
+ # @param application_object [Object] The object returned from a field whose type must be determined
1208
+ # @param context [GraphQL::Query::Context] The query context for the currently-executing query
1209
+ # @return [Class<GraphQL::Schema::Object] The Object type definition to use for `obj`
1210
+ def resolve_type(abstract_type, application_object, context)
1211
+ raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(abstract_type, application_object, context) must be implemented to use Union types, Interface types, `loads:`, or `run_partials` (tried to resolve: #{abstract_type.name})"
1540
1212
  end
1541
1213
  # rubocop:enable Lint/DuplicateMethods
1542
1214
 
1543
1215
  def inherited(child_class)
1544
1216
  if self == GraphQL::Schema
1545
1217
  child_class.directives(default_directives.values)
1218
+ child_class.extend(SubclassGetReferencesTo)
1219
+ end
1220
+ # Make sure the child class has these built out, so that
1221
+ # subclasses can be modified by later calls to `trace_with`
1222
+ own_trace_modes.each do |name, _class|
1223
+ child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
1546
1224
  end
1547
1225
  child_class.singleton_class.prepend(ResolveTypeWithType)
1548
- super
1549
- end
1550
1226
 
1551
- def object_from_id(node_id, ctx)
1552
- raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(node_id, ctx) must be implemented to load by ID (tried to load from id `#{node_id}`)"
1227
+ if use_visibility_profile?
1228
+ vis = self.visibility
1229
+ child_class.visibility = vis.dup_for(child_class)
1230
+ end
1231
+ super
1553
1232
  end
1554
1233
 
1555
- def id_from_object(object, type, ctx)
1556
- raise GraphQL::RequiredImplementationMissingError, "#{self.name}.id_from_object(object, type, ctx) must be implemented to create global ids (tried to create an id for `#{object.inspect}`)"
1234
+ # Fetch an object based on an incoming ID and the current context. This method should return an object
1235
+ # from your application, or return `nil` if there is no object or the object shouldn't be available to this operation.
1236
+ #
1237
+ # @example Fetching an object with Rails's GlobalID
1238
+ # def self.object_from_id(object_id, _context)
1239
+ # GlobalID.find(global_id)
1240
+ # # TODO: use `context[:current_user]` to determine if this object is authorized.
1241
+ # end
1242
+ # @param object_id [String] The ID to fetch an object for. This may be client-provided (as in `node(id: ...)` or `loads:`) or previously stored by the schema (eg, by the `ObjectCache`)
1243
+ # @param context [GraphQL::Query::Context] The context for the currently-executing operation
1244
+ # @return [Object, nil] The application which `object_id` references, or `nil` if there is no object or the current operation shouldn't have access to the object
1245
+ # @see id_from_object which produces these IDs
1246
+ def object_from_id(object_id, context)
1247
+ raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(object_id, context) must be implemented to load by ID (tried to load from id `#{object_id}`)"
1248
+ end
1249
+
1250
+ # Return a stable ID string for `object` so that it can be refetched later, using {.object_from_id}.
1251
+ #
1252
+ # [GlobalID](https://github.com/rails/globalid) and [SQIDs](https://sqids.org/ruby) can both be used to create IDs.
1253
+ #
1254
+ # @example Using Rails's GlobalID to generate IDs
1255
+ # def self.id_from_object(application_object, graphql_type, context)
1256
+ # application_object.to_gid_param
1257
+ # end
1258
+ #
1259
+ # @param application_object [Object] Some object encountered by GraphQL-Ruby while running a query
1260
+ # @param graphql_type [Class, Module] The type that GraphQL-Ruby is using for `application_object` during this query
1261
+ # @param context [GraphQL::Query::Context] The context for the operation that is currently running
1262
+ # @return [String] A stable identifier which can be passed to {.object_from_id} later to re-fetch `application_object`
1263
+ def id_from_object(application_object, graphql_type, context)
1264
+ raise GraphQL::RequiredImplementationMissingError, "#{self.name}.id_from_object(application_object, graphql_type, context) must be implemented to create global ids (tried to create an id for `#{application_object.inspect}`)"
1557
1265
  end
1558
1266
 
1559
1267
  def visible?(member, ctx)
1560
- member.type_class.visible?(ctx)
1268
+ member.visible?(ctx)
1561
1269
  end
1562
1270
 
1563
- def accessible?(member, ctx)
1564
- member.type_class.accessible?(ctx)
1271
+ def schema_directive(dir_class, **options)
1272
+ @own_schema_directives ||= []
1273
+ Member::HasDirectives.add_directive(self, @own_schema_directives, dir_class, options)
1565
1274
  end
1566
1275
 
1567
- # This hook is called when a client tries to access one or more
1568
- # fields that fail the `accessible?` check.
1569
- #
1570
- # By default, an error is added to the response. Override this hook to
1571
- # track metrics or return a different error to the client.
1572
- #
1573
- # @param error [InaccessibleFieldsError] The analysis error for this check
1574
- # @return [AnalysisError, nil] Return an error to skip the query
1575
- def inaccessible_fields(error)
1576
- error
1276
+ def schema_directives
1277
+ Member::HasDirectives.get_directives(self, @own_schema_directives, :schema_directives)
1577
1278
  end
1578
1279
 
1280
+ # Called when a type is needed by name at runtime
1281
+ def load_type(type_name, ctx)
1282
+ get_type(type_name, ctx)
1283
+ end
1579
1284
  # This hook is called when an object fails an `authorized?` check.
1580
1285
  # You might report to your bug tracker here, so you can correct
1581
1286
  # the field resolvers not to return unauthorized objects.
@@ -1611,58 +1316,78 @@ module GraphQL
1611
1316
  unauthorized_object(unauthorized_error)
1612
1317
  end
1613
1318
 
1614
- def type_error(type_err, ctx)
1615
- DefaultTypeError.call(type_err, ctx)
1319
+ # Called at runtime when GraphQL-Ruby encounters a mismatch between the application behavior
1320
+ # and the GraphQL type system.
1321
+ #
1322
+ # The default implementation of this method is to follow the GraphQL specification,
1323
+ # but you can override this to report errors to your bug tracker or customize error handling.
1324
+ # @param type_error [GraphQL::Error] several specific error classes are passed here, see the default implementation for details
1325
+ # @param context [GraphQL::Query::Context] the context for the currently-running operation
1326
+ # @return [void]
1327
+ # @raise [GraphQL::ExecutionError] to return this error to the client
1328
+ # @raise [GraphQL::Error] to crash the query and raise a developer-facing error
1329
+ def type_error(type_error, context)
1330
+ case type_error
1331
+ when GraphQL::InvalidNullError
1332
+ execution_error = GraphQL::ExecutionError.new(type_error.message, ast_node: type_error.ast_node)
1333
+ execution_error.path = context[:current_path]
1334
+
1335
+ context.errors << execution_error
1336
+ when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
1337
+ raise type_error
1338
+ when GraphQL::IntegerDecodingError
1339
+ nil
1340
+ end
1616
1341
  end
1617
1342
 
1618
- # A function to call when {#execute} receives an invalid query string
1343
+ # A function to call when {.execute} receives an invalid query string
1619
1344
  #
1620
1345
  # The default is to add the error to `context.errors`
1621
- # @param err [GraphQL::ParseError] The error encountered during parsing
1346
+ # @param parse_err [GraphQL::ParseError] The error encountered during parsing
1622
1347
  # @param ctx [GraphQL::Query::Context] The context for the query where the error occurred
1623
1348
  # @return void
1624
1349
  def parse_error(parse_err, ctx)
1625
1350
  ctx.errors.push(parse_err)
1626
1351
  end
1627
1352
 
1628
- # @return [GraphQL::Execution::Errors]
1629
- def error_handler
1630
- @error_handler ||= GraphQL::Execution::Errors.new(self)
1631
- end
1632
-
1633
1353
  def lazy_resolve(lazy_class, value_method)
1634
1354
  lazy_methods.set(lazy_class, value_method)
1635
1355
  end
1636
1356
 
1637
1357
  def instrument(instrument_step, instrumenter, options = {})
1638
- if instrument_step == :field
1639
- GraphQL::Deprecation.warn "Field instrumentation (#{instrumenter.inspect}) will be removed in GraphQL-Ruby 2.0, please upgrade to field extensions: https://graphql-ruby.org/type_definitions/field_extensions.html"
1640
- end
1641
-
1642
- step = if instrument_step == :field && options[:after_built_ins]
1643
- :field_after_built_ins
1644
- else
1645
- instrument_step
1646
- end
1358
+ warn <<~WARN
1359
+ Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html"
1360
+ (From `#{self}.instrument(#{instrument_step}, #{instrumenter})` at #{caller(1, 1).first})
1647
1361
 
1648
- own_instrumenters[step] << instrumenter
1362
+ WARN
1363
+ trace_with(Tracing::LegacyHooksTrace)
1364
+ own_instrumenters[instrument_step] << instrumenter
1649
1365
  end
1650
1366
 
1651
1367
  # Add several directives at once
1652
1368
  # @param new_directives [Class]
1653
1369
  def directives(*new_directives)
1654
- if new_directives.any?
1370
+ if !new_directives.empty?
1655
1371
  new_directives.flatten.each { |d| directive(d) }
1656
1372
  end
1657
1373
 
1658
- find_inherited_value(:directives, default_directives).merge(own_directives)
1374
+ inherited_dirs = find_inherited_value(:directives, default_directives)
1375
+ if !own_directives.empty?
1376
+ inherited_dirs.merge(own_directives)
1377
+ else
1378
+ inherited_dirs
1379
+ end
1659
1380
  end
1660
1381
 
1661
1382
  # Attach a single directive to this schema
1662
1383
  # @param new_directive [Class]
1663
1384
  # @return void
1664
1385
  def directive(new_directive)
1665
- add_type_and_traverse(new_directive, root: false)
1386
+ if use_visibility_profile?
1387
+ own_directives[new_directive.graphql_name] = new_directive
1388
+ else
1389
+ add_type_and_traverse(new_directive, root: false)
1390
+ end
1666
1391
  end
1667
1392
 
1668
1393
  def default_directives
@@ -1670,10 +1395,31 @@ module GraphQL
1670
1395
  "include" => GraphQL::Schema::Directive::Include,
1671
1396
  "skip" => GraphQL::Schema::Directive::Skip,
1672
1397
  "deprecated" => GraphQL::Schema::Directive::Deprecated,
1398
+ "oneOf" => GraphQL::Schema::Directive::OneOf,
1399
+ "specifiedBy" => GraphQL::Schema::Directive::SpecifiedBy,
1673
1400
  }.freeze
1674
1401
  end
1675
1402
 
1676
- def tracer(new_tracer)
1403
+ # @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema
1404
+ attr_accessor :detailed_trace
1405
+
1406
+ # @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex})
1407
+ # @return [Boolean] When `true`, save a detailed trace for this query.
1408
+ # @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true
1409
+ def detailed_trace?(query)
1410
+ raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition."
1411
+ end
1412
+
1413
+ def tracer(new_tracer, silence_deprecation_warning: false)
1414
+ if !silence_deprecation_warning
1415
+ warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
1416
+ warn " #{caller(1, 1).first}"
1417
+ end
1418
+ default_trace = trace_class_for(:default, build: true)
1419
+ if default_trace.nil? || !(default_trace < GraphQL::Tracing::CallLegacyTracers)
1420
+ trace_with(GraphQL::Tracing::CallLegacyTracers)
1421
+ end
1422
+
1677
1423
  own_tracers << new_tracer
1678
1424
  end
1679
1425
 
@@ -1681,27 +1427,118 @@ module GraphQL
1681
1427
  find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
1682
1428
  end
1683
1429
 
1684
- def query_analyzer(new_analyzer)
1685
- if new_analyzer == GraphQL::Authorization::Analyzer
1686
- GraphQL::Deprecation.warn("The Authorization query analyzer is deprecated. Authorizing at query runtime is generally a better idea.")
1430
+ # Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime.
1431
+ #
1432
+ # You can attach a module to run in only _some_ circumstances by using `mode:`. When a module is added with `mode:`,
1433
+ # it will only run for queries with a matching `context[:trace_mode]`.
1434
+ #
1435
+ # Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration).
1436
+ #
1437
+ # @example Adding a trace in a special mode
1438
+ # # only runs when `query.context[:trace_mode]` is `:special`
1439
+ # trace_with SpecialTrace, mode: :special
1440
+ #
1441
+ # @param trace_mod [Module] A module that implements tracing methods
1442
+ # @param mode [Symbol] Trace module will only be used for this trade mode
1443
+ # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1444
+ # @return [void]
1445
+ # @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
1446
+ def trace_with(trace_mod, mode: :default, **options)
1447
+ if mode.is_a?(Array)
1448
+ mode.each { |m| trace_with(trace_mod, mode: m, **options) }
1449
+ else
1450
+ tc = own_trace_modes[mode] ||= build_trace_mode(mode)
1451
+ tc.include(trace_mod)
1452
+ own_trace_modules[mode] << trace_mod
1453
+ add_trace_options_for(mode, options)
1454
+ if mode == :default
1455
+ # This module is being added as a default tracer. If any other mode classes
1456
+ # have already been created, but get their default behavior from a superclass,
1457
+ # Then mix this into this schema's subclass.
1458
+ # (But don't mix it into mode classes that aren't default-based.)
1459
+ own_trace_modes.each do |other_mode_name, other_mode_class|
1460
+ if other_mode_class < DefaultTraceClass
1461
+ # Don't add it back to the inheritance tree if it's already there
1462
+ if !(other_mode_class < trace_mod)
1463
+ other_mode_class.include(trace_mod)
1464
+ end
1465
+ # Add any options so they'll be available
1466
+ add_trace_options_for(other_mode_name, options)
1467
+ end
1468
+ end
1469
+ end
1687
1470
  end
1688
- own_query_analyzers << new_analyzer
1471
+ nil
1689
1472
  end
1690
1473
 
1691
- def query_analyzers
1692
- find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers
1474
+ # The options hash for this trace mode
1475
+ # @return [Hash]
1476
+ def trace_options_for(mode)
1477
+ @trace_options_for_mode ||= {}
1478
+ @trace_options_for_mode[mode] ||= begin
1479
+ # It may be time to create an options hash for a mode that wasn't registered yet.
1480
+ # Mix in the default options in that case.
1481
+ default_options = mode == :default ? EMPTY_HASH : trace_options_for(:default)
1482
+ # Make sure this returns a new object so that other hashes aren't modified later
1483
+ if superclass.respond_to?(:trace_options_for)
1484
+ superclass.trace_options_for(mode).merge(default_options)
1485
+ else
1486
+ default_options.dup
1487
+ end
1488
+ end
1693
1489
  end
1694
1490
 
1695
- def middleware(new_middleware = nil)
1696
- if new_middleware
1697
- GraphQL::Deprecation.warn "Middleware will be removed in GraphQL-Ruby 2.0, please upgrade to Field Extensions: https://graphql-ruby.org/type_definitions/field_extensions.html"
1698
- own_middleware << new_middleware
1491
+ # Create a trace instance which will include the trace modules specified for the optional mode.
1492
+ #
1493
+ # If no `mode:` is given, then {default_trace_mode} will be used.
1494
+ #
1495
+ # If this schema is using {Tracing::DetailedTrace} and {.detailed_trace?} returns `true`, then
1496
+ # DetailedTrace's mode will override the passed-in `mode`.
1497
+ #
1498
+ # @param mode [Symbol] Trace modules for this trade mode will be included
1499
+ # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1500
+ # @return [Tracing::Trace]
1501
+ def new_trace(mode: nil, **options)
1502
+ should_sample = if detailed_trace
1503
+ if (query = options[:query])
1504
+ detailed_trace?(query)
1505
+ elsif (multiplex = options[:multiplex])
1506
+ if multiplex.queries.length == 1
1507
+ detailed_trace?(multiplex.queries.first)
1508
+ else
1509
+ detailed_trace?(multiplex)
1510
+ end
1511
+ end
1512
+ else
1513
+ false
1514
+ end
1515
+
1516
+ if should_sample
1517
+ mode = detailed_trace.trace_mode
1699
1518
  else
1700
- # TODO make sure this is cached when running a query
1701
- MiddlewareChain.new(steps: all_middleware, final_step: GraphQL::Execution::Execute::FieldResolveStep)
1519
+ target = options[:query] || options[:multiplex]
1520
+ mode ||= target && target.context[:trace_mode]
1702
1521
  end
1522
+
1523
+ trace_mode = mode || default_trace_mode
1524
+ base_trace_options = trace_options_for(trace_mode)
1525
+ trace_options = base_trace_options.merge(options)
1526
+ trace_class_for_mode = trace_class_for(trace_mode, build: true)
1527
+ trace_class_for_mode.new(**trace_options)
1528
+ end
1529
+
1530
+ # @param new_analyzer [Class<GraphQL::Analysis::Analyzer>] An analyzer to run on queries to this schema
1531
+ # @see GraphQL::Analysis the analysis system
1532
+ def query_analyzer(new_analyzer)
1533
+ own_query_analyzers << new_analyzer
1703
1534
  end
1704
1535
 
1536
+ def query_analyzers
1537
+ find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers
1538
+ end
1539
+
1540
+ # @param new_analyzer [Class<GraphQL::Analysis::Analyzer>] An analyzer to run on multiplexes to this schema
1541
+ # @see GraphQL::Analysis the analysis system
1705
1542
  def multiplex_analyzer(new_analyzer)
1706
1543
  own_multiplex_analyzers << new_analyzer
1707
1544
  end
@@ -1720,7 +1557,7 @@ module GraphQL
1720
1557
 
1721
1558
  # Execute a query on itself.
1722
1559
  # @see {Query#initialize} for arguments.
1723
- # @return [Hash] query result, ready to be serialized as JSON
1560
+ # @return [GraphQL::Query::Result] query result, ready to be serialized as JSON
1724
1561
  def execute(query_str = nil, **kwargs)
1725
1562
  if query_str
1726
1563
  kwargs[:query] = query_str
@@ -1730,7 +1567,9 @@ module GraphQL
1730
1567
  {
1731
1568
  backtrace: ctx[:backtrace],
1732
1569
  tracers: ctx[:tracers],
1570
+ trace: ctx[:trace],
1733
1571
  dataloader: ctx[:dataloader],
1572
+ trace_mode: ctx[:trace_mode],
1734
1573
  }
1735
1574
  else
1736
1575
  {}
@@ -1755,17 +1594,13 @@ module GraphQL
1755
1594
  # }
1756
1595
  #
1757
1596
  # @see {Query#initialize} for query keyword arguments
1758
- # @see {Execution::Multiplex#run_queries} for multiplex keyword arguments
1597
+ # @see {Execution::Multiplex#run_all} for multiplex keyword arguments
1759
1598
  # @param queries [Array<Hash>] Keyword arguments for each query
1760
- # @param context [Hash] Multiplex-level context
1761
- # @return [Array<Hash>] One result for each query in the input
1599
+ # @option kwargs [Hash] :context ({}) Multiplex-level context
1600
+ # @option kwargs [nil, Integer] :max_complexity (nil)
1601
+ # @return [Array<GraphQL::Query::Result>] One result for each query in the input
1762
1602
  def multiplex(queries, **kwargs)
1763
- schema = if interpreter?
1764
- self
1765
- else
1766
- graphql_definition
1767
- end
1768
- GraphQL::Execution::Multiplex.run_all(schema, queries, **kwargs)
1603
+ GraphQL::Execution::Interpreter.run_all(self, queries, **kwargs)
1769
1604
  end
1770
1605
 
1771
1606
  def instrumenters
@@ -1777,24 +1612,275 @@ module GraphQL
1777
1612
 
1778
1613
  # @api private
1779
1614
  def add_subscription_extension_if_necessary
1780
- if interpreter? && !defined?(@subscription_extension_added) && subscription && self.subscriptions
1615
+ # TODO: when there's a proper API for extending root types, migrat this to use it.
1616
+ if !defined?(@subscription_extension_added) && @subscription_object.is_a?(Class) && self.subscriptions
1781
1617
  @subscription_extension_added = true
1782
- if subscription.singleton_class.ancestors.include?(Subscriptions::SubscriptionRoot)
1783
- GraphQL::Deprecation.warn("`extend Subscriptions::SubscriptionRoot` is no longer required; you may remove it from #{self}'s `subscription` root type (#{subscription}).")
1784
- else
1785
- subscription.all_field_definitions.each do |field|
1618
+ subscription.all_field_definitions.each do |field|
1619
+ if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) }
1786
1620
  field.extension(Subscriptions::DefaultSubscriptionResolveExtension)
1787
1621
  end
1788
1622
  end
1789
1623
  end
1790
1624
  end
1791
1625
 
1626
+ # Called when execution encounters a `SystemStackError`. By default, it adds a client-facing error to the response.
1627
+ # You could modify this method to report this error to your bug tracker.
1628
+ # @param query [GraphQL::Query]
1629
+ # @param err [SystemStackError]
1630
+ # @return [void]
1792
1631
  def query_stack_error(query, err)
1793
1632
  query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute."))
1794
1633
  end
1795
1634
 
1635
+ # Call the given block at the right time, either:
1636
+ # - Right away, if `value` is not registered with `lazy_resolve`
1637
+ # - After resolving `value`, if it's registered with `lazy_resolve` (eg, `Promise`)
1638
+ # @api private
1639
+ def after_lazy(value, &block)
1640
+ if lazy?(value)
1641
+ GraphQL::Execution::Lazy.new do
1642
+ result = sync_lazy(value)
1643
+ # The returned result might also be lazy, so check it, too
1644
+ after_lazy(result, &block)
1645
+ end
1646
+ else
1647
+ yield(value) if block_given?
1648
+ end
1649
+ end
1650
+
1651
+ # Override this method to handle lazy objects in a custom way.
1652
+ # @param value [Object] an instance of a class registered with {.lazy_resolve}
1653
+ # @return [Object] A GraphQL-ready (non-lazy) object
1654
+ # @api private
1655
+ def sync_lazy(value)
1656
+ lazy_method = lazy_method_name(value)
1657
+ if lazy_method
1658
+ synced_value = value.public_send(lazy_method)
1659
+ sync_lazy(synced_value)
1660
+ else
1661
+ value
1662
+ end
1663
+ end
1664
+
1665
+ # @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {.lazy_resolve}.
1666
+ def lazy_method_name(obj)
1667
+ lazy_methods.get(obj)
1668
+ end
1669
+
1670
+ # @return [Boolean] True if this object should be lazily resolved
1671
+ def lazy?(obj)
1672
+ !!lazy_method_name(obj)
1673
+ end
1674
+
1675
+ # Return a lazy if any of `maybe_lazies` are lazy,
1676
+ # otherwise, call the block eagerly and return the result.
1677
+ # @param maybe_lazies [Array]
1678
+ # @api private
1679
+ def after_any_lazies(maybe_lazies)
1680
+ if maybe_lazies.any? { |l| lazy?(l) }
1681
+ GraphQL::Execution::Lazy.all(maybe_lazies).then do |result|
1682
+ yield result
1683
+ end
1684
+ else
1685
+ yield maybe_lazies
1686
+ end
1687
+ end
1688
+
1689
+ # Returns `DidYouMean` if it's defined.
1690
+ # Override this to return `nil` if you don't want to use `DidYouMean`
1691
+ def did_you_mean(new_dym = NOT_CONFIGURED)
1692
+ if NOT_CONFIGURED.equal?(new_dym)
1693
+ if defined?(@did_you_mean)
1694
+ @did_you_mean
1695
+ else
1696
+ find_inherited_value(:did_you_mean, defined?(DidYouMean) ? DidYouMean : nil)
1697
+ end
1698
+ else
1699
+ @did_you_mean = new_dym
1700
+ end
1701
+ end
1702
+
1703
+
1704
+ # This setting controls how GraphQL-Ruby handles empty selections on Union types.
1705
+ #
1706
+ # To opt into future, spec-compliant behavior where these selections are rejected, set this to `false`.
1707
+ #
1708
+ # If you need to support previous, non-spec behavior which allowed selecting union fields
1709
+ # but *not* selecting any fields on that union, set this to `true` to continue allowing that behavior.
1710
+ #
1711
+ # If this is `true`, then {.legacy_invalid_empty_selections_on_union} will be called with {Query} objects
1712
+ # with that kind of selections. You must implement that method
1713
+ # @param new_value [Boolean]
1714
+ # @return [true, false, nil]
1715
+ def allow_legacy_invalid_empty_selections_on_union(new_value = NOT_CONFIGURED)
1716
+ if NOT_CONFIGURED.equal?(new_value)
1717
+ if defined?(@allow_legacy_invalid_empty_selections_on_union)
1718
+ @allow_legacy_invalid_empty_selections_on_union
1719
+ else
1720
+ find_inherited_value(:allow_legacy_invalid_empty_selections_on_union)
1721
+ end
1722
+ else
1723
+ @allow_legacy_invalid_empty_selections_on_union = new_value
1724
+ end
1725
+ end
1726
+
1727
+ # This method is called during validation when a previously-allowed, but non-spec
1728
+ # query is encountered where a union field has no child selections on it.
1729
+ #
1730
+ # You should implement this method to log the violation so that you can contact clients
1731
+ # and notify them about changing their queries. Then return a suitable value to
1732
+ # tell GraphQL-Ruby how to continue.
1733
+ # @param query [GraphQL::Query]
1734
+ # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
1735
+ # @return [String] A validation error to return for this query
1736
+ # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
1737
+ def legacy_invalid_empty_selections_on_union(query)
1738
+ raise "Implement `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario"
1739
+ end
1740
+
1741
+ # This setting controls how GraphQL-Ruby handles overlapping selections on scalar types when the types
1742
+ # don't match.
1743
+ #
1744
+ # When set to `false`, GraphQL-Ruby will reject those queries with a validation error (as per the GraphQL spec).
1745
+ #
1746
+ # When set to `true`, GraphQL-Ruby will call {.legacy_invalid_return_type_conflicts} when the scenario is encountered.
1747
+ #
1748
+ # @param new_value [Boolean] `true` permits the legacy behavior, `false` rejects it.
1749
+ # @return [true, false, nil]
1750
+ def allow_legacy_invalid_return_type_conflicts(new_value = NOT_CONFIGURED)
1751
+ if NOT_CONFIGURED.equal?(new_value)
1752
+ if defined?(@allow_legacy_invalid_return_type_conflicts)
1753
+ @allow_legacy_invalid_return_type_conflicts
1754
+ else
1755
+ find_inherited_value(:allow_legacy_invalid_return_type_conflicts)
1756
+ end
1757
+ else
1758
+ @allow_legacy_invalid_return_type_conflicts = new_value
1759
+ end
1760
+ end
1761
+
1762
+ # This method is called when the query contains fields which don't contain matching scalar types.
1763
+ # This was previously allowed by GraphQL-Ruby but it's a violation of the GraphQL spec.
1764
+ #
1765
+ # You should implement this method to log the violation so that you observe usage of these fields.
1766
+ # Fixing this scenario might mean adding new fields, and telling clients to use those fields.
1767
+ # (Changing the field return type would be a breaking change, but if it works for your client use cases,
1768
+ # that might work, too.)
1769
+ #
1770
+ # @param query [GraphQL::Query]
1771
+ # @param type1 [Module] A GraphQL type definition
1772
+ # @param type2 [Module] A GraphQL type definition
1773
+ # @param node1 [GraphQL::Language::Nodes::Field] This node is recognized as conflicting. You might call `.line` and `.col` for custom error reporting.
1774
+ # @param node2 [GraphQL::Language::Nodes::Field] The other node recognized as conflicting.
1775
+ # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
1776
+ # @return [String] A validation error to return for this query
1777
+ # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
1778
+ def legacy_invalid_return_type_conflicts(query, type1, type2, node1, node2)
1779
+ raise "Implement #{self}.legacy_invalid_return_type_conflicts to handle this invalid selection"
1780
+ end
1781
+
1782
+ # The legacy complexity implementation included several bugs:
1783
+ #
1784
+ # - In some cases, it used the lexically _last_ field to determine a cost, instead of calculating the maximum among selections
1785
+ # - In some cases, it called field complexity hooks repeatedly (when it should have only called them once)
1786
+ #
1787
+ # The future implementation may produce higher total complexity scores, so it's not active by default yet. You can opt into
1788
+ # the future default behavior by configuring `:future` here. Or, you can choose a mode for each query with {.complexity_cost_calculation_mode_for}.
1789
+ #
1790
+ # The legacy mode is currently maintained alongside the future one, but it will be removed in a future GraphQL-Ruby version.
1791
+ #
1792
+ # If you choose `:compare`, you must also implement {.legacy_complexity_cost_calculation_mismatch} to handle the input somehow.
1793
+ #
1794
+ # @example Opting into the future calculation mode
1795
+ # complexity_cost_calculation_mode(:future)
1796
+ #
1797
+ # @example Choosing the legacy mode (which will work until that mode is removed...)
1798
+ # complexity_cost_calculation_mode(:legacy)
1799
+ #
1800
+ # @example Run both modes for every query, call {.legacy_complexity_cost_calculation_mismatch} when they don't match:
1801
+ # complexity_cost_calculation_mode(:compare)
1802
+ def complexity_cost_calculation_mode(new_mode = NOT_CONFIGURED)
1803
+ if NOT_CONFIGURED.equal?(new_mode)
1804
+ if defined?(@complexity_cost_calculation_mode)
1805
+ @complexity_cost_calculation_mode
1806
+ else
1807
+ find_inherited_value(:complexity_cost_calculation_mode)
1808
+ end
1809
+ else
1810
+ @complexity_cost_calculation_mode = new_mode
1811
+ end
1812
+ end
1813
+
1814
+ # Implement this method to produce a per-query complexity cost calculation mode. (Technically, it's per-multiplex.)
1815
+ #
1816
+ # This is a way to check the compatibility of queries coming to your API without adding overhead of running `:compare`
1817
+ # for every query. You could sample traffic, turn it off/on with feature flags, or anything else.
1818
+ #
1819
+ # @example Sampling traffic
1820
+ # def self.complexity_cost_calculation_mode_for(_context)
1821
+ # if rand < 0.1 # 10% of the time
1822
+ # :compare
1823
+ # else
1824
+ # :legacy
1825
+ # end
1826
+ # end
1827
+ #
1828
+ # @example Using a feature flag to manage future mode
1829
+ # def complexity_cost_calculation_mode_for(context)
1830
+ # current_user = context[:current_user]
1831
+ # if Flipper.enabled?(:future_complexity_cost, current_user)
1832
+ # :future
1833
+ # elsif rand < 0.5 # 50%
1834
+ # :compare
1835
+ # else
1836
+ # :legacy
1837
+ # end
1838
+ # end
1839
+ #
1840
+ # @param multiplex_context [Hash] The context for the currently-running {Execution::Multiplex} (which contains one or more queries)
1841
+ # @return [:future] Use the new calculation algorithm -- may be higher than `:legacy`
1842
+ # @return [:legacy] Use the legacy calculation algorithm, warts and all
1843
+ # @return [:compare] Run both algorithms and call {.legacy_complexity_cost_calculation_mismatch} if they don't match
1844
+ def complexity_cost_calculation_mode_for(multiplex_context)
1845
+ complexity_cost_calculation_mode
1846
+ end
1847
+
1848
+ # Implement this method in your schema to handle mismatches when `:compare` is used.
1849
+ #
1850
+ # @example Logging the mismatch
1851
+ # def self.legacy_cost_calculation_mismatch(multiplex, future_cost, legacy_cost)
1852
+ # client_id = multiplex.context[:api_client].id
1853
+ # operation_names = multiplex.queries.map { |q| q.selected_operation_name || "anonymous" }.join(", ")
1854
+ # Stats.increment(:complexity_mismatch, tags: { client: client_id, ops: operation_names })
1855
+ # legacy_cost
1856
+ # end
1857
+ # @see Query::Context#add_error Adding an error to the response to notify the client
1858
+ # @see Query::Context#response_extensions Adding key-value pairs to the response `"extensions" => { ... }`
1859
+ # @param multiplex [GraphQL::Execution::Multiplex]
1860
+ # @param future_complexity_cost [Integer]
1861
+ # @param legacy_complexity_cost [Integer]
1862
+ # @return [Integer] the cost to use for this query (probably one of `future_complexity_cost` or `legacy_complexity_cost`)
1863
+ def legacy_complexity_cost_calculation_mismatch(multiplex, future_complexity_cost, legacy_complexity_cost)
1864
+ raise "Implement #{self}.legacy_complexity_cost(multiplex, future_complexity_cost, legacy_complexity_cost) to handle this mismatch (#{future_complexity_cost} vs. #{legacy_complexity_cost}) and return a value to use"
1865
+ end
1866
+
1796
1867
  private
1797
1868
 
1869
+ def add_trace_options_for(mode, new_options)
1870
+ if mode == :default
1871
+ own_trace_modes.each do |mode_name, t_class|
1872
+ if t_class <= DefaultTraceClass
1873
+ t_opts = trace_options_for(mode_name)
1874
+ t_opts.merge!(new_options)
1875
+ end
1876
+ end
1877
+ else
1878
+ t_opts = trace_options_for(mode)
1879
+ t_opts.merge!(new_options)
1880
+ end
1881
+ nil
1882
+ end
1883
+
1798
1884
  # @param t [Module, Array<Module>]
1799
1885
  # @return [void]
1800
1886
  def add_type_and_traverse(t, root:)
@@ -1838,7 +1924,8 @@ module GraphQL
1838
1924
  own_union_memberships.merge!(addition.union_memberships)
1839
1925
 
1840
1926
  addition.references.each { |thing, pointers|
1841
- pointers.each { |pointer| references_to(thing, from: pointer) }
1927
+ prev_refs = own_references_to[thing] || []
1928
+ own_references_to[thing] = prev_refs | pointers.to_a
1842
1929
  }
1843
1930
 
1844
1931
  addition.directives.each { |dir_class| own_directives[dir_class.graphql_name] = dir_class }
@@ -1856,7 +1943,7 @@ module GraphQL
1856
1943
  else
1857
1944
  @lazy_methods = GraphQL::Execution::Lazy::LazyMethodMap.new
1858
1945
  @lazy_methods.set(GraphQL::Execution::Lazy, :value)
1859
- @lazy_methods.set(GraphQL::Dataloader::Request, :load)
1946
+ @lazy_methods.set(GraphQL::Dataloader::Request, :load_with_deprecation_warning)
1860
1947
  end
1861
1948
  end
1862
1949
  @lazy_methods
@@ -1866,6 +1953,10 @@ module GraphQL
1866
1953
  @own_types ||= {}
1867
1954
  end
1868
1955
 
1956
+ def own_references_to
1957
+ @own_references_to ||= {}.compare_by_identity
1958
+ end
1959
+
1869
1960
  def non_introspection_types
1870
1961
  find_inherited_value(:non_introspection_types, EMPTY_HASH).merge(own_types)
1871
1962
  end
@@ -1879,7 +1970,7 @@ module GraphQL
1879
1970
  end
1880
1971
 
1881
1972
  def own_possible_types
1882
- @own_possible_types ||= {}
1973
+ @own_possible_types ||= {}.compare_by_identity
1883
1974
  end
1884
1975
 
1885
1976
  def own_union_memberships
@@ -1902,68 +1993,40 @@ module GraphQL
1902
1993
  @defined_query_analyzers ||= []
1903
1994
  end
1904
1995
 
1905
- def all_middleware
1906
- find_inherited_value(:all_middleware, EMPTY_ARRAY) + own_middleware
1907
- end
1908
-
1909
- def own_middleware
1910
- @own_middleware ||= []
1911
- end
1912
-
1913
1996
  def own_multiplex_analyzers
1914
1997
  @own_multiplex_analyzers ||= []
1915
1998
  end
1916
- end
1917
-
1918
- def dataloader_class
1919
- self.class.dataloader_class
1920
- end
1921
-
1922
- # Install these here so that subclasses will also install it.
1923
- use(GraphQL::Pagination::Connections)
1924
-
1925
- protected
1926
-
1927
- def rescues?
1928
- !!@rescue_middleware
1929
- end
1930
1999
 
1931
- # Lazily create a middleware and add it to the schema
1932
- # (Don't add it if it's not used)
1933
- def rescue_middleware
1934
- @rescue_middleware ||= GraphQL::Schema::RescueMiddleware.new.tap { |m| middleware.insert(0, m) }
2000
+ # This is overridden in subclasses to check the inheritance chain
2001
+ def get_references_to(type_defn)
2002
+ own_references_to[type_defn]
2003
+ end
1935
2004
  end
1936
2005
 
1937
- private
1938
-
1939
- def rebuild_artifacts
1940
- if @rebuilding_artifacts
1941
- raise CyclicalDefinitionError, "Part of the schema build process re-triggered the schema build process, causing an infinite loop. Avoid using Schema#types, Schema#possible_types, and Schema#get_field during schema build."
1942
- else
1943
- @rebuilding_artifacts = true
1944
- @introspection_system = Schema::IntrospectionSystem.new(self)
1945
- traversal = Traversal.new(self)
1946
- @types = traversal.type_map
1947
- @root_types = [query, mutation, subscription]
1948
- @instrumented_field_map = traversal.instrumented_field_map
1949
- @type_reference_map = traversal.type_reference_map
1950
- @union_memberships = traversal.union_memberships
1951
- @find_cache = {}
1952
- @finder = Finder.new(self)
1953
- end
1954
- ensure
1955
- @rebuilding_artifacts = false
2006
+ module SubclassGetReferencesTo
2007
+ def get_references_to(type_defn)
2008
+ own_refs = own_references_to[type_defn]
2009
+ inherited_refs = superclass.references_to(type_defn)
2010
+ if inherited_refs&.any?
2011
+ if own_refs&.any?
2012
+ own_refs + inherited_refs
2013
+ else
2014
+ inherited_refs
2015
+ end
2016
+ else
2017
+ own_refs
2018
+ end
2019
+ end
1956
2020
  end
1957
2021
 
1958
- class CyclicalDefinitionError < GraphQL::Error
1959
- end
2022
+ # Install these here so that subclasses will also install it.
2023
+ self.connections = GraphQL::Pagination::Connections.new(schema: self)
1960
2024
 
1961
- def with_definition_error_check
1962
- if @definition_error
1963
- raise @definition_error
1964
- else
1965
- yield
1966
- end
2025
+ # @api private
2026
+ module DefaultTraceClass
1967
2027
  end
1968
2028
  end
1969
2029
  end
2030
+
2031
+ require "graphql/schema/loader"
2032
+ require "graphql/schema/printer"