graphql 2.0.20 → 2.0.22
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql/backtrace/trace.rb +96 -0
- data/lib/graphql/backtrace.rb +6 -1
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +33 -33
- data/lib/graphql/execution/interpreter/runtime.rb +274 -209
- data/lib/graphql/execution/interpreter.rb +2 -3
- data/lib/graphql/execution/lookahead.rb +1 -1
- data/lib/graphql/filter.rb +8 -2
- data/lib/graphql/language/document_from_schema_definition.rb +37 -17
- data/lib/graphql/language/lexer.rb +5 -3
- data/lib/graphql/language/nodes.rb +2 -2
- data/lib/graphql/language/parser.rb +475 -458
- data/lib/graphql/language/parser.y +5 -1
- data/lib/graphql/pagination/connection.rb +5 -5
- data/lib/graphql/query/context.rb +22 -12
- data/lib/graphql/query/null_context.rb +4 -1
- data/lib/graphql/query.rb +25 -11
- data/lib/graphql/schema/argument.rb +12 -14
- data/lib/graphql/schema/build_from_definition.rb +15 -3
- data/lib/graphql/schema/enum_value.rb +2 -5
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +17 -16
- data/lib/graphql/schema/field_extension.rb +1 -4
- data/lib/graphql/schema/find_inherited_value.rb +2 -7
- data/lib/graphql/schema/input_object.rb +1 -1
- data/lib/graphql/schema/member/has_arguments.rb +10 -8
- data/lib/graphql/schema/member/has_directives.rb +4 -6
- data/lib/graphql/schema/member/has_fields.rb +80 -36
- data/lib/graphql/schema/member/has_validators.rb +2 -2
- data/lib/graphql/schema/object.rb +1 -1
- data/lib/graphql/schema/printer.rb +3 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
- data/lib/graphql/schema/resolver.rb +8 -8
- data/lib/graphql/schema/validator.rb +1 -1
- data/lib/graphql/schema/warden.rb +11 -3
- data/lib/graphql/schema.rb +41 -12
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +2 -2
- data/lib/graphql/tracing/appsignal_trace.rb +6 -0
- data/lib/graphql/tracing/legacy_trace.rb +65 -0
- data/lib/graphql/tracing/notifications_trace.rb +5 -1
- data/lib/graphql/tracing/platform_trace.rb +21 -19
- data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +1 -1
- data/lib/graphql/tracing/trace.rb +75 -0
- data/lib/graphql/tracing.rb +4 -123
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +4 -0
- data/readme.md +1 -1
- 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
|
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
|
31
|
+
include GraphQL::EmptyObjects
|
32
32
|
|
33
33
|
def validators
|
34
34
|
inherited_validators = superclass.validators
|
@@ -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.
|
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.
|
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.
|
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.
|
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.
|
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 =
|
315
|
-
if new_max_page_size !=
|
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 =
|
336
|
-
if new_default_page_size !=
|
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
|
@@ -38,7 +38,9 @@ module GraphQL
|
|
38
38
|
# @api private
|
39
39
|
class Warden
|
40
40
|
def self.from_context(context)
|
41
|
-
|
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 =
|
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 =
|
data/lib/graphql/schema.rb
CHANGED
@@ -145,18 +145,41 @@ module GraphQL
|
|
145
145
|
|
146
146
|
def trace_class(new_class = nil)
|
147
147
|
if new_class
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
|
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?(@
|
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
|
-
|
942
|
-
|
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
|
-
|
969
|
-
|
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
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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 =
|
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 = [
|
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?
|
@@ -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.
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
53
|
-
|
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::
|
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
|