graphql 2.2.17 → 2.5.16

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 (240) 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_generator.rb +46 -0
  4. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  5. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  6. data/lib/generators/graphql/templates/schema.erb +3 -0
  7. data/lib/generators/graphql/type_generator.rb +1 -1
  8. data/lib/graphql/analysis/analyzer.rb +90 -0
  9. data/lib/graphql/analysis/field_usage.rb +82 -0
  10. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  11. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  12. data/lib/graphql/analysis/query_complexity.rb +263 -0
  13. data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
  14. data/lib/graphql/analysis/visitor.rb +280 -0
  15. data/lib/graphql/analysis.rb +95 -1
  16. data/lib/graphql/autoload.rb +38 -0
  17. data/lib/graphql/backtrace/table.rb +118 -55
  18. data/lib/graphql/backtrace.rb +1 -19
  19. data/lib/graphql/current.rb +57 -0
  20. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  21. data/lib/graphql/dashboard/installable.rb +22 -0
  22. data/lib/graphql/dashboard/limiters.rb +93 -0
  23. data/lib/graphql/dashboard/operation_store.rb +199 -0
  24. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  25. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  26. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  27. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  28. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  29. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  30. data/lib/graphql/dashboard/statics/icon.png +0 -0
  31. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  37. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  38. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  39. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  40. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  41. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  42. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  43. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  44. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  45. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  46. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  47. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  48. data/lib/graphql/dashboard.rb +158 -0
  49. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  50. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  51. data/lib/graphql/dataloader/async_dataloader.rb +46 -19
  52. data/lib/graphql/dataloader/null_dataloader.rb +51 -10
  53. data/lib/graphql/dataloader/source.rb +20 -9
  54. data/lib/graphql/dataloader.rb +153 -45
  55. data/lib/graphql/date_encoding_error.rb +1 -1
  56. data/lib/graphql/dig.rb +2 -1
  57. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  58. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  59. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  60. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +63 -5
  61. data/lib/graphql/execution/interpreter/runtime.rb +321 -222
  62. data/lib/graphql/execution/interpreter.rb +23 -30
  63. data/lib/graphql/execution/lookahead.rb +18 -11
  64. data/lib/graphql/execution/multiplex.rb +6 -5
  65. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  66. data/lib/graphql/introspection/directive_type.rb +1 -1
  67. data/lib/graphql/introspection/entry_points.rb +2 -2
  68. data/lib/graphql/introspection/field_type.rb +1 -1
  69. data/lib/graphql/introspection/schema_type.rb +6 -11
  70. data/lib/graphql/introspection/type_type.rb +5 -5
  71. data/lib/graphql/invalid_name_error.rb +1 -1
  72. data/lib/graphql/invalid_null_error.rb +20 -17
  73. data/lib/graphql/language/cache.rb +13 -0
  74. data/lib/graphql/language/comment.rb +18 -0
  75. data/lib/graphql/language/document_from_schema_definition.rb +64 -35
  76. data/lib/graphql/language/lexer.rb +72 -42
  77. data/lib/graphql/language/nodes.rb +93 -52
  78. data/lib/graphql/language/parser.rb +168 -61
  79. data/lib/graphql/language/printer.rb +31 -15
  80. data/lib/graphql/language/sanitized_printer.rb +1 -1
  81. data/lib/graphql/language.rb +61 -1
  82. data/lib/graphql/pagination/connection.rb +1 -1
  83. data/lib/graphql/query/context/scoped_context.rb +1 -1
  84. data/lib/graphql/query/context.rb +46 -47
  85. data/lib/graphql/query/null_context.rb +3 -5
  86. data/lib/graphql/query/partial.rb +179 -0
  87. data/lib/graphql/query/validation_pipeline.rb +2 -2
  88. data/lib/graphql/query/variable_validation_error.rb +1 -1
  89. data/lib/graphql/query.rb +123 -69
  90. data/lib/graphql/railtie.rb +7 -0
  91. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  92. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  93. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  94. data/lib/graphql/rubocop.rb +2 -0
  95. data/lib/graphql/schema/addition.rb +26 -13
  96. data/lib/graphql/schema/always_visible.rb +7 -2
  97. data/lib/graphql/schema/argument.rb +57 -8
  98. data/lib/graphql/schema/build_from_definition.rb +116 -49
  99. data/lib/graphql/schema/directive/flagged.rb +4 -2
  100. data/lib/graphql/schema/directive.rb +54 -2
  101. data/lib/graphql/schema/enum.rb +107 -24
  102. data/lib/graphql/schema/enum_value.rb +10 -2
  103. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  104. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  105. data/lib/graphql/schema/field.rb +134 -45
  106. data/lib/graphql/schema/field_extension.rb +1 -1
  107. data/lib/graphql/schema/has_single_input_argument.rb +6 -2
  108. data/lib/graphql/schema/input_object.rb +122 -64
  109. data/lib/graphql/schema/interface.rb +23 -5
  110. data/lib/graphql/schema/introspection_system.rb +6 -17
  111. data/lib/graphql/schema/late_bound_type.rb +4 -0
  112. data/lib/graphql/schema/list.rb +3 -3
  113. data/lib/graphql/schema/loader.rb +3 -2
  114. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  115. data/lib/graphql/schema/member/has_arguments.rb +44 -58
  116. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  117. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  118. data/lib/graphql/schema/member/has_directives.rb +4 -4
  119. data/lib/graphql/schema/member/has_fields.rb +26 -6
  120. data/lib/graphql/schema/member/has_interfaces.rb +6 -6
  121. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  122. data/lib/graphql/schema/member/has_validators.rb +1 -1
  123. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  124. data/lib/graphql/schema/member/type_system_helpers.rb +17 -4
  125. data/lib/graphql/schema/member.rb +1 -0
  126. data/lib/graphql/schema/mutation.rb +7 -0
  127. data/lib/graphql/schema/object.rb +25 -8
  128. data/lib/graphql/schema/printer.rb +1 -0
  129. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  130. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  131. data/lib/graphql/schema/resolver.rb +29 -23
  132. data/lib/graphql/schema/scalar.rb +1 -6
  133. data/lib/graphql/schema/subscription.rb +52 -6
  134. data/lib/graphql/schema/timeout.rb +19 -2
  135. data/lib/graphql/schema/type_expression.rb +2 -2
  136. data/lib/graphql/schema/union.rb +1 -1
  137. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  138. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  139. data/lib/graphql/schema/validator.rb +3 -1
  140. data/lib/graphql/schema/visibility/migration.rb +188 -0
  141. data/lib/graphql/schema/visibility/profile.rb +445 -0
  142. data/lib/graphql/schema/visibility/visit.rb +190 -0
  143. data/lib/graphql/schema/visibility.rb +311 -0
  144. data/lib/graphql/schema/warden.rb +190 -20
  145. data/lib/graphql/schema.rb +695 -167
  146. data/lib/graphql/static_validation/all_rules.rb +2 -2
  147. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  148. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  149. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  150. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  151. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  152. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  153. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  154. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  155. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  156. data/lib/graphql/static_validation/rules/fields_will_merge.rb +88 -25
  157. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  158. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  159. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  160. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  161. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  162. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  163. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  164. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  165. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  166. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  167. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  168. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  169. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  170. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  171. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  172. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  173. data/lib/graphql/static_validation/validation_context.rb +18 -2
  174. data/lib/graphql/static_validation/validator.rb +6 -1
  175. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +5 -3
  176. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  177. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  178. data/lib/graphql/subscriptions/event.rb +13 -2
  179. data/lib/graphql/subscriptions/serialize.rb +1 -1
  180. data/lib/graphql/subscriptions.rb +7 -5
  181. data/lib/graphql/testing/helpers.rb +48 -16
  182. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  183. data/lib/graphql/testing.rb +1 -0
  184. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  185. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  186. data/lib/graphql/tracing/appoptics_trace.rb +5 -1
  187. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  188. data/lib/graphql/tracing/appsignal_trace.rb +32 -59
  189. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  190. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  191. data/lib/graphql/tracing/data_dog_trace.rb +46 -162
  192. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  193. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  194. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  195. data/lib/graphql/tracing/detailed_trace.rb +141 -0
  196. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  197. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  198. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  199. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  200. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  201. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  202. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  203. data/lib/graphql/tracing/null_trace.rb +9 -0
  204. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  205. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  206. data/lib/graphql/tracing/perfetto_trace.rb +818 -0
  207. data/lib/graphql/tracing/platform_tracing.rb +1 -1
  208. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  209. data/lib/graphql/tracing/prometheus_trace.rb +73 -73
  210. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  211. data/lib/graphql/tracing/scout_trace.rb +32 -58
  212. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  213. data/lib/graphql/tracing/sentry_trace.rb +64 -98
  214. data/lib/graphql/tracing/statsd_trace.rb +33 -45
  215. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  216. data/lib/graphql/tracing/trace.rb +111 -1
  217. data/lib/graphql/tracing.rb +31 -30
  218. data/lib/graphql/type_kinds.rb +2 -1
  219. data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
  220. data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
  221. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  222. data/lib/graphql/types.rb +18 -11
  223. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  224. data/lib/graphql/version.rb +1 -1
  225. data/lib/graphql.rb +64 -54
  226. metadata +197 -22
  227. data/lib/graphql/analysis/ast/analyzer.rb +0 -91
  228. data/lib/graphql/analysis/ast/field_usage.rb +0 -82
  229. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  230. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  231. data/lib/graphql/analysis/ast/query_complexity.rb +0 -182
  232. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  233. data/lib/graphql/analysis/ast.rb +0 -94
  234. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  235. data/lib/graphql/backtrace/trace.rb +0 -93
  236. data/lib/graphql/backtrace/tracer.rb +0 -80
  237. data/lib/graphql/language/token.rb +0 -34
  238. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  239. data/lib/graphql/schema/null_mask.rb +0 -11
  240. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -5,10 +5,9 @@ require "graphql/schema/always_visible"
