graphql 2.4.3 → 2.5.1

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 (165) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +2 -1
  3. data/lib/graphql/analysis/visitor.rb +38 -41
  4. data/lib/graphql/analysis.rb +15 -12
  5. data/lib/graphql/autoload.rb +38 -0
  6. data/lib/graphql/backtrace/table.rb +118 -55
  7. data/lib/graphql/backtrace.rb +1 -19
  8. data/lib/graphql/current.rb +6 -1
  9. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  10. data/lib/graphql/dashboard/installable.rb +22 -0
  11. data/lib/graphql/dashboard/limiters.rb +93 -0
  12. data/lib/graphql/dashboard/operation_store.rb +199 -0
  13. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  14. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  15. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  16. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  17. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  18. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  19. data/lib/graphql/dashboard/statics/icon.png +0 -0
  20. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  21. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  22. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  23. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  24. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  25. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  26. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  36. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  37. data/lib/graphql/dashboard.rb +158 -0
  38. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  39. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  40. data/lib/graphql/dataloader/async_dataloader.rb +21 -9
  41. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  42. data/lib/graphql/dataloader/source.rb +3 -3
  43. data/lib/graphql/dataloader.rb +43 -14
  44. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  45. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -4
  46. data/lib/graphql/execution/interpreter/runtime.rb +78 -41
  47. data/lib/graphql/execution/interpreter.rb +16 -7
  48. data/lib/graphql/execution/multiplex.rb +1 -5
  49. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  50. data/lib/graphql/invalid_name_error.rb +1 -1
  51. data/lib/graphql/invalid_null_error.rb +5 -15
  52. data/lib/graphql/language/cache.rb +13 -0
  53. data/lib/graphql/language/document_from_schema_definition.rb +8 -7
  54. data/lib/graphql/language/lexer.rb +11 -4
  55. data/lib/graphql/language/nodes.rb +3 -0
  56. data/lib/graphql/language/parser.rb +15 -8
  57. data/lib/graphql/language/printer.rb +8 -8
  58. data/lib/graphql/language/static_visitor.rb +37 -33
  59. data/lib/graphql/language/visitor.rb +59 -55
  60. data/lib/graphql/pagination/connection.rb +1 -1
  61. data/lib/graphql/query/context/scoped_context.rb +1 -1
  62. data/lib/graphql/query/context.rb +6 -5
  63. data/lib/graphql/query/variable_validation_error.rb +1 -1
  64. data/lib/graphql/query.rb +19 -23
  65. data/lib/graphql/railtie.rb +7 -0
  66. data/lib/graphql/schema/addition.rb +1 -1
  67. data/lib/graphql/schema/argument.rb +7 -8
  68. data/lib/graphql/schema/build_from_definition.rb +99 -53
  69. data/lib/graphql/schema/directive/flagged.rb +1 -1
  70. data/lib/graphql/schema/directive.rb +2 -2
  71. data/lib/graphql/schema/enum.rb +36 -1
  72. data/lib/graphql/schema/enum_value.rb +1 -1
  73. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  74. data/lib/graphql/schema/field.rb +12 -12
  75. data/lib/graphql/schema/field_extension.rb +1 -1
  76. data/lib/graphql/schema/has_single_input_argument.rb +3 -1
  77. data/lib/graphql/schema/input_object.rb +76 -39
  78. data/lib/graphql/schema/interface.rb +3 -2
  79. data/lib/graphql/schema/loader.rb +1 -1
  80. data/lib/graphql/schema/member/has_arguments.rb +25 -17
  81. data/lib/graphql/schema/member/has_dataloader.rb +60 -0
  82. data/lib/graphql/schema/member/has_directives.rb +4 -4
  83. data/lib/graphql/schema/member/has_fields.rb +19 -1
  84. data/lib/graphql/schema/member/has_interfaces.rb +5 -5
  85. data/lib/graphql/schema/member/has_validators.rb +1 -1
  86. data/lib/graphql/schema/member/scoped.rb +1 -1
  87. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  88. data/lib/graphql/schema/member.rb +1 -0
  89. data/lib/graphql/schema/object.rb +25 -8
  90. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  91. data/lib/graphql/schema/resolver.rb +11 -10
  92. data/lib/graphql/schema/subscription.rb +52 -6
  93. data/lib/graphql/schema/union.rb +1 -1
  94. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  95. data/lib/graphql/schema/validator.rb +1 -1
  96. data/lib/graphql/schema/visibility/migration.rb +1 -0
  97. data/lib/graphql/schema/visibility/profile.rb +69 -237
  98. data/lib/graphql/schema/visibility/visit.rb +190 -0
  99. data/lib/graphql/schema/visibility.rb +169 -28
  100. data/lib/graphql/schema/warden.rb +18 -5
  101. data/lib/graphql/schema.rb +93 -44
  102. data/lib/graphql/static_validation/all_rules.rb +1 -1
  103. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  104. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +1 -1
  105. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  106. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  107. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  108. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  109. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  110. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  111. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  112. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  113. data/lib/graphql/static_validation/validation_context.rb +1 -0
  114. data/lib/graphql/static_validation/validator.rb +6 -1
  115. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  116. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  117. data/lib/graphql/subscriptions/event.rb +12 -1
  118. data/lib/graphql/subscriptions/serialize.rb +1 -1
  119. data/lib/graphql/subscriptions.rb +1 -1
  120. data/lib/graphql/testing/helpers.rb +7 -4
  121. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  122. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  123. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  124. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  125. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  126. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  127. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  128. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  129. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  130. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  131. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  132. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  133. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  134. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  135. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  136. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  137. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  138. data/lib/graphql/tracing/notifications_trace.rb +182 -34
  139. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  140. data/lib/graphql/tracing/null_trace.rb +9 -0
  141. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  142. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  143. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  144. data/lib/graphql/tracing/platform_trace.rb +5 -0
  145. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  146. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  147. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  148. data/lib/graphql/tracing/scout_trace.rb +32 -55
  149. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  150. data/lib/graphql/tracing/sentry_trace.rb +62 -94
  151. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  152. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  153. data/lib/graphql/tracing/trace.rb +111 -1
  154. data/lib/graphql/tracing.rb +31 -30
  155. data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
  156. data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
  157. data/lib/graphql/types.rb +18 -11
  158. data/lib/graphql/version.rb +1 -1
  159. data/lib/graphql.rb +55 -47
  160. metadata +146 -11
  161. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  162. data/lib/graphql/backtrace/trace.rb +0 -93
  163. data/lib/graphql/backtrace/tracer.rb +0 -80
  164. data/lib/graphql/schema/null_mask.rb +0 -11
  165. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -7,7 +7,6 @@ require "graphql/schema/find_inherited_value"
