graphql 2.0.32 → 2.5.22

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 (308) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  4. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  5. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  6. data/lib/generators/graphql/install_generator.rb +49 -0
  7. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  8. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  9. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  10. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  11. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  12. data/lib/generators/graphql/templates/base_field.erb +2 -0
  13. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  14. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  15. data/lib/generators/graphql/templates/base_object.erb +2 -0
  16. data/lib/generators/graphql/templates/base_resolver.erb +8 -0
  17. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  18. data/lib/generators/graphql/templates/base_union.erb +2 -0
  19. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  20. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  21. data/lib/generators/graphql/templates/loader.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +2 -0
  24. data/lib/generators/graphql/templates/query_type.erb +2 -0
  25. data/lib/generators/graphql/templates/schema.erb +5 -0
  26. data/lib/generators/graphql/type_generator.rb +1 -1
  27. data/lib/graphql/analysis/analyzer.rb +90 -0
  28. data/lib/graphql/analysis/field_usage.rb +82 -0
  29. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  30. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  31. data/lib/graphql/analysis/query_complexity.rb +263 -0
  32. data/lib/graphql/analysis/query_depth.rb +58 -0
  33. data/lib/graphql/analysis/visitor.rb +280 -0
  34. data/lib/graphql/analysis.rb +95 -1
  35. data/lib/graphql/autoload.rb +38 -0
  36. data/lib/graphql/backtrace/table.rb +118 -55
  37. data/lib/graphql/backtrace.rb +1 -19
  38. data/lib/graphql/coercion_error.rb +1 -9
  39. data/lib/graphql/current.rb +57 -0
  40. data/lib/graphql/dashboard/application_controller.rb +41 -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/landings_controller.rb +9 -0
  44. data/lib/graphql/dashboard/limiters.rb +93 -0
  45. data/lib/graphql/dashboard/operation_store.rb +199 -0
  46. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  47. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  48. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  49. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  50. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  51. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  52. data/lib/graphql/dashboard/statics/icon.png +0 -0
  53. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  54. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  55. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  56. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  57. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  58. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  59. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  60. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  61. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  62. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  63. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  64. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  65. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  66. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  67. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  68. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  69. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  70. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  71. data/lib/graphql/dashboard.rb +96 -0
  72. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  73. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  74. data/lib/graphql/dataloader/async_dataloader.rb +112 -0
  75. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  76. data/lib/graphql/dataloader/request.rb +5 -0
  77. data/lib/graphql/dataloader/source.rb +35 -12
  78. data/lib/graphql/dataloader.rb +224 -149
  79. data/lib/graphql/date_encoding_error.rb +1 -1
  80. data/lib/graphql/dig.rb +2 -1
  81. data/lib/graphql/duration_encoding_error.rb +16 -0
  82. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  83. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  84. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  85. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +228 -0
  86. data/lib/graphql/execution/interpreter/runtime.rb +363 -434
  87. data/lib/graphql/execution/interpreter.rb +91 -164
  88. data/lib/graphql/execution/lookahead.rb +105 -31
  89. data/lib/graphql/execution/multiplex.rb +7 -6
  90. data/lib/graphql/execution/next/field_resolve_step.rb +711 -0
  91. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  92. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  93. data/lib/graphql/execution/next/runner.rb +389 -0
  94. data/lib/graphql/execution/next/selections_step.rb +37 -0
  95. data/lib/graphql/execution/next.rb +70 -0
  96. data/lib/graphql/execution.rb +1 -0
  97. data/lib/graphql/execution_error.rb +13 -10
  98. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  99. data/lib/graphql/introspection/directive_type.rb +7 -3
  100. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  101. data/lib/graphql/introspection/entry_points.rb +20 -6
  102. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  103. data/lib/graphql/introspection/field_type.rb +13 -5
  104. data/lib/graphql/introspection/input_value_type.rb +21 -13
  105. data/lib/graphql/introspection/schema_type.rb +8 -11
  106. data/lib/graphql/introspection/type_type.rb +64 -28
  107. data/lib/graphql/invalid_name_error.rb +1 -1
  108. data/lib/graphql/invalid_null_error.rb +26 -17
  109. data/lib/graphql/language/block_string.rb +34 -18
  110. data/lib/graphql/language/cache.rb +13 -0
  111. data/lib/graphql/language/comment.rb +18 -0
  112. data/lib/graphql/language/definition_slice.rb +1 -1
  113. data/lib/graphql/language/document_from_schema_definition.rb +90 -61
  114. data/lib/graphql/language/lexer.rb +319 -193
  115. data/lib/graphql/language/nodes.rb +136 -77
  116. data/lib/graphql/language/parser.rb +807 -1985
  117. data/lib/graphql/language/printer.rb +324 -151
  118. data/lib/graphql/language/sanitized_printer.rb +21 -23
  119. data/lib/graphql/language/static_visitor.rb +171 -0
  120. data/lib/graphql/language/visitor.rb +23 -83
  121. data/lib/graphql/language.rb +71 -1
  122. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  123. data/lib/graphql/pagination/array_connection.rb +6 -6
  124. data/lib/graphql/pagination/connection.rb +30 -1
  125. data/lib/graphql/pagination/connections.rb +32 -0
  126. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  127. data/lib/graphql/query/context/scoped_context.rb +101 -0
  128. data/lib/graphql/query/context.rb +82 -144
  129. data/lib/graphql/query/null_context.rb +15 -18
  130. data/lib/graphql/query/partial.rb +179 -0
  131. data/lib/graphql/query/validation_pipeline.rb +4 -4
  132. data/lib/graphql/query/variable_validation_error.rb +1 -1
  133. data/lib/graphql/query/variables.rb +3 -3
  134. data/lib/graphql/query.rb +126 -81
  135. data/lib/graphql/railtie.rb +16 -6
  136. data/lib/graphql/rake_task.rb +3 -12
  137. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  138. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  139. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  140. data/lib/graphql/rubocop.rb +2 -0
  141. data/lib/graphql/schema/addition.rb +26 -13
  142. data/lib/graphql/schema/always_visible.rb +7 -2
  143. data/lib/graphql/schema/argument.rb +75 -9
  144. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  145. data/lib/graphql/schema/build_from_definition.rb +123 -60
  146. data/lib/graphql/schema/directive/flagged.rb +4 -2
  147. data/lib/graphql/schema/directive/one_of.rb +12 -0
  148. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  149. data/lib/graphql/schema/directive.rb +54 -2
  150. data/lib/graphql/schema/enum.rb +110 -27
  151. data/lib/graphql/schema/enum_value.rb +10 -2
  152. data/lib/graphql/schema/field/connection_extension.rb +15 -49
  153. data/lib/graphql/schema/field/scope_extension.rb +23 -7
  154. data/lib/graphql/schema/field.rb +245 -118
  155. data/lib/graphql/schema/field_extension.rb +34 -1
  156. data/lib/graphql/schema/has_single_input_argument.rb +160 -0
  157. data/lib/graphql/schema/input_object.rb +116 -60
  158. data/lib/graphql/schema/interface.rb +34 -16
  159. data/lib/graphql/schema/introspection_system.rb +8 -17
  160. data/lib/graphql/schema/late_bound_type.rb +4 -0
  161. data/lib/graphql/schema/list.rb +3 -3
  162. data/lib/graphql/schema/loader.rb +3 -4
  163. data/lib/graphql/schema/member/base_dsl_methods.rb +18 -2
  164. data/lib/graphql/schema/member/has_arguments.rb +132 -100
  165. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  166. data/lib/graphql/schema/member/has_dataloader.rb +99 -0
  167. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  168. data/lib/graphql/schema/member/has_directives.rb +4 -4
  169. data/lib/graphql/schema/member/has_fields.rb +115 -15
  170. data/lib/graphql/schema/member/has_interfaces.rb +26 -12
  171. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  172. data/lib/graphql/schema/member/has_validators.rb +1 -1
  173. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  174. data/lib/graphql/schema/member/scoped.rb +19 -0
  175. data/lib/graphql/schema/member/type_system_helpers.rb +17 -4
  176. data/lib/graphql/schema/member/validates_input.rb +3 -3
  177. data/lib/graphql/schema/member.rb +6 -0
  178. data/lib/graphql/schema/mutation.rb +7 -0
  179. data/lib/graphql/schema/object.rb +34 -8
  180. data/lib/graphql/schema/printer.rb +9 -7
  181. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  182. data/lib/graphql/schema/relay_classic_mutation.rb +6 -129
  183. data/lib/graphql/schema/resolver.rb +90 -32
  184. data/lib/graphql/schema/scalar.rb +4 -9
  185. data/lib/graphql/schema/subscription.rb +63 -10
  186. data/lib/graphql/schema/timeout.rb +19 -2
  187. data/lib/graphql/schema/type_expression.rb +2 -2
  188. data/lib/graphql/schema/union.rb +2 -2
  189. data/lib/graphql/schema/unique_within_type.rb +1 -1
  190. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  191. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  192. data/lib/graphql/schema/validator.rb +3 -1
  193. data/lib/graphql/schema/visibility/migration.rb +188 -0
  194. data/lib/graphql/schema/visibility/profile.rb +445 -0
  195. data/lib/graphql/schema/visibility/visit.rb +190 -0
  196. data/lib/graphql/schema/visibility.rb +311 -0
  197. data/lib/graphql/schema/warden.rb +275 -103
  198. data/lib/graphql/schema.rb +950 -210
  199. data/lib/graphql/static_validation/all_rules.rb +3 -3
  200. data/lib/graphql/static_validation/base_visitor.rb +7 -6
  201. data/lib/graphql/static_validation/literal_validator.rb +6 -7
  202. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  203. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  204. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  205. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  206. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  207. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  208. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  209. data/lib/graphql/static_validation/rules/fields_will_merge.rb +88 -25
  210. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  211. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  212. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  213. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  214. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  215. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  216. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  217. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  218. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +5 -5
  219. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  220. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  221. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  222. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  223. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  224. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  225. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  226. data/lib/graphql/static_validation/validation_context.rb +21 -5
  227. data/lib/graphql/static_validation/validator.rb +9 -1
  228. data/lib/graphql/static_validation.rb +0 -1
  229. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -5
  230. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  231. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  232. data/lib/graphql/subscriptions/event.rb +21 -4
  233. data/lib/graphql/subscriptions/serialize.rb +3 -1
  234. data/lib/graphql/subscriptions.rb +21 -17
  235. data/lib/graphql/testing/helpers.rb +161 -0
  236. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  237. data/lib/graphql/testing.rb +3 -0
  238. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  239. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  240. data/lib/graphql/tracing/appoptics_trace.rb +7 -3
  241. data/lib/graphql/tracing/appoptics_tracing.rb +9 -2
  242. data/lib/graphql/tracing/appsignal_trace.rb +32 -59
  243. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  244. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  245. data/lib/graphql/tracing/data_dog_trace.rb +46 -162
  246. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  247. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  248. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  249. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  250. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  251. data/lib/graphql/tracing/legacy_hooks_trace.rb +75 -0
  252. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  253. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  254. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  255. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  256. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  257. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  258. data/lib/graphql/tracing/null_trace.rb +9 -0
  259. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  260. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  261. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  262. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  263. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +5 -1
  264. data/lib/graphql/tracing/prometheus_trace.rb +73 -73
  265. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  266. data/lib/graphql/tracing/scout_trace.rb +32 -58
  267. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  268. data/lib/graphql/tracing/sentry_trace.rb +82 -0
  269. data/lib/graphql/tracing/statsd_trace.rb +33 -45
  270. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  271. data/lib/graphql/tracing/trace.rb +112 -1
  272. data/lib/graphql/tracing.rb +31 -28
  273. data/lib/graphql/type_kinds.rb +2 -1
  274. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  275. data/lib/graphql/types/relay/connection_behaviors.rb +44 -2
  276. data/lib/graphql/types/relay/edge_behaviors.rb +18 -0
  277. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  278. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  279. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  280. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  281. data/lib/graphql/types.rb +18 -10
  282. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  283. data/lib/graphql/unauthorized_error.rb +5 -1
  284. data/lib/graphql/version.rb +1 -1
  285. data/lib/graphql.rb +71 -54
  286. data/readme.md +12 -2
  287. metadata +233 -37
  288. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  289. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  290. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  291. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  292. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  293. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  294. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  295. data/lib/graphql/analysis/ast.rb +0 -81
  296. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  297. data/lib/graphql/backtrace/trace.rb +0 -96
  298. data/lib/graphql/backtrace/tracer.rb +0 -80
  299. data/lib/graphql/deprecation.rb +0 -9
  300. data/lib/graphql/filter.rb +0 -59
  301. data/lib/graphql/language/parser.y +0 -560
  302. data/lib/graphql/language/token.rb +0 -34
  303. data/lib/graphql/schema/base_64_bp.rb +0 -26
  304. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  305. data/lib/graphql/schema/null_mask.rb +0 -11
  306. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
  307. data/lib/graphql/static_validation/type_stack.rb +0 -216
  308. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
