graphql 2.4.8 → 2.4.11

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/backtrace/table.rb +95 -55
  3. data/lib/graphql/backtrace.rb +1 -19
  4. data/lib/graphql/current.rb +5 -0
  5. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  6. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  7. data/lib/graphql/dashboard/statics/dashboard.css +3 -0
  8. data/lib/graphql/dashboard/statics/dashboard.js +78 -0
  9. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  10. data/lib/graphql/dashboard/statics/icon.png +0 -0
  11. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  12. data/lib/graphql/dashboard/views/graphql/dashboard/traces/index.html.erb +63 -0
  13. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +60 -0
  14. data/lib/graphql/dashboard.rb +142 -0
  15. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  16. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  17. data/lib/graphql/dataloader/async_dataloader.rb +17 -5
  18. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  19. data/lib/graphql/dataloader/source.rb +2 -2
  20. data/lib/graphql/dataloader.rb +37 -5
  21. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -4
  22. data/lib/graphql/execution/interpreter/runtime.rb +60 -33
  23. data/lib/graphql/execution/interpreter.rb +9 -1
  24. data/lib/graphql/execution/multiplex.rb +0 -4
  25. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  26. data/lib/graphql/invalid_name_error.rb +1 -1
  27. data/lib/graphql/invalid_null_error.rb +6 -12
  28. data/lib/graphql/language/parser.rb +1 -1
  29. data/lib/graphql/query.rb +8 -12
  30. data/lib/graphql/schema/enum.rb +36 -1
  31. data/lib/graphql/schema/input_object.rb +1 -1
  32. data/lib/graphql/schema/interface.rb +1 -0
  33. data/lib/graphql/schema/member/has_dataloader.rb +60 -0
  34. data/lib/graphql/schema/member.rb +1 -0
  35. data/lib/graphql/schema/object.rb +17 -8
  36. data/lib/graphql/schema/resolver.rb +2 -5
  37. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  38. data/lib/graphql/schema/visibility/profile.rb +5 -5
  39. data/lib/graphql/schema/visibility.rb +14 -9
  40. data/lib/graphql/schema.rb +54 -28
  41. data/lib/graphql/static_validation/validator.rb +6 -1
  42. data/lib/graphql/subscriptions/serialize.rb +1 -3
  43. data/lib/graphql/tracing/active_support_notifications_trace.rb +6 -2
  44. data/lib/graphql/tracing/appoptics_trace.rb +3 -1
  45. data/lib/graphql/tracing/appsignal_trace.rb +6 -0
  46. data/lib/graphql/tracing/data_dog_trace.rb +5 -0
  47. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  48. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  49. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  50. data/lib/graphql/tracing/new_relic_trace.rb +147 -41
  51. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  52. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  53. data/lib/graphql/tracing/perfetto_trace.rb +737 -0
  54. data/lib/graphql/tracing/prometheus_trace.rb +22 -0
  55. data/lib/graphql/tracing/scout_trace.rb +6 -0
  56. data/lib/graphql/tracing/sentry_trace.rb +5 -0
  57. data/lib/graphql/tracing/statsd_trace.rb +9 -0
  58. data/lib/graphql/tracing/trace.rb +125 -1
  59. data/lib/graphql/tracing.rb +2 -0
  60. data/lib/graphql/version.rb +1 -1
  61. data/lib/graphql.rb +3 -0
  62. metadata +148 -10
  63. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  64. data/lib/graphql/backtrace/trace.rb +0 -93
  65. data/lib/graphql/backtrace/tracer.rb +0 -80
  66. data/lib/graphql/schema/null_mask.rb +0 -11
@@ -2,7 +2,7 @@
2
2
  module GraphQL
3
3
  # Raised automatically when a field's resolve function returns `nil`
4
4
  # for a non-null field.
5
- class InvalidNullError < GraphQL::RuntimeTypeError
5
+ class InvalidNullError < GraphQL::Error
6
6
  # @return [GraphQL::BaseType] The owner of {#field}
7
7
  attr_reader :parent_type