5
5
  require "graphql/schema/base_64_encoder"
6
6
  require "graphql/schema/find_inherited_value"
7
7
  require "graphql/schema/finder"
8
- require "graphql/schema/invalid_type_error"
9
8
  require "graphql/schema/introspection_system"
10
9
  require "graphql/schema/late_bound_type"
11
- require "graphql/schema/null_mask"
10
+ require "graphql/schema/ractor_shareable"
12
11
  require "graphql/schema/timeout"
13
12
  require "graphql/schema/type_expression"
14
13
  require "graphql/schema/unique_within_type"
@@ -46,6 +45,7 @@ require "graphql/schema/mutation"
46
45
  require "graphql/schema/has_single_input_argument"
47
46
  require "graphql/schema/relay_classic_mutation"
48
47
  require "graphql/schema/subscription"
48
+ require "graphql/schema/visibility"
49
49
 
50
50
  module GraphQL
51
51
  # A GraphQL schema which may be queried with {GraphQL::Query}.
@@ -61,7 +61,7 @@ module GraphQL
61
61
  # Any undiscoverable types may be provided with the `types` configuration.
62
62
  #
63
63
  # Schemas can restrict large incoming queries with `max_depth` and `max_complexity` configurations.
64
- # (These configurations can be overridden by specific calls to {Schema#execute})
64
+ # (These configurations can be overridden by specific calls to {Schema.execute})
65
65
  #