+ require "logger"
2
3
  require "graphql/schema/addition"
3
4
  require "graphql/schema/always_visible"
4
5
  require "graphql/schema/base_64_encoder"
5
6
  require "graphql/schema/find_inherited_value"
6
7
  require "graphql/schema/finder"
7
- require "graphql/schema/invalid_type_error"
8
8
  require "graphql/schema/introspection_system"
9
9
  require "graphql/schema/late_bound_type"
10
- require "graphql/schema/null_mask"
10
+ require "graphql/schema/ractor_shareable"
11
11
  require "graphql/schema/timeout"
12
12
  require "graphql/schema/type_expression"
13
13
  require "graphql/schema/unique_within_type"
@@ -37,12 +37,15 @@ require "graphql/schema/directive/skip"
37
37
  require "graphql/schema/directive/feature"
38
38
  require "graphql/schema/directive/flagged"
39
39
  require "graphql/schema/directive/transform"
40
+ require "graphql/schema/directive/specified_by"
40
41
  require "graphql/schema/type_membership"
41
42
 
42
43
  require "graphql/schema/resolver"
43
44
  require "graphql/schema/mutation"
45
+ require "graphql/schema/has_single_input_argument"
44
46
  require "graphql/schema/relay_classic_mutation"
45
47
  require "graphql/schema/subscription"
48
+ require "graphql/schema/visibility"
46
49
 
47
50
  module GraphQL
48
51
  # A GraphQL schema which may be queried with {GraphQL::Query}.
@@ -58,11 +61,7 @@ module GraphQL
58
61
  # Any undiscoverable types may be provided with the `types` configuration.
59
62
  #
60
63
  # Schemas can restrict large incoming queries with `max_depth` and `max_complexity` configurations.
61
- # (These configurations can be overridden by specific calls to {Schema#execute})
62
- #
63
- # Schemas can specify how queries should be executed against them.
64
- # `query_execution_strategy`, `mutation_execution_strategy` and `subscription_execution_strategy`
65
- # each apply to corresponding root types.
64
+ # (These configurations can be overridden by specific calls to {Schema.execute})
66
65
  #
67
66
  # @example defining a schema
68
67
  # class MySchema < GraphQL::Schema
@@ -74,6 +73,9 @@ module GraphQL
74
73
  class Schema
75
74
  extend GraphQL::Schema::Member::HasAstNode
76
75
  extend GraphQL::Schema::FindInheritedValue
76
+ extend Autoload
77
+
78
+ autoload :BUILT_IN_TYPES, "graphql/schema/built_in_types"
77
79
 
78
80
  class DuplicateNamesError < GraphQL::Error
79
81
  attr_reader :duplicated_name
@@ -110,7 +112,7 @@ module GraphQL
110
112
  # @param parser [Object] An object for handling definition string parsing (must respond to `parse`)
111
113
  # @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
112
114
  # @return [Class] the schema described by `document`
113
- def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {})
115
+ def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {}, base_types: {})
114
116
  # If the file ends in `.graphql` or `.graphqls`, treat it like a filepath
115
117
  if definition_or_path.end_with?(".graphql") || definition_or_path.end_with?(".graphqls")
116
118
  GraphQL::Schema::BuildFromDefinition.from_definition_path(
@@ -119,6 +121,7 @@ module GraphQL
119
121
  default_resolve: default_resolve,
120
122
  parser: parser,
121
123
  using: using,
124
+ base_types: base_types,
122
125
  )
123
126
  else
124
127
  GraphQL::Schema::BuildFromDefinition.from_definition(
@@ -127,6 +130,7 @@ module GraphQL
127
130
  default_resolve: default_resolve,
128
131
  parser: parser,
129
132
  using: using,
133
+ base_types: base_types,
130
134
  )
131
135
  end
132
136
  end
@@ -144,53 +148,95 @@ module GraphQL
144
148
  @subscriptions = new_implementation
145
149
  end
146
150
 
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
161
+ else
162
+ :default
163
+ end
164
+ end
165
+
147
166
  def trace_class(new_class = nil)
148
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)
149
173
  trace_mode(:default, new_class)
150
- backtrace_class = Class.new(new_class)
151
- backtrace_class.include(GraphQL::Backtrace::Trace)
152
- trace_mode(:default_backtrace, backtrace_class)
153
174
  end
154
- trace_class_for(:default)
175
+ trace_class_for(:default, build: true)
155
176
  end
156
177
 
157
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.
158
- def trace_class_for(mode)
159
- @trace_modes ||= {}
160
- @trace_modes[mode] ||= begin
161
- case mode
162
- when :default
163
- superclass_base_class = if superclass.respond_to?(:trace_class_for)
164
- superclass.trace_class_for(mode)
165
- else
166
- GraphQL::Tracing::Trace
167
- end
168
- Class.new(superclass_base_class)
169
- when :default_backtrace
170
- schema_base_class = trace_class_for(:default)
171
- Class.new(schema_base_class) do
172
- include(GraphQL::Backtrace::Trace)
173
- end
174
- else
175
- mods = trace_modules_for(mode)
176
- Class.new(trace_class_for(:default)) do
177
- mods.any? && include(*mods)
178
- end
179
- end
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)
186
+ else
187
+ nil
180
188
  end
181
189
  end
182
190
 
183
191
  # Configure `trace_class` to be used whenever `context: { trace_mode: mode_name }` is requested.
184
- # `:default` is used when no `trace_mode: ...` 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
+ #
185
200
  # @param mode_name [Symbol]
186
201
  # @param trace_class [Class] subclass of GraphQL::Tracing::Trace
187
202
  # @return void
188
203
  def trace_mode(mode_name, trace_class)
189
- @trace_modes ||= {}
190
- @trace_modes[mode_name] = trace_class
204
+ own_trace_modes[mode_name] = trace_class
191
205
  nil
192
206
  end
193
207
 
208
+ def own_trace_modes
209
+ @own_trace_modes ||= {}
210
+ end
211
+
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
229
+ end
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)
236
+ end
237
+ end
238
+ end
239
+
194
240
  def own_trace_modules
195
241
  @own_trace_modules ||= Hash.new { |h, k| h[k] = [] }
196
242
  end
@@ -206,7 +252,7 @@ module GraphQL
206
252
 
207
253
 
208
254
  # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
209
- # @see {#as_json}
255
+ # @see #as_json Return a Hash representation of the schema
210
256
  # @return [String]
211
257
  def to_json(**args)
212
258
  JSON.pretty_generate(as_json(**args))
@@ -214,15 +260,13 @@ module GraphQL
214
260
 
215
261
  # Return the Hash response of {Introspection::INTROSPECTION_QUERY}.
216
262
  # @param context [Hash]
217
- # @param only [<#call(member, ctx)>]
218
- # @param except [<#call(member, ctx)>]
219
263
  # @param include_deprecated_args [Boolean] If true, deprecated arguments will be included in the JSON response
220
264
  # @param include_schema_description [Boolean] If true, the schema's description will be queried and included in the response
221
265
  # @param include_is_repeatable [Boolean] If true, `isRepeatable: true|false` will be included with the schema's directives
222
266
  # @param include_specified_by_url [Boolean] If true, scalar types' `specifiedByUrl:` will be included in the response
223
267
  # @param include_is_one_of [Boolean] If true, `isOneOf: true|false` will be included with input objects
224
268
  # @return [Hash] GraphQL result
225
- def as_json(only: nil, except: nil, context: {}, include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false)
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)
226
270
  introspection_query = Introspection.query(
227
271
  include_deprecated_args: include_deprecated_args,
228
272
  include_schema_description: include_schema_description,
@@ -231,16 +275,14 @@ module GraphQL
231
275
  include_specified_by_url: include_specified_by_url,
232
276
  )
233
277
 
234
- execute(introspection_query, only: only, except: except, context: context).to_h
278
+ execute(introspection_query, context: context).to_h
235
279
  end
236
280
 
237
281
  # Return the GraphQL IDL for the schema
238
282
  # @param context [Hash]
239
- # @param only [<#call(member, ctx)>]
240
- # @param except [<#call(member, ctx)>]
241
283
  # @return [String]
242
- def to_definition(only: nil, except: nil, context: {})
243
- 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)
244
286
  end
245
287
 
246
288
  # Return the GraphQL::Language::Document IDL AST for the schema
@@ -268,26 +310,15 @@ module GraphQL
268
310
  @find_cache[path] ||= @finder.find(path)
269
311
  end
270
312
 
271
- def default_filter
272
- GraphQL::Filter.new(except: default_mask)
273
- end
274
-
275
- def default_mask(new_mask = nil)
276
- if new_mask
277
- line = caller(2, 10).find { |l| !l.include?("lib/graphql") }
278
- GraphQL::Deprecation.warn("GraphQL::Filter and Schema.mask are deprecated and will be removed in v2.1.0. Implement `visible?` on your schema members instead (https://graphql-ruby.org/authorization/visibility.html).\n #{line}")
279
- @own_default_mask = new_mask
280
- else
281
- @own_default_mask || find_inherited_value(:default_mask, Schema::NullMask)
282
- end
283
- end
284
-
285
313
  def static_validator