8
8
 
@@ -12,23 +12,17 @@ module GraphQL
12
12
  # @return [nil, GraphQL::ExecutionError] The invalid value for this field
13
13
  attr_reader :value
14
14
 
15
- def initialize(parent_type, field, value)
15
+ # @return [GraphQL::Language::Nodes::Field] the field where the error occurred
16
+ attr_reader :ast_node
17
+
18
+ def initialize(parent_type, field, value, ast_node)
16
19
  @parent_type = parent_type
17
20
  @field = field
18
21
  @value = value
22
+ @ast_node = ast_node
19
23
  super("Cannot return null for non-nullable field #{@parent_type.graphql_name}.#{@field.graphql_name}")
20
24
  end
21
25
 
22
- # @return [Hash] An entry for the response's "errors" key
23
- def to_h
24
- { "message" => message }
25
- end
26
-
27
- # @deprecated always false
28
- def parent_error?
29
- false
30
- end
31
-
32
26
  class << self
33
27
  attr_accessor :parent_class
34
28
 
@@ -161,7 +161,7 @@ module GraphQL
161
161
  expect_token(:VAR_SIGN)
162
162
  var_name = parse_name
163
163
  expect_token(:COLON)
164
- var_type = self.type
164
+ var_type = self.type || raise_parse_error("Missing type definition for variable: $#{var_name}")
165
165
  default_value = if at?(:EQUALS)
166
166
  advance_token
167
167
  value
data/lib/graphql/query.rb CHANGED
@@ -97,21 +97,22 @@ module GraphQL
97
97
  # @param root_value [Object] the object used to resolve fields on the root type
98
98
  # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
99
99
  # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
100
- # @param visibility_profile [Symbol]
100
+ # @param visibility_profile [Symbol] Another way to assign `context[:visibility_profile]`
101
101
  def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, visibility_profile: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_visibility_profile: nil)
102
102
  # Even if `variables: nil` is passed, use an empty hash for simpler logic
103
103
  variables ||= {}
104
104
  @schema = schema
105
105
  @context = schema.context_class.new(query: self, values: context)
106
+ if visibility_profile
107
+ @context[:visibility_profile] ||= visibility_profile
108
+ end
106
109
 
107
110
  if use_visibility_profile.nil?
108
111
  use_visibility_profile = warden ? false : schema.use_visibility_profile?
109
112
  end
110
113
 
111
- @visibility_profile = visibility_profile
112
-
113
114
  if use_visibility_profile
114
- @visibility_profile = @schema.visibility.profile_for(@context, visibility_profile)
115
+ @visibility_profile = @schema.visibility.profile_for(@context)
115
116
  @warden = Schema::Warden::NullWarden.new(context: @context, schema: @schema)
116
117
  else
117
118
  @visibility_profile = nil
@@ -127,14 +128,6 @@ module GraphQL
127
128
  context_tracers = (context ? context.fetch(:tracers, []) : [])
128
129
  @tracers = schema.tracers + context_tracers
129
130
 
130
- # Support `ctx[:backtrace] = true` for wrapping backtraces
131
- if context && context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer)
132
- if schema.trace_class <= GraphQL::Tracing::CallLegacyTracers
133
- context_tracers += [GraphQL::Backtrace::Tracer]
134
- @tracers << GraphQL::Backtrace::Tracer
135
- end
136
- end
137
-
138
131
  if !context_tracers.empty? && !(schema.trace_class <= GraphQL::Tracing::CallLegacyTracers)
139
132
  raise ArgumentError, "context[:tracers] are not supported without `trace_with(GraphQL::Tracing::CallLegacyTracers)` in the schema configuration, please add it."
140
133
  end
@@ -448,6 +441,7 @@ module GraphQL
448
441
  @warden ||= @schema.warden_class.new(schema: @schema, context: @context)
449
442
  parse_error = nil
450
443
  @document ||= begin
444
+ current_trace.begin_parse(query_string)
451
445
  if query_string