7
7
  require "graphql/schema/finder"
8
8
  require "graphql/schema/introspection_system"
9
9
  require "graphql/schema/late_bound_type"
10
- require "graphql/schema/null_mask"
11
10
  require "graphql/schema/timeout"
12
11
  require "graphql/schema/type_expression"
13
12
  require "graphql/schema/unique_within_type"
@@ -73,6 +72,9 @@ module GraphQL
73
72
  class Schema
74
73
  extend GraphQL::Schema::Member::HasAstNode
75
74
  extend GraphQL::Schema::FindInheritedValue
75
+ extend Autoload
76
+
77
+ autoload :BUILT_IN_TYPES, "graphql/schema/built_in_types"
76
78
 
77
79
  class DuplicateNamesError < GraphQL::Error
78
80
  attr_reader :duplicated_name
@@ -109,7 +111,7 @@ module GraphQL
109
111
  # @param parser [Object] An object for handling definition string parsing (must respond to `parse`)
110
112
  # @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
111
113
  # @return [Class] the schema described by `document`
112
- def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {})
114
+ def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {}, base_types: {})
113
115
  # If the file ends in `.graphql` or `.graphqls`, treat it like a filepath
114
116
  if definition_or_path.end_with?(".graphql") || definition_or_path.end_with?(".graphqls")
115
117
  GraphQL::Schema::BuildFromDefinition.from_definition_path(
@@ -118,6 +120,7 @@ module GraphQL
118
120
  default_resolve: default_resolve,
119
121
  parser: parser,
120
122
  using: using,
123
+ base_types: base_types,
121
124
  )
122
125
  else
123
126
  GraphQL::Schema::BuildFromDefinition.from_definition(
@@ -126,6 +129,7 @@ module GraphQL
126
129
  default_resolve: default_resolve,
127
130
  parser: parser,
128
131
  using: using,
132
+ base_types: base_types,
129
133
  )
130
134
  end
131
135
  end
@@ -164,9 +168,6 @@ module GraphQL
164
168
  mods.each { |mod| new_class.include(mod) }