66
66
  # @example defining a schema
67
67
  # class MySchema < GraphQL::Schema
@@ -73,6 +73,9 @@ module GraphQL
73
73
  class Schema
74
74
  extend GraphQL::Schema::Member::HasAstNode
75
75
  extend GraphQL::Schema::FindInheritedValue
76
+ extend Autoload
77
+
78
+ autoload :BUILT_IN_TYPES, "graphql/schema/built_in_types"
76
79
 
77
80
  class DuplicateNamesError < GraphQL::Error
78
81
  attr_reader :duplicated_name
@@ -109,7 +112,7 @@ module GraphQL
109
112
  # @param parser [Object] An object for handling definition string parsing (must respond to `parse`)
110
113
  # @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
111
114
  # @return [Class] the schema described by `document`
112
- 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: {})
113
116
  # If the file ends in `.graphql` or `.graphqls`, treat it like a filepath
114
117
  if definition_or_path.end_with?(".graphql") || definition_or_path.end_with?(".graphqls")
115
118
  GraphQL::Schema::BuildFromDefinition.from_definition_path(
@@ -118,6 +121,7 @@ module GraphQL
118
121
  default_resolve: default_resolve,
119
122
  parser: parser,
120
123
  using: using,
124
+ base_types: base_types,
121
125
  )
122
126
  else
123
127
  GraphQL::Schema::BuildFromDefinition.from_definition(
@@ -126,6 +130,7 @@ module GraphQL
126
130
  default_resolve: default_resolve,
127
131
  parser: parser,
128
132
  using: using,
133
+ base_types: base_types,
129
134
  )
130
135
  end
131
136
  end
@@ -144,10 +149,12 @@ module GraphQL
144
149
  end
145
150
 
146
151
  # @param new_mode [Symbol] If configured, this will be used when `context: { trace_mode: ... }` isn't set.
147
- def default_trace_mode(new_mode = nil)
148
- if new_mode
152
+ def default_trace_mode(new_mode = NOT_CONFIGURED)
153
+ if !NOT_CONFIGURED.equal?(new_mode)
149
154
  @default_trace_mode = new_mode
150
- elsif defined?(@default_trace_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
151
158
  @default_trace_mode
152
159
  elsif superclass.respond_to?(:default_trace_mode)
153
160
  superclass.default_trace_mode
@@ -162,10 +169,8 @@ module GraphQL
162
169
  # re-apply them here
163
170
  mods = trace_modules_for(:default)
164
171
  mods.each { |mod| new_class.include(mod) }
172
+ new_class.include(DefaultTraceClass)
165
173
  trace_mode(:default, new_class)
166
- backtrace_class = Class.new(new_class)
167
- backtrace_class.include(GraphQL::Backtrace::Trace)
168
- trace_mode(:default_backtrace, backtrace_class)
169
174
  end
170
175
  trace_class_for(:default, build: true)
171
176
  end
@@ -187,7 +192,7 @@ module GraphQL
187
192
  # {default_trace_mode} is used when no `trace_mode: ...` is requested.
188
193
  #
189
194
  # When a `trace_class` is added this way, it will _not_ receive other modules added with `trace_with(...)`
190
- # unless `trace_mode` is explicitly given. (This class will not recieve any default trace modules.)
195
+ # unless `trace_mode` is explicitly given. (This class will not receive any default trace modules.)
191
196
  #
192
197
  # Subclasses of the schema will use `trace_class` as a base class for this mode and those
193
198
  # subclass also will _not_ receive default tracing modules.
@@ -204,24 +209,14 @@ module GraphQL
204
209
  @own_trace_modes ||= {}
205
210
  end
206
211
 
207
- module DefaultTraceClass
208
- end
209
-
210
- private_constant :DefaultTraceClass
211
-
212
212
  def build_trace_mode(mode)
213
213
  case mode
214
214
  when :default
215
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)) || GraphQL::Tracing::Trace
217
- Class.new(base_class) do
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
218
  include DefaultTraceClass
219
- end
220
- when :default_backtrace
221
- schema_base_class = trace_class_for(:default, build: true)
222
- Class.new(schema_base_class) do
223
- include(GraphQL::Backtrace::Trace)
224
- end
219
+ end)
225
220
  else
226
221
  # First, see if the superclass has a custom-defined class for this.
227
222
  # Then, if it doesn't, use this class's default trace
@@ -237,7 +232,7 @@ module GraphQL
237
232
  add_trace_options_for(mode, default_options)
238
233
 
239
234
  Class.new(base_class) do
240
- mods.any? && include(*mods)
235
+ !mods.empty? && include(*mods)
241
236
  end
242
237
  end
243
238
  end
@@ -257,7 +252,7 @@ module GraphQL
257
252
 
258
253
 