286
314
  GraphQL::StaticValidation::Validator.new(schema: self)
287
315
  end
288
316
 
317
+ # Add `plugin` to this schema
318
+ # @param plugin [#use] A Schema plugin
319
+ # @return void
289
320
  def use(plugin, **kwargs)
290
- if kwargs.any?
321
+ if !kwargs.empty?
291
322
  plugin.use(self, **kwargs)
292
323
  else
293
324
  plugin.use(self)
@@ -299,10 +330,20 @@ module GraphQL
299
330
  find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins
300
331
  end
301
332
 
333
+ attr_writer :null_context
334
+
335
+ def null_context
336
+ @null_context || GraphQL::Query::NullContext.instance
337
+ end
338
+
302
339
  # Build a map of `{ name => type }` and return it
303
340
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
304
341
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
305
- def types(context = GraphQL::Query::NullContext)
342
+ def types(context = null_context)
343
+ if use_visibility_profile?
344
+ types = Visibility::Profile.from_context(context, self)
345
+ return types.all_types_h
346
+ end
306
347
  all_types = non_introspection_types.merge(introspection_system.types)
307
348
  visible_types = {}
308
349
  all_types.each do |k, v|
@@ -328,27 +369,37 @@ module GraphQL
328
369
  end
329
370
 
330
371
  # @param type_name [String]
372
+ # @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
373
+ # @param use_visibility_profile Private, for migration to {Schema::Visibility}
331
374
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
332
- def get_type(type_name, context = GraphQL::Query::NullContext)
375
+ def get_type(type_name, context = null_context, use_visibility_profile = use_visibility_profile?)
376
+ if use_visibility_profile
377
+ profile = Visibility::Profile.from_context(context, self)
378
+ return profile.type(type_name)
379
+ end
333
380
  local_entry = own_types[type_name]
334
381
  type_defn = case local_entry
335
382
  when nil
336
383
  nil
337
384
  when Array
338
- visible_t = nil
339
- warden = Warden.from_context(context)
340
- local_entry.each do |t|
341
- if warden.visible_type?(t, context)
342
- if visible_t.nil?
343
- visible_t = t
344
- else
345
- raise DuplicateNamesError.new(
346
- duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
347
- )
385
+ if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
386
+ local_entry
387
+ else
388
+ visible_t = nil
389
+ warden = Warden.from_context(context)
390
+ local_entry.each do |t|
391
+ if warden.visible_type?(t, context)
392
+ if visible_t.nil?
393
+ visible_t = t
394
+ else
395
+ raise DuplicateNamesError.new(
396
+ duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
397
+ )
398
+ end
348
399
  end
349
400
  end
401
+ visible_t
350
402
  end
351
- visible_t
352
403
  when Module
353
404
  local_entry
354
405
  else
@@ -357,7 +408,12 @@ module GraphQL
357
408
 
358
409
  type_defn ||
359
410
  introspection_system.types[type_name] || # todo context-specific introspection?
360
- (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context) : nil)
411
+ (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context, use_visibility_profile) : nil)
412
+ end
413
+
414
+ # @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility?
415
+ def has_defined_type?(type_name)
416
+ own_types.key?(type_name) || introspection_system.types.key?(type_name) || (superclass.respond_to?(:has_defined_type?) ? superclass.has_defined_type?(type_name) : false)
361
417
  end
362
418
 
363
419
  # @api private
@@ -379,55 +435,127 @@ module GraphQL
379
435
  end
380
436
  end
381
437
 
382
- def new_connections?
383
- !!connections
384
- end
385
-
386
- def query(new_query_object = nil)
387
- if new_query_object
438
+ # Get or set the root `query { ... }` object for this schema.
439
+ #
440
+ # @example Using `Types::Query` as the entry-point
441
+ # query { Types::Query }
442
+ #
443
+ # @param new_query_object [Class<GraphQL::Schema::Object>] The root type to use for queries
444
+ # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root query type.
445
+ # @return [Class<GraphQL::Schema::Object>, nil] The configured query root type, if there is one.
446
+ def query(new_query_object = nil, &lazy_load_block)
447
+ if new_query_object || block_given?
388
448
  if @query_object
389
- raise GraphQL::Error, "Second definition of `query(...)` (#{new_query_object.inspect}) is invalid, already configured with #{@query_object.inspect}"
449
+ dup_defn = new_query_object || yield
450
+ raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
451
+ elsif use_visibility_profile?
452
+ if block_given?
453
+ if visibility.preload?
454
+ @query_object = lazy_load_block.call
455
+ self.visibility.query_configured(@query_object)
456
+ else
457
+ @query_object = lazy_load_block
458
+ end
459
+ else
460
+ @query_object = new_query_object
461
+ self.visibility.query_configured(@query_object)
462
+ end
390
463
  else
391
- @query_object = new_query_object
392
- add_type_and_traverse(new_query_object, root: true)
393
- nil
464
+ @query_object = new_query_object || lazy_load_block.call
465
+ add_type_and_traverse(@query_object, root: true)
394
466
  end
467
+ nil
468
+ elsif @query_object.is_a?(Proc)
469
+ @query_object = @query_object.call
470
+ self.visibility&.query_configured(@query_object)
471
+ @query_object
395
472
  else
396
473
  @query_object || find_inherited_value(:query)
397
474
  end
398
475
  end
399
476
 
400
- def mutation(new_mutation_object = nil)
401
- if new_mutation_object
477
+ # Get or set the root `mutation { ... }` object for this schema.
478
+ #
479
+ # @example Using `Types::Mutation` as the entry-point
480
+ # mutation { Types::Mutation }
481
+ #
482
+ # @param new_mutation_object [Class<GraphQL::Schema::Object>] The root type to use for mutations
483
+ # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root mutation type.
484
+ # @return [Class<GraphQL::Schema::Object>, nil] The configured mutation root type, if there is one.
485
+ def mutation(new_mutation_object = nil, &lazy_load_block)
486
+ if new_mutation_object || block_given?
402
487
  if @mutation_object
403
- raise GraphQL::Error, "Second definition of `mutation(...)` (#{new_mutation_object.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
488
+ dup_defn = new_mutation_object || yield
489
+ raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
490
+ elsif use_visibility_profile?
491
+ if block_given?
492
+ if visibility.preload?
493
+ @mutation_object = lazy_load_block.call
494
+ self.visibility.mutation_configured(@mutation_object)
495
+ else
496
+ @mutation_object = lazy_load_block
497
+ end
498
+ else
499
+ @mutation_object = new_mutation_object
500
+ self.visibility.mutation_configured(@mutation_object)
501
+ end
404
502
  else
405
- @mutation_object = new_mutation_object
406
- add_type_and_traverse(new_mutation_object, root: true)
407
- nil
503
+ @mutation_object = new_mutation_object || lazy_load_block.call
504
+ add_type_and_traverse(@mutation_object, root: true)
408
505
  end
506
+ nil
507
+ elsif @mutation_object.is_a?(Proc)
508
+ @mutation_object = @mutation_object.call
509
+ self.visibility&.mutation_configured(@mutation_object)
510
+ @mutation_object
409
511
  else
410
512
  @mutation_object || find_inherited_value(:mutation)
411
513
  end
412
514
  end
413
515
 
414
- def subscription(new_subscription_object = nil)
415
- if new_subscription_object
516
+ # Get or set the root `subscription { ... }` object for this schema.
517
+ #
518
+ # @example Using `Types::Subscription` as the entry-point
519
+ # subscription { Types::Subscription }
520
+ #
521
+ # @param new_subscription_object [Class<GraphQL::Schema::Object>] The root type to use for subscriptions
522
+ # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root subscription type.
523
+ # @return [Class<GraphQL::Schema::Object>, nil] The configured subscription root type, if there is one.
524
+ def subscription(new_subscription_object = nil, &lazy_load_block)
525
+ if new_subscription_object || block_given?
416
526
  if @subscription_object
417
- raise GraphQL::Error, "Second definition of `subscription(...)` (#{new_subscription_object.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
527
+ dup_defn = new_subscription_object || yield
528
+ raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
529
+ elsif use_visibility_profile?
530
+ if block_given?
531
+ if visibility.preload?
532
+ @subscription_object = lazy_load_block.call
533
+ visibility.subscription_configured(@subscription_object)
534
+ else
535
+ @subscription_object = lazy_load_block
536
+ end
537
+ else
538
+ @subscription_object = new_subscription_object
539
+ self.visibility.subscription_configured(@subscription_object)
540
+ end
541
+ add_subscription_extension_if_necessary
418
542
  else
419
- @subscription_object = new_subscription_object
543
+ @subscription_object = new_subscription_object || lazy_load_block.call
420
544
  add_subscription_extension_if_necessary
421
- add_type_and_traverse(new_subscription_object, root: true)
422
- nil
545
+ add_type_and_traverse(@subscription_object, root: true)
423
546
  end
547
+ nil
548
+ elsif @subscription_object.is_a?(Proc)
549
+ @subscription_object = @subscription_object.call
550
+ add_subscription_extension_if_necessary
551
+ self.visibility.subscription_configured(@subscription_object)
552
+ @subscription_object
424
553
  else
425
554
  @subscription_object || find_inherited_value(:subscription)
426
555
  end
427
556
  end
428
557
 
429
- # @see [GraphQL::Schema::Warden] Restricted access to root types
430
- # @return [GraphQL::ObjectType, nil]
558
+ # @api private
431
559
  def root_type_for_operation(operation)
432
560
  case operation
433
561
  when "query"
@@ -441,10 +569,16 @@ module GraphQL
441
569
  end
442
570
  end
443
571
 
572
+ # @return [Array<Class>] The root types (query, mutation, subscription) defined for this schema
444
573
  def root_types
445
- @root_types
574
+ if use_visibility_profile?
575
+ [query, mutation, subscription].compact
576
+ else
577
+ @root_types
578
+ end
446
579
  end
447
580
 
581
+ # @api private
448
582
  def warden_class
449
583
  if defined?(@warden_class)
450
584
  @warden_class
@@ -455,18 +589,54 @@ module GraphQL
455
589
  end
456
590
  end
457
591
 
592
+ # @api private
458
593
  attr_writer :warden_class
459
594
 
595
+ # @api private
596
+ def visibility_profile_class
597
+ if defined?(@visibility_profile_class)
598
+ @visibility_profile_class
599
+ elsif superclass.respond_to?(:visibility_profile_class)
600
+ superclass.visibility_profile_class
601
+ else
602
+ GraphQL::Schema::Visibility::Profile
603
+ end
604
+ end
605
+
606
+ # @api private
607
+ attr_writer :visibility_profile_class, :use_visibility_profile
608
+ # @api private
609
+ attr_accessor :visibility
610
+ # @api private
611
+ def use_visibility_profile?
612
+ if defined?(@use_visibility_profile)
613
+ @use_visibility_profile
614
+ elsif superclass.respond_to?(:use_visibility_profile?)
615
+ superclass.use_visibility_profile?
616
+ else
617
+ false
618
+ end
619
+ end
620
+
460
621
  # @param type [Module] The type definition whose possible types you want to see