165
169
  new_class.include(DefaultTraceClass)
166
170
  trace_mode(:default, new_class)
167
- backtrace_class = Class.new(new_class)
168
- backtrace_class.include(GraphQL::Backtrace::Trace)
169
- trace_mode(:default_backtrace, backtrace_class)
170
171
  end
171
172
  trace_class_for(:default, build: true)
172
173
  end
@@ -213,11 +214,6 @@ module GraphQL
213
214
  const_set(:DefaultTrace, Class.new(base_class) do
214
215
  include DefaultTraceClass
215
216
  end)
216
- when :default_backtrace
217
- schema_base_class = trace_class_for(:default, build: true)
218
- const_set(:DefaultTraceBacktrace, Class.new(schema_base_class) do
219
- include(GraphQL::Backtrace::Trace)
220
- end)
221
217
  else
222
218
  # First, see if the superclass has a custom-defined class for this.
223
219
  # Then, if it doesn't, use this class's default trace
@@ -233,7 +229,7 @@ module GraphQL
233
229
  add_trace_options_for(mode, default_options)
234
230
 
235
231
  Class.new(base_class) do
236
- mods.any? && include(*mods)
232
+ !mods.empty? && include(*mods)
237
233
  end
238
234
  end
239
235
  end
@@ -321,7 +317,7 @@ module GraphQL
321
317
  # @param plugin [#use] A Schema plugin
322
318
  # @return void
323
319
  def use(plugin, **kwargs)
324
- if kwargs.any?
320
+ if !kwargs.empty?
325
321
  plugin.use(self, **kwargs)
326
322
  else
327
323
  plugin.use(self)
@@ -446,7 +442,12 @@ module GraphQL
446
442
  raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
447
443
  elsif use_visibility_profile?
448
444
  if block_given?
449
- @query_object = lazy_load_block
445
+ if visibility.preload?
446
+ @query_object = lazy_load_block.call
447
+ self.visibility.query_configured(@query_object)
448
+ else
449
+ @query_object = lazy_load_block
450
+ end
450
451
  else
451
452
  @query_object = new_query_object
452
453
  self.visibility.query_configured(@query_object)
@@ -480,7 +481,12 @@ module GraphQL
480
481
  raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
481
482
  elsif use_visibility_profile?
482
483
  if block_given?
483
- @mutation_object = lazy_load_block
484
+ if visibility.preload?
485
+ @mutation_object = lazy_load_block.call
486
+ self.visibility.mutation_configured(@mutation_object)
487
+ else
488
+ @mutation_object = lazy_load_block
489
+ end
484
490
  else
485
491
  @mutation_object = new_mutation_object
486
492
  self.visibility.mutation_configured(@mutation_object)
@@ -514,7 +520,12 @@ module GraphQL
514
520
  raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
515
521
  elsif use_visibility_profile?
516
522
  if block_given?
517
- @subscription_object = lazy_load_block
523
+ if visibility.preload?
524
+ @subscription_object = lazy_load_block.call
525
+ visibility.subscription_configured(@subscription_object)
526
+ else
527
+ @subscription_object = lazy_load_block
528
+ end
518
529
  else
519
530
  @subscription_object = new_subscription_object
520
531
  self.visibility.subscription_configured(@subscription_object)
@@ -676,7 +687,7 @@ module GraphQL
676
687
  # and generally speaking, we won't inherit any values.
677
688
  # So optimize the most common case -- don't create a duplicate Hash.
678
689
  inherited_value = find_inherited_value(:references_to, EMPTY_HASH)
679
- if inherited_value.any?
690
+ if !inherited_value.empty?
680
691
  inherited_value.merge(own_references_to)
681
692
  else
682
693
  own_references_to
@@ -812,13 +823,13 @@ module GraphQL
812
823
 
813
824
  attr_writer :validate_timeout
814
825
 
815
- def validate_timeout(new_validate_timeout = nil)
816
- if new_validate_timeout
826
+ def validate_timeout(new_validate_timeout = NOT_CONFIGURED)
827
+ if !NOT_CONFIGURED.equal?(new_validate_timeout)
817
828
  @validate_timeout = new_validate_timeout
818
829
  elsif defined?(@validate_timeout)
819
830
  @validate_timeout
820
831
  else
821
- find_inherited_value(:validate_timeout)
832
+ find_inherited_value(:validate_timeout) || 3
822
833
  end
823
834
  end