259
254
  # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
260
- # @see {#as_json}
255
+ # @see #as_json Return a Hash representation of the schema
261
256
  # @return [String]
262
257
  def to_json(**args)
263
258
  JSON.pretty_generate(as_json(**args))
@@ -265,8 +260,6 @@ module GraphQL
265
260
 
266
261
  # Return the Hash response of {Introspection::INTROSPECTION_QUERY}.
267
262
  # @param context [Hash]
268
- # @param only [<#call(member, ctx)>]
269
- # @param except [<#call(member, ctx)>]
270
263
  # @param include_deprecated_args [Boolean] If true, deprecated arguments will be included in the JSON response
271
264
  # @param include_schema_description [Boolean] If true, the schema's description will be queried and included in the response
272
265
  # @param include_is_repeatable [Boolean] If true, `isRepeatable: true|false` will be included with the schema's directives
@@ -321,8 +314,11 @@ module GraphQL
321
314
  GraphQL::StaticValidation::Validator.new(schema: self)
322
315
  end
323
316
 
317
+ # Add `plugin` to this schema
318
+ # @param plugin [#use] A Schema plugin
319
+ # @return void
324
320
  def use(plugin, **kwargs)
325
- if kwargs.any?
321
+ if !kwargs.empty?
326
322
  plugin.use(self, **kwargs)
327
323
  else
328
324
  plugin.use(self)
@@ -338,6 +334,10 @@ module GraphQL
338
334
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
339
335
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
340
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
341
341
  all_types = non_introspection_types.merge(introspection_system.types)
342
342
  visible_types = {}
343
343
  all_types.each do |k, v|
@@ -363,27 +363,37 @@ module GraphQL
363
363
  end
364
364
 
365
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}
366
368
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
367
- def get_type(type_name, context = GraphQL::Query::NullContext.instance)
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
368
374
  local_entry = own_types[type_name]
369
375
  type_defn = case local_entry
370
376
  when nil
371
377
  nil
372
378
  when Array
373
- visible_t = nil
374
- warden = Warden.from_context(context)
375
- local_entry.each do |t|
376
- if warden.visible_type?(t, context)
377
- if visible_t.nil?
378
- visible_t = t
379
- else
380
- raise DuplicateNamesError.new(
381
- duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
382
- )
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
383
393
  end
384
394
  end
395
+ visible_t
385
396
  end
386
- visible_t
387
397
  when Module
388
398
  local_entry
389
399
  else
@@ -392,7 +402,7 @@ module GraphQL
392
402
 
393
403
  type_defn ||
394
404
  introspection_system.types[type_name] || # todo context-specific introspection?
395
- (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)
396
406
  end
397
407
 
398
408
  # @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility?
@@ -419,55 +429,127 @@ module GraphQL
419
429
  end
420
430
  end
421
431
 
422
- def new_connections?
423
- !!connections
424
- end
425
-
426
- def query(new_query_object = nil)
427
- 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?
428
442
  if @query_object
429
- 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
430
457
  else
431
- @query_object = new_query_object
432
- add_type_and_traverse(new_query_object, root: true)
433
- nil
458
+ @query_object = new_query_object || lazy_load_block.call
459
+ add_type_and_traverse(@query_object, root: true)
434
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
435
466
  else
436
467
  @query_object || find_inherited_value(:query)
437
468
  end
438
469
  end
439
470
 
440
- def mutation(new_mutation_object = nil)
441
- 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?
442
481
  if @mutation_object
443
- 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
444
496
  else
445
- @mutation_object = new_mutation_object
446
- add_type_and_traverse(new_mutation_object, root: true)
447
- nil
497
+ @mutation_object = new_mutation_object || lazy_load_block.call
498
+ add_type_and_traverse(@mutation_object, root: true)
448
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
449
505
  else
450
506
  @mutation_object || find_inherited_value(:mutation)
451
507
  end
452
508
  end
453
509
 
454
- def subscription(new_subscription_object = nil)
455
- 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?
456
520
  if @subscription_object
457
- 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
458
536
  else
459
- @subscription_object = new_subscription_object
537
+ @subscription_object = new_subscription_object || lazy_load_block.call
460
538
  add_subscription_extension_if_necessary
461
- add_type_and_traverse(new_subscription_object, root: true)
462
- nil
539
+ add_type_and_traverse(@subscription_object, root: true)
463
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
464
547
  else
465
548
  @subscription_object || find_inherited_value(:subscription)
466
549
  end
467
550
  end
468
551
 
469
- # @see [GraphQL::Schema::Warden] Restricted access to root types
470
- # @return [GraphQL::ObjectType, nil]
552
+ # @api private
471
553
  def root_type_for_operation(operation)
472
554
  case operation
473
555
  when "query"
@@ -481,10 +563,16 @@ module GraphQL
481
563
  end
482
564
  end
483
565
 
566
+ # @return [Array<Class>] The root types (query, mutation, subscription) defined for this schema
484
567
  def root_types
485
- @root_types
568
+ if use_visibility_profile?
569
+ [query, mutation, subscription].compact
570
+ else
571
+ @root_types
572
+ end
486
573
  end
487
574
 
575
+ # @api private
488
576
  def warden_class
489
577
  if defined?(@warden_class)
490
578
  @warden_class
@@ -495,18 +583,54 @@ module GraphQL
495
583
  end
496
584
  end
497
585
 
586
+ # @api private
498
587
  attr_writer :warden_class
