graphql 2.0.17 → 2.0.18
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.
- checksums.yaml +4 -4
- data/lib/graphql/analysis/ast.rb +2 -2
- data/lib/graphql/backtrace/tracer.rb +1 -1
- data/lib/graphql/execution/interpreter/resolve.rb +19 -0
- data/lib/graphql/execution/interpreter/runtime.rb +96 -88
- data/lib/graphql/execution/interpreter.rb +8 -13
- data/lib/graphql/execution/lazy.rb +2 -4
- data/lib/graphql/execution/multiplex.rb +2 -1
- data/lib/graphql/graphql_ext.bundle +0 -0
- data/lib/graphql/language/lexer.rb +216 -1505
- data/lib/graphql/language/lexer.ri +744 -0
- data/lib/graphql/language/parser.rb +9 -9
- data/lib/graphql/language/parser.y +9 -9
- data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
- data/lib/graphql/query/context.rb +45 -11
- data/lib/graphql/query.rb +15 -2
- data/lib/graphql/schema/field.rb +31 -19
- data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
- data/lib/graphql/schema/member/has_fields.rb +6 -1
- data/lib/graphql/schema/object.rb +2 -4
- data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
- data/lib/graphql/schema/timeout.rb +23 -27
- data/lib/graphql/schema/warden.rb +8 -1
- data/lib/graphql/schema.rb +34 -0
- data/lib/graphql/static_validation/validator.rb +1 -1
- data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
- data/lib/graphql/tracing/appoptics_trace.rb +231 -0
- data/lib/graphql/tracing/appsignal_trace.rb +66 -0
- data/lib/graphql/tracing/data_dog_trace.rb +148 -0
- data/lib/graphql/tracing/new_relic_trace.rb +75 -0
- data/lib/graphql/tracing/notifications_trace.rb +41 -0
- data/lib/graphql/tracing/platform_trace.rb +107 -0
- data/lib/graphql/tracing/platform_tracing.rb +15 -3
- data/lib/graphql/tracing/prometheus_trace.rb +89 -0
- data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
- data/lib/graphql/tracing/scout_trace.rb +72 -0
- data/lib/graphql/tracing/statsd_trace.rb +56 -0
- data/lib/graphql/tracing.rb +136 -39
- data/lib/graphql/type_kinds.rb +6 -3
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +7 -8
- metadata +14 -3
- data/lib/graphql/language/lexer.rl +0 -280
@@ -16,22 +16,22 @@ module_eval(<<'...end parser.y/module_eval...', 'parser.y', 448)
|
|
16
16
|
|
17
17
|
EMPTY_ARRAY = [].freeze
|
18
18
|
|
19
|
-
def initialize(query_string, filename:,
|
19
|
+
def initialize(query_string, filename:, trace: Tracing::NullTrace)
|
20
20
|
raise GraphQL::ParseError.new("No query string was present", nil, nil, query_string) if query_string.nil?
|
21
21
|
@query_string = query_string
|
22
22
|
@filename = filename
|
23
|
-
@
|
23
|
+
@trace = trace
|
24
24
|
@reused_next_token = [nil, nil]
|
25
25
|
end
|
26
26
|
|
27
27
|
def parse_document
|
28
28
|
@document ||= begin
|
29
29
|
# Break the string into tokens
|
30
|
-
@
|
30
|
+
@trace.lex(query_string: @query_string) do
|
31
31
|
@tokens ||= GraphQL.scan(@query_string)
|
32
32
|
end
|
33
33
|
# From the tokens, build an AST
|
34
|
-
@
|
34
|
+
@trace.parse(query_string: @query_string) do
|
35
35
|
if @tokens.empty?
|
36
36
|
raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @query_string)
|
37
37
|
else
|
@@ -44,17 +44,17 @@ end
|
|
44
44
|
class << self
|
45
45
|
attr_accessor :cache
|
46
46
|
|
47
|
-
def parse(query_string, filename: nil,
|
48
|
-
new(query_string, filename: filename,
|
47
|
+
def parse(query_string, filename: nil, trace: GraphQL::Tracing::NullTrace)
|
48
|
+
new(query_string, filename: filename, trace: trace).parse_document
|
49
49
|
end
|
50
50
|
|
51
|
-
def parse_file(filename,
|
51
|
+
def parse_file(filename, trace: GraphQL::Tracing::NullTrace)
|
52
52
|
if cache
|
53
53
|
cache.fetch(filename) do
|
54
|
-
parse(File.read(filename), filename: filename,
|
54
|
+
parse(File.read(filename), filename: filename, trace: trace)
|
55
55
|
end
|
56
56
|
else
|
57
|
-
parse(File.read(filename), filename: filename,
|
57
|
+
parse(File.read(filename), filename: filename, trace: trace)
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
@@ -448,22 +448,22 @@ end
|
|
448
448
|
|
449
449
|
EMPTY_ARRAY = [].freeze
|
450
450
|
|
451
|
-
def initialize(query_string, filename:,
|
451
|
+
def initialize(query_string, filename:, trace: Tracing::NullTrace)
|
452
452
|
raise GraphQL::ParseError.new("No query string was present", nil, nil, query_string) if query_string.nil?
|
453
453
|
@query_string = query_string
|
454
454
|
@filename = filename
|
455
|
-
@
|
455
|
+
@trace = trace
|
456
456
|
@reused_next_token = [nil, nil]
|
457
457
|
end
|
458
458
|
|
459
459
|
def parse_document
|
460
460
|
@document ||= begin
|
461
461
|
# Break the string into tokens
|
462
|
-
@
|
462
|
+
@trace.lex(query_string: @query_string) do
|
463
463
|
@tokens ||= GraphQL.scan(@query_string)
|
464
464
|
end
|
465
465
|
# From the tokens, build an AST
|
466
|
-
@
|
466
|
+
@trace.parse(query_string: @query_string) do
|
467
467
|
if @tokens.empty?
|
468
468
|
raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @query_string)
|
469
469
|
else
|
@@ -476,17 +476,17 @@ end
|
|
476
476
|
class << self
|
477
477
|
attr_accessor :cache
|
478
478
|
|
479
|
-
def parse(query_string, filename: nil,
|
480
|
-
new(query_string, filename: filename,
|
479
|
+
def parse(query_string, filename: nil, trace: GraphQL::Tracing::NullTrace)
|
480
|
+
new(query_string, filename: filename, trace: trace).parse_document
|
481
481
|
end
|
482
482
|
|
483
|
-
def parse_file(filename,
|
483
|
+
def parse_file(filename, trace: GraphQL::Tracing::NullTrace)
|
484
484
|
if cache
|
485
485
|
cache.fetch(filename) do
|
486
|
-
parse(File.read(filename), filename: filename,
|
486
|
+
parse(File.read(filename), filename: filename, trace: trace)
|
487
487
|
end
|
488
488
|
else
|
489
|
-
parse(File.read(filename), filename: filename,
|
489
|
+
parse(File.read(filename), filename: filename, trace: trace)
|
490
490
|
end
|
491
491
|
end
|
492
492
|
end
|
@@ -7,14 +7,6 @@ module GraphQL
|
|
7
7
|
class ActiveRecordRelationConnection < Pagination::RelationConnection
|
8
8
|
private
|
9
9
|
|
10
|
-
def relation_larger_than(relation, initial_offset, size)
|
11
|
-
if already_loaded?(relation)
|
12
|
-
(relation.size + initial_offset) > size
|
13
|
-
else
|
14
|
-
set_offset(sliced_nodes, initial_offset + size).exists?
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
10
|
def relation_count(relation)
|
19
11
|
int_or_hash = if already_loaded?(relation)
|
20
12
|
relation.size
|
@@ -72,6 +72,18 @@ module GraphQL
|
|
72
72
|
# @return [Array<String, Integer>] The current position in the result
|
73
73
|
attr_reader :path
|
74
74
|
|
75
|
+
module EmptyScopedContext
|
76
|
+
EMPTY_HASH = {}.freeze
|
77
|
+
|
78
|
+
def self.key?(k)
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.merged_context
|
83
|
+
EMPTY_HASH
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
75
87
|
# Make a new context which delegates key lookup to `values`
|
76
88
|
# @param query [GraphQL::Query] the query who owns this context
|
77
89
|
# @param values [Hash] A hash of arbitrary values which will be accessible at query-time
|
@@ -87,13 +99,14 @@ module GraphQL
|
|
87
99
|
@path = []
|
88
100
|
@value = nil
|
89
101
|
@context = self # for SharedMethods TODO delete sharedmethods
|
90
|
-
@scoped_context =
|
102
|
+
@scoped_context = EmptyScopedContext
|
91
103
|
end
|
92
104
|
|
93
105
|
class ScopedContext
|
94
106
|
def initialize(query_context)
|
95
107
|
@query_context = query_context
|
96
108
|
@scoped_contexts = {}
|
109
|
+
@all_keys = Set.new
|
97
110
|
@no_path = [].freeze
|
98
111
|
end
|
99
112
|
|
@@ -106,6 +119,7 @@ module GraphQL
|
|
106
119
|
end
|
107
120
|
|
108
121
|
def merge!(hash)
|
122
|
+
@all_keys.merge(hash.keys)
|
109
123
|
ctx = @scoped_contexts
|
110
124
|
current_path.each do |path_part|
|
111
125
|
ctx = ctx[path_part] ||= { parent: ctx }
|
@@ -114,15 +128,12 @@ module GraphQL
|
|
114
128
|
this_scoped_ctx.merge!(hash)
|
115
129
|
end
|
116
130
|
|
117
|
-
def current_path
|
118
|
-
thread_info = Thread.current[:__graphql_runtime_info]
|
119
|
-
(thread_info && thread_info[:current_path]) || @no_path
|
120
|
-
end
|
121
|
-
|
122
131
|
def key?(key)
|
123
|
-
|
124
|
-
|
125
|
-
|
132
|
+
if @all_keys.include?(key)
|
133
|
+
each_present_path_ctx do |path_ctx|
|
134
|
+
if path_ctx.key?(key)
|
135
|
+
return true
|
136
|
+
end
|
126
137
|
end
|
127
138
|
end
|
128
139
|
false
|
@@ -137,6 +148,10 @@ module GraphQL
|
|
137
148
|
nil
|
138
149
|
end
|
139
150
|
|
151
|
+
def current_path
|
152
|
+
@query_context.current_path || @no_path
|
153
|
+
end
|
154
|
+
|
140
155
|
def dig(key, *other_keys)
|
141
156
|
each_present_path_ctx do |path_ctx|
|
142
157
|
if path_ctx.key?(key)
|
@@ -209,14 +224,30 @@ module GraphQL
|
|
209
224
|
elsif @provided_values.key?(key)
|
210
225
|
@provided_values[key]
|
211
226
|
elsif RUNTIME_METADATA_KEYS.include?(key)
|
212
|
-
|
213
|
-
|
227
|
+
if key == :current_path
|
228
|
+
current_path
|
229
|
+
else
|
230
|
+
thread_info = Thread.current[:__graphql_runtime_info]
|
231
|
+
thread_info && thread_info[key]
|
232
|
+
end
|
214
233
|
else
|
215
234
|
# not found
|
216
235
|
nil
|
217
236
|
end
|
218
237
|
end
|
219
238
|
|
239
|
+
def current_path
|
240
|
+
thread_info = Thread.current[:__graphql_runtime_info]
|
241
|
+
path = thread_info &&
|
242
|
+
(result = thread_info[:current_result]) &&
|
243
|
+
(result.path)
|
244
|
+
if path && (rn = thread_info[:current_result_name])
|
245
|
+
path = path.dup
|
246
|
+
path.push(rn)
|
247
|
+
end
|
248
|
+
path
|
249
|
+
end
|
250
|
+
|
220
251
|
def delete(key)
|
221
252
|
if @scoped_context.key?(key)
|
222
253
|
@scoped_context.delete(key)
|
@@ -298,6 +329,9 @@ module GraphQL
|
|
298
329
|
end
|
299
330
|
|
300
331
|
def scoped_merge!(hash)
|
332
|
+
if @scoped_context == EmptyScopedContext
|
333
|
+
@scoped_context = ScopedContext.new(self)
|
334
|
+
end
|
301
335
|
@scoped_context.merge!(hash)
|
302
336
|
end
|
303
337
|
|
data/lib/graphql/query.rb
CHANGED
@@ -95,12 +95,20 @@ module GraphQL
|
|
95
95
|
@fragments = nil
|
96
96
|
@operations = nil
|
97
97
|
@validate = validate
|
98
|
-
|
98
|
+
context_tracers = (context ? context.fetch(:tracers, []) : [])
|
99
|
+
@tracers = schema.tracers + context_tracers
|
100
|
+
|
99
101
|
# Support `ctx[:backtrace] = true` for wrapping backtraces
|
100
102
|
if context && context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer)
|
103
|
+
context_tracers += [GraphQL::Backtrace::Tracer]
|
101
104
|
@tracers << GraphQL::Backtrace::Tracer
|
102
105
|
end
|
103
106
|
|
107
|
+
if context_tracers.any? && !(schema.trace_class <= GraphQL::Tracing::LegacyTrace)
|
108
|
+
raise ArgumentError, "context[:tracers] and context[:backtrace] are not supported without `tracer_class(GraphQL::Tracing::LegacyTrace)` in the schema configuration, please add it."
|
109
|
+
end
|
110
|
+
|
111
|
+
|
104
112
|
@analysis_errors = []
|
105
113
|
if variables.is_a?(String)
|
106
114
|
raise ArgumentError, "Query variables should be a Hash, not a String. Try JSON.parse to prepare variables."
|
@@ -157,6 +165,11 @@ module GraphQL
|
|
157
165
|
|
158
166
|
attr_accessor :multiplex
|
159
167
|
|
168
|
+
# @return [GraphQL::Tracing::Trace]
|
169
|
+
def current_trace
|
170
|
+
@current_trace ||= multiplex ? multiplex.current_trace : schema.new_trace(multiplex: multiplex, query: self)
|
171
|
+
end
|
172
|
+
|
160
173
|
def subscription_update?
|
161
174
|
@subscription_topic && subscription?
|
162
175
|
end
|
@@ -362,7 +375,7 @@ module GraphQL
|
|
362
375
|
parse_error = nil
|
363
376
|
@document ||= begin
|
364
377
|
if query_string
|
365
|
-
GraphQL.parse(query_string,
|
378
|
+
GraphQL.parse(query_string, trace: self.current_trace)
|
366
379
|
end
|
367
380
|
rescue GraphQL::ParseError => err
|
368
381
|
parse_error = err
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -219,7 +219,7 @@ module GraphQL
|
|
219
219
|
# @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
|
220
220
|
# @param validates [Array<Hash>] Configurations for validating this field
|
221
221
|
# @fallback_value [Object] A fallback value if the method is not defined
|
222
|
-
def initialize(type: nil, name: nil, owner: nil, null: nil, description:
|
222
|
+
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: :not_given, &definition_block)
|
223
223
|
if name.nil?
|
224
224
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
225
225
|
end
|
@@ -233,9 +233,10 @@ module GraphQL
|
|
233
233
|
|
234
234
|
@underscored_name = -Member::BuildType.underscore(name_s)
|
235
235
|
@name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
|
236
|
-
|
237
|
-
|
238
|
-
|
236
|
+
|
237
|
+
@description = description
|
238
|
+
@type = @owner_type = @own_validators = @own_directives = @own_arguments = nil # these will be prepared later if necessary
|
239
|
+
|
239
240
|
self.deprecation_reason = deprecation_reason
|
240
241
|
|
241
242
|
if method && hash_key && dig
|
@@ -257,6 +258,9 @@ module GraphQL
|
|
257
258
|
if hash_key
|
258
259
|
@hash_key = hash_key
|
259
260
|
@hash_key_str = hash_key.to_s
|
261
|
+
else
|
262
|
+
@hash_key = NOT_CONFIGURED
|
263
|
+
@hash_key_str = NOT_CONFIGURED
|
260
264
|
end
|
261
265
|
|
262
266
|
@method_str = -method_name.to_s
|
@@ -272,15 +276,11 @@ module GraphQL
|
|
272
276
|
true
|
273
277
|
end
|
274
278
|
@connection = connection
|
275
|
-
@
|
276
|
-
@
|
277
|
-
@has_default_page_size = default_page_size != :not_given
|
278
|
-
@default_page_size = default_page_size == :not_given ? nil : default_page_size
|
279
|
+
@max_page_size = max_page_size
|
280
|
+
@default_page_size = default_page_size
|
279
281
|
@introspection = introspection
|
280
282
|
@extras = extras
|
281
|
-
|
282
|
-
@broadcastable = broadcastable
|
283
|
-
end
|
283
|
+
@broadcastable = broadcastable
|
284
284
|
@resolver_class = resolver_class
|
285
285
|
@scope = scope
|
286
286
|
@trace = trace
|
@@ -355,7 +355,7 @@ module GraphQL
|
|
355
355
|
# @return [Boolean, nil]
|
356
356
|
# @see GraphQL::Subscriptions::BroadcastAnalyzer
|
357
357
|
def broadcastable?
|
358
|
-
if
|
358
|
+
if !NOT_CONFIGURED.equal?(@broadcastable)
|
359
359
|
@broadcastable
|
360
360
|
elsif @resolver_class
|
361
361
|
@resolver_class.broadcastable?
|
@@ -369,10 +369,10 @@ module GraphQL
|
|
369
369
|
def description(text = nil)
|
370
370
|
if text
|
371
371
|
@description = text
|
372
|
-
elsif
|
372
|
+
elsif !NOT_CONFIGURED.equal?(@description)
|
373
373
|
@description
|
374
374
|
elsif @resolver_class
|
375
|
-
@
|
375
|
+
@resolver_class.description
|
376
376
|
else
|
377
377
|
nil
|
378
378
|
end
|
@@ -544,22 +544,34 @@ module GraphQL
|
|
544
544
|
|
545
545
|
# @return [Boolean] True if this field's {#max_page_size} should override the schema default.
|
546
546
|
def has_max_page_size?
|
547
|
-
@
|
547
|
+
!NOT_CONFIGURED.equal?(@max_page_size) || (@resolver_class && @resolver_class.has_max_page_size?)
|
548
548
|
end
|
549
549
|
|
550
550
|
# @return [Integer, nil] Applied to connections if {#has_max_page_size?}
|
551
551
|
def max_page_size
|
552
|
-
|
552
|
+
if !NOT_CONFIGURED.equal?(@max_page_size)
|
553
|
+
@max_page_size
|
554
|
+
elsif @resolver_class && @resolver_class.has_max_page_size?
|
555
|
+
@resolver_class.max_page_size
|
556
|
+
else
|
557
|
+
nil
|
558
|
+
end
|
553
559
|
end
|
554
560
|
|
555
561
|
# @return [Boolean] True if this field's {#default_page_size} should override the schema default.
|
556
562
|
def has_default_page_size?
|
557
|
-
@
|
563
|
+
!NOT_CONFIGURED.equal?(@default_page_size) || (@resolver_class && @resolver_class.has_default_page_size?)
|
558
564
|
end
|
559
565
|
|
560
566
|
# @return [Integer, nil] Applied to connections if {#has_default_page_size?}
|
561
567
|
def default_page_size
|
562
|
-
|
568
|
+
if !NOT_CONFIGURED.equal?(@default_page_size)
|
569
|
+
@default_page_size
|
570
|
+
elsif @resolver_class && @resolver_class.has_default_page_size?
|
571
|
+
@resolver_class.default_page_size
|
572
|
+
else
|
573
|
+
nil
|
574
|
+
end
|
563
575
|
end
|
564
576
|
|
565
577
|
class MissingReturnTypeError < GraphQL::Error; end
|
@@ -661,7 +673,7 @@ module GraphQL
|
|
661
673
|
|
662
674
|
inner_object = obj.object
|
663
675
|
|
664
|
-
if
|
676
|
+
if !NOT_CONFIGURED.equal?(@hash_key)
|
665
677
|
hash_value = if inner_object.is_a?(Hash)
|
666
678
|
inner_object.key?(@hash_key) ? inner_object[@hash_key] : inner_object[@hash_key_str]
|
667
679
|
elsif inner_object.respond_to?(:[])
|
@@ -5,17 +5,16 @@ module GraphQL
|
|
5
5
|
class Member
|
6
6
|
module HasDeprecationReason
|
7
7
|
# @return [String, nil] Explains why this member was deprecated (if present, this will be marked deprecated in introspection)
|
8
|
-
|
9
|
-
dir = self.directives.find { |d| d.is_a?(GraphQL::Schema::Directive::Deprecated) }
|
10
|
-
dir && dir.arguments[:reason] # rubocop:disable Development/ContextIsPassedCop -- definition-related
|
11
|
-
end
|
8
|
+
attr_reader :deprecation_reason
|
12
9
|
|
13
10
|
# Set the deprecation reason for this member, or remove it by assigning `nil`
|
14
11
|
# @param text [String, nil]
|
15
12
|
def deprecation_reason=(text)
|
13
|
+
@deprecation_reason = text
|
16
14
|
if text.nil?
|
17
15
|
remove_directive(GraphQL::Schema::Directive::Deprecated)
|
18
16
|
else
|
17
|
+
# This removes a previously-attached directive, if there is one:
|
19
18
|
directive(GraphQL::Schema::Directive::Deprecated, reason: text)
|
20
19
|
end
|
21
20
|
end
|
@@ -72,7 +72,12 @@ module GraphQL
|
|
72
72
|
def add_field(field_defn, method_conflict_warning: field_defn.method_conflict_warning?)
|
73
73
|
# Check that `field_defn.original_name` equals `resolver_method` and `method_sym` --
|
74
74
|
# that shows that no override value was given manually.
|
75
|
-
if method_conflict_warning &&
|
75
|
+
if method_conflict_warning &&
|
76
|
+
CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) &&
|
77
|
+
field_defn.original_name == field_defn.resolver_method &&
|
78
|
+
field_defn.original_name == field_defn.method_sym &&
|
79
|
+
field_defn.hash_key == NOT_CONFIGURED &&
|
80
|
+
field_defn.dig_keys.nil?
|
76
81
|
warn(conflict_field_name_warning(field_defn))
|
77
82
|
end
|
78
83
|
prev_defn = own_fields[field_defn.name]
|
@@ -48,9 +48,7 @@ module GraphQL
|
|
48
48
|
# @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
|
49
49
|
# @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
|
50
50
|
def authorized_new(object, context)
|
51
|
-
|
52
|
-
|
53
|
-
maybe_lazy_auth_val = context.query.trace("authorized", trace_payload) do
|
51
|
+
maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
|
54
52
|
begin
|
55
53
|
authorized?(object, context)
|
56
54
|
rescue GraphQL::UnauthorizedError => err
|
@@ -62,7 +60,7 @@ module GraphQL
|
|
62
60
|
|
63
61
|
auth_val = if context.schema.lazy?(maybe_lazy_auth_val)
|
64
62
|
GraphQL::Execution::Lazy.new do
|
65
|
-
context.query.
|
63
|
+
context.query.current_trace.authorized_lazy(query: context.query, type: self, object: object) do
|
66
64
|
context.schema.sync_lazy(maybe_lazy_auth_val)
|
67
65
|
end
|
68
66
|
end
|
@@ -89,16 +89,16 @@ module GraphQL
|
|
89
89
|
def generate_payload_type
|
90
90
|
resolver_name = graphql_name
|
91
91
|
resolver_fields = all_field_definitions
|
92
|
-
Class.new(object_class)
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
end
|
92
|
+
pt = Class.new(object_class)
|
93
|
+
pt.graphql_name("#{resolver_name}Payload")
|
94
|
+
pt.description("Autogenerated return type of #{resolver_name}.")
|
95
|
+
resolver_fields.each do |f|
|
96
|
+
# Reattach the already-defined field here
|
97
|
+
# (The field's `.owner` will still point to the mutation, not the object type, I think)
|
98
|
+
# Don't re-warn about a method conflict. Since this type is generated, it should be fixed in the resolver instead.
|
99
|
+
pt.add_field(f, method_conflict_warning: false)
|
101
100
|
end
|
101
|
+
pt
|
102
102
|
end
|
103
103
|
end
|
104
104
|
end
|
@@ -33,60 +33,56 @@ module GraphQL
|
|
33
33
|
# end
|
34
34
|
#
|
35
35
|
class Timeout
|
36
|
-
def self.use(schema,
|
37
|
-
|
38
|
-
schema.
|
36
|
+
def self.use(schema, max_seconds: nil)
|
37
|
+
timeout = self.new(max_seconds: max_seconds)
|
38
|
+
schema.trace_with(self::Trace, timeout: timeout)
|
39
39
|
end
|
40
40
|
|
41
|
-
# @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields
|
42
41
|
def initialize(max_seconds:)
|
43
42
|
@max_seconds = max_seconds
|
44
43
|
end
|
45
44
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
module Trace
|
46
|
+
# @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields
|
47
|
+
def initialize(timeout:, **rest)
|
48
|
+
@timeout = timeout
|
49
|
+
super
|
50
|
+
end
|
51
|
+
|
52
|
+
def execute_multiplex(multiplex:)
|
53
|
+
multiplex.queries.each do |query|
|
54
|
+
timeout_duration_s = @timeout.max_seconds(query)
|
51
55
|
timeout_state = if timeout_duration_s == false
|
52
56
|
# if the method returns `false`, don't apply a timeout
|
53
57
|
false
|
54
58
|
else
|
55
59
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
56
|
-
timeout_at = now + (
|
60
|
+
timeout_at = now + (timeout_duration_s * 1000)
|
57
61
|
{
|
58
62
|
timeout_at: timeout_at,
|
59
63
|
timed_out: false
|
60
64
|
}
|
61
65
|
end
|
62
|
-
query.context.namespace(
|
66
|
+
query.context.namespace(@timeout)[:state] = timeout_state
|
63
67
|
end
|
68
|
+
super
|
69
|
+
end
|
64
70
|
|
65
|
-
|
66
|
-
|
67
|
-
query_context = data[:context] || data[:query].context
|
68
|
-
timeout_state = query_context.namespace(self.class).fetch(:state)
|
71
|
+
def execute_field(query:, field:, **_rest)
|
72
|
+
timeout_state = query.context.namespace(@timeout).fetch(:state)
|
69
73
|
# If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query.
|
70
74
|
if timeout_state != false && Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
|
71
|
-
error =
|
72
|
-
GraphQL::Schema::Timeout::TimeoutError.new(query_context.parent_type, query_context.field)
|
73
|
-
else
|
74
|
-
field = data.fetch(:field)
|
75
|
-
GraphQL::Schema::Timeout::TimeoutError.new(field.owner, field)
|
76
|
-
end
|
77
|
-
|
75
|
+
error = GraphQL::Schema::Timeout::TimeoutError.new(field)
|
78
76
|
# Only invoke the timeout callback for the first timeout
|
79
77
|
if !timeout_state[:timed_out]
|
80
78
|
timeout_state[:timed_out] = true
|
81
|
-
handle_timeout(error,
|
79
|
+
@timeout.handle_timeout(error, query)
|
82
80
|
end
|
83
81
|
|
84
82
|
error
|
85
83
|
else
|
86
84
|
yield
|
87
85
|
end
|
88
|
-
else
|
89
|
-
yield
|
90
86
|
end
|
91
87
|
end
|
92
88
|
|
@@ -114,8 +110,8 @@ module GraphQL
|
|
114
110
|
# to take this error and raise a new one which _doesn't_ descend from {GraphQL::ExecutionError},
|
115
111
|
# such as `RuntimeError`.
|
116
112
|
class TimeoutError < GraphQL::ExecutionError
|
117
|
-
def initialize(
|
118
|
-
super("Timeout on #{
|
113
|
+
def initialize(field)
|
114
|
+
super("Timeout on #{field.path}")
|
119
115
|
end
|
120
116
|
end
|
121
117
|
end
|
@@ -96,6 +96,13 @@ module GraphQL
|
|
96
96
|
@subscription = @schema.subscription
|
97
97
|
@context = context
|
98
98
|
@visibility_cache = read_through { |m| filter.call(m, context) }
|
99
|
+
# Initialize all ivars to improve object shape consistency:
|
100
|
+
@types = @visible_types = @reachable_types = @visible_parent_fields =
|
101
|
+
@visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
|
102
|
+
@visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
|
103
|
+
@visible_and_reachable_type = @unions = @unfiltered_interfaces = @references_to =
|
104
|
+
@reachable_type_set =
|
105
|
+
nil
|
99
106
|
end
|
100
107
|
|
101
108
|
# @return [Hash<String, GraphQL::BaseType>] Visible types in the schema
|
@@ -347,7 +354,7 @@ module GraphQL
|
|
347
354
|
end
|
348
355
|
|
349
356
|
def reachable_type_set
|
350
|
-
return @reachable_type_set if
|
357
|
+
return @reachable_type_set if @reachable_type_set
|
351
358
|
|
352
359
|
@reachable_type_set = Set.new
|
353
360
|
rt_hash = {}
|
data/lib/graphql/schema.rb
CHANGED
@@ -143,6 +143,15 @@ module GraphQL
|
|
143
143
|
@subscriptions = new_implementation
|
144
144
|
end
|
145
145
|
|
146
|
+
def trace_class(new_class = nil)
|
147
|
+
if new_class
|
148
|
+
@trace_class = new_class
|
149
|
+
elsif !defined?(@trace_class)
|
150
|
+
@trace_class = Class.new(GraphQL::Tracing::Trace)
|
151
|
+
end
|
152
|
+
@trace_class
|
153
|
+
end
|
154
|
+
|
146
155
|
# Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
|
147
156
|
# @see {#as_json}
|
148
157
|
# @return [String]
|
@@ -926,6 +935,12 @@ module GraphQL
|
|
926
935
|
end
|
927
936
|
|
928
937
|
def tracer(new_tracer)
|
938
|
+
if defined?(@trace_class) && !(@trace_class < GraphQL::Tracing::LegacyTrace)
|
939
|
+
raise ArgumentError, "Can't add tracer after configuring a `trace_class`, use GraphQL::Tracing::LegacyTrace to merge legacy tracers into a trace class instead."
|
940
|
+
elsif !defined?(@trace_class)
|
941
|
+
@trace_class = Class.new(GraphQL::Tracing::LegacyTrace)
|
942
|
+
end
|
943
|
+
|
929
944
|
own_tracers << new_tracer
|
930
945
|
end
|
931
946
|
|
@@ -933,6 +948,25 @@ module GraphQL
|
|
933
948
|
find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
|
934
949
|
end
|
935
950
|
|
951
|
+
# Mix `trace_mod` into this schema's `Trace` class so that its methods
|
952
|
+
# will be called at runtime.
|
953
|
+
#
|
954
|
+
# @param trace_mod [Module] A module that implements tracing methods
|
955
|
+
# @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
|
956
|
+
# @return [void]
|
957
|
+
def trace_with(trace_mod, **options)
|
958
|
+
@trace_options ||= {}
|
959
|
+
@trace_options.merge!(options)
|
960
|
+
trace_class.include(trace_mod)
|
961
|
+
end
|
962
|
+
|
963
|
+
def new_trace(**options)
|
964
|
+
if defined?(@trace_options)
|
965
|
+
options = @trace_options.merge(options)
|
966
|
+
end
|
967
|
+
trace_class.new(**options)
|
968
|
+
end
|
969
|
+
|
936
970
|
def query_analyzer(new_analyzer)
|
937
971
|
own_query_analyzers << new_analyzer
|
938
972
|
end
|
@@ -27,7 +27,7 @@ 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
|
-
query.
|
30
|
+
query.current_trace.validate(validate: validate, query: query) do
|
31
31
|
errors = if validate == false
|
32
32
|
[]
|
33
33
|
else
|