graphql 2.4.5 → 2.5.0

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +2 -1
  3. data/lib/graphql/analysis/visitor.rb +37 -40
  4. data/lib/graphql/analysis.rb +12 -9
  5. data/lib/graphql/autoload.rb +1 -0
  6. data/lib/graphql/backtrace/table.rb +118 -55
  7. data/lib/graphql/backtrace.rb +1 -19
  8. data/lib/graphql/current.rb +5 -0
  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 +17 -5
  41. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  42. data/lib/graphql/dataloader/source.rb +2 -2
  43. data/lib/graphql/dataloader.rb +37 -5
  44. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -4
  45. data/lib/graphql/execution/interpreter/runtime.rb +71 -34
  46. data/lib/graphql/execution/interpreter.rb +12 -3
  47. data/lib/graphql/execution/multiplex.rb +1 -5
  48. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  49. data/lib/graphql/invalid_name_error.rb +1 -1
  50. data/lib/graphql/invalid_null_error.rb +5 -15
  51. data/lib/graphql/language/lexer.rb +7 -3
  52. data/lib/graphql/language/nodes.rb +3 -0
  53. data/lib/graphql/language/parser.rb +15 -8
  54. data/lib/graphql/language/static_visitor.rb +37 -33
  55. data/lib/graphql/language/visitor.rb +59 -55
  56. data/lib/graphql/query.rb +7 -13
  57. data/lib/graphql/railtie.rb +1 -1
  58. data/lib/graphql/schema/argument.rb +7 -8
  59. data/lib/graphql/schema/build_from_definition.rb +99 -53
  60. data/lib/graphql/schema/directive.rb +1 -1
  61. data/lib/graphql/schema/enum.rb +36 -1
  62. data/lib/graphql/schema/enum_value.rb +1 -1
  63. data/lib/graphql/schema/field.rb +2 -2
  64. data/lib/graphql/schema/input_object.rb +16 -16
  65. data/lib/graphql/schema/interface.rb +2 -1
  66. data/lib/graphql/schema/member/has_arguments.rb +13 -5
  67. data/lib/graphql/schema/member/has_dataloader.rb +60 -0
  68. data/lib/graphql/schema/member/has_directives.rb +1 -1
  69. data/lib/graphql/schema/member/has_fields.rb +1 -1
  70. data/lib/graphql/schema/member/has_interfaces.rb +1 -1
  71. data/lib/graphql/schema/member/scoped.rb +1 -1
  72. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  73. data/lib/graphql/schema/member.rb +1 -0
  74. data/lib/graphql/schema/object.rb +17 -8
  75. data/lib/graphql/schema/resolver.rb +6 -5
  76. data/lib/graphql/schema/subscription.rb +50 -4
  77. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  78. data/lib/graphql/schema/visibility/migration.rb +1 -0
  79. data/lib/graphql/schema/visibility/profile.rb +11 -5
  80. data/lib/graphql/schema/visibility.rb +14 -9
  81. data/lib/graphql/schema/warden.rb +14 -1
  82. data/lib/graphql/schema.rb +63 -35
  83. data/lib/graphql/static_validation/all_rules.rb +1 -1
  84. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  85. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  86. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  87. data/lib/graphql/static_validation/validator.rb +6 -1
  88. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  89. data/lib/graphql/subscriptions/event.rb +12 -1
  90. data/lib/graphql/subscriptions/serialize.rb +1 -1
  91. data/lib/graphql/testing/helpers.rb +5 -2
  92. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  93. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  94. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  95. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  96. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  97. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  98. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  99. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  100. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  101. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  102. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  103. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  104. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  105. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  106. data/lib/graphql/tracing/monitor_trace.rb +285 -0
  107. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  108. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  109. data/lib/graphql/tracing/notifications_trace.rb +182 -34
  110. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  111. data/lib/graphql/tracing/null_trace.rb +9 -0
  112. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  113. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  114. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  115. data/lib/graphql/tracing/platform_trace.rb +5 -0
  116. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  117. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  118. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  119. data/lib/graphql/tracing/scout_trace.rb +32 -55
  120. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  121. data/lib/graphql/tracing/sentry_trace.rb +63 -94
  122. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  123. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  124. data/lib/graphql/tracing/trace.rb +111 -1
  125. data/lib/graphql/tracing.rb +31 -30
  126. data/lib/graphql/types/relay/connection_behaviors.rb +1 -1
  127. data/lib/graphql/types/relay/edge_behaviors.rb +1 -1
  128. data/lib/graphql/version.rb +1 -1
  129. data/lib/graphql.rb +4 -29
  130. metadata +144 -11
  131. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  132. data/lib/graphql/backtrace/trace.rb +0 -93
  133. data/lib/graphql/backtrace/tracer.rb +0 -80
  134. data/lib/graphql/schema/null_mask.rb +0 -11
  135. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -13,6 +13,10 @@ module GraphQL