452
446
  GraphQL.parse(query_string, trace: self.current_trace, max_tokens: @schema.max_query_string_tokens)
453
447
  end
@@ -455,6 +449,8 @@ module GraphQL
455
449
  parse_error = err
456
450
  @schema.parse_error(err, @context)
457
451
  nil
452
+ ensure
453
+ current_trace.end_parse(query_string)
458
454
  end
459
455
 
460
456
  @fragments = {}
@@ -61,12 +61,19 @@ module GraphQL
61
61
  # @option kwargs [String] :description, the GraphQL description for this value, present in documentation
62
62
  # @option kwargs [String] :comment, the GraphQL comment for this value, present in documentation
63
63
  # @option kwargs [::Object] :value the translated Ruby value for this object (defaults to `graphql_name`)
64
+ # @option kwargs [::Object] :value_method, the method name to fetch `graphql_name` (defaults to `graphql_name.downcase`)
64
65
  # @option kwargs [String] :deprecation_reason if this object is deprecated, include a message here
66
+ # @param value_method [Symbol, false] A method to generate for this value, or `false` to skip generation
65
67
  # @return [void]
66
68
  # @see {Schema::EnumValue} which handles these inputs by default
67
- def value(*args, **kwargs, &block)
69
+ def value(*args, value_method: nil, **kwargs, &block)
68
70
  kwargs[:owner] = self
69
71
  value = enum_value_class.new(*args, **kwargs, &block)
72
+
73
+ if value_method || (value_methods && value_method != false)
74
+ generate_value_method(value, value_method)
75
+ end
76
+
70
77
  key = value.graphql_name
71
78
  prev_value = own_values[key]
72
79
  case prev_value
@@ -154,6 +161,18 @@ module GraphQL
154
161
  end
155
162
  end
156
163
 
164
+ def value_methods(new_value = NOT_CONFIGURED)
165
+ if NOT_CONFIGURED.equal?(new_value)
166
+ if @value_methods != nil
167
+ @value_methods
168
+ else
169
+ find_inherited_value(:value_methods, false)
170
+ end
171
+ else
172
+ @value_methods = new_value
173
+ end
174
+ end
175
+
157
176
  def kind
158
177
  GraphQL::TypeKinds::ENUM
159
178
  end
@@ -215,6 +234,7 @@ module GraphQL
215
234
  # because they would end up with names like `#<Class0x1234>::UnresolvedValueError` which messes up bug trackers
216
235
  child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
217
236
  end
237
+ child_class.class_eval { @value_methods = nil }
218
238
  super
219
239
  end
220
240
 
@@ -223,6 +243,21 @@ module GraphQL
223
243
  def own_values
224
244
  @own_values ||= {}
225
245
  end
246
+
247
+ def generate_value_method(value, configured_value_method)
248
+ return if configured_value_method == false
249
+
250
+ value_method_name = configured_value_method || value.graphql_name.downcase
251
+
252
+ if respond_to?(value_method_name.to_sym)
253
+ warn "Failed to define value method for :#{value_method_name}, because " \
254
+ "#{value.owner.name || value.owner.graphql_name} already responds to that method. Use `value_method:` to override the method name " \
255
+ "or `value_method: false` to disable Enum value method generation."
256
+ return
257
+ end
258
+
259
+ instance_eval("def #{value_method_name}; #{value.graphql_name.inspect}; end;", __FILE__, __LINE__)
260
+ end
226
261
  end
227
262
 
228
263
  enum_value_class(GraphQL::Schema::EnumValue)
@@ -156,7 +156,7 @@ module GraphQL
156
156
  def #{method_name}