622
+ # @param context [GraphQL::Query::Context] used for filtering visible possible types at runtime
623
+ # @param use_visibility_profile Private, for migration to {Schema::Visibility}
461
624
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
462
625
  # @return [Array<Module>] Possible types for `type`, if it's given.
463
- def possible_types(type = nil, context = GraphQL::Query::NullContext)
626
+ def possible_types(type = nil, context = null_context, use_visibility_profile = use_visibility_profile?)
627
+ if use_visibility_profile
628
+ if type
629
+ return Visibility::Profile.from_context(context, self).possible_types(type)
630
+ else
631
+ raise "Schema.possible_types is not implemented for `use_visibility_profile?`"
632
+ end
633
+ end
464
634
  if type
465
635
  # TODO duck-typing `.possible_types` would probably be nicer here
466
636
  if type.kind.union?
467
637
  type.possible_types(context: context)
468
638
  else
469
- stored_possible_types = own_possible_types[type.graphql_name]
639
+ stored_possible_types = own_possible_types[type]
470
640
  visible_possible_types = if stored_possible_types && type.kind.interface?
471
641
  stored_possible_types.select do |possible_type|
472
642
  possible_type.interfaces(context).include?(type)
@@ -475,10 +645,10 @@ module GraphQL
475
645
  stored_possible_types
476
646
  end
477
647
  visible_possible_types ||
478
- introspection_system.possible_types[type.graphql_name] ||
648
+ introspection_system.possible_types[type] ||
479
649
  (
480
650
  superclass.respond_to?(:possible_types) ?
481
- superclass.possible_types(type, context) :
651
+ superclass.possible_types(type, context, use_visibility_profile) :
482
652
  EMPTY_ARRAY
483
653
  )
484
654
  end
@@ -513,38 +683,45 @@ module GraphQL
513
683
  attr_writer :dataloader_class
514
684
 
515
685
  def references_to(to_type = nil, from: nil)
516
- @own_references_to ||= Hash.new { |h, k| h[k] = [] }
517
686
  if to_type
518
- if !to_type.is_a?(String)
519
- to_type = to_type.graphql_name
520
- end
521
-
522
687
  if from
523
- @own_references_to[to_type] << from
688
+ refs = own_references_to[to_type] ||= []
689
+ refs << from
524
690
  else
525
- own_refs = @own_references_to[to_type]
526
- inherited_refs = find_inherited_value(:references_to, EMPTY_HASH)[to_type] || EMPTY_ARRAY
527
- own_refs + inherited_refs
691
+ get_references_to(to_type) || EMPTY_ARRAY
528
692
  end
529
693
  else
530
694
  # `@own_references_to` can be quite large for big schemas,
531
695
  # and generally speaking, we won't inherit any values.
532
696
  # So optimize the most common case -- don't create a duplicate Hash.
533
697
  inherited_value = find_inherited_value(:references_to, EMPTY_HASH)
534
- if inherited_value.any?
535
- inherited_value.merge(@own_references_to)
698
+ if !inherited_value.empty?
699
+ inherited_value.merge(own_references_to)
536
700
  else
537
- @own_references_to
701
+ own_references_to
538
702
  end
539
703
  end
540
704
  end
541
705
 
542
- def type_from_ast(ast_node, context: nil)
543
- type_owner = context ? context.warden : self
544
- GraphQL::Schema::TypeExpression.build_type(type_owner, ast_node)
706
+ def type_from_ast(ast_node, context: self.query_class.new(self, "{ __typename }").context)
707
+ GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
545
708
  end
546
709
 
547
- def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext)
710
+ def get_field(type_or_name, field_name, context = null_context, use_visibility_profile = use_visibility_profile?)
711
+ if use_visibility_profile
712
+ profile = Visibility::Profile.from_context(context, self)
713
+ parent_type = case type_or_name
714
+ when String
715
+ profile.type(type_or_name)
716
+ when Module
717
+ type_or_name
718
+ when LateBoundType
719
+ profile.type(type_or_name.name)
720
+ else
721
+ raise GraphQL::InvariantError, "Unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
722
+ end
723
+ return profile.field(parent_type, field_name)
724
+ end
548
725
  parent_type = case type_or_name
549
726
  when LateBoundType
550
727
  get_type(type_or_name.name, context)
@@ -567,24 +744,31 @@ module GraphQL
567
744
  end
568
745
  end
569
746
 
570
- def get_fields(type, context = GraphQL::Query::NullContext)
747
+ def get_fields(type, context = null_context)
571
748
  type.fields(context)
572
749
  end
573
750
 
751
+ # Pass a custom introspection module here to use it for this schema.
752
+ # @param new_introspection_namespace [Module] If given, use this module for custom introspection on the schema
753
+ # @return [Module, nil] The configured namespace, if there is one
574
754
  def introspection(new_introspection_namespace = nil)
575
755
  if new_introspection_namespace
576
756
  @introspection = new_introspection_namespace
577
757
  # reset this cached value:
578
758
  @introspection_system = nil
759
+ introspection_system
760
+ @introspection
579
761
  else
580
762
  @introspection || find_inherited_value(:introspection)
581
763
  end
582
764
  end
583
765
 
766
+ # @return [Schema::IntrospectionSystem] Based on {introspection}
584
767
  def introspection_system
585
768
  if !@introspection_system
586
769
  @introspection_system = Schema::IntrospectionSystem.new(self)
587
770
  @introspection_system.resolve_late_bindings
771
+ self.visibility&.introspection_system_configured(@introspection_system)
588
772
  end
589
773
  @introspection_system
590
774
  end
@@ -604,6 +788,17 @@ module GraphQL
604
788
  end
605
789
  end
606
790
 
791
+ # A limit on the number of tokens to accept on incoming query strings.
792
+ # Use this to prevent parsing maliciously-large query strings.
793
+ # @return [nil, Integer]
794
+ def max_query_string_tokens(new_max_tokens = NOT_CONFIGURED)
795
+ if NOT_CONFIGURED.equal?(new_max_tokens)
796
+ defined?(@max_query_string_tokens) ? @max_query_string_tokens : find_inherited_value(:max_query_string_tokens)
797
+ else
798
+ @max_query_string_tokens = new_max_tokens
799
+ end
800
+ end
801
+
607
802
  def default_page_size(new_default_page_size = nil)
608
803
  if new_default_page_size
609
804
  @default_page_size = new_default_page_size
@@ -612,39 +807,51 @@ module GraphQL
612
807
  end
613
808
  end
614
809
 
615
- def query_execution_strategy(new_query_execution_strategy = nil)
810
+ def query_execution_strategy(new_query_execution_strategy = nil, deprecation_warning: true)
811
+ if deprecation_warning
812
+ warn "GraphQL::Schema.query_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead."
813
+ warn " #{caller(1, 1).first}"
814
+ end
616
815
  if new_query_execution_strategy
617
816
  @query_execution_strategy = new_query_execution_strategy
618
817
  else
619
- @query_execution_strategy || find_inherited_value(:query_execution_strategy, self.default_execution_strategy)
818
+ @query_execution_strategy || (superclass.respond_to?(:query_execution_strategy) ? superclass.query_execution_strategy(deprecation_warning: false) : self.default_execution_strategy)
620
819
  end
621
820
  end
622
821
 
623
- def mutation_execution_strategy(new_mutation_execution_strategy = nil)
822
+ def mutation_execution_strategy(new_mutation_execution_strategy = nil, deprecation_warning: true)
823
+ if deprecation_warning
824
+ warn "GraphQL::Schema.mutation_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead."
825
+ warn " #{caller(1, 1).first}"
826
+ end
624
827
  if new_mutation_execution_strategy
625
828
  @mutation_execution_strategy = new_mutation_execution_strategy
626
829
  else
627
- @mutation_execution_strategy || find_inherited_value(:mutation_execution_strategy, self.default_execution_strategy)
830
+ @mutation_execution_strategy || (superclass.respond_to?(:mutation_execution_strategy) ? superclass.mutation_execution_strategy(deprecation_warning: false) : self.default_execution_strategy)
628
831
  end
629
832
  end
630
833
 
631
- def subscription_execution_strategy(new_subscription_execution_strategy = nil)
834
+ def subscription_execution_strategy(new_subscription_execution_strategy = nil, deprecation_warning: true)
835
+ if deprecation_warning
836
+ warn "GraphQL::Schema.subscription_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead."
837
+ warn " #{caller(1, 1).first}"
838
+ end
632
839
  if new_subscription_execution_strategy
633
840
  @subscription_execution_strategy = new_subscription_execution_strategy
634
841
  else
635
- @subscription_execution_strategy || find_inherited_value(:subscription_execution_strategy, self.default_execution_strategy)
842
+ @subscription_execution_strategy || (superclass.respond_to?(:subscription_execution_strategy) ? superclass.subscription_execution_strategy(deprecation_warning: false) : self.default_execution_strategy)
636
843
  end
637
844
  end
638
845
 
639
846
  attr_writer :validate_timeout
640
847
 
641
- def validate_timeout(new_validate_timeout = nil)
642
- if new_validate_timeout
848
+ def validate_timeout(new_validate_timeout = NOT_CONFIGURED)
849
+ if !NOT_CONFIGURED.equal?(new_validate_timeout)
643
850
  @validate_timeout = new_validate_timeout
644
851
  elsif defined?(@validate_timeout)
645
852
  @validate_timeout
646
853
  else
647
- find_inherited_value(:validate_timeout)
854
+ find_inherited_value(:validate_timeout) || 3
648
855
  end
649
856
  end
650
857
 
@@ -657,7 +864,7 @@ module GraphQL
657
864
  else
658
865
  string_or_document
659
866
  end
660
- query = GraphQL::Query.new(self, document: doc, context: context)
867
+ query = query_class.new(self, document: doc, context: context)
661
868
  validator_opts = { schema: self }
662
869
  rules && (validator_opts[:rules] = rules)
663
870
  validator = GraphQL::StaticValidation::Validator.new(**validator_opts)
@@ -665,23 +872,31 @@ module GraphQL
665
872
  res[:errors]
666
873
  end
667
874
 
875
+ # @param new_query_class [Class<GraphQL::Query>] A subclass to use when executing queries
876
+ def query_class(new_query_class = NOT_CONFIGURED)
877
+ if NOT_CONFIGURED.equal?(new_query_class)
878
+ @query_class || (superclass.respond_to?(:query_class) ? superclass.query_class : GraphQL::Query)
879
+ else
880
+ @query_class = new_query_class
881
+ end
882
+ end
883
+
668
884
  attr_writer :validate_max_errors
669
885
 
670
- def validate_max_errors(new_validate_max_errors = nil)
671
- if new_validate_max_errors
672
- @validate_max_errors = new_validate_max_errors
673
- elsif defined?(@validate_max_errors)
674
- @validate_max_errors
886
+ def validate_max_errors(new_validate_max_errors = NOT_CONFIGURED)
887
+ if NOT_CONFIGURED.equal?(new_validate_max_errors)
888
+ defined?(@validate_max_errors) ? @validate_max_errors : find_inherited_value(:validate_max_errors)
675
889
  else