824
835
 
@@ -962,7 +973,7 @@ module GraphQL
962
973
  # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema
963
974
  # @return [Array<Module>] Type definitions added to this schema
964
975
  def extra_types(*new_extra_types)
965
- if new_extra_types.any?
976
+ if !new_extra_types.empty?
966
977
  new_extra_types = new_extra_types.flatten
967
978
  @own_extra_types ||= []
968
979
  @own_extra_types.concat(new_extra_types)
@@ -987,10 +998,10 @@ module GraphQL
987
998
  # @param new_orphan_types [Array<Class<GraphQL::Schema::Object>>] Object types to register as implementations of interfaces in the schema.
988
999
  # @return [Array<Class<GraphQL::Schema::Object>>] All previously-registered orphan types for this schema
989
1000
  def orphan_types(*new_orphan_types)
990
- if new_orphan_types.any?
1001
+ if !new_orphan_types.empty?
991
1002
  new_orphan_types = new_orphan_types.flatten
992
1003
  non_object_types = new_orphan_types.reject { |ot| ot.is_a?(Class) && ot < GraphQL::Schema::Object }
993
- if non_object_types.any?
1004
+ if !non_object_types.empty?
994
1005
  raise ArgumentError, <<~ERR
995
1006
  Only object type classes should be added as `orphan_types(...)`.
996
1007
 
@@ -1007,7 +1018,7 @@ module GraphQL
1007
1018
 
1008
1019
  inherited_ot = find_inherited_value(:orphan_types, nil)
1009
1020
  if inherited_ot
1010
- if own_orphan_types.any?
1021
+ if !own_orphan_types.empty?
1011
1022
  inherited_ot + own_orphan_types
1012
1023
  else
1013
1024
  inherited_ot
@@ -1100,6 +1111,9 @@ module GraphQL
1100
1111
  }
1101
1112
  end
1102
1113
 
1114
+ # @api private
1115
+ attr_accessor :using_backtrace
1116
+
1103
1117
  # @api private
1104
1118
  def handle_or_reraise(context, err)
1105
1119
  handler = Execution::Errors.find_handler_for(self, err.class)
@@ -1113,6 +1127,10 @@ module GraphQL
1113
1127
  end
1114
1128
  handler[:handler].call(err, obj, args, context, field)
1115
1129
  else
1130
+ if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError)
1131
+ err = GraphQL::Backtrace::TracedError.new(err, context)
1132
+ end
1133
+
1116
1134
  raise err
1117
1135
  end
1118
1136
  end
@@ -1282,7 +1300,10 @@ module GraphQL
1282
1300
  def type_error(type_error, ctx)
1283
1301
  case type_error
1284
1302
  when GraphQL::InvalidNullError
1285
- ctx.errors << type_error
1303
+ execution_error = GraphQL::ExecutionError.new(type_error.message, ast_node: type_error.ast_node)
1304
+ execution_error.path = ctx[:current_path]
1305
+
1306
+ ctx.errors << execution_error
1286
1307
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
1287
1308
  raise type_error
1288
1309
  when GraphQL::IntegerDecodingError
@@ -1317,12 +1338,12 @@ module GraphQL
1317
1338
  # Add several directives at once
1318
1339
  # @param new_directives [Class]
1319
1340
  def directives(*new_directives)
1320
- if new_directives.any?
1341
+ if !new_directives.empty?
1321
1342
  new_directives.flatten.each { |d| directive(d) }
1322
1343
  end
1323
1344
 
1324
1345
  inherited_dirs = find_inherited_value(:directives, default_directives)
1325
- if own_directives.any?
1346
+ if !own_directives.empty?
1326
1347
  inherited_dirs.merge(own_directives)
1327
1348
  else
1328
1349
  inherited_dirs
@@ -1350,6 +1371,16 @@ module GraphQL
1350
1371
  }.freeze
1351
1372
  end
1352
1373
 
1374
+ # @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema
1375
+ attr_accessor :detailed_trace
1376
+
1377
+ # @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex})
1378
+ # @return [Boolean] When `true`, save a detailed trace for this query.
1379
+ # @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true
1380
+ def detailed_trace?(query)
1381
+ raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition."
1382
+ end
1383
+
1353
1384
  def tracer(new_tracer, silence_deprecation_warning: false)
1354
1385
  if !silence_deprecation_warning
1355
1386
  warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