157
157
  self[#{method_name.inspect}]
158
158
  end
159
- alias_method :#{method_name}, :#{method_name}
159
+ alias_method #{method_name.inspect}, #{method_name.inspect}
160
160
  RUBY
161
161
  end
162
162
  argument_defn
@@ -13,6 +13,7 @@ module GraphQL
13
13
  include GraphQL::Schema::Member::Scoped
14
14
  include GraphQL::Schema::Member::HasAstNode
15
15
  include GraphQL::Schema::Member::HasUnresolvedTypeError
16
+ include GraphQL::Schema::Member::HasDataloader
16
17
  include GraphQL::Schema::Member::HasDirectives
17
18
  include GraphQL::Schema::Member::HasInterfaces
18
19
 
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module HasDataloader
7
+ # @return [GraphQL::Dataloader] The dataloader for the currently-running query
8
+ def dataloader
9
+ context.dataloader
10
+ end
11
+
12
+ # A shortcut method for loading a key from a source.
13
+ # Identical to `dataloader.with(source_class, *source_args).load(load_key)`
14
+ # @param source_class [Class<GraphQL::Dataloader::Source>]
15
+ # @param source_args [Array<Object>] Any extra parameters defined in `source_class`'s `initialize` method
16
+ # @param load_key [Object] The key to look up using `def fetch`
17
+ def dataload(source_class, *source_args, load_key)
18
+ dataloader.with(source_class, *source_args).load(load_key)
19
+ end
20
+
21
+ # Find an object with ActiveRecord via {Dataloader::ActiveRecordSource}.
22
+ # @param model [Class<ActiveRecord::Base>]
23
+ # @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided
24
+ # @param find_by [Symbol, String] A column name to look the record up by. (Defaults to the model's primary key.)
25
+ # @return [ActiveRecord::Base, nil]
26
+ # @example Finding a record by ID
27
+ # dataload_record(Post, 5) # Like `Post.find(5)`, but dataloaded
28
+ # @example Finding a record by another attribute
29
+ # dataload_record(User, "matz", find_by: :handle) # Like `User.find_by(handle: "matz")`, but dataloaded
30
+ def dataload_record(model, find_by_value, find_by: nil)
31
+ source = if find_by
32
+ dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by)
33
+ else
34
+ dataloader.with(Dataloader::ActiveRecordSource, model)
35
+ end
36
+
37
+ source.load(find_by_value)
38
+ end
39
+
40
+ # Look up an associated record using a Rails association.
41
+ # @param association_name [Symbol] A `belongs_to` or `has_one` association. (If a `has_many` association is named here, it will be selected without pagination.)
42
+ # @param record [ActiveRecord::Base] The object that the association belongs to.
43
+ # @param scope [ActiveRecord::Relation] A scope to look up the associated record in
44
+ # @return [ActiveRecord::Base, nil] The associated record, if there is one
45
+ # @example Looking up a belongs_to on the current object
46
+ # dataload_association(:parent) # Equivalent to `object.parent`, but dataloaded
47
+ # @example Looking up an associated record on some other object
48
+ # dataload_association(:post, comment) # Equivalent to `comment.post`, but dataloaded
49
+ def dataload_association(record = object, association_name, scope: nil)
50
+ source = if scope
51
+ dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope)
52
+ else
53
+ dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name)
54
+ end
55
+ source.load(record)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -2,6 +2,7 @@
2
2
  require 'graphql/schema/member/base_dsl_methods'
3
3
  require 'graphql/schema/member/graphql_type_names'
4
4
  require 'graphql/schema/member/has_ast_node'
5
+ require 'graphql/schema/member/has_dataloader'
5
6
  require 'graphql/schema/member/has_directives'
6
7
  require 'graphql/schema/member/has_deprecation_reason'
7
8
  require 'graphql/schema/member/has_interfaces'
@@ -7,6 +7,7 @@ module GraphQL
7
7
  class Object < GraphQL::Schema::Member
8
8
  extend GraphQL::Schema::Member::HasFields
9
9
  extend GraphQL::Schema::Member::HasInterfaces
10
+ include Member::HasDataloader
10
11
 
11
12
  # Raised when an Object doesn't have any field defined and hasn't explicitly opted out of this requirement
12
13
  class FieldsAreRequiredError < GraphQL::Error
@@ -65,20 +66,28 @@ module GraphQL
65
66
  # @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
66
67
  # @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
67
68
  def authorized_new(object, context)