676
- find_inherited_value(:validate_max_errors)
890
+ @validate_max_errors = new_validate_max_errors
677
891
  end
678
892
  end
679
893
 
680
894
  attr_writer :max_complexity
681
895
 
682
- def max_complexity(max_complexity = nil)
896
+ def max_complexity(max_complexity = nil, count_introspection_fields: true)
683
897
  if max_complexity
684
898
  @max_complexity = max_complexity
899
+ @max_complexity_count_introspection_fields = count_introspection_fields
685
900
  elsif defined?(@max_complexity)
686
901
  @max_complexity
687
902
  else
@@ -689,24 +904,23 @@ module GraphQL
689
904
  end
690
905
  end
691
906
 
907
+ def max_complexity_count_introspection_fields
908
+ if defined?(@max_complexity_count_introspection_fields)
909
+ @max_complexity_count_introspection_fields
910
+ else
911
+ find_inherited_value(:max_complexity_count_introspection_fields, true)
912
+ end
913
+ end
914
+
692
915
  attr_writer :analysis_engine
693
916
 
694
917
  def analysis_engine
695
918
  @analysis_engine || find_inherited_value(:analysis_engine, self.default_analysis_engine)
696
919
  end
697
920
 
698
- def using_ast_analysis?
699
- true
700
- end
701
-
702
- def interpreter?
703
- true
704
- end
705
-
706
- attr_writer :interpreter
707
-
708
921
  def error_bubbling(new_error_bubbling = nil)
709
922
  if !new_error_bubbling.nil?
923
+ warn("error_bubbling(#{new_error_bubbling.inspect}) is deprecated; the default value of `false` will be the only option in GraphQL-Ruby 3.0")
710
924
  @error_bubbling = new_error_bubbling
711
925
  else
712
926
  @error_bubbling.nil? ? find_inherited_value(:error_bubbling) : @error_bubbling
@@ -717,9 +931,10 @@ module GraphQL
717
931
 
718
932
  attr_writer :max_depth
719
933
 
720
- def max_depth(new_max_depth = nil)
934
+ def max_depth(new_max_depth = nil, count_introspection_fields: true)
721
935
  if new_max_depth
722
936
  @max_depth = new_max_depth
937
+ @count_introspection_fields = count_introspection_fields
723
938
  elsif defined?(@max_depth)
724
939
  @max_depth
725
940
  else
@@ -727,6 +942,14 @@ module GraphQL
727
942
  end
728
943
  end
729
944
 
945
+ def count_introspection_fields
946
+ if defined?(@count_introspection_fields)
947
+ @count_introspection_fields
948
+ else
949
+ find_inherited_value(:count_introspection_fields, true)
950
+ end
951
+ end
952
+
730
953
  def disable_introspection_entry_points
731
954
  @disable_introspection_entry_points = true
732
955
  # TODO: this clears the cache made in `def types`. But this is not a great solution.
@@ -769,14 +992,62 @@ module GraphQL
769
992
  end
770
993
  end
771
994
 
995
+ # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema
996
+ # @return [Array<Module>] Type definitions added to this schema
997
+ def extra_types(*new_extra_types)
998
+ if !new_extra_types.empty?
999
+ new_extra_types = new_extra_types.flatten
1000
+ @own_extra_types ||= []
1001
+ @own_extra_types.concat(new_extra_types)
1002
+ end
1003
+ inherited_et = find_inherited_value(:extra_types, nil)
1004
+ if inherited_et
1005
+ if @own_extra_types
1006
+ inherited_et + @own_extra_types
1007
+ else
1008
+ inherited_et
1009
+ end
1010
+ else
1011
+ @own_extra_types || EMPTY_ARRAY
1012
+ end
1013
+ end
1014
+
1015
+ # Tell the schema about these types so that they can be registered as implementations of interfaces in the schema.
1016
+ #
1017
+ # This method must be used when an object type is connected to the schema as an interface implementor but
1018
+ # 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.
1019
+ #
1020
+ # @param new_orphan_types [Array<Class<GraphQL::Schema::Object>>] Object types to register as implementations of interfaces in the schema.
1021
+ # @return [Array<Class<GraphQL::Schema::Object>>] All previously-registered orphan types for this schema
772
1022
  def orphan_types(*new_orphan_types)
773
- if new_orphan_types.any?
1023
+ if !new_orphan_types.empty?
774
1024
  new_orphan_types = new_orphan_types.flatten
775
- add_type_and_traverse(new_orphan_types, root: false)
1025
+ non_object_types = new_orphan_types.reject { |ot| ot.is_a?(Class) && ot < GraphQL::Schema::Object }
1026
+ if !non_object_types.empty?
1027
+ raise ArgumentError, <<~ERR
1028
+ Only object type classes should be added as `orphan_types(...)`.
1029
+
1030
+ - Remove these no-op types from `orphan_types`: #{non_object_types.map { |t| "#{t.inspect} (#{t.kind.name})"}.join(", ")}
1031
+ - See https://graphql-ruby.org/type_definitions/interfaces.html#orphan-types
1032
+
1033
+ To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
1034
+ ERR
1035
+ end
1036
+ add_type_and_traverse(new_orphan_types, root: false) unless use_visibility_profile?
776
1037
  own_orphan_types.concat(new_orphan_types.flatten)
1038
+ self.visibility&.orphan_types_configured(new_orphan_types)
777
1039
  end
778
1040
 
779
- find_inherited_value(:orphan_types, EMPTY_ARRAY) + own_orphan_types
1041
+ inherited_ot = find_inherited_value(:orphan_types, nil)
1042
+ if inherited_ot
1043
+ if !own_orphan_types.empty?
1044
+ inherited_ot + own_orphan_types
1045
+ else
1046
+ inherited_ot
1047
+ end
1048
+ else
1049
+ own_orphan_types
1050
+ end
780
1051
  end
781
1052
 
782
1053
  def default_execution_strategy
@@ -795,6 +1066,41 @@ module GraphQL
795
1066
  end
796
1067
  end
797
1068
 
1069
+
1070
+ # @param new_default_logger [#log] Something to use for logging messages
1071
+ def default_logger(new_default_logger = NOT_CONFIGURED)
1072
+ if NOT_CONFIGURED.equal?(new_default_logger)
1073
+ if defined?(@default_logger)
1074
+ @default_logger
1075
+ elsif superclass.respond_to?(:default_logger)
1076
+ superclass.default_logger
1077
+ elsif defined?(Rails) && Rails.respond_to?(:logger) && (rails_logger = Rails.logger)
1078
+ rails_logger
1079
+ else
1080
+ def_logger = Logger.new($stdout)
1081
+ def_logger.info! # It doesn't output debug info by default
1082
+ def_logger
1083
+ end
1084
+ elsif new_default_logger == nil
1085
+ @default_logger = Logger.new(IO::NULL)
1086
+ else
1087
+ @default_logger = new_default_logger
1088
+ end
1089
+ end
1090
+
1091
+ # @param context [GraphQL::Query::Context, nil]
1092
+ # @return [Logger] A logger to use for this context configuration, falling back to {.default_logger}
1093
+ def logger_for(context)
1094
+ if context && context[:logger] == false
1095
+ Logger.new(IO::NULL)
1096
+ elsif context && (l = context[:logger])
1097
+ l
1098
+ else
1099
+ default_logger
1100
+ end
1101
+ end
1102
+
1103
+ # @param new_context_class [Class<GraphQL::Query::Context>] A subclass to use when executing queries
798
1104
  def context_class(new_context_class = nil)
799
1105
  if new_context_class
800
1106
  @context_class = new_context_class
@@ -803,28 +1109,46 @@ module GraphQL
803
1109
  end
804
1110
  end
805
1111
 
1112
+ # Register a handler for errors raised during execution. The handlers can return a new value or raise a new error.
1113
+ #
1114
+ # @example Handling "not found" with a client-facing error
1115
+ # rescue_from(ActiveRecord::NotFound) { raise GraphQL::ExecutionError, "An object could not be found" }
1116
+ #
1117
+ # @param err_classes [Array<StandardError>] Classes which should be rescued by `handler_block`
1118
+ # @param handler_block The code to run when one of those errors is raised during execution
1119
+ # @yieldparam error [StandardError] An instance of one of the configured `err_classes`
1120
+ # @yieldparam object [Object] The current application object in the query when the error was raised
1121
+ # @yieldparam arguments [GraphQL::Query::Arguments] The current field arguments when the error was raised
1122
+ # @yieldparam context [GraphQL::Query::Context] The context for the currently-running operation
1123
+ # @yieldreturn [Object] Some object to use in the place where this error was raised
1124
+ # @raise [GraphQL::ExecutionError] In the handler, raise to add a client-facing error to the response
1125
+ # @raise [StandardError] In the handler, raise to crash the query with a developer-facing error
806
1126
  def rescue_from(*err_classes, &handler_block)
807
1127
  err_classes.each do |err_class|
808
1128
  Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block)
809
1129
  end
810
1130
  end
811
1131
 
812
- NEW_HANDLER_HASH = ->(h, k) {
813
- h[k] = {
814
- class: k,
815
- handler: nil,
816
- subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
817
- }
818
- }
819
-
820
1132
  def error_handlers
821
- @error_handlers ||= {
822
- class: nil,
823
- handler: nil,
824
- subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
825
- }
1133
+ @error_handlers ||= begin
1134
+ new_handler_hash = ->(h, k) {
1135
+ h[k] = {
1136
+ class: k,
1137
+ handler: nil,
1138
+ subclass_handlers: Hash.new(&new_handler_hash),
1139
+ }
1140
+ }
1141
+ {
1142
+ class: nil,
1143
+ handler: nil,
1144
+ subclass_handlers: Hash.new(&new_handler_hash),
1145
+ }
1146
+ end
826
1147
  end
827
1148
 
1149
+ # @api private
1150
+ attr_accessor :using_backtrace
1151
+
828
1152
  # @api private
829
1153
  def handle_or_reraise(context, err)
830
1154
  handler = Execution::Errors.find_handler_for(self, err.class)
@@ -838,6 +1162,10 @@ module GraphQL
838
1162
  end
839
1163
  handler[:handler].call(err, obj, args, context, field)
840
1164
  else
1165
+ if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError)
1166
+ err = GraphQL::Backtrace::TracedError.new(err, context)
1167
+ end
1168
+
841
1169
  raise err
842
1170
  end
843
1171
  end
@@ -869,29 +1197,78 @@ module GraphQL
869
1197
  end
870
1198
  end
871
1199
 
