graphql 2.0.20 → 2.0.22

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/backtrace/trace.rb +96 -0
  3. data/lib/graphql/backtrace.rb +6 -1
  4. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  5. data/lib/graphql/execution/interpreter/arguments_cache.rb +33 -33
  6. data/lib/graphql/execution/interpreter/runtime.rb +274 -209
  7. data/lib/graphql/execution/interpreter.rb +2 -3
  8. data/lib/graphql/execution/lookahead.rb +1 -1
  9. data/lib/graphql/filter.rb +8 -2
  10. data/lib/graphql/language/document_from_schema_definition.rb +37 -17
  11. data/lib/graphql/language/lexer.rb +5 -3
  12. data/lib/graphql/language/nodes.rb +2 -2
  13. data/lib/graphql/language/parser.rb +475 -458
  14. data/lib/graphql/language/parser.y +5 -1
  15. data/lib/graphql/pagination/connection.rb +5 -5
  16. data/lib/graphql/query/context.rb +22 -12
  17. data/lib/graphql/query/null_context.rb +4 -1
  18. data/lib/graphql/query.rb +25 -11
  19. data/lib/graphql/schema/argument.rb +12 -14
  20. data/lib/graphql/schema/build_from_definition.rb +15 -3
  21. data/lib/graphql/schema/enum_value.rb +2 -5
  22. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  23. data/lib/graphql/schema/field.rb +17 -16
  24. data/lib/graphql/schema/field_extension.rb +1 -4
  25. data/lib/graphql/schema/find_inherited_value.rb +2 -7
  26. data/lib/graphql/schema/input_object.rb +1 -1
  27. data/lib/graphql/schema/member/has_arguments.rb +10 -8
  28. data/lib/graphql/schema/member/has_directives.rb +4 -6
  29. data/lib/graphql/schema/member/has_fields.rb +80 -36
  30. data/lib/graphql/schema/member/has_validators.rb +2 -2
  31. data/lib/graphql/schema/object.rb +1 -1
  32. data/lib/graphql/schema/printer.rb +3 -1
  33. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  34. data/lib/graphql/schema/resolver.rb +8 -8
  35. data/lib/graphql/schema/validator.rb +1 -1
  36. data/lib/graphql/schema/warden.rb +11 -3
  37. data/lib/graphql/schema.rb +41 -12
  38. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
  39. data/lib/graphql/static_validation/rules/fields_will_merge.rb +2 -2
  40. data/lib/graphql/tracing/appsignal_trace.rb +6 -0
  41. data/lib/graphql/tracing/legacy_trace.rb +65 -0
  42. data/lib/graphql/tracing/notifications_trace.rb +5 -1
  43. data/lib/graphql/tracing/platform_trace.rb +21 -19
  44. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +1 -1
  45. data/lib/graphql/tracing/trace.rb +75 -0
  46. data/lib/graphql/tracing.rb +4 -123
  47. data/lib/graphql/version.rb +1 -1
  48. data/lib/graphql.rb +4 -0
  49. data/readme.md +1 -1
  50. metadata +6 -3
@@ -14,42 +14,6 @@ module GraphQL
14
14
  field_defn
15
15
  end
16
16
 