68
- maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
69
- begin
70
- authorized?(object, context)
71
- rescue GraphQL::UnauthorizedError => err
72
- context.schema.unauthorized_object(err)
73
- rescue StandardError => err
74
- context.query.handle_or_reraise(err)
69
+ context.query.current_trace.begin_authorized(self, object, context)
70
+ begin
71
+ maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
72
+ begin
73
+ authorized?(object, context)
74
+ rescue GraphQL::UnauthorizedError => err
75
+ context.schema.unauthorized_object(err)
76
+ rescue StandardError => err
77
+ context.query.handle_or_reraise(err)
78
+ end
75
79
  end
80
+ ensure
81
+ context.query.current_trace.end_authorized(self, object, context, maybe_lazy_auth_val)
76
82
  end
77
83
 
78
84
  auth_val = if context.schema.lazy?(maybe_lazy_auth_val)
79
85
  GraphQL::Execution::Lazy.new do
86
+ context.query.current_trace.begin_authorized(self, object, context)
80
87
  context.query.current_trace.authorized_lazy(query: context.query, type: self, object: object) do
81
- context.schema.sync_lazy(maybe_lazy_auth_val)
88
+ res = context.schema.sync_lazy(maybe_lazy_auth_val)
89
+ context.query.current_trace.end_authorized(self, object, context, res)
90
+ res
82
91
  end
83
92
  end
84
93
  else
@@ -22,11 +22,13 @@ module GraphQL
22
22
  include Schema::Member::GraphQLTypeNames
23
23
  # Really we only need description & comment from here, but:
24
24
  extend Schema::Member::BaseDSLMethods
25
+ extend Member::BaseDSLMethods::ConfigurationExtension
25
26
  extend GraphQL::Schema::Member::HasArguments
26
27
  extend GraphQL::Schema::Member::HasValidators
27
28
  include Schema::Member::HasPath
28
29
  extend Schema::Member::HasPath
29
30
  extend Schema::Member::HasDirectives
31
+ include Schema::Member::HasDataloader
30
32
 
31
33
  # @param object [Object] The application object that this field is being resolved on
32
34
  # @param context [GraphQL::Query::Context]
@@ -49,11 +51,6 @@ module GraphQL
49
51
  # @return [GraphQL::Query::Context]
50
52
  attr_reader :context
51
53
 
52
- # @return [GraphQL::Dataloader]
53
- def dataloader
54
- context.dataloader
55
- end
56
-
57
54
  # @return [GraphQL::Schema::Field]
58
55
  attr_reader :field
59
56
 
@@ -51,19 +51,36 @@ module GraphQL
51
51
  end
52
52
 
53
53
  def validate(_object, context, value)
54
- matched_conditions = 0
54
+ fully_matched_conditions = 0
55
+ partially_matched_conditions = 0
55
56
 
56
57
  if !value.nil?
57
58
  @one_of.each do |one_of_condition|
58
59
  case one_of_condition
59
60
  when Symbol
60
61
  if value.key?(one_of_condition)
61
- matched_conditions += 1
62
+ fully_matched_conditions += 1
62
63
  end
63
64
  when Array
64
- if one_of_condition.all? { |k| value.key?(k) }
65
- matched_conditions += 1
66
- break
65
+ any_match = false
66
+ full_match = true
67
+
68
+ one_of_condition.each do |k|
69
+ if value.key?(k)
70
+ any_match = true
71
+ else
72
+ full_match = false
73
+ end
74
+ end
75
+
76
+ partial_match = !full_match && any_match
77
+
78
+ if full_match
79
+ fully_matched_conditions += 1
80
+ end
81
+
82
+ if partial_match
83
+ partially_matched_conditions += 1
67
84
  end
68
85
  else
69
86
  raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
@@ -71,7 +88,7 @@ module GraphQL
71
88
  end
72
89
  end
73
90
 
74
- if matched_conditions == 1
91
+ if fully_matched_conditions == 1 && partially_matched_conditions == 0
75
92
  nil # OK