872
- def resolve_type(type, obj, ctx)
873
- if type.kind.object?
874
- type
875
- else
876
- 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})"
877
- end
1200
+ # GraphQL-Ruby calls this method during execution when it needs the application to determine the type to use for an object.
1201
+ #
1202
+ # Usually, this object was returned from a field whose return type is an {GraphQL::Schema::Interface} or a {GraphQL::Schema::Union}.
1203
+ # 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.
1204
+ #
1205
+ # @example Returning a GraphQL type based on the object's class name
1206
+ # class MySchema < GraphQL::Schema
1207
+ # def resolve_type(_abs_type, object, _context)
1208
+ # graphql_type_name = "Types::#{object.class.name}Type"
1209
+ # graphql_type_name.constantize # If this raises a NameError, then come implement special cases in this method
1210
+ # end
1211
+ # end
1212
+ # @param abstract_type [Class, Module, nil] The Interface or Union type which is being resolved, if there is one
1213
+ # @param application_object [Object] The object returned from a field whose type must be determined
1214
+ # @param context [GraphQL::Query::Context] The query context for the currently-executing query
1215
+ # @return [Class<GraphQL::Schema::Object] The Object type definition to use for `obj`
1216
+ def resolve_type(abstract_type, application_object, context)
1217
+ 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})"
878
1218
  end
879
1219
  # rubocop:enable Lint/DuplicateMethods
880
1220
 
881
1221
  def inherited(child_class)
882
1222
  if self == GraphQL::Schema
883
1223
  child_class.directives(default_directives.values)
1224
+ child_class.extend(SubclassGetReferencesTo)
1225
+ end
1226
+ # Make sure the child class has these built out, so that
1227
+ # subclasses can be modified by later calls to `trace_with`
1228
+ own_trace_modes.each do |name, _class|
1229
+ child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
884
1230
  end
885
1231
  child_class.singleton_class.prepend(ResolveTypeWithType)
886
- super
887
- end
888
1232
 
889
- def object_from_id(node_id, ctx)
890
- 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}`)"
1233
+ if use_visibility_profile?
1234
+ vis = self.visibility
1235
+ child_class.visibility = vis.dup_for(child_class)
1236
+ end
1237
+ child_class.null_context = Query::NullContext.new(schema: child_class)
1238
+ super
891
1239
  end
892
1240
 
893
- def id_from_object(object, type, ctx)
894
- 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}`)"
1241
+ # Fetch an object based on an incoming ID and the current context. This method should return an object
1242
+ # from your application, or return `nil` if there is no object or the object shouldn't be available to this operation.
1243
+ #
1244
+ # @example Fetching an object with Rails's GlobalID
1245
+ # def self.object_from_id(object_id, _context)
1246
+ # GlobalID.find(global_id)
1247
+ # # TODO: use `context[:current_user]` to determine if this object is authorized.
1248
+ # end
1249
+ # @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`)
1250
+ # @param context [GraphQL::Query::Context] The context for the currently-executing operation
1251
+ # @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
1252
+ # @see id_from_object which produces these IDs
1253
+ def object_from_id(object_id, context)
1254
+ 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}`)"
1255
+ end
1256
+
1257
+ # Return a stable ID string for `object` so that it can be refetched later, using {.object_from_id}.
1258
+ #
1259
+ # [GlobalID](https://github.com/rails/globalid) and [SQIDs](https://sqids.org/ruby) can both be used to create IDs.
1260
+ #
1261
+ # @example Using Rails's GlobalID to generate IDs
1262
+ # def self.id_from_object(application_object, graphql_type, context)
1263
+ # application_object.to_gid_param
1264
+ # end
1265
+ #
1266
+ # @param application_object [Object] Some object encountered by GraphQL-Ruby while running a query
1267
+ # @param graphql_type [Class, Module] The type that GraphQL-Ruby is using for `application_object` during this query
1268
+ # @param context [GraphQL::Query::Context] The context for the operation that is currently running
1269
+ # @return [String] A stable identifier which can be passed to {.object_from_id} later to re-fetch `application_object`
1270
+ def id_from_object(application_object, graphql_type, context)
1271
+ 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}`)"
895
1272
  end
896
1273
 
897
1274
  def visible?(member, ctx)
@@ -907,6 +1284,10 @@ module GraphQL
907
1284
  Member::HasDirectives.get_directives(self, @own_schema_directives, :schema_directives)
908
1285
  end
909
1286
 
1287
+ # Called when a type is needed by name at runtime
1288
+ def load_type(type_name, ctx)
1289
+ get_type(type_name, ctx)
1290
+ end
910
1291
  # This hook is called when an object fails an `authorized?` check.
911
1292
  # You might report to your bug tracker here, so you can correct
912
1293
  # the field resolvers not to return unauthorized objects.
@@ -942,10 +1323,24 @@ module GraphQL
942
1323
  unauthorized_object(unauthorized_error)
943
1324
  end
944
1325
 
945
- def type_error(type_error, ctx)
1326
+ # Called at runtime when GraphQL-Ruby encounters a mismatch between the application behavior
1327
+ # and the GraphQL type system.
1328
+ #
1329
+ # The default implementation of this method is to follow the GraphQL specification,
1330
+ # but you can override this to report errors to your bug tracker or customize error handling.
1331
+ # @param type_error [GraphQL::Error] several specific error classes are passed here, see the default implementation for details
1332
+ # @param context [GraphQL::Query::Context] the context for the currently-running operation
1333
+ # @return [void]
1334
+ # @raise [GraphQL::ExecutionError] to return this error to the client
1335
+ # @raise [GraphQL::Error] to crash the query and raise a developer-facing error
1336
+ def type_error(type_error, context)
946
1337
  case type_error
947
1338
  when GraphQL::InvalidNullError
948
- ctx.errors << type_error
1339
+ execution_error = GraphQL::ExecutionError.new(type_error.message, ast_nodes: type_error.ast_nodes)
1340
+ execution_error.path = type_error.path || context[:current_path]
1341
+
1342
+ context.errors << execution_error
1343
+ execution_error
949
1344
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
950
1345
  raise type_error
951
1346
  when GraphQL::IntegerDecodingError
@@ -953,7 +1348,7 @@ module GraphQL
953
1348
  end
954
1349
  end
955
1350
 
956
- # A function to call when {#execute} receives an invalid query string
1351
+ # A function to call when {.execute} receives an invalid query string
957
1352
  #
958
1353
  # The default is to add the error to `context.errors`
959
1354
  # @param parse_err [GraphQL::ParseError] The error encountered during parsing
@@ -967,25 +1362,58 @@ module GraphQL
967
1362
  lazy_methods.set(lazy_class, value_method)
968
1363
  end
969
1364
 
1365
+ def uses_raw_value?
1366
+ !!@uses_raw_value
1367
+ end
1368
+
1369
+ def uses_raw_value(new_val)
1370
+ @uses_raw_value = new_val
1371
+ end
1372
+
1373
+ def resolves_lazies?
1374
+ lazy_method_count = 0
1375
+ lazy_methods.each do |k, v|
1376
+ if !v.nil?
1377
+ lazy_method_count += 1
1378
+ end
1379
+ end
1380
+ lazy_method_count > 2
1381
+ end
1382
+
970
1383
  def instrument(instrument_step, instrumenter, options = {})
1384
+ warn <<~WARN
1385
+ Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html"
1386
+ (From `#{self}.instrument(#{instrument_step}, #{instrumenter})` at #{caller(1, 1).first})
1387
+
1388
+ WARN
1389
+ trace_with(Tracing::LegacyHooksTrace)
971
1390
  own_instrumenters[instrument_step] << instrumenter
972
1391
  end
973
1392
 
974
1393
  # Add several directives at once
975
1394
  # @param new_directives [Class]
976
1395
  def directives(*new_directives)
977
- if new_directives.any?
1396
+ if !new_directives.empty?
978
1397
  new_directives.flatten.each { |d| directive(d) }
979
1398
  end
980
1399
 
981
- find_inherited_value(:directives, default_directives).merge(own_directives)
1400
+ inherited_dirs = find_inherited_value(:directives, default_directives)
1401
+ if !own_directives.empty?
1402
+ inherited_dirs.merge(own_directives)
1403
+ else
1404
+ inherited_dirs
1405
+ end
982
1406
  end
983
1407
 
984
1408
  # Attach a single directive to this schema
985
1409
  # @param new_directive [Class]
986
1410
  # @return void
987
1411
  def directive(new_directive)
988
- add_type_and_traverse(new_directive, root: false)
1412
+ if use_visibility_profile?
1413
+ own_directives[new_directive.graphql_name] = new_directive
1414
+ else
1415
+ add_type_and_traverse(new_directive, root: false)
1416
+ end
989
1417
  end
990
1418
 
991
1419
  def default_directives
@@ -994,11 +1422,27 @@ module GraphQL
994
1422
  "skip" => GraphQL::Schema::Directive::Skip,
995
1423
  "deprecated" => GraphQL::Schema::Directive::Deprecated,
996
1424
  "oneOf" => GraphQL::Schema::Directive::OneOf,
1425
+ "specifiedBy" => GraphQL::Schema::Directive::SpecifiedBy,
997
1426
  }.freeze
998
1427
  end
999
1428
 
1000
- def tracer(new_tracer)
1001
- if !(trace_class_for(:default) < GraphQL::Tracing::CallLegacyTracers)
1429
+ # @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema
1430
+ attr_accessor :detailed_trace
1431
+
1432
+ # @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex})
1433
+ # @return [Boolean] When `true`, save a detailed trace for this query.
1434
+ # @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true
1435
+ def detailed_trace?(query)
1436
+ raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition."
1437
+ end
1438
+
1439
+ def tracer(new_tracer, silence_deprecation_warning: false)
1440
+ if !silence_deprecation_warning
1441
+ warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
1442
+ warn " #{caller(1, 1).first}"
1443
+ end
1444
+ default_trace = trace_class_for(:default, build: true)
1445
+ if default_trace.nil? || !(default_trace < GraphQL::Tracing::CallLegacyTracers)
1002
1446
  trace_with(GraphQL::Tracing::CallLegacyTracers)
1003
1447
  end
1004
1448
 
@@ -1009,24 +1453,46 @@ module GraphQL
1009
1453
  find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
1010
1454
  end
1011
1455
 
1012
- # Mix `trace_mod` into this schema's `Trace` class so that its methods
1013
- # will be called at runtime.
1456
+ # Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime.
1457
+ #
1458
+ # You can attach a module to run in only _some_ circumstances by using `mode:`. When a module is added with `mode:`,
1459
+ # it will only run for queries with a matching `context[:trace_mode]`.
1460
+ #
1461
+ # Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration).
1462
+ #
1463
+ # @example Adding a trace in a special mode
1464
+ # # only runs when `query.context[:trace_mode]` is `:special`
1465
+ # trace_with SpecialTrace, mode: :special
1014
1466
  #
1015
1467
  # @param trace_mod [Module] A module that implements tracing methods
1016
1468
  # @param mode [Symbol] Trace module will only be used for this trade mode
1017
1469
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1018
1470
  # @return [void]
1471
+ # @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
1019
1472
  def trace_with(trace_mod, mode: :default, **options)
1020
1473
  if mode.is_a?(Array)
1021
1474
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
1022
1475
  else
1023
- tc = trace_class_for(mode)
1476
+ tc = own_trace_modes[mode] ||= build_trace_mode(mode)
1024
1477
  tc.include(trace_mod)