13
13
  # @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?`)
14
14
  # @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results
15
15
  def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails) ? Rails.env.production? : nil), migration_errors: false)
16
+ profiles&.each { |name, ctx|
17
+ ctx[:visibility_profile] = name
18
+ ctx.freeze
19
+ }
16
20
  schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors)
17
21
  if preload
18
22
  schema.visibility.preload
@@ -81,8 +85,7 @@ module GraphQL
81
85
  types_to_visit.compact!
82
86
  ensure_all_loaded(types_to_visit)
83
87
  @profiles.each do |profile_name, example_ctx|
84
- example_ctx[:visibility_profile] = profile_name
85
- prof = profile_for(example_ctx, profile_name)
88
+ prof = profile_for(example_ctx)
86
89
  prof.all_types # force loading
87
90
  end
88
91
  end
@@ -145,7 +148,7 @@ module GraphQL
145
148
 
146
149
  attr_reader :cached_profiles
147
150
 
148
- def profile_for(context, visibility_profile)
151
+ def profile_for(context, visibility_profile = context[:visibility_profile])
149
152
  if !@profiles.empty?
150
153
  if visibility_profile.nil?
151
154
  if @dynamic
@@ -160,7 +163,8 @@ module GraphQL
160
163
  elsif !@profiles.include?(visibility_profile)
161
164
  raise ArgumentError, "`#{visibility_profile.inspect}` isn't allowed for `visibility_profile:` (must be one of #{@profiles.keys.map(&:inspect).join(", ")}). Or, add `#{visibility_profile.inspect}` to the list of profiles in the schema definition."
162
165
  else
163
- @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: context, schema: @schema)
166
+ profile_ctx = @profiles[visibility_profile]
167
+ @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: profile_ctx, schema: @schema)
164
168
  end
165
169
  elsif context.is_a?(Query::NullContext)
166
170
  top_level_profile
@@ -222,7 +226,9 @@ module GraphQL
222
226
  elsif member.respond_to?(:interface_type_memberships)
223
227
  member.interface_type_memberships.each do |itm|
224
228
  @all_references[itm.abstract_type] << member
225
- @interface_type_memberships[itm.abstract_type] << itm
229
+ # `itm.object_type` may not actually be `member` if this implementation
230
+ # is inherited from a superclass
231
+ @interface_type_memberships[itm.abstract_type] << [itm, member]
226
232
  end
227
233
  elsif member < GraphQL::Schema::Union
228
234
  @unions_for_references << member
@@ -271,13 +277,12 @@ module GraphQL
271
277
 
272
278
  # TODO: somehow don't iterate over all these,
273
279
  # only the ones that may have been modified
274
- @interface_type_memberships.each do |int_type, type_memberships|
280
+ @interface_type_memberships.each do |int_type, type_membership_pairs|
275
281
  referers = @all_references[int_type].select { |r| r.is_a?(GraphQL::Schema::Field) }
276
282
  if !referers.empty?
277
- type_memberships.each do |type_membership|
278
- implementor_type = type_membership.object_type
283
+ type_membership_pairs.each do |(type_membership, impl_type)|
279
284
  # Add new items only:
280
- @all_references[implementor_type] |= referers
285
+ @all_references[impl_type] |= referers
281
286
  end
282
287
  end
283
288
  end
@@ -72,6 +72,7 @@ module GraphQL
72
72
  def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
73
73
  def arguments(owner, ctx); owner.arguments(ctx); end
74
74
  def loadable?(type, ctx); type.visible?(ctx); end
75
+ def loadable_possible_types(type, ctx); type.possible_types(ctx); end
75
76
  def visibility_profile