17
- # @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
18
- def fields(context = GraphQL::Query::NullContext)
19
- warden = Warden.from_context(context)
20
- is_object = self.respond_to?(:kind) && self.kind.object?
21
- # Local overrides take precedence over inherited fields
22
- visible_fields = {}
23
- for ancestor in ancestors
24
- if ancestor.respond_to?(:own_fields) &&
25
- (is_object ? visible_interface_implementation?(ancestor, context, warden) : true)
26
-
27
- ancestor.own_fields.each do |field_name, fields_entry|
28
- # Choose the most local definition that passes `.visible?` --
29
- # stop checking for fields by name once one has been found.
30
- if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
31
- visible_fields[field_name] = f
32
- end
33
- end
34
- end
35
- end
36
- visible_fields
37
- end
38
-
39
- def get_field(field_name, context = GraphQL::Query::NullContext)
40
- warden = Warden.from_context(context)
41
- is_object = self.respond_to?(:kind) && self.kind.object?
42
- for ancestor in ancestors
43
- if ancestor.respond_to?(:own_fields) &&
44
- (is_object ? visible_interface_implementation?(ancestor, context, warden) : true) &&
45
- (f_entry = ancestor.own_fields[field_name]) &&
46
- (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
47
- return f
48
- end
49
- end
50
- nil
51
- end
52
-
53
17
  # A list of Ruby keywords.
54
18
  #
55
19
  # @api private
@@ -132,6 +96,86 @@ module GraphQL
132
96
  all_fields
133
97
  end
134
98
 
99
+ module InterfaceMethods
100
+ def get_field(field_name, context = GraphQL::Query::NullContext)
101
+ warden = Warden.from_context(context)
102
+ for ancestor in ancestors
103
+ if ancestor.respond_to?(:own_fields) &&
104
+ (f_entry = ancestor.own_fields[field_name]) &&
105
+ (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
106
+ return f
107
+ end
108
+ end
109
+ nil
110
+ end
111
+
112
+ # @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
113
+ def fields(context = GraphQL::Query::NullContext)
114
+ warden = Warden.from_context(context)
115
+ # Local overrides take precedence over inherited fields
116
+ visible_fields = {}
117
+ for ancestor in ancestors
118
+ if ancestor.respond_to?(:own_fields)
119
+ ancestor.own_fields.each do |field_name, fields_entry|
120
+ # Choose the most local definition that passes `.visible?` --
121
+ # stop checking for fields by name once one has been found.
122
+ if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
123
+ visible_fields[field_name] = f
124
+ end
125
+ end
126
+ end
127
+ end
128
+ visible_fields
129
+ end
130
+ end
131
+
132
+ module ObjectMethods
133
+ def get_field(field_name, context = GraphQL::Query::NullContext)
134
+ # Objects need to check that the interface implementation is visible, too
135
+ warden = Warden.from_context(context)
136
+ for ancestor in ancestors
137
+ if ancestor.respond_to?(:own_fields) &&
138
+ visible_interface_implementation?(ancestor, context, warden) &&
139
+ (f_entry = ancestor.own_fields[field_name]) &&
140
+ (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
141
+ return f
142
+ end
143
+ end
144
+ nil
145
+ end
146
+
147
+ # @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
148
+ def fields(context = GraphQL::Query::NullContext)
149
+ # Objects need to check that the interface implementation is visible, too
150
+ warden = Warden.from_context(context)
151
+ # Local overrides take precedence over inherited fields
152
+ visible_fields = {}
153
+ for ancestor in ancestors
154
+ if ancestor.respond_to?(:own_fields) && visible_interface_implementation?(ancestor, context, warden)
155
+ ancestor.own_fields.each do |field_name, fields_entry|
156
+ # Choose the most local definition that passes `.visible?` --
157
+ # stop checking for fields by name once one has been found.
158
+ if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
159
+ visible_fields[field_name] = f
160
+ end
161
+ end
162
+ end
163
+ end
164
+ visible_fields
165
+ end
166
+ end
167
+
168
+ def self.included(child_class)
169
+ # Included in an interface definition methods module
170
+ child_class.include(InterfaceMethods)
171
+ super
172
+ end
173
+
174
+ def self.extended(child_class)
175
+ child_class.extend(ObjectMethods)
176
+ super
177
+ end
178
+
135
179
  private
136
180
 
137
181
  def inherited(subclass)
@@ -3,7 +3,7 @@ module GraphQL
3
3
  class Schema
4
4
  class Member
5
5
  module HasValidators
6
- include Schema::FindInheritedValue::EmptyObjects
6
+ include GraphQL::EmptyObjects
7
7
 
8
8
  # Build {GraphQL::Schema::Validator}s based on the given configuration
9
9
  # and use them for this schema member
@@ -28,7 +28,7 @@ module GraphQL
28
28
  end
29
29
 
30
30
  module ClassValidators
31
- include Schema::FindInheritedValue::EmptyObjects
31
+ include GraphQL::EmptyObjects
32
32
 
33
33
  def validators
34
34
  inherited_validators = superclass.validators
@@ -68,7 +68,7 @@ module GraphQL
68
68
  maybe_lazy_auth_val
69
69
  end
70
70
 
71
- context.schema.after_lazy(auth_val) do |is_authorized|
71
+ context.query.after_lazy(auth_val) do |is_authorized|
72
72
  if is_authorized
73
73
  self.new(object, context)
74
74
  else
@@ -57,12 +57,14 @@ module GraphQL
57
57
  query_root = Class.new(GraphQL::Schema::Object) do
58
58
  graphql_name "Root"
59
59
  field :throwaway_field, String
60
+ def self.visible?(ctx)
61
+ false
62
+ end
60
63
  end
61
64
  schema = Class.new(GraphQL::Schema) { query(query_root) }
62
65
 
63
66
  introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new(
64
67
  schema,
65
- except: ->(member, _) { member.graphql_name == "Root" },
66
68
  include_introspection_types: true,
67
69
  include_built_in_directives: true,
68
70
  ).document
@@ -60,7 +60,7 @@ module GraphQL
60
60
  super()
61
61
  end
62
62
 
63
- context.schema.after_lazy(return_value) do |return_hash|
63
+ context.query.after_lazy(return_value) do |return_hash|
64
64
  # It might be an error
65
65
  if return_hash.is_a?(Hash)
66
66
  return_hash[:client_mutation_id] = client_mutation_id
@@ -70,7 +70,7 @@ module GraphQL
70
70
  else
71
71
  ready?
72
72
  end
73
- context.schema.after_lazy(ready_val) do |is_ready, ready_early_return|
73
+ context.query.after_lazy(ready_val) do |is_ready, ready_early_return|
74
74
  if ready_early_return
75
75
  if is_ready != false
76
76
  raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{ready_early_return.inspect}]"
@@ -81,7 +81,7 @@ module GraphQL
81
81
  # Then call each prepare hook, which may return a different value
82
82
  # for that argument, or may return a lazy object
83
83
  load_arguments_val = load_arguments(args)
84
- context.schema.after_lazy(load_arguments_val) do |loaded_args|
84
+ context.query.after_lazy(load_arguments_val) do |loaded_args|
85
85
  @prepared_arguments = loaded_args
86
86
  Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field)
87
87
  # Then call `authorized?`, which may raise or may return a lazy object
@@ -90,7 +90,7 @@ module GraphQL
90
90
  else
91
91
  authorized?
92
92
  end
93
- context.schema.after_lazy(authorized_val) do |(authorized_result, early_return)|
93
+ context.query.after_lazy(authorized_val) do |(authorized_result, early_return)|
94
94
  # If the `authorized?` returned two values, `false, early_return`,
95
95
  # then use the early return value instead of continuing
96
96
  if early_return
@@ -185,7 +185,7 @@ module GraphQL
185
185
  if arg_defn
186
186
  prepped_value = prepared_args[key] = arg_defn.load_and_authorize_value(self, value, context)
187
187
  if context.schema.lazy?(prepped_value)
188
- prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
188
+ prepare_lazies << context.query.after_lazy(prepped_value) do |finished_prepped_value|
189
189
  prepared_args[key] = finished_prepped_value
190
190
  end
191
191
  end
@@ -311,8 +311,8 @@ module GraphQL
311
311
  # (`nil` means "unlimited max page size".)
312
312
  # @param max_page_size [Integer, nil] Set a new value
313
313
  # @return [Integer, nil] The `max_page_size` assigned to fields that use this resolver
314
- def max_page_size(new_max_page_size = :not_given)
315
- if new_max_page_size != :not_given
314
+ def max_page_size(new_max_page_size = NOT_CONFIGURED)
315
+ if new_max_page_size != NOT_CONFIGURED
316
316
  @max_page_size = new_max_page_size
317
317
  elsif defined?(@max_page_size)
318
318
  @max_page_size
@@ -332,8 +332,8 @@ module GraphQL
332
332
  # (`nil` means "unlimited default page size".)
333
333
  # @param default_page_size [Integer, nil] Set a new value
334
334
  # @return [Integer, nil] The `default_page_size` assigned to fields that use this resolver
335
- def default_page_size(new_default_page_size = :not_given)
336
- if new_default_page_size != :not_given
335
+ def default_page_size(new_default_page_size = NOT_CONFIGURED)
336
+ if new_default_page_size != NOT_CONFIGURED
337
337
  @default_page_size = new_default_page_size
338
338
  elsif defined?(@default_page_size)
339
339
  @default_page_size
@@ -102,7 +102,7 @@ module GraphQL
102
102
 
103
103
  self.all_validators = {}
104
104
 
105
- include Schema::FindInheritedValue::EmptyObjects
105
+ include GraphQL::EmptyObjects
106
106
 
107
107
  class ValidationFailedError < GraphQL::ExecutionError
108
108
  attr_reader :errors
@@ -38,7 +38,9 @@ module GraphQL
38
38
  # @api private
39
39
  class Warden
40
40
  def self.from_context(context)
41
- (context.respond_to?(:warden) && context.warden) || PassThruWarden
41
+ context.warden # this might be a hash which won't respond to this
42
+ rescue
43
+ PassThruWarden
42
44
  end
43
45
 
44
46
  # @param visibility_method [Symbol] a Warden method to call for this entry
@@ -88,14 +90,20 @@ module GraphQL
88
90
  # @param filter [<#call(member)>] Objects are hidden when `.call(member, ctx)` returns true
89
91
  # @param context [GraphQL::Query::Context]
90
92
  # @param schema [GraphQL::Schema]
91
- def initialize(filter, context:, schema:)
93
+ def initialize(filter = nil, context:, schema:)
92
94
  @schema = schema
93
95
  # Cache these to avoid repeated hits to the inheritance chain when one isn't present
94
96
  @query = @schema.query
95
97
  @mutation = @schema.mutation
96
98
  @subscription = @schema.subscription
97
99
  @context = context
98
- @visibility_cache = read_through { |m| filter.call(m, context) }
100
+ @visibility_cache = if filter
101
+ read_through { |m| filter.call(m, context) }
102
+ else
103
+ read_through { |m| schema.visible?(m, context) }
104
+ end
105
+
106
+ @visibility_cache.compare_by_identity
99
107
  # Initialize all ivars to improve object shape consistency:
100
108
  @types = @visible_types = @reachable_types = @visible_parent_fields =
101
109
  @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
@@ -145,18 +145,41 @@ module GraphQL
145
145
 
146
146
  def trace_class(new_class = nil)
147
147
  if new_class
148
- @trace_class = new_class
149
- elsif !defined?(@trace_class)
150
- parent_trace_class = if superclass.respond_to?(:trace_class)
151
- superclass.trace_class
148
+ trace_mode(:default, new_class)
149
+ backtrace_class = Class.new(new_class)
150
+ backtrace_class.include(GraphQL::Backtrace::Trace)
151
+ trace_mode(:default_backtrace, backtrace_class)
152
+ end
153
+ trace_class_for(:default)
154
+ end
155
+
156
+ # @return [Class] Return the trace class to use for this mode, looking one up on the superclass if this Schema doesn't have one defined.
157
+ def trace_class_for(mode)
158
+ @trace_modes ||= {}
159
+ @trace_modes[mode] ||= begin
160
+ base_class = if superclass.respond_to?(:trace_class_for)
161
+ superclass.trace_class_for(mode)
162
+ elsif mode == :default_backtrace
163
+ GraphQL::Backtrace::DefaultBacktraceTrace
152
164
  else
153
165
  GraphQL::Tracing::Trace
154
166
  end
155
- @trace_class = Class.new(parent_trace_class)
167
+ Class.new(base_class)
156
168
  end
157
- @trace_class
158
169
  end
159
170
 
171
+ # Configure `trace_class` to be used whenever `context: { trace_mode: mode_name }` is requested.
172
+ # `:default` is used when no `trace_mode: ...` is requested.
173
+ # @param mode_name [Symbol]
174
+ # @param trace_class [Class] subclass of GraphQL::Tracing::Trace
175
+ # @return void
176
+ def trace_mode(mode_name, trace_class)
177
+ @trace_modes ||= {}
178
+ @trace_modes[mode_name] = trace_class
179
+ nil
180
+ end
181
+
182
+
160
183
  # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
161
184
  # @see {#as_json}
162
185
  # @return [String]
@@ -226,6 +249,8 @@ module GraphQL
226
249
 
227
250
  def default_mask(new_mask = nil)
228
251
  if new_mask
252
+ line = caller(2, 10).find { |l| !l.include?("lib/graphql") }
253
+ GraphQL::Deprecation.warn("GraphQL::Filter and Schema.mask are deprecated and will be removed in v2.1.0. Implement `visible?` on your schema members instead (https://graphql-ruby.org/authorization/visibility.html).\n #{line}")
229
254
  @own_default_mask = new_mask
230
255
  else
231
256
  @own_default_mask || find_inherited_value(:default_mask, Schema::NullMask)
@@ -936,10 +961,10 @@ module GraphQL
936
961
  end
937
962
 
938
963
  def tracer(new_tracer)
939
- if defined?(@trace_class) && !(@trace_class < GraphQL::Tracing::LegacyTrace)
964
+ if defined?(@trace_modes) && !(trace_class_for(:default) < GraphQL::Tracing::LegacyTrace)
940
965
  raise ArgumentError, "Can't add tracer after configuring a `trace_class`, use GraphQL::Tracing::LegacyTrace to merge legacy tracers into a trace class instead."
941
- elsif !defined?(@trace_class)
942
- @trace_class = Class.new(GraphQL::Tracing::LegacyTrace)
966
+ else
967
+ trace_mode(:default, Class.new(GraphQL::Tracing::LegacyTrace))
943
968
  end
944
969
 
945
970
  own_tracers << new_tracer
@@ -965,10 +990,14 @@ module GraphQL
965
990
  end
966
991
 
967
992
  def new_trace(**options)
968
- if defined?(@trace_options)
969
- options = trace_options.merge(options)
993
+ options = trace_options.merge(options)
994
+ trace_mode = if (target = options[:query] || options[:multiplex]) && target.context[:backtrace]
995
+ :default_backtrace
996
+ else
997
+ :default
970
998
  end
971
- trace_class.new(**options)
999
+ trace = trace_class_for(trace_mode).new(**options)
1000
+ trace
972
1001
  end
973
1002
 
974
1003
  def query_analyzer(new_analyzer)
@@ -26,11 +26,19 @@ module GraphQL
26
26
  msg = if resolved_type.nil?
27
27
  nil
28
28
  elsif resolved_type.kind.scalar? && ast_node.selections.any?
29
- if ast_node.selections.first.is_a?(GraphQL::Language::Nodes::InlineFragment)
30
- "Selections can't be made on scalars (%{node_name} returns #{resolved_type.graphql_name} but has inline fragments [#{ast_node.selections.map(&:type).map(&:name).join(", ")}])"
31
- else
32
- "Selections can't be made on scalars (%{node_name} returns #{resolved_type.graphql_name} but has selections [#{ast_node.selections.map(&:name).join(", ")}])"
29
+ selection_strs = ast_node.selections.map do |n|
30
+ case n
31
+ when GraphQL::Language::Nodes::InlineFragment
32
+ "\"... on #{n.type.name} { ... }\""
33
+ when GraphQL::Language::Nodes::Field
34
+ "\"#{n.name}\""
35
+ when GraphQL::Language::Nodes::FragmentSpread
36
+ "\"#{n.name}\""
37
+ else
38
+ raise "Invariant: unexpected selection node: #{n}"
39
+ end
33
40
  end
41
+ "Selections can't be made on scalars (%{node_name} returns #{resolved_type.graphql_name} but has selections [#{selection_strs.join(", ")}])"
34
42
  elsif resolved_type.kind.fields? && ast_node.selections.empty?
35
43
  "Field must have selections (%{node_name} returns #{resolved_type.graphql_name} but has no selections. Did you mean '#{ast_node.name} { ... }'?)"
36
44
  else
@@ -9,7 +9,7 @@ module GraphQL
9
9
  # without ambiguity.
10
10
  #
11
11
  # Original Algorithm: https://github.com/graphql/graphql-js/blob/master/src/validation/rules/OverlappingFieldsCanBeMerged.js
12
- NO_ARGS = {}.freeze
12
+ NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
13
13
 
14
14
  Field = Struct.new(:node, :definition, :owner_type, :parents)
15
15
  FragmentSpread = Struct.new(:name, :parents)
@@ -323,7 +323,7 @@ module GraphQL
323
323
  end
324
324
  end
325
325
 
326
- NO_SELECTIONS = [{}.freeze, [].freeze].freeze
326
+ NO_SELECTIONS = [GraphQL::EmptyObjects::EMPTY_HASH, GraphQL::EmptyObjects::EMPTY_ARRAY].freeze
327
327
 
328
328
  def fields_and_fragments_from_selection(node, owner_type:, parents:)
329
329
  if node.selections.empty?
@@ -55,6 +55,12 @@ module GraphQL
55
55
  end
56
56
  end
57
57
 
58
+ def platform_resolve_type(platform_key)
59
+ Appsignal.instrument(platform_key) do
60
+ yield
61
+ end
62
+ end
63
+
58
64
  def platform_field_key(field)
59
65
  "#{field.owner.graphql_name}.#{field.graphql_name}.graphql"
60
66
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Tracing
4
+ # This trace class calls legacy-style tracer with payload hashes.
5
+ # New-style `trace_with` modules significantly reduce the overhead of tracing,
6
+ # but that advantage is lost when legacy-style tracers are also used (since the payload hashes are still constructed).
7
+ class LegacyTrace < Trace
8
+ def lex(query_string:)
9
+ (@multiplex || @query).trace("lex", { query_string: query_string }) { super }
10
+ end
11
+
12
+ def parse(query_string:)
13
+ (@multiplex || @query).trace("parse", { query_string: query_string }) { super }
14
+ end
15
+
16
+ def validate(query:, validate:)
17
+ query.trace("validate", { validate: validate, query: query }) { super }
18
+ end
19
+
20
+ def analyze_multiplex(multiplex:)
21
+ multiplex.trace("analyze_multiplex", { multiplex: multiplex }) { super }
22
+ end
23
+
24
+ def analyze_query(query:)
25
+ query.trace("analyze_query", { query: query }) { super }
26
+ end
27
+
28
+ def execute_multiplex(multiplex:)
29
+ multiplex.trace("execute_multiplex", { multiplex: multiplex }) { super }
30
+ end
31
+
32
+ def execute_query(query:)
33
+ query.trace("execute_query", { query: query }) { super }
34
+ end
35
+
36
+ def execute_query_lazy(query:, multiplex:)
37
+ multiplex.trace("execute_query_lazy", { multiplex: multiplex, query: query }) { super }
38
+ end
39
+
40
+ def execute_field(field:, query:, ast_node:, arguments:, object:)
41
+ query.trace("execute_field", { field: field, query: query, ast_node: ast_node, arguments: arguments, object: object, owner: field.owner, path: query.context[:current_path] }) { super }
42
+ end
43
+
44
+ def execute_field_lazy(field:, query:, ast_node:, arguments:, object:)
45
+ query.trace("execute_field_lazy", { field: field, query: query, ast_node: ast_node, arguments: arguments, object: object, owner: field.owner, path: query.context[:current_path] }) { super }
46
+ end
47
+
48
+ def authorized(query:, type:, object:)
49
+ query.trace("authorized", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
50
+ end
51
+
52
+ def authorized_lazy(query:, type:, object:)
53
+ query.trace("authorized_lazy", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
54
+ end
55
+
56
+ def resolve_type(query:, type:, object:)
57
+ query.trace("resolve_type", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
58
+ end
59
+
60
+ def resolve_type_lazy(query:, type:, object:)
61
+ query.trace("resolve_type_lazy", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,12 +1,13 @@
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
  # This implementation forwards events to a notification handler (i.e.
6
8
  # ActiveSupport::Notifications or Dry::Monitor::Notifications)
7
9
  # with a `graphql` suffix.
8
10
  module NotificationsTrace
9
- include PlatformTrace
10
11
  # Initialize a new NotificationsTracing instance
11
12
  #
12
13
  # @param engine [#instrument(key, metadata, block)] The notifications engine to use
@@ -21,6 +22,7 @@ module GraphQL
21
22
  "validate" => "validate.graphql",
22
23
  "analyze_multiplex" => "analyze_multiplex.graphql",
23
24
  "analyze_query" => "analyze_query.graphql",
25
+ "execute_multiplex" => "execute_multiplex.graphql",
24
26
  "execute_query" => "execute_query.graphql",
25
27
  "execute_query_lazy" => "execute_query_lazy.graphql",
26
28
  "execute_field" => "execute_field.graphql",
@@ -36,6 +38,8 @@ module GraphQL
36
38
  end
37
39
  RUBY
38
40
  end
41
+
42
+ include PlatformTrace
39
43
  end
40
44
  end
41
45
  end
@@ -29,28 +29,30 @@ module GraphQL
29
29
  child_class.instance_method(:platform_execute_field).arity != 1
30
30
 
31
31
  [:execute_field, :execute_field_lazy].each do |field_trace_method|
32
- child_class.module_eval <<-RUBY, __FILE__, __LINE__
33
- def #{field_trace_method}(query:, field:, ast_node:, arguments:, object:)
34
- return_type = field.type.unwrap
35
- trace_field = if return_type.kind.scalar? || return_type.kind.enum?
36
- (field.trace.nil? && @trace_scalars) || field.trace
37
- else
38
- true
39
- end
40
- platform_key = if trace_field
41
- @platform_field_key_cache[field]
42
- else
43
- nil
44
- end
45
- if platform_key && trace_field
46
- platform_#{field_trace_method}(platform_key#{pass_data_to_execute_field ? ", { query: query, field: field, ast_node: ast_node, arguments: arguments, object: object }" : ""}) do
32
+ if !child_class.method_defined?(field_trace_method)
33
+ child_class.module_eval <<-RUBY, __FILE__, __LINE__
34
+ def #{field_trace_method}(query:, field:, ast_node:, arguments:, object:)
35
+ return_type = field.type.unwrap
36
+ trace_field = if return_type.kind.scalar? || return_type.kind.enum?
37
+ (field.trace.nil? && @trace_scalars) || field.trace
38
+ else
39
+ true
40
+ end
41
+ platform_key = if trace_field
42
+ @platform_field_key_cache[field]
43
+ else
44
+ nil
45
+ end
46
+ if platform_key && trace_field
47
+ platform_#{field_trace_method}(platform_key#{pass_data_to_execute_field ? ", { query: query, field: field, ast_node: ast_node, arguments: arguments, object: object }" : ""}) do
48
+ super
49
+ end
50
+ else
47
51
  super
48
52
  end
49
- else
50
- super
51
53
  end
52
- end
53
- RUBY
54
+ RUBY
55
+ end
54
56
  end
55
57
 
56
58
 
@@ -5,7 +5,7 @@ module GraphQL
5
5
  class PrometheusTracing < PlatformTracing
6
6
  class GraphQLCollector < ::PrometheusExporter::Server::TypeCollector
7
7
  def initialize
8
- @graphql_gauge = PrometheusExporter::Metric::Summary.new(
8
+ @graphql_gauge = PrometheusExporter::Metric::Base.default_aggregation.new(
9
9
  'graphql_duration_seconds',
10
10
  'Time spent in GraphQL operations, in seconds'
11
11
  )
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Tracing
4
+ # This is the base class for a `trace` instance whose methods are called during query execution.
5
+ # "Trace modes" are subclasses of this with custom tracing modules mixed in.
6
+ #
7
+ # A trace module may implement any of the methods on `Trace`, being sure to call `super`
8
+ # to continue any tracing hooks and call the actual runtime behavior. See {GraphQL::Backtrace::Trace} for example.
9
+ #
10
+ class Trace
11
+ # @param multiplex [GraphQL::Execution::Multiplex, nil]
12
+ # @param query [GraphQL::Query, nil]
13
+ def initialize(multiplex: nil, query: nil, **_options)
14
+ @multiplex = multiplex
15
+ @query = query
16
+ end
17
+
18
+ def lex(query_string:)
19
+ yield
20
+ end
21
+
22
+ def parse(query_string:)
23
+ yield
24
+ end
25
+
26
+ def validate(query:, validate:)
27
+ yield
28
+ end
29
+
30
+ def analyze_multiplex(multiplex:)
31
+ yield
32
+ end
33
+
34
+ def analyze_query(query:)
35
+ yield
36
+ end
37
+
38
+ def execute_multiplex(multiplex:)
39
+ yield
40
+ end
41
+
42
+ def execute_query(query:)
43
+ yield
44
+ end
45
+
46
+ def execute_query_lazy(query:, multiplex:)
47
+ yield
48
+ end
49
+
50
+ def execute_field(field:, query:, ast_node:, arguments:, object:)
51
+ yield
52
+ end
53
+
54
+ def execute_field_lazy(field:, query:, ast_node:, arguments:, object:)
55
+ yield
56
+ end
57
+
58
+ def authorized(query:, type:, object:)
59
+ yield
60
+ end
61
+
62
+ def authorized_lazy(query:, type:, object:)
63
+ yield
64
+ end
65
+
66
+ def resolve_type(query:, type:, object:)
67
+ yield
68
+ end
69
+
70
+ def resolve_type_lazy(query:, type:, object:)
71
+ yield
72
+ end
73
+ end
74
+ end
75
+ end