499
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
+
500
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}
501
618
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
502
619
  # @return [Array<Module>] Possible types for `type`, if it's given.
503
- def possible_types(type = nil, context = GraphQL::Query::NullContext.instance)
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
504
628
  if type
505
629
  # TODO duck-typing `.possible_types` would probably be nicer here
506
630
  if type.kind.union?
507
631
  type.possible_types(context: context)
508
632
  else
509
- stored_possible_types = own_possible_types[type.graphql_name]
633
+ stored_possible_types = own_possible_types[type]
510
634
  visible_possible_types = if stored_possible_types && type.kind.interface?
511
635
  stored_possible_types.select do |possible_type|
512
636
  possible_type.interfaces(context).include?(type)
@@ -515,10 +639,10 @@ module GraphQL
515
639
  stored_possible_types
516
640
  end
517
641
  visible_possible_types ||
518
- introspection_system.possible_types[type.graphql_name] ||
642
+ introspection_system.possible_types[type] ||
519
643
  (
520
644
  superclass.respond_to?(:possible_types) ?
521
- superclass.possible_types(type, context) :
645
+ superclass.possible_types(type, context, use_visibility_profile) :
522
646
  EMPTY_ARRAY
523
647
  )
524
648
  end
@@ -553,14 +677,9 @@ module GraphQL
553
677
  attr_writer :dataloader_class
554
678
 
555
679
  def references_to(to_type = nil, from: nil)
556
- @own_references_to ||= {}
557
680
  if to_type
558
- if !to_type.is_a?(String)
559
- to_type = to_type.graphql_name
560
- end
561
-
562
681
  if from
563
- refs = @own_references_to[to_type] ||= []
682
+ refs = own_references_to[to_type] ||= []
564
683
  refs << from
565
684
  else
566
685
  get_references_to(to_type) || EMPTY_ARRAY
@@ -570,20 +689,33 @@ module GraphQL
570
689
  # and generally speaking, we won't inherit any values.
571
690
  # So optimize the most common case -- don't create a duplicate Hash.
572
691
  inherited_value = find_inherited_value(:references_to, EMPTY_HASH)
573
- if inherited_value.any?
574
- inherited_value.merge(@own_references_to)
692
+ if !inherited_value.empty?
693
+ inherited_value.merge(own_references_to)
575
694
  else
576
- @own_references_to
695
+ own_references_to
577
696
  end
578
697
  end
579
698
  end
580
699
 
581
- def type_from_ast(ast_node, context: nil)
582
- type_owner = context ? context.warden : self
583
- 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)
584
702
  end
585
703
 
586
- def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance)
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
587
719
  parent_type = case type_or_name
588
720
  when LateBoundType
589
721
  get_type(type_or_name.name, context)
@@ -610,20 +742,27 @@ module GraphQL
610
742
  type.fields(context)
611
743
  end
612
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
613
748
  def introspection(new_introspection_namespace = nil)
614
749
  if new_introspection_namespace
615
750
  @introspection = new_introspection_namespace
616
751
  # reset this cached value:
617
752
  @introspection_system = nil
753
+ introspection_system
754
+ @introspection
618
755
  else
619
756
  @introspection || find_inherited_value(:introspection)
620
757
  end
621
758
  end
622
759
 
760
+ # @return [Schema::IntrospectionSystem] Based on {introspection}
623
761
  def introspection_system
624
762
  if !@introspection_system
625
763
  @introspection_system = Schema::IntrospectionSystem.new(self)
626
764
  @introspection_system.resolve_late_bindings
765
+ self.visibility&.introspection_system_configured(@introspection_system)
627
766
  end
628
767
  @introspection_system
629
768
  end
@@ -643,6 +782,17 @@ module GraphQL
643
782
  end
644
783
  end
645
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
+
646
796
  def default_page_size(new_default_page_size = nil)
647
797
  if new_default_page_size
648
798
  @default_page_size = new_default_page_size
@@ -689,13 +839,13 @@ module GraphQL
689
839
 
690
840
  attr_writer :validate_timeout
691
841
 
692
- def validate_timeout(new_validate_timeout = nil)
693
- if new_validate_timeout
842
+ def validate_timeout(new_validate_timeout = NOT_CONFIGURED)
843
+ if !NOT_CONFIGURED.equal?(new_validate_timeout)
694
844
  @validate_timeout = new_validate_timeout
695
845
  elsif defined?(@validate_timeout)
696
846
  @validate_timeout
697
847
  else
698
- find_inherited_value(:validate_timeout)
848
+ find_inherited_value(:validate_timeout) || 3
699
849
  end
700
850
  end
701
851
 
@@ -716,6 +866,7 @@ module GraphQL
716
866
  res[:errors]
717
867
  end
718
868
 
869
+ # @param new_query_class [Class<GraphQL::Query>] A subclass to use when executing queries
719
870
  def query_class(new_query_class = NOT_CONFIGURED)
720
871
  if NOT_CONFIGURED.equal?(new_query_class)
721
872
  @query_class || (superclass.respond_to?(:query_class) ? superclass.query_class : GraphQL::Query)
@@ -726,21 +877,20 @@ module GraphQL
726
877
 
727
878
  attr_writer :validate_max_errors
728
879
 
729
- def validate_max_errors(new_validate_max_errors = nil)
730
- if new_validate_max_errors
731
- @validate_max_errors = new_validate_max_errors
732
- elsif defined?(@validate_max_errors)
733
- @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)
734
883
  else