76
77
  @visibility_profile ||= Warden::VisibilityProfile.new(self)
77
78
  end
@@ -106,6 +107,7 @@ module GraphQL
106
107
  def get_field(parent_type, field_name); @schema.get_field(parent_type, field_name); end
107
108
  def reachable_type?(type_name); true; end
108
109
  def loadable?(type, _ctx); true; end
110
+ def loadable_possible_types(union_type, _ctx); union_type.possible_types; end
109
111
  def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop
110
112
  def possible_types(type_defn); @schema.possible_types(type_defn, Query::NullContext.instance, false); end
111
113
  def interfaces(obj_type); obj_type.interfaces; end
@@ -180,6 +182,10 @@ module GraphQL
180
182
  @warden.loadable?(t, ctx)
181
183
  end
182
184
 
185
+ def loadable_possible_types(t, ctx)
186
+ @warden.loadable_possible_types(t, ctx)
187
+ end
188
+
183
189
  def reachable_type?(type_name)
184
190
  !!@warden.reachable_type?(type_name)
185
191
  end
@@ -204,7 +210,7 @@ module GraphQL
204
210
  @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
205
211
  @visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
206
212
  @visible_and_reachable_type = @unions = @unfiltered_interfaces =
207
- @reachable_type_set = @visibility_profile =
213
+ @reachable_type_set = @visibility_profile = @loadable_possible_types =
208
214
  nil
209
215
  @skip_warning = schema.plugins.any? { |(plugin, _opts)| plugin == GraphQL::Schema::Warden }
210
216
  end
@@ -229,6 +235,13 @@ module GraphQL
229
235
  !reachable_type_set.include?(type) && visible_type?(type)
230
236
  end
231
237
 
238
+ def loadable_possible_types(union_type, _ctx)
239
+ @loadable_possible_types ||= read_through do |t|
240
+ t.possible_types # unfiltered
241
+ end
242
+ @loadable_possible_types[union_type]
243
+ end
244
+
232
245
  # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
233
246
  def get_type(type_name)
234
247
  @visible_types ||= read_through do |name|
@@ -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"
@@ -47,8 +46,6 @@ require "graphql/schema/relay_classic_mutation"
47
46
  require "graphql/schema/subscription"
48
47
  require "graphql/schema/visibility"
49
48
 
50
- GraphQL.ensure_eager_load!
51
-
52
49
  module GraphQL
53
50
  # A GraphQL schema which may be queried with {GraphQL::Query}.
54
51
  #
@@ -75,6 +72,9 @@ module GraphQL
75
72
  class Schema
76
73
  extend GraphQL::Schema::Member::HasAstNode
77
74
  extend GraphQL::Schema::FindInheritedValue
75
+ extend Autoload
76
+
77
+ autoload :BUILT_IN_TYPES, "graphql/schema/built_in_types"
78
78
 
79
79
  class DuplicateNamesError < GraphQL::Error
80
80
  attr_reader :duplicated_name
@@ -111,7 +111,7 @@ module GraphQL
111
111
  # @param parser [Object] An object for handling definition string parsing (must respond to `parse`)
112
112
  # @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
113
113
  # @return [Class] the schema described by `document`
114
- 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: {})
115
115
  # If the file ends in `.graphql` or `.graphqls`, treat it like a filepath
116
116
  if definition_or_path.end_with?(".graphql") || definition_or_path.end_with?(".graphqls")
117
117
  GraphQL::Schema::BuildFromDefinition.from_definition_path(
@@ -120,6 +120,7 @@ module GraphQL
120
120
  default_resolve: default_resolve,
121
121
  parser: parser,
122
122
  using: using,
123
+ base_types: base_types,
123
124
  )
124
125
  else
125
126
  GraphQL::Schema::BuildFromDefinition.from_definition(
@@ -128,6 +129,7 @@ module GraphQL
128
129
  default_resolve: default_resolve,
129
130
  parser: parser,
130
131
  using: using,
132
+ base_types: base_types,
131
133
  )
132
134
  end
133
135
  end
@@ -166,9 +168,6 @@ module GraphQL
166
168
  mods.each { |mod| new_class.include(mod) }
167
169
  new_class.include(DefaultTraceClass)
168
170
  trace_mode(:default, new_class)
