graphql 2.0.17.2 → 2.0.18
Sign up to get free protection for your applications and to get access to all the features.
- 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 -21
- 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 +39 -1
- 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?(:[])
|
@@ -740,8 +752,6 @@ module GraphQL
|
|
740
752
|
end
|
741
753
|
# if the line above doesn't raise, re-raise
|
742
754
|
raise
|
743
|
-
rescue GraphQL::ExecutionError => err
|
744
|
-
err
|
745
755
|
end
|
746
756
|
|
747
757
|
# @param ctx [GraphQL::Query::Context]
|
@@ -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 = {}
|