735
- find_inherited_value(:validate_max_errors)
884
+ @validate_max_errors = new_validate_max_errors
736
885
  end
737
886
  end
738
887
 
739
888
  attr_writer :max_complexity
740
889
 
741
- def max_complexity(max_complexity = nil)
890
+ def max_complexity(max_complexity = nil, count_introspection_fields: true)
742
891
  if max_complexity
743
892
  @max_complexity = max_complexity
893
+ @max_complexity_count_introspection_fields = count_introspection_fields
744
894
  elsif defined?(@max_complexity)
745
895
  @max_complexity
746
896
  else
@@ -748,22 +898,20 @@ module GraphQL
748
898
  end
749
899
  end
750
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
+
751
909
  attr_writer :analysis_engine
752
910
 
753
911
  def analysis_engine
754
912
  @analysis_engine || find_inherited_value(:analysis_engine, self.default_analysis_engine)
755
913
  end
756
914
 
757
- def using_ast_analysis?
758
- true
759
- end
760
-
761
- def interpreter?
762
- true
763
- end
764
-
765
- attr_writer :interpreter
766
-
767
915
  def error_bubbling(new_error_bubbling = nil)
768
916
  if !new_error_bubbling.nil?
769
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")
@@ -841,7 +989,7 @@ module GraphQL
841
989
  # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema
842
990
  # @return [Array<Module>] Type definitions added to this schema
843
991
  def extra_types(*new_extra_types)
844
- if new_extra_types.any?
992
+ if !new_extra_types.empty?
845
993
  new_extra_types = new_extra_types.flatten
846
994
  @own_extra_types ||= []
847
995
  @own_extra_types.concat(new_extra_types)
@@ -858,16 +1006,35 @@ module GraphQL
858
1006
  end
859
1007
  end
860
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
861
1016
  def orphan_types(*new_orphan_types)
862
- if new_orphan_types.any?
1017
+ if !new_orphan_types.empty?
863
1018
  new_orphan_types = new_orphan_types.flatten
864
- 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?
865
1031
  own_orphan_types.concat(new_orphan_types.flatten)
1032
+ self.visibility&.orphan_types_configured(new_orphan_types)
866
1033
  end
867
1034
 
868
1035
  inherited_ot = find_inherited_value(:orphan_types, nil)
869
1036
  if inherited_ot
870
- if own_orphan_types.any?
1037
+ if !own_orphan_types.empty?
871
1038
  inherited_ot + own_orphan_types
872
1039
  else
873
1040
  inherited_ot
@@ -893,6 +1060,8 @@ module GraphQL
893
1060
  end
894
1061
  end
895
1062
 
1063
+
1064
+ # @param new_default_logger [#log] Something to use for logging messages
896
1065
  def default_logger(new_default_logger = NOT_CONFIGURED)
897
1066
  if NOT_CONFIGURED.equal?(new_default_logger)
898
1067
  if defined?(@default_logger)
@@ -913,6 +1082,19 @@ module GraphQL
913
1082
  end
914
1083
  end
915
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
916
1098
  def context_class(new_context_class = nil)
917
1099
  if new_context_class
918
1100
  @context_class = new_context_class
@@ -921,28 +1103,46 @@ module GraphQL
921
1103
  end
922
1104
  end
923
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
924
1120
  def rescue_from(*err_classes, &handler_block)
925
1121
  err_classes.each do |err_class|
926
1122
  Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block)
927
1123
  end
928
1124
  end
929
1125
 
930
- NEW_HANDLER_HASH = ->(h, k) {
931
- h[k] = {
932
- class: k,
933
- handler: nil,
934
- subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
935
- }
936
- }
937
-
938
1126
  def error_handlers
939
- @error_handlers ||= {
940
- class: nil,
941
- handler: nil,
942
- subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
943
- }
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
944
1141
  end
945
1142
 
1143
+ # @api private
1144
+ attr_accessor :using_backtrace
1145
+
946
1146
  # @api private
947
1147
  def handle_or_reraise(context, err)
948
1148
  handler = Execution::Errors.find_handler_for(self, err.class)
@@ -956,6 +1156,10 @@ module GraphQL
956
1156
  end
957
1157
  handler[:handler].call(err, obj, args, context, field)
958
1158
  else
1159
+ if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError)
1160
+ err = GraphQL::Backtrace::TracedError.new(err, context)
1161
+ end
1162
+
959
1163
  raise err
960
1164
  end
961
1165
  end
@@ -987,8 +1191,24 @@ module GraphQL
987
1191
  end
988
1192
  end
989
1193
 
990
- def resolve_type(type, obj, ctx)
991
- raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(type, obj, ctx) must be implemented to use Union types, Interface types, or `loads:` (tried to resolve: #{type.name})"
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})"
992
1212
  end
993
1213
  # rubocop:enable Lint/DuplicateMethods
994
1214
 
@@ -1003,15 +1223,45 @@ module GraphQL
1003
1223
  child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
1004
1224
  end
1005
1225
  child_class.singleton_class.prepend(ResolveTypeWithType)
1006
- super
1007
- end
1008
1226
 
1009
- def object_from_id(node_id, ctx)
1010
- 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
1011
1232
  end
1012
1233
 