169
- backtrace_class = Class.new(new_class)
170
- backtrace_class.include(GraphQL::Backtrace::Trace)
171
- trace_mode(:default_backtrace, backtrace_class)
172
171
  end
173
172
  trace_class_for(:default, build: true)
174
173
  end
@@ -215,11 +214,6 @@ module GraphQL
215
214
  const_set(:DefaultTrace, Class.new(base_class) do
216
215
  include DefaultTraceClass
217
216
  end)
218
- when :default_backtrace
219
- schema_base_class = trace_class_for(:default, build: true)
220
- const_set(:DefaultTraceBacktrace, Class.new(schema_base_class) do
221
- include(GraphQL::Backtrace::Trace)
222
- end)
223
217
  else
224
218
  # First, see if the superclass has a custom-defined class for this.
225
219
  # Then, if it doesn't, use this class's default trace
@@ -829,13 +823,13 @@ module GraphQL
829
823
 
830
824
  attr_writer :validate_timeout
831
825
 
832
- def validate_timeout(new_validate_timeout = nil)
833
- if new_validate_timeout
826
+ def validate_timeout(new_validate_timeout = NOT_CONFIGURED)
827
+ if !NOT_CONFIGURED.equal?(new_validate_timeout)
834
828
  @validate_timeout = new_validate_timeout
835
829
  elsif defined?(@validate_timeout)
836
830
  @validate_timeout
837
831
  else
838
- find_inherited_value(:validate_timeout)
832
+ find_inherited_value(:validate_timeout) || 3
839
833
  end
840
834
  end
841
835
 
@@ -1117,6 +1111,9 @@ module GraphQL
1117
1111
  }
1118
1112
  end
1119
1113
 
1114
+ # @api private
1115
+ attr_accessor :using_backtrace
1116
+
1120
1117
  # @api private
1121
1118
  def handle_or_reraise(context, err)
1122
1119
  handler = Execution::Errors.find_handler_for(self, err.class)
@@ -1130,6 +1127,10 @@ module GraphQL
1130
1127
  end
1131
1128
  handler[:handler].call(err, obj, args, context, field)
1132
1129
  else
1130
+ if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError)
1131
+ err = GraphQL::Backtrace::TracedError.new(err, context)
1132
+ end
1133
+
1133
1134
  raise err
1134
1135
  end
1135
1136
  end
@@ -1299,7 +1300,10 @@ module GraphQL
1299
1300
  def type_error(type_error, ctx)
1300
1301
  case type_error
1301
1302
  when GraphQL::InvalidNullError
1302
- 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
1303
1307
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
1304
1308
  raise type_error
1305
1309
  when GraphQL::IntegerDecodingError
@@ -1367,6 +1371,16 @@ module GraphQL
1367
1371
  }.freeze
1368
1372
  end
1369
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
+
1370
1384
  def tracer(new_tracer, silence_deprecation_warning: false)
1371
1385
  if !silence_deprecation_warning
1372
1386
  warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
@@ -1384,14 +1398,22 @@ module GraphQL
1384
1398
  find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
1385
1399
  end
1386
1400
 
1387
- # Mix `trace_mod` into this schema's `Trace` class so that its methods
1388
- # 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
1389
1411
  #
1390
1412
  # @param trace_mod [Module] A module that implements tracing methods
1391
1413
  # @param mode [Symbol] Trace module will only be used for this trade mode
1392
1414
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1393
1415
  # @return [void]
1394
- # @see GraphQL::Tracing::Trace for available tracing methods
1416
+ # @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
1395
1417
  def trace_with(trace_mod, mode: :default, **options)
1396
1418
  if mode.is_a?(Array)
1397
1419
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
@@ -1441,29 +1463,36 @@ module GraphQL
1441
1463
  #
1442
1464
  # If no `mode:` is given, then {default_trace_mode} will be used.
1443
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
+ #
1444
1469
  # @param mode [Symbol] Trace modules for this trade mode will be included
1445
1470
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1446
1471
  # @return [Tracing::Trace]
1447
1472
  def new_trace(mode: nil, **options)
1448
- target = options[:query] || options[:multiplex]
1449
- mode ||= target && target.context[:trace_mode]
1450
-
1451
- trace_mode = if mode
1452
- mode
1453
- elsif target && target.context[:backtrace]
1454
- if default_trace_mode != :default
1455
- raise ArgumentError, "Can't use `context[:backtrace]` with a custom default trace mode (`#{dm.inspect}`)"
1456
- else
1457
- own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace)
1458
- options_trace_mode = :default
1459
- :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
1460
1482
  end