1025
- if mode != :default
1026
- own_trace_modules[mode] << trace_mod
1478
+ own_trace_modules[mode] << trace_mod
1479
+ add_trace_options_for(mode, options)
1480
+ if mode == :default
1481
+ # This module is being added as a default tracer. If any other mode classes
1482
+ # have already been created, but get their default behavior from a superclass,
1483
+ # Then mix this into this schema's subclass.
1484
+ # (But don't mix it into mode classes that aren't default-based.)
1485
+ own_trace_modes.each do |other_mode_name, other_mode_class|
1486
+ if other_mode_class < DefaultTraceClass
1487
+ # Don't add it back to the inheritance tree if it's already there
1488
+ if !(other_mode_class < trace_mod)
1489
+ other_mode_class.include(trace_mod)
1490
+ end
1491
+ # Add any options so they'll be available
1492
+ add_trace_options_for(other_mode_name, options)
1493
+ end
1494
+ end
1027
1495
  end
1028
- t_opts = trace_options_for(mode)
1029
- t_opts.merge!(options)
1030
1496
  end
1031
1497
  nil
1032
1498
  end
@@ -1036,37 +1502,59 @@ module GraphQL
1036
1502
  def trace_options_for(mode)
1037
1503
  @trace_options_for_mode ||= {}
1038
1504
  @trace_options_for_mode[mode] ||= begin
1505
+ # It may be time to create an options hash for a mode that wasn't registered yet.
1506
+ # Mix in the default options in that case.
1507
+ default_options = mode == :default ? EMPTY_HASH : trace_options_for(:default)
1508
+ # Make sure this returns a new object so that other hashes aren't modified later
1039
1509
  if superclass.respond_to?(:trace_options_for)
1040
- superclass.trace_options_for(mode).dup
1510
+ superclass.trace_options_for(mode).merge(default_options)
1041
1511
  else
1042
- {}
1512
+ default_options.dup
1043
1513
  end
1044
1514
  end
1045
1515
  end
1046
1516
 
1047
1517
  # Create a trace instance which will include the trace modules specified for the optional mode.
1048
1518
  #
1519
+ # If no `mode:` is given, then {default_trace_mode} will be used.
1520
+ #
1521
+ # If this schema is using {Tracing::DetailedTrace} and {.detailed_trace?} returns `true`, then
1522
+ # DetailedTrace's mode will override the passed-in `mode`.
1523
+ #
1049
1524
  # @param mode [Symbol] Trace modules for this trade mode will be included
1050
1525
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1051
1526
  # @return [Tracing::Trace]
1052
1527
  def new_trace(mode: nil, **options)
1053
- target = options[:query] || options[:multiplex]
1054
- mode ||= target && target.context[:trace_mode]
1528
+ should_sample = if detailed_trace
1529
+ if (query = options[:query])
1530
+ detailed_trace?(query)
1531
+ elsif (multiplex = options[:multiplex])
1532
+ if multiplex.queries.length == 1
1533
+ detailed_trace?(multiplex.queries.first)
1534
+ else
1535
+ detailed_trace?(multiplex)
1536
+ end
1537
+ end
1538
+ else
1539
+ false
1540
+ end
1055
1541
 
1056
- trace_mode = if mode
1057
- mode
1058
- elsif target && target.context[:backtrace]
1059
- :default_backtrace
1542
+ if should_sample
1543
+ mode = detailed_trace.trace_mode
1060
1544
  else
1061
- :default
1545
+ target = options[:query] || options[:multiplex]
1546
+ mode ||= target && target.context[:trace_mode]
1062
1547
  end
1063
1548
 
1549
+ trace_mode = mode || default_trace_mode
1064
1550
  base_trace_options = trace_options_for(trace_mode)
1065
1551
  trace_options = base_trace_options.merge(options)
1066
- trace_class_for_mode = trace_class_for(trace_mode)
1552
+ trace_class_for_mode = trace_class_for(trace_mode, build: true)
1067
1553
  trace_class_for_mode.new(**trace_options)
1068
1554
  end
1069
1555
 
1556
+ # @param new_analyzer [Class<GraphQL::Analysis::Analyzer>] An analyzer to run on queries to this schema
1557
+ # @see GraphQL::Analysis the analysis system
1070
1558
  def query_analyzer(new_analyzer)
1071
1559
  own_query_analyzers << new_analyzer
1072
1560
  end
@@ -1075,6 +1563,8 @@ module GraphQL
1075
1563
  find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers
1076
1564
  end
1077
1565
 
1566
+ # @param new_analyzer [Class<GraphQL::Analysis::Analyzer>] An analyzer to run on multiplexes to this schema
1567
+ # @see GraphQL::Analysis the analysis system
1078
1568
  def multiplex_analyzer(new_analyzer)
1079
1569
  own_multiplex_analyzers << new_analyzer
1080
1570
  end
@@ -1093,7 +1583,7 @@ module GraphQL
1093
1583
 
1094
1584
  # Execute a query on itself.
1095
1585
  # @see {Query#initialize} for arguments.
1096
- # @return [Hash] query result, ready to be serialized as JSON
1586
+ # @return [GraphQL::Query::Result] query result, ready to be serialized as JSON
1097
1587
  def execute(query_str = nil, **kwargs)
1098
1588
  if query_str
1099
1589
  kwargs[:query] = query_str
@@ -1132,8 +1622,9 @@ module GraphQL
1132
1622
  # @see {Query#initialize} for query keyword arguments
1133
1623
  # @see {Execution::Multiplex#run_all} for multiplex keyword arguments
1134
1624
  # @param queries [Array<Hash>] Keyword arguments for each query
1135
- # @param context [Hash] Multiplex-level context
1136
- # @return [Array<Hash>] One result for each query in the input
1625
+ # @option kwargs [Hash] :context ({}) Multiplex-level context
1626
+ # @option kwargs [nil, Integer] :max_complexity (nil)
1627
+ # @return [Array<GraphQL::Query::Result>] One result for each query in the input
1137
1628
  def multiplex(queries, **kwargs)
1138
1629
  GraphQL::Execution::Interpreter.run_all(self, queries, **kwargs)
1139
1630
  end
@@ -1147,7 +1638,8 @@ module GraphQL
1147
1638
 
1148
1639
  # @api private
1149
1640
  def add_subscription_extension_if_necessary
1150
- if !defined?(@subscription_extension_added) && subscription && self.subscriptions
1641
+ # TODO: when there's a proper API for extending root types, migrat this to use it.
1642
+ if !defined?(@subscription_extension_added) && @subscription_object.is_a?(Class) && self.subscriptions
1151
1643
  @subscription_extension_added = true
1152
1644
  subscription.all_field_definitions.each do |field|
1153
1645
  if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) }
@@ -1157,6 +1649,11 @@ module GraphQL
1157
1649
  end
1158
1650
  end
1159
1651
 
1652
+ # Called when execution encounters a `SystemStackError`. By default, it adds a client-facing error to the response.
1653
+ # You could modify this method to report this error to your bug tracker.
1654
+ # @param query [GraphQL::Query]
1655
+ # @param err [SystemStackError]
1656
+ # @return [void]
1160
1657
  def query_stack_error(query, err)
1161
1658
  query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute."))
1162
1659
  end
@@ -1191,7 +1688,7 @@ module GraphQL
1191
1688
  end
1192
1689
  end
1193
1690
 
1194
- # @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {#lazy_resolve}.
1691
+ # @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {.lazy_resolve}.
1195
1692
  def lazy_method_name(obj)
1196
1693
  lazy_methods.get(obj)
1197
1694
  end
@@ -1215,8 +1712,218 @@ module GraphQL
1215
1712
  end
1216
1713
  end
1217
1714
 