@@ -1367,14 +1398,22 @@ module GraphQL
1367
1398
  find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
1368
1399
  end
1369
1400
 
1370
- # Mix `trace_mod` into this schema's `Trace` class so that its methods
1371
- # will be called at runtime.
1401
+ # Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime.
1402
+ #
1403
+ # You can attach a module to run in only _some_ circumstances by using `mode:`. When a module is added with `mode:`,
1404
+ # it will only run for queries with a matching `context[:trace_mode]`.
1405
+ #
1406
+ # Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration).
1407
+ #
1408
+ # @example Adding a trace in a special mode
1409
+ # # only runs when `query.context[:trace_mode]` is `:special`
1410
+ # trace_with SpecialTrace, mode: :special
1372
1411
  #
1373
1412
  # @param trace_mod [Module] A module that implements tracing methods
1374
1413
  # @param mode [Symbol] Trace module will only be used for this trade mode
1375
1414
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1376
1415
  # @return [void]
1377
- # @see GraphQL::Tracing::Trace for available tracing methods
1416
+ # @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
1378
1417
  def trace_with(trace_mod, mode: :default, **options)
1379
1418
  if mode.is_a?(Array)
1380
1419
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
@@ -1424,29 +1463,36 @@ module GraphQL
1424
1463
  #
1425
1464
  # If no `mode:` is given, then {default_trace_mode} will be used.
1426
1465
  #
1466
+ # If this schema is using {Tracing::DetailedTrace} and {.detailed_trace?} returns `true`, then
1467
+ # DetailedTrace's mode will override the passed-in `mode`.
1468
+ #
1427
1469
  # @param mode [Symbol] Trace modules for this trade mode will be included
1428
1470
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1429
1471
  # @return [Tracing::Trace]
1430
1472
  def new_trace(mode: nil, **options)
1431
- target = options[:query] || options[:multiplex]
1432
- mode ||= target && target.context[:trace_mode]
1433
-
1434
- trace_mode = if mode
1435
- mode
1436
- elsif target && target.context[:backtrace]
1437
- if default_trace_mode != :default
1438
- raise ArgumentError, "Can't use `context[:backtrace]` with a custom default trace mode (`#{dm.inspect}`)"
1439
- else
1440
- own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace)
1441
- options_trace_mode = :default
1442
- :default_backtrace
1473
+ should_sample = if detailed_trace
1474
+ if (query = options[:query])
1475
+ detailed_trace?(query)
1476
+ elsif (multiplex = options[:multiplex])
1477
+ if multiplex.queries.length == 1
1478
+ detailed_trace?(multiplex.queries.first)
1479
+ else
1480
+ detailed_trace?(multiplex)
1481
+ end
1443
1482
  end
1444
1483
  else
1445
- default_trace_mode
1484
+ false
1485
+ end
1486
+
1487
+ if should_sample
1488
+ mode = detailed_trace.trace_mode
1489
+ else
1490
+ target = options[:query] || options[:multiplex]
1491
+ mode ||= target && target.context[:trace_mode]
1446
1492
  end
1447
1493
 
1448
- options_trace_mode ||= trace_mode
1449
- base_trace_options = trace_options_for(options_trace_mode)
1494
+ trace_mode = mode || default_trace_mode
1495
+ base_trace_options = trace_options_for(trace_mode)
1450
1496
  trace_options = base_trace_options.merge(options)
1451
1497
  trace_class_for_mode = trace_class_for(trace_mode, build: true)
1452
1498
  trace_class_for_mode.new(**trace_options)
@@ -1787,3 +1833,6 @@ module GraphQL
1787
1833
  end
1788
1834
  end
1789
1835
  end
1836
+
1837
+ require "graphql/schema/loader"
1838
+ require "graphql/schema/printer"
@@ -34,7 +34,7 @@ module GraphQL
34
34
  GraphQL::StaticValidation::VariableUsagesAreAllowed,
35
35
  GraphQL::StaticValidation::MutationRootExists,
36
36
  GraphQL::StaticValidation::QueryRootExists,
37
- GraphQL::StaticValidation::SubscriptionRootExists,
37
+ GraphQL::StaticValidation::SubscriptionRootExistsAndSingleSubscriptionSelection,
38
38
  GraphQL::StaticValidation::InputObjectNamesAreUnique,
39
39
  GraphQL::StaticValidation::OneOfInputObjectsAreValid,
40
40
  ]