1461
1483
  else
1462
- 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]
1463
1492
  end
1464
1493
 
1465
- options_trace_mode ||= trace_mode
1466
- 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)
1467
1496
  trace_options = base_trace_options.merge(options)
1468
1497
  trace_class_for_mode = trace_class_for(trace_mode, build: true)
1469
1498
  trace_class_for_mode.new(**trace_options)
@@ -1805,6 +1834,5 @@ module GraphQL
1805
1834
  end
1806
1835
  end
1807
1836
 
1808
- require "graphql/schema/built_in_types"
1809
1837
  require "graphql/schema/loader"
1810
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
  ]
@@ -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
@@ -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
@@ -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
@@ -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.
@@ -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)
@@ -43,18 +43,21 @@ 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)
@@ -1,11 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'graphql/tracing/notifications_trace'
3
+ require "graphql/tracing/notifications_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
- # This implementation forwards events to ActiveSupport::Notifications
8
- # with a `graphql` suffix.
7
+ # This implementation forwards events to ActiveSupport::Notifications with a `graphql` suffix.
8
+ #
9
+ # @example Sending execution events to ActiveSupport::Notifications
10
+ # class MySchema < GraphQL::Schema
11
+ # trace_with(GraphQL::Tracing::ActiveSupportNotificationsTrace)
12
+ # end
13
+ #
14
+ # @example Subscribing to GraphQL events with ActiveSupport::Notifications
15
+ # ActiveSupport::Notifications.subscribe(/graphql/) do |event|
16
+ # pp event.name
17
+ # pp event.payload
18
+ # end
19
+ #
9
20
  module ActiveSupportNotificationsTrace
10
21
  include NotificationsTrace
11
22
  def initialize(engine: ActiveSupport::Notifications, **rest)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'graphql/tracing/notifications_tracing'
3
+ require "graphql/tracing/notifications_tracing"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "graphql/tracing/platform_trace"
4
+
3
5
  module GraphQL
4
6
  module Tracing
5
7
 
@@ -20,14 +22,18 @@ module GraphQL
20
22
  # These GraphQL events will show up as 'graphql.execute' spans
21
23
  EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
22
24
 
25
+
23
26
  # During auto-instrumentation this version of AppOpticsTracing is compared
24
27
  # with the version provided in the appoptics_apm gem, so that the newer
25
28
  # version of the class can be used
26
29
 
30
+
27
31
  def self.version
28
32
  Gem::Version.new('1.0.0')
29
33
  end
30
34
 
35
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
36
+
31
37
  [
32
38
  'lex',
33
39
  'parse',
@@ -55,6 +61,8 @@ module GraphQL
55
61
  RUBY
56
62
  end
57
63
 
64
+ # rubocop:enable Development/NoEvalCop
65
+
58
66
  def execute_field(query:, field:, ast_node:, arguments:, object:)
59
67
  return_type = field.type.unwrap
60
68
  trace_field = if return_type.kind.scalar? || return_type.kind.enum?
@@ -81,7 +89,7 @@ module GraphQL
81
89
  end
82
90
  end
83
91
 
84
- def execute_field_lazy(query:, field:, ast_node:, arguments:, object:)
92
+ def execute_field_lazy(query:, field:, ast_node:, arguments:, object:) # rubocop:disable Development/TraceCallsSuperCop
85
93
  execute_field(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
86
94
  end
87
95
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "graphql/tracing/platform_tracing"
4
+
3
5
  module GraphQL
4
6
  module Tracing
5
7
 
@@ -20,6 +22,11 @@ module GraphQL
20
22
  # These GraphQL events will show up as 'graphql.execute' spans
21
23
  EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
22
24
 
25
+ def initialize(...)
26
+ warn "GraphQL::Tracing::AppOptics tracing is deprecated; update to SolarWindsAPM instead, which uses OpenTelemetry."
27
+ super
28
+ end
29
+
23
30
  # During auto-instrumentation this version of AppOpticsTracing is compared
24
31
  # with the version provided in the appoptics_apm gem, so that the newer
25
32
  # version of the class can be used