76
93
  else
77
94
  @message || build_message(context)
@@ -18,7 +18,7 @@ module GraphQL
18
18
  if ctx.respond_to?(:types) && (types = ctx.types).is_a?(self)
19
19
  types
20
20
  else
21
- schema.visibility.profile_for(ctx, nil)
21
+ schema.visibility.profile_for(ctx)
22
22
  end
23
23
  end
24
24
 
@@ -159,7 +159,7 @@ module GraphQL
159
159
  end
160
160
  end
161
161
  end
162
- visible_f.ensure_loaded
162
+ visible_f&.ensure_loaded
163
163
  elsif f && @cached_visible_fields[owner][f.ensure_loaded]
164
164
  f
165
165
  else
@@ -319,9 +319,9 @@ module GraphQL
319
319
  case type.kind.name
320
320
  when "INTERFACE"
321
321
  pts = []
322
- @schema.visibility.all_interface_type_memberships[type].each do |itm|
323
- if @cached_visible[itm] && (ot = itm.object_type) && @cached_visible[ot] && referenced?(ot)
324
- pts << ot
322
+ @schema.visibility.all_interface_type_memberships[type].each do |(itm, impl_type)|
323
+ if @cached_visible[itm] && @cached_visible[impl_type] && referenced?(impl_type)
324
+ pts << impl_type
325
325
  end
326
326
  end
327
327
  pts
@@ -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
@@ -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"
@@ -167,9 +166,6 @@ module GraphQL
167
166
  mods.each { |mod| new_class.include(mod) }
168
167
  new_class.include(DefaultTraceClass)
169
168
  trace_mode(:default, new_class)
170
- backtrace_class = Class.new(new_class)
171
- backtrace_class.include(GraphQL::Backtrace::Trace)
172
- trace_mode(:default_backtrace, backtrace_class)
173
169
  end
174
170
  trace_class_for(:default, build: true)
175
171
  end
@@ -216,11 +212,6 @@ module GraphQL
216
212
  const_set(:DefaultTrace, Class.new(base_class) do
217
213
  include DefaultTraceClass
218
214
  end)
219
- when :default_backtrace
220
- schema_base_class = trace_class_for(:default, build: true)
221
- const_set(:DefaultTraceBacktrace, Class.new(schema_base_class) do
222
- include(GraphQL::Backtrace::Trace)
223
- end)
224
215
  else
225
216
  # First, see if the superclass has a custom-defined class for this.
226
217
  # Then, if it doesn't, use this class's default trace
@@ -1118,6 +1109,9 @@ module GraphQL
1118
1109
  }
1119
1110
  end
1120
1111
 
1112
+ # @api private
1113
+ attr_accessor :using_backtrace
1114
+
1121
1115
  # @api private
1122
1116
  def handle_or_reraise(context, err)
1123
1117
  handler = Execution::Errors.find_handler_for(self, err.class)
@@ -1131,6 +1125,10 @@ module GraphQL
1131
1125
  end
1132
1126
  handler[:handler].call(err, obj, args, context, field)
1133
1127
  else
1128
+ if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError)
1129
+ err = GraphQL::Backtrace::TracedError.new(err, context)
1130
+ end
1131
+
1134
1132
  raise err
1135
1133
  end
1136
1134
  end
@@ -1300,7 +1298,10 @@ module GraphQL
1300
1298
  def type_error(type_error, ctx)
1301
1299
  case type_error
1302
1300
  when GraphQL::InvalidNullError
1303
- ctx.errors << type_error
1301
+ execution_error = GraphQL::ExecutionError.new(type_error.message, ast_node: type_error.ast_node)
1302
+ execution_error.path = ctx[:current_path]
1303
+
1304
+ ctx.errors << execution_error
1304
1305
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
1305
1306
  raise type_error
1306
1307
  when GraphQL::IntegerDecodingError
@@ -1368,6 +1369,16 @@ module GraphQL
1368
1369
  }.freeze
1369
1370
  end
1370
1371
 