@@ -16,7 +16,7 @@ module GraphQL
16
16
 
17
17
  def validate_arguments(node)
18
18
  argument_defns = node.arguments
19
- if argument_defns.any?
19
+ if !argument_defns.empty?
20
20
  args_by_name = Hash.new { |h, k| h[k] = [] }
21
21
  argument_defns.each { |a| args_by_name[a.name] << a }
22
22
  args_by_name.each do |name, defns|
@@ -25,7 +25,7 @@ module GraphQL
25
25
  def validate_field_selections(ast_node, resolved_type)
26
26
  msg = if resolved_type.nil?
27
27
  nil
28
- elsif ast_node.selections.any? && resolved_type.kind.leaf?
28
+ elsif !ast_node.selections.empty? && resolved_type.kind.leaf?
29
29
  selection_strs = ast_node.selections.map do |n|
30
30
  case n
31
31
  when GraphQL::Language::Nodes::InlineFragment
@@ -345,7 +345,7 @@ module GraphQL
345
345
  fields << Field.new(node, definition, owner_type, parents)
346
346
  when GraphQL::Language::Nodes::InlineFragment
347
347
  fragment_type = node.type ? @types.type(node.type.name) : owner_type
348
- find_fields_and_fragments(node.selections, parents: [*parents, fragment_type], owner_type: owner_type, fields: fields, fragment_spreads: fragment_spreads) if fragment_type
348
+ find_fields_and_fragments(node.selections, parents: [*parents, fragment_type], owner_type: fragment_type, fields: fields, fragment_spreads: fragment_spreads) if fragment_type
349
349
  when GraphQL::Language::Nodes::FragmentSpread
350
350
  fragment_spreads << FragmentSpread.new(node.name, parents)
351
351
  end
@@ -32,7 +32,7 @@ module GraphQL
32
32
 
33
33
  def on_document(node, parent)
34
34
  super
35
- if @schema_definition_nodes.any?
35
+ if !@schema_definition_nodes.empty?
36
36
  add_error(GraphQL::StaticValidation::NoDefinitionsArePresentError.new(%|Query cannot contain schema definitions|, nodes: @schema_definition_nodes))
37
37
  end
38
38
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class NotSingleSubscriptionError < StaticValidation::Error
5
+ def initialize(message, path: nil, nodes: [])
6
+ super(message, path: path, nodes: nodes)
7
+ end
8
+
9
+ # A hash representation of this Message
10
+ def to_h
11
+ extensions = {
12
+ "code" => code,
13
+ }
14
+
15
+ super.merge({
16
+ "extensions" => extensions
17
+ })
18
+ end
19
+
20
+ def code
21
+ "notSingleSubscription"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -24,7 +24,7 @@ module GraphQL
24
24
  .map!(&:name)
25
25
 
26
26
  missing_names = required_argument_names - present_argument_names