1013
- def id_from_object(object, type, ctx)
1014
- 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}`)"
1015
1265
  end
1016
1266
 
1017
1267
  def visible?(member, ctx)
@@ -1027,6 +1277,10 @@ module GraphQL
1027
1277
  Member::HasDirectives.get_directives(self, @own_schema_directives, :schema_directives)
1028
1278
  end
1029
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
1030
1284
  # This hook is called when an object fails an `authorized?` check.
1031
1285
  # You might report to your bug tracker here, so you can correct
1032
1286
  # the field resolvers not to return unauthorized objects.
@@ -1062,10 +1316,23 @@ module GraphQL
1062
1316
  unauthorized_object(unauthorized_error)
1063
1317
  end
1064
1318
 
1065
- 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)
1066
1330
  case type_error
1067
1331
  when GraphQL::InvalidNullError
1068
- 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
1069
1336
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
1070
1337
  raise type_error
1071
1338
  when GraphQL::IntegerDecodingError
@@ -1073,7 +1340,7 @@ module GraphQL
1073
1340
  end
1074
1341
  end
1075
1342
 
1076
- # A function to call when {#execute} receives an invalid query string
1343
+ # A function to call when {.execute} receives an invalid query string
1077
1344
  #
1078
1345
  # The default is to add the error to `context.errors`
1079
1346
  # @param parse_err [GraphQL::ParseError] The error encountered during parsing
@@ -1100,12 +1367,12 @@ module GraphQL
1100
1367
  # Add several directives at once
1101
1368
  # @param new_directives [Class]
1102
1369
  def directives(*new_directives)
1103
- if new_directives.any?
1370
+ if !new_directives.empty?
1104
1371
  new_directives.flatten.each { |d| directive(d) }
1105
1372
  end
1106
1373
 
1107
1374
  inherited_dirs = find_inherited_value(:directives, default_directives)
1108
- if own_directives.any?
1375
+ if !own_directives.empty?
1109
1376
  inherited_dirs.merge(own_directives)
1110
1377
  else
1111
1378
  inherited_dirs
@@ -1116,7 +1383,11 @@ module GraphQL
1116
1383
  # @param new_directive [Class]
1117
1384
  # @return void
1118
1385
  def directive(new_directive)
1119
- 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
1120
1391
  end
1121
1392
 
1122
1393
  def default_directives
@@ -1129,7 +1400,21 @@ module GraphQL
1129
1400
  }.freeze
1130
1401
  end
1131
1402
 
1132
- def tracer(new_tracer)
1403
+ # @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema
1404
+ attr_accessor :detailed_trace
1405
+
1406
+ # @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex})
1407
+ # @return [Boolean] When `true`, save a detailed trace for this query.
1408
+ # @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true
1409
+ def detailed_trace?(query)
1410
+ raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition."
1411
+ end
1412
+
1413
+ def tracer(new_tracer, silence_deprecation_warning: false)
1414
+ if !silence_deprecation_warning
1415
+ warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
1416
+ warn " #{caller(1, 1).first}"
1417
+ end
1133
1418
  default_trace = trace_class_for(:default, build: true)
1134
1419
  if default_trace.nil? || !(default_trace < GraphQL::Tracing::CallLegacyTracers)
1135
1420
  trace_with(GraphQL::Tracing::CallLegacyTracers)
@@ -1142,13 +1427,22 @@ module GraphQL
1142
1427
  find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
1143
1428
  end
1144
1429
 
1145
- # Mix `trace_mod` into this schema's `Trace` class so that its methods
1146
- # 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
1147
1440
  #
1148
1441
  # @param trace_mod [Module] A module that implements tracing methods
1149
1442
  # @param mode [Symbol] Trace module will only be used for this trade mode
1150
1443
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1151
1444
  # @return [void]
1445
+ # @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
1152
1446
  def trace_with(trace_mod, mode: :default, **options)
1153
1447
  if mode.is_a?(Array)
1154
1448
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
@@ -1198,34 +1492,43 @@ module GraphQL
1198
1492
  #
1199
1493
  # If no `mode:` is given, then {default_trace_mode} will be used.
1200
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
+ #
1201
1498
  # @param mode [Symbol] Trace modules for this trade mode will be included
1202
1499
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1203
1500
  # @return [Tracing::Trace]
1204
1501
  def new_trace(mode: nil, **options)
1205
- target = options[:query] || options[:multiplex]
1206
- mode ||= target && target.context[:trace_mode]
1207
-
1208
- trace_mode = if mode
1209
- mode
1210
- elsif target && target.context[:backtrace]
1211
- if default_trace_mode != :default
1212
- raise ArgumentError, "Can't use `context[:backtrace]` with a custom default trace mode (`#{dm.inspect}`)"
1213
- else
1214
- own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace)
1215
- options_trace_mode = :default
1216
- :default_backtrace
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
1217
1511
  end
1218
1512
  else
1219
- default_trace_mode
1513
+ false
1514
+ end
1515
+
1516
+ if should_sample
1517
+ mode = detailed_trace.trace_mode
1518
+ else
1519
+ target = options[:query] || options[:multiplex]
1520
+ mode ||= target && target.context[:trace_mode]
1220
1521
  end
1221
1522
 
1222
- options_trace_mode ||= trace_mode
1223
- base_trace_options = trace_options_for(options_trace_mode)
1523
+ trace_mode = mode || default_trace_mode
1524
+ base_trace_options = trace_options_for(trace_mode)
1224
1525
  trace_options = base_trace_options.merge(options)
