graphql 2.0.31 → 2.5.18

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