27
- if missing_names.any?
27
+ if !missing_names.empty?
28
28
  add_error(GraphQL::StaticValidation::RequiredArgumentsArePresentError.new(
29
29
  "#{ast_node.class.name.split("::").last} '#{ast_node.name}' is missing required arguments: #{missing_names.join(", ")}",
30
30
  nodes: ast_node,
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ module SubscriptionRootExistsAndSingleSubscriptionSelection
5
+ def on_operation_definition(node, parent)
6
+ if node.operation_type == "subscription"
7
+ if context.types.subscription_root.nil?
8
+ add_error(GraphQL::StaticValidation::SubscriptionRootExistsError.new(
9
+ 'Schema is not configured for subscriptions',
10
+ nodes: node
11
+ ))
12
+ elsif node.selections.size != 1
13
+ add_error(GraphQL::StaticValidation::NotSingleSubscriptionError.new(
14
+ 'A subscription operation may only have one selection',
15
+ nodes: node,
16
+ ))
17
+ else
18
+ super
19
+ end
20
+ else
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -21,7 +21,7 @@ module GraphQL
21
21
 
22
22
  DIRECTIVE_NODE_HOOKS.each do |method_name|
23
23
  define_method(method_name) do |node, parent|
24
- if node.directives.any?
24
+ if !node.directives.empty?
25
25
  validate_directive_location(node)
26
26
  end
27
27
  super(node, parent)
@@ -4,7 +4,7 @@ module GraphQL
4
4
  module VariableNamesAreUnique
5
5
  def on_operation_definition(node, parent)
6
6
  var_defns = node.variables
7
- if var_defns.any?
7
+ if !var_defns.empty?
8
8
  vars_by_name = Hash.new { |h, k| h[k] = [] }
9
9
  var_defns.each { |v| vars_by_name[v.name] << v }
10
10
  vars_by_name.each do |name, defns|
@@ -21,7 +21,7 @@ module GraphQL
21
21
  end
22
22
  node_values = node_values.select { |value| value.is_a? GraphQL::Language::Nodes::VariableIdentifier }
23
23
 
24
- if node_values.any?
24
+ if !node_values.empty?
25
25
  argument_owner = case parent
26
26
  when GraphQL::Language::Nodes::Field
27
27
  context.field_definition
@@ -29,6 +29,7 @@ module GraphQL
29
29
  @visitor = visitor_class.new(document, self)
30
30
  end
31
31
 
32
+ # TODO stop using def_delegators because of Array allocations
32
33
  def_delegators :@visitor,
33
34
  :path, :type_definition, :field_definition, :argument_definition,
34
35
  :parent_type_definition, :directive_definition, :object_types, :dependencies
@@ -27,6 +27,8 @@ module GraphQL
27
27
  # @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit.
28
28
  # @return [Array<Hash>]
29
29
  def validate(query, validate: true, timeout: nil, max_errors: nil)
30
+ errors = nil
31
+ query.current_trace.begin_validate(query, validate)
30
32
  query.current_trace.validate(validate: validate, query: query) do
31
33
  begin_t = Time.now
32
34
  errors = if validate == false
@@ -58,10 +60,13 @@ module GraphQL
58
60
  }
59
61
  end
60
62
  rescue GraphQL::ExecutionError => e
63
+ errors = [e]
61
64
  {
62
65
  remaining_timeout: nil,
63
- errors: [e],
66
+ errors: errors,
64
67
  }
68
+ ensure
69
+ query.current_trace.end_validate(query, validate, errors)
65
70
  end
66
71
 
67
72
  # Invoked when static validation times out.
@@ -171,7 +171,7 @@ module GraphQL
171
171
  events_by_fingerprint = @events[topic]
172
172
  object = nil
173
173
  events_by_fingerprint.each do |_fingerprint, events|
174
- if events.any? && events.first == initial_event
174
+ if !events.empty? && events.first == initial_event
175
175
  # The fingerprint has told us that this response should be shared by all subscribers,
176
176
  # so just run it once, then deliver the result to every subscriber
177
177
  first_event = events.first
@@ -20,12 +20,22 @@ module GraphQL
20
20
  def after_resolve(value:, context:, object:, arguments:, **rest)
21
21
  if value.is_a?(GraphQL::ExecutionError)
22
22
  value
23
+ elsif @field.resolver&.method_defined?(:subscription_written?) &&
24
+ (subscription_namespace = context.namespace(:subscriptions)) &&
25
+ (subscriptions_by_path = subscription_namespace[:subscriptions])
26
+ (subscription_instance = subscriptions_by_path[context.current_path])
27
+ # If it was already written, don't append this event to be written later
28
+ if !subscription_instance.subscription_written?
29
+ events = context.namespace(:subscriptions)[:events]
30
+ events << subscription_instance.event
31
+ end
32
+ value
23
33
  elsif (events = context.namespace(:subscriptions)[:events])
24
34
  # This is the first execution, so gather an Event
25
35
  # for the backend to register:
26
36
  event = Subscriptions::Event.new(
27
37
  name: field.name,
28
- arguments: arguments_without_field_extras(arguments: arguments),
38
+ arguments: arguments,
29
39
  context: context,
30
40
  field: field,
31
41
  )
@@ -33,7 +43,7 @@ module GraphQL
33
43
  value
34
44
  elsif context.query.subscription_topic == Subscriptions::Event.serialize(
35
45
  field.name,
36
- arguments_without_field_extras(arguments: arguments),
46
+ arguments,
37
47
  field,
38
48
  scope: (field.subscription_scope ? context[field.subscription_scope] : nil),
39
49
  )
@@ -45,14 +55,6 @@ module GraphQL
45
55
  context.skip
46
56
  end
47
57
  end
48
-
49
- private
50
-
51
- def arguments_without_field_extras(arguments:)
52
- arguments.dup.tap do |event_args|
53
- field.extras.each { |k| event_args.delete(k) }
54
- end
55
- end
56
58
  end
57
59
  end
58
60
  end
@@ -20,7 +20,7 @@ module GraphQL
20
20
 
21
21
  def initialize(name:, arguments:, field: nil, context: nil, scope: nil)
22
22
  @name = name
23
- @arguments = arguments
23
+ @arguments = self.class.arguments_without_field_extras(arguments: arguments, field: field)
24
24
  @context = context
25
25
  field ||= context.field
26
26
  scope_key = field.subscription_scope
@@ -39,6 +39,7 @@ module GraphQL
39
39
  # @return [String] an identifier for this unit of subscription
40
40
  def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext.instance)