1225
1526
  trace_class_for_mode = trace_class_for(trace_mode, build: true)
1226
1527
  trace_class_for_mode.new(**trace_options)
1227
1528
  end
1228
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
1229
1532
  def query_analyzer(new_analyzer)
1230
1533
  own_query_analyzers << new_analyzer
1231
1534
  end
@@ -1234,6 +1537,8 @@ module GraphQL
1234
1537
  find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers
1235
1538
  end
1236
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
1237
1542
  def multiplex_analyzer(new_analyzer)
1238
1543
  own_multiplex_analyzers << new_analyzer
1239
1544
  end
@@ -1252,7 +1557,7 @@ module GraphQL
1252
1557
 
1253
1558
  # Execute a query on itself.
1254
1559
  # @see {Query#initialize} for arguments.
1255
- # @return [Hash] query result, ready to be serialized as JSON
1560
+ # @return [GraphQL::Query::Result] query result, ready to be serialized as JSON
1256
1561
  def execute(query_str = nil, **kwargs)
1257
1562
  if query_str
1258
1563
  kwargs[:query] = query_str
@@ -1291,8 +1596,9 @@ module GraphQL
1291
1596
  # @see {Query#initialize} for query keyword arguments
1292
1597
  # @see {Execution::Multiplex#run_all} for multiplex keyword arguments
1293
1598
  # @param queries [Array<Hash>] Keyword arguments for each query
1294
- # @param context [Hash] Multiplex-level context
1295
- # @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
1296
1602
  def multiplex(queries, **kwargs)
1297
1603
  GraphQL::Execution::Interpreter.run_all(self, queries, **kwargs)
1298
1604
  end
@@ -1306,7 +1612,8 @@ module GraphQL
1306
1612
 
1307
1613
  # @api private
1308
1614
  def add_subscription_extension_if_necessary
1309
- 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
1310
1617
  @subscription_extension_added = true
1311
1618
  subscription.all_field_definitions.each do |field|
1312
1619
  if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) }
@@ -1316,6 +1623,11 @@ module GraphQL
1316
1623
  end
1317
1624
  end
1318
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]
1319
1631
  def query_stack_error(query, err)
1320
1632
  query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute."))
1321
1633
  end
@@ -1350,7 +1662,7 @@ module GraphQL
1350
1662
  end
1351
1663
  end
1352
1664
 
1353
- # @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}.
1354
1666
  def lazy_method_name(obj)
1355
1667
  lazy_methods.get(obj)
1356
1668
  end
@@ -1374,11 +1686,215 @@ module GraphQL
1374
1686
  end
1375
1687
  end
1376
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
+
1377
1884
  private
1378
1885
 
1379
1886
  def add_trace_options_for(mode, new_options)
1380
- t_opts = trace_options_for(mode)
1381
- t_opts.merge!(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
1382
1898
  nil
1383
1899
  end
1384
1900
 
@@ -1425,7 +1941,8 @@ module GraphQL
1425
1941
  own_union_memberships.merge!(addition.union_memberships)
1426
1942
 
1427
1943
  addition.references.each { |thing, pointers|
1428
- 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
1429
1946
  }
1430
1947
 
1431
1948
  addition.directives.each { |dir_class| own_directives[dir_class.graphql_name] = dir_class }
@@ -1453,6 +1970,10 @@ module GraphQL
1453
1970
  @own_types ||= {}
1454
1971
  end
1455
1972
 
1973
+ def own_references_to
1974
+ @own_references_to ||= {}.compare_by_identity
1975
+ end
1976
+
1456
1977
  def non_introspection_types
1457
1978
  find_inherited_value(:non_introspection_types, EMPTY_HASH).merge(own_types)
1458
1979
  end
@@ -1466,7 +1987,7 @@ module GraphQL
1466
1987
  end
1467
1988
 
1468
1989
  def own_possible_types
1469
- @own_possible_types ||= {}
1990
+ @own_possible_types ||= {}.compare_by_identity
1470
1991
  end
1471
1992
 
1472
1993
  def own_union_memberships
@@ -1494,15 +2015,15 @@ module GraphQL
1494
2015
  end
1495
2016
 
1496
2017
  # This is overridden in subclasses to check the inheritance chain
1497
- def get_references_to(type_name)
1498
- @own_references_to[type_name]
2018
+ def get_references_to(type_defn)
2019
+ own_references_to[type_defn]
1499
2020
  end
1500
2021
  end
1501
2022
 
1502
2023
  module SubclassGetReferencesTo
1503
- def get_references_to(type_name)
1504
- own_refs = @own_references_to[type_name]
1505
- inherited_refs = superclass.references_to(type_name)
2024
+ def get_references_to(type_defn)
2025
+ own_refs = own_references_to[type_defn]
2026
+ inherited_refs = superclass.references_to(type_defn)
1506
2027
  if inherited_refs&.any?
1507
2028
  if own_refs&.any?
1508
2029
  own_refs + inherited_refs
@@ -1517,5 +2038,12 @@ module GraphQL
1517
2038
 
1518
2039
  # Install these here so that subclasses will also install it.
1519
2040
  self.connections = GraphQL::Pagination::Connections.new(schema: self)
2041
+
2042
+ # @api private
2043
+ module DefaultTraceClass
2044
+ end
1520
2045
  end
1521
2046
  end
2047
+
2048
+ require "graphql/schema/loader"
2049
+ require "graphql/schema/printer"