1372
+ # @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema
1373
+ attr_accessor :detailed_trace
1374
+
1375
+ # @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex})
1376
+ # @return [Boolean] When `true`, save a detailed trace for this query.
1377
+ # @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true
1378
+ def detailed_trace?(query)
1379
+ raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition."
1380
+ end
1381
+
1371
1382
  def tracer(new_tracer, silence_deprecation_warning: false)
1372
1383
  if !silence_deprecation_warning
1373
1384
  warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
@@ -1385,14 +1396,22 @@ module GraphQL
1385
1396
  find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
1386
1397
  end
1387
1398
 
1388
- # Mix `trace_mod` into this schema's `Trace` class so that its methods
1389
- # will be called at runtime.
1399
+ # Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime.
1400
+ #
1401
+ # You can attach a module to run in only _some_ circumstances by using `mode:`. When a module is added with `mode:`,
1402
+ # it will only run for queries with a matching `context[:trace_mode]`.
1403
+ #
1404
+ # Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration).
1405
+ #
1406
+ # @example Adding a trace in a special mode
1407
+ # # only runs when `query.context[:trace_mode]` is `:special`
1408
+ # trace_with SpecialTrace, mode: :special
1390
1409
  #
1391
1410
  # @param trace_mod [Module] A module that implements tracing methods
1392
1411
  # @param mode [Symbol] Trace module will only be used for this trade mode
1393
1412
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1394
1413
  # @return [void]
1395
- # @see GraphQL::Tracing::Trace for available tracing methods
1414
+ # @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
1396
1415
  def trace_with(trace_mod, mode: :default, **options)
1397
1416
  if mode.is_a?(Array)
1398
1417
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
@@ -1442,29 +1461,36 @@ module GraphQL
1442
1461
  #
1443
1462
  # If no `mode:` is given, then {default_trace_mode} will be used.
1444
1463
  #
1464
+ # If this schema is using {Tracing::DetailedTrace} and {.detailed_trace?} returns `true`, then
1465
+ # DetailedTrace's mode will override the passed-in `mode`.
1466
+ #
1445
1467
  # @param mode [Symbol] Trace modules for this trade mode will be included
1446
1468
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1447
1469
  # @return [Tracing::Trace]
1448
1470
  def new_trace(mode: nil, **options)
1449
- target = options[:query] || options[:multiplex]
1450
- mode ||= target && target.context[:trace_mode]
1451
-
1452
- trace_mode = if mode
1453
- mode
1454
- elsif target && target.context[:backtrace]
1455
- if default_trace_mode != :default
1456
- raise ArgumentError, "Can't use `context[:backtrace]` with a custom default trace mode (`#{dm.inspect}`)"
1457
- else
1458
- own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace)
1459
- options_trace_mode = :default
1460
- :default_backtrace
1471
+ should_sample = if detailed_trace
1472
+ if (query = options[:query])
1473
+ detailed_trace?(query)
1474
+ elsif (multiplex = options[:multiplex])
1475
+ if multiplex.queries.length == 1
1476
+ detailed_trace?(multiplex.queries.first)
1477
+ else
1478
+ detailed_trace?(multiplex)
1479
+ end
1461
1480
  end
1462
1481
  else
1463
- default_trace_mode
1482
+ false
1483
+ end
1484
+
1485
+ if should_sample
1486
+ mode = detailed_trace.trace_mode
1487
+ else
1488
+ target = options[:query] || options[:multiplex]
1489
+ mode ||= target && target.context[:trace_mode]
1464
1490
  end
1465
1491
 
1466
- options_trace_mode ||= trace_mode
1467
- base_trace_options = trace_options_for(options_trace_mode)
1492
+ trace_mode = mode || default_trace_mode
1493
+ base_trace_options = trace_options_for(trace_mode)
1468
1494
  trace_options = base_trace_options.merge(options)
1469
1495
  trace_class_for_mode = trace_class_for(trace_mode, build: true)
1470
1496
  trace_class_for_mode.new(**trace_options)
@@ -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.