1715
+ # Returns `DidYouMean` if it's defined.
1716
+ # Override this to return `nil` if you don't want to use `DidYouMean`
1717
+ def did_you_mean(new_dym = NOT_CONFIGURED)
1718
+ if NOT_CONFIGURED.equal?(new_dym)
1719
+ if defined?(@did_you_mean)
1720
+ @did_you_mean
1721
+ else
1722
+ find_inherited_value(:did_you_mean, defined?(DidYouMean) ? DidYouMean : nil)
1723
+ end
1724
+ else
1725
+ @did_you_mean = new_dym
1726
+ end
1727
+ end
1728
+
1729
+
1730
+ # This setting controls how GraphQL-Ruby handles empty selections on Union types.
1731
+ #
1732
+ # To opt into future, spec-compliant behavior where these selections are rejected, set this to `false`.
1733
+ #
1734
+ # If you need to support previous, non-spec behavior which allowed selecting union fields
1735
+ # but *not* selecting any fields on that union, set this to `true` to continue allowing that behavior.
1736
+ #
1737
+ # If this is `true`, then {.legacy_invalid_empty_selections_on_union_with_type} will be called with {Query} objects
1738
+ # with that kind of selections. You must implement that method
1739
+ # @param new_value [Boolean]
1740
+ # @return [true, false, nil]
1741
+ def allow_legacy_invalid_empty_selections_on_union(new_value = NOT_CONFIGURED)
1742
+ if NOT_CONFIGURED.equal?(new_value)
1743
+ if defined?(@allow_legacy_invalid_empty_selections_on_union)
1744
+ @allow_legacy_invalid_empty_selections_on_union
1745
+ else
1746
+ find_inherited_value(:allow_legacy_invalid_empty_selections_on_union)
1747
+ end
1748
+ else
1749
+ @allow_legacy_invalid_empty_selections_on_union = new_value
1750
+ end
1751
+ end
1752
+
1753
+ # This method is called during validation when a previously-allowed, but non-spec
1754
+ # query is encountered where a union field has no child selections on it.
1755
+ #
1756
+ # If `legacy_invalid_empty_selections_on_union_with_type` is overridden, this method will not be called.
1757
+ #
1758
+ # You should implement this method or `legacy_invalid_empty_selections_on_union_with_type`
1759
+ # to log the violation so that you can contact clients and notify them about changing their queries.
1760
+ # Then return a suitable value to tell GraphQL-Ruby how to continue.
1761
+ # @param query [GraphQL::Query]
1762
+ # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
1763
+ # @return [String] A validation error to return for this query
1764
+ # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
1765
+ def legacy_invalid_empty_selections_on_union(query)
1766
+ raise "Implement `def self.legacy_invalid_empty_selections_on_union_with_type(query, type)` or `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario"
1767
+ end
1768
+
1769
+ # This method is called during validation when a previously-allowed, but non-spec
1770
+ # query is encountered where a union field has no child selections on it.
1771
+ #
1772
+ # You should implement this method to log the violation so that you can contact clients
1773
+ # and notify them about changing their queries. Then return a suitable value to
1774
+ # tell GraphQL-Ruby how to continue.
1775
+ # @param query [GraphQL::Query]
1776
+ # @param type [Module] A GraphQL type definition
1777
+ # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
1778
+ # @return [String] A validation error to return for this query
1779
+ # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
1780
+ def legacy_invalid_empty_selections_on_union_with_type(query, type)
1781
+ legacy_invalid_empty_selections_on_union(query)
1782
+ end
1783
+
1784
+ # This setting controls how GraphQL-Ruby handles overlapping selections on scalar types when the types
1785
+ # don't match.
1786
+ #
1787
+ # When set to `false`, GraphQL-Ruby will reject those queries with a validation error (as per the GraphQL spec).
1788
+ #
1789
+ # When set to `true`, GraphQL-Ruby will call {.legacy_invalid_return_type_conflicts} when the scenario is encountered.
1790
+ #
1791
+ # @param new_value [Boolean] `true` permits the legacy behavior, `false` rejects it.
1792
+ # @return [true, false, nil]
1793
+ def allow_legacy_invalid_return_type_conflicts(new_value = NOT_CONFIGURED)
1794
+ if NOT_CONFIGURED.equal?(new_value)
1795
+ if defined?(@allow_legacy_invalid_return_type_conflicts)
1796
+ @allow_legacy_invalid_return_type_conflicts
1797
+ else
1798
+ find_inherited_value(:allow_legacy_invalid_return_type_conflicts)
1799
+ end
1800
+ else
1801
+ @allow_legacy_invalid_return_type_conflicts = new_value
1802
+ end
1803
+ end
1804
+
1805
+ # This method is called when the query contains fields which don't contain matching scalar types.
1806
+ # This was previously allowed by GraphQL-Ruby but it's a violation of the GraphQL spec.
1807
+ #
1808
+ # You should implement this method to log the violation so that you observe usage of these fields.
1809
+ # Fixing this scenario might mean adding new fields, and telling clients to use those fields.
1810
+ # (Changing the field return type would be a breaking change, but if it works for your client use cases,
1811
+ # that might work, too.)
1812
+ #
1813
+ # @param query [GraphQL::Query]
1814
+ # @param type1 [Module] A GraphQL type definition
1815
+ # @param type2 [Module] A GraphQL type definition
1816
+ # @param node1 [GraphQL::Language::Nodes::Field] This node is recognized as conflicting. You might call `.line` and `.col` for custom error reporting.
1817
+ # @param node2 [GraphQL::Language::Nodes::Field] The other node recognized as conflicting.
1818
+ # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
1819
+ # @return [String] A validation error to return for this query
1820
+ # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
1821
+ def legacy_invalid_return_type_conflicts(query, type1, type2, node1, node2)
1822
+ raise "Implement #{self}.legacy_invalid_return_type_conflicts to handle this invalid selection"
1823
+ end
1824
+
1825
+ # The legacy complexity implementation included several bugs:
1826
+ #
1827
+ # - In some cases, it used the lexically _last_ field to determine a cost, instead of calculating the maximum among selections
1828
+ # - In some cases, it called field complexity hooks repeatedly (when it should have only called them once)
1829
+ #
1830
+ # The future implementation may produce higher total complexity scores, so it's not active by default yet. You can opt into
1831
+ # the future default behavior by configuring `:future` here. Or, you can choose a mode for each query with {.complexity_cost_calculation_mode_for}.
1832
+ #
1833
+ # The legacy mode is currently maintained alongside the future one, but it will be removed in a future GraphQL-Ruby version.
1834
+ #
1835
+ # If you choose `:compare`, you must also implement {.legacy_complexity_cost_calculation_mismatch} to handle the input somehow.
1836
+ #
1837
+ # @example Opting into the future calculation mode
1838
+ # complexity_cost_calculation_mode(:future)
1839
+ #
1840
+ # @example Choosing the legacy mode (which will work until that mode is removed...)
1841
+ # complexity_cost_calculation_mode(:legacy)
1842
+ #
1843
+ # @example Run both modes for every query, call {.legacy_complexity_cost_calculation_mismatch} when they don't match:
1844
+ # complexity_cost_calculation_mode(:compare)
1845
+ def complexity_cost_calculation_mode(new_mode = NOT_CONFIGURED)
1846
+ if NOT_CONFIGURED.equal?(new_mode)
1847
+ if defined?(@complexity_cost_calculation_mode)
1848
+ @complexity_cost_calculation_mode
1849
+ else
1850
+ find_inherited_value(:complexity_cost_calculation_mode)
1851
+ end
1852
+ else
1853
+ @complexity_cost_calculation_mode = new_mode
1854
+ end
1855
+ end
1856
+
1857
+ # Implement this method to produce a per-query complexity cost calculation mode. (Technically, it's per-multiplex.)
1858
+ #
1859
+ # This is a way to check the compatibility of queries coming to your API without adding overhead of running `:compare`
1860
+ # for every query. You could sample traffic, turn it off/on with feature flags, or anything else.
1861
+ #
1862
+ # @example Sampling traffic
1863
+ # def self.complexity_cost_calculation_mode_for(_context)
1864
+ # if rand < 0.1 # 10% of the time
1865
+ # :compare
1866
+ # else
1867
+ # :legacy
1868
+ # end
1869
+ # end
1870
+ #
1871
+ # @example Using a feature flag to manage future mode
1872
+ # def complexity_cost_calculation_mode_for(context)
1873
+ # current_user = context[:current_user]
1874
+ # if Flipper.enabled?(:future_complexity_cost, current_user)
1875
+ # :future
1876
+ # elsif rand < 0.5 # 50%
1877
+ # :compare
1878
+ # else
1879
+ # :legacy
1880
+ # end
1881
+ # end
1882
+ #
1883
+ # @param multiplex_context [Hash] The context for the currently-running {Execution::Multiplex} (which contains one or more queries)
1884
+ # @return [:future] Use the new calculation algorithm -- may be higher than `:legacy`
1885
+ # @return [:legacy] Use the legacy calculation algorithm, warts and all
1886
+ # @return [:compare] Run both algorithms and call {.legacy_complexity_cost_calculation_mismatch} if they don't match
1887
+ def complexity_cost_calculation_mode_for(multiplex_context)
1888
+ complexity_cost_calculation_mode
1889
+ end
1890
+
1891
+ # Implement this method in your schema to handle mismatches when `:compare` is used.
1892
+ #
1893
+ # @example Logging the mismatch
1894
+ # def self.legacy_cost_calculation_mismatch(multiplex, future_cost, legacy_cost)
1895
+ # client_id = multiplex.context[:api_client].id
1896
+ # operation_names = multiplex.queries.map { |q| q.selected_operation_name || "anonymous" }.join(", ")
1897
+ # Stats.increment(:complexity_mismatch, tags: { client: client_id, ops: operation_names })
1898
+ # legacy_cost
1899
+ # end
1900
+ # @see Query::Context#add_error Adding an error to the response to notify the client
1901
+ # @see Query::Context#response_extensions Adding key-value pairs to the response `"extensions" => { ... }`
1902
+ # @param multiplex [GraphQL::Execution::Multiplex]
1903
+ # @param future_complexity_cost [Integer]
1904
+ # @param legacy_complexity_cost [Integer]
1905
+ # @return [Integer] the cost to use for this query (probably one of `future_complexity_cost` or `legacy_complexity_cost`)
1906
+ def legacy_complexity_cost_calculation_mismatch(multiplex, future_complexity_cost, legacy_complexity_cost)
1907
+ 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"
1908
+ end
1909
+
1218
1910
  private
1219
1911
 
1912
+ def add_trace_options_for(mode, new_options)
1913
+ if mode == :default
1914
+ own_trace_modes.each do |mode_name, t_class|
1915
+ if t_class <= DefaultTraceClass
1916
+ t_opts = trace_options_for(mode_name)
1917
+ t_opts.merge!(new_options)
1918
+ end
1919
+ end
1920
+ else
1921
+ t_opts = trace_options_for(mode)
1922
+ t_opts.merge!(new_options)
1923
+ end
1924
+ nil
1925
+ end
1926
+
1220
1927
  # @param t [Module, Array<Module>]
1221
1928
  # @return [void]
1222
1929
  def add_type_and_traverse(t, root:)
@@ -1260,7 +1967,8 @@ module GraphQL
1260
1967
  own_union_memberships.merge!(addition.union_memberships)
1261
1968
 
1262
1969
  addition.references.each { |thing, pointers|
1263
- pointers.each { |pointer| references_to(thing, from: pointer) }
1970
+ prev_refs = own_references_to[thing] || []
1971
+ own_references_to[thing] = prev_refs | pointers.to_a
1264
1972
  }
1265
1973
 
1266
1974
  addition.directives.each { |dir_class| own_directives[dir_class.graphql_name] = dir_class }
@@ -1278,7 +1986,7 @@ module GraphQL
1278
1986
  else
1279
1987
  @lazy_methods = GraphQL::Execution::Lazy::LazyMethodMap.new
1280
1988
  @lazy_methods.set(GraphQL::Execution::Lazy, :value)
1281
- @lazy_methods.set(GraphQL::Dataloader::Request, :load)
1989
+ @lazy_methods.set(GraphQL::Dataloader::Request, :load_with_deprecation_warning)
1282
1990
  end
1283
1991
  end
1284
1992
  @lazy_methods
@@ -1288,6 +1996,10 @@ module GraphQL
1288
1996
  @own_types ||= {}
1289
1997
  end
1290
1998
 
1999
+ def own_references_to
2000
+ @own_references_to ||= {}.compare_by_identity
2001
+ end
2002
+
1291
2003
  def non_introspection_types
1292
2004
  find_inherited_value(:non_introspection_types, EMPTY_HASH).merge(own_types)
1293
2005
  end
@@ -1301,7 +2013,7 @@ module GraphQL
1301
2013
  end
1302
2014
 
1303
2015
  def own_possible_types
1304
- @own_possible_types ||= {}
2016
+ @own_possible_types ||= {}.compare_by_identity
1305
2017
  end
1306
2018
 
1307
2019
  def own_union_memberships
@@ -1327,9 +2039,37 @@ module GraphQL
1327
2039
  def own_multiplex_analyzers
1328
2040
  @own_multiplex_analyzers ||= []
1329
2041
  end
2042
+
2043
+ # This is overridden in subclasses to check the inheritance chain
2044
+ def get_references_to(type_defn)
2045
+ own_references_to[type_defn]
2046
+ end
2047
+ end
2048
+
2049
+ module SubclassGetReferencesTo
2050
+ def get_references_to(type_defn)
2051
+ own_refs = own_references_to[type_defn]
2052
+ inherited_refs = superclass.references_to(type_defn)
2053
+ if inherited_refs&.any?
2054
+ if own_refs&.any?
2055
+ own_refs + inherited_refs
2056
+ else
2057
+ inherited_refs
2058
+ end
2059
+ else
2060
+ own_refs
2061
+ end
2062
+ end
1330
2063
  end
1331
2064
 
1332
2065
  # Install these here so that subclasses will also install it.
1333
2066
  self.connections = GraphQL::Pagination::Connections.new(schema: self)
2067
+
2068
+ # @api private
2069
+ module DefaultTraceClass
2070
+ end
1334
2071
  end
1335
2072
  end
2073
+
2074
+ require "graphql/schema/loader"
2075
+ require "graphql/schema/printer"