41
41
  subscription = field.resolver || GraphQL::Schema::Subscription
42
+ arguments = arguments_without_field_extras(field: field, arguments: arguments)
42
43
  normalized_args = stringify_args(field, arguments.to_h, context)
43
44
  subscription.topic_for(arguments: normalized_args, field: field, scope: scope)
44
45
  end
@@ -60,6 +61,16 @@ module GraphQL
60
61
  end
61
62
 
62
63
  class << self
64
+ def arguments_without_field_extras(arguments:, field:)
65
+ if !field.extras.empty?
66
+ arguments = arguments.dup
67
+ field.extras.each do |extra_key|
68
+ arguments.delete(extra_key)
69
+ end
70
+ end
71
+ arguments
72
+ end
73
+
63
74
  private
64
75
 
65
76
  # This method does not support cyclic references in the Hash,
@@ -146,7 +146,7 @@ module GraphQL
146
146
  elsif obj.is_a?(Date) || obj.is_a?(Time)
147
147
  # DateTime extends Date; for TimeWithZone, call `.utc` first.
148
148
  { TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] }
149
- elsif obj.is_a?(OpenStruct)
149
+ elsif defined?(OpenStruct) && obj.is_a?(OpenStruct)
150
150
  { OPEN_STRUCT_KEY => dump_value(obj.to_h) }
151
151
  elsif defined?(ActiveRecord::Relation) && obj.is_a?(ActiveRecord::Relation)
152
152
  dump_value(obj.to_a)
@@ -291,7 +291,7 @@ module GraphQL
291
291
  end
292
292
  end
293
293
 
294
- if missing_arg_names.any?
294
+ if !missing_arg_names.empty?
295
295
  arg_owner_name = if arg_owner.is_a?(GraphQL::Schema::Field)
296
296
  arg_owner.path
297
297
  elsif arg_owner.is_a?(Class)
@@ -43,22 +43,25 @@ module GraphQL
43
43
  type_name, *field_names = field_path.split(".")
44
44
  dummy_query = GraphQL::Query.new(schema, "{ __typename }", context: context)
45
45
  query_context = dummy_query.context
46
+ dataloader = query_context.dataloader
46
47
  object_type = dummy_query.types.type(type_name) # rubocop:disable Development/ContextIsPassedCop
47
48
  if object_type
48
49
  graphql_result = object
49
50
  field_names.each do |field_name|
50
51
  inner_object = graphql_result
51
- graphql_result = object_type.wrap(inner_object, query_context)
52
+ dataloader.run_isolated {
53
+ graphql_result = object_type.wrap(inner_object, query_context)
54
+ }
52
55
  if graphql_result.nil?
53
56
  return nil
54
57
  end
55
58
  visible_field = dummy_query.types.field(object_type, field_name) # rubocop:disable Development/ContextIsPassedCop
56
59
  if visible_field
57
- dummy_query.context.dataloader.run_isolated {
60
+ dataloader.run_isolated {
58
61
  query_context[:current_field] = visible_field
59
62
  field_args = visible_field.coerce_arguments(graphql_result, arguments, query_context)
60
63
  field_args = schema.sync_lazy(field_args)
61
- if visible_field.extras.any?
64
+ if !visible_field.extras.empty?
62
65
  extra_args = {}
63
66
  visible_field.extras.each do |extra|
64
67
  extra_args[extra] = case extra
@@ -92,7 +95,7 @@ module GraphQL
92
95
  end
93
96
  graphql_result
94
97
  else
95
- unfiltered_type = Schema::Visibility::Profile.null_profile(schema: schema, context: context).type(type_name)
98
+ unfiltered_type = schema.use_visibility_profile? ? schema.visibility.get_type(type_name) : schema.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
96
99
  if unfiltered_type
97
100
  raise TypeNotVisibleError.new(type_name: type_name)
98
101
  else