graphql 2.4.11 → 2.4.12
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.
- checksums.yaml +4 -4
- data/lib/graphql/analysis/visitor.rb +35 -40
- data/lib/graphql/analysis.rb +12 -9
- data/lib/graphql/execution/interpreter/runtime.rb +1 -1
- data/lib/graphql/invalid_null_error.rb +1 -5
- data/lib/graphql/language/lexer.rb +7 -3
- data/lib/graphql/language/parser.rb +1 -1
- data/lib/graphql/schema/resolver.rb +5 -1
- data/lib/graphql/schema.rb +3 -3
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
- data/lib/graphql/tracing/new_relic_trace.rb +38 -23
- data/lib/graphql/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b105157510cb5779715118d5f28f49822a9b3b86b0faec0fdc86a2331904f667
|
4
|
+
data.tar.gz: 9230b70e18665505190c673579fa66b2906d04009f9e46d844990bafdba374da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e64decd9e23e3dcc23ced201998963ca0894e38161659a153d9c1ae918dce90c8ae365a3eb1debd753a78f65a1ffe3bf281ee0bb285cd1a0f6cd572cbb31455a
|
7
|
+
data.tar.gz: 4f0b3ac80a45a5db47e96abacc7fb935ed20c5a0ebe65ba2ca04e20c0bac58f1ec73e2e4af42c05307c76fd358c36bcfbd5529479201cf104540a50448298b80
|
@@ -10,7 +10,7 @@ module GraphQL
|
|
10
10
|
#
|
11
11
|
# @see {GraphQL::Analysis::Analyzer} AST Analyzers for queries
|
12
12
|
class Visitor < GraphQL::Language::StaticVisitor
|
13
|
-
def initialize(query:, analyzers:)
|
13
|
+
def initialize(query:, analyzers:, timeout:)
|
14
14
|
@analyzers = analyzers
|
15
15
|
@path = []
|
16
16
|
@object_types = []
|
@@ -24,6 +24,11 @@ module GraphQL
|
|
24
24
|
@types = query.types
|
25
25
|
@response_path = []
|
26
26
|
@skip_stack = [false]
|
27
|
+
@timeout_time = if timeout
|
28
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) + timeout
|
29
|
+
else
|
30
|
+
Float::INFINITY
|
31
|
+
end
|
27
32
|
super(query.selected_operation)
|
28
33
|
end
|
29
34
|
|
@@ -72,21 +77,17 @@ module GraphQL
|
|
72
77
|
module_eval <<-RUBY, __FILE__, __LINE__
|
73
78
|
def call_on_enter_#{node_type}(node, parent)
|
74
79
|
@analyzers.each do |a|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
@rescued_errors << err
|
79
|
-
end
|
80
|
+
a.on_enter_#{node_type}(node, parent, self)
|
81
|
+
rescue AnalysisError => err
|
82
|
+
@rescued_errors << err
|
80
83
|
end
|
81
84
|
end
|
82
85
|
|
83
86
|
def call_on_leave_#{node_type}(node, parent)
|
84
87
|
@analyzers.each do |a|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
@rescued_errors << err
|
89
|
-
end
|
88
|
+
a.on_leave_#{node_type}(node, parent, self)
|
89
|
+
rescue AnalysisError => err
|
90
|
+
@rescued_errors << err
|
90
91
|
end
|
91
92
|
end
|
92
93
|
|
@@ -94,6 +95,7 @@ module GraphQL
|
|
94
95
|
end
|
95
96
|
|
96
97
|
def on_operation_definition(node, parent)
|
98
|
+
check_timeout
|
97
99
|
object_type = @schema.root_type_for_operation(node.operation_type)
|
98
100
|
@object_types.push(object_type)
|
99
101
|
@path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
|
@@ -104,31 +106,27 @@ module GraphQL
|
|
104
106
|
@path.pop
|
105
107
|
end
|
106
108
|
|
107
|
-
def on_fragment_definition(node, parent)
|
108
|
-
on_fragment_with_type(node) do
|
109
|
-
@path.push("fragment #{node.name}")
|
110
|
-
@in_fragment_def = false
|
111
|
-
call_on_enter_fragment_definition(node, parent)
|
112
|
-
super
|
113
|
-
@in_fragment_def = false
|
114
|
-
call_on_leave_fragment_definition(node, parent)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
109
|
def on_inline_fragment(node, parent)
|
119
|
-
|
120
|
-
|
121
|
-
@
|
122
|
-
|
123
|
-
|
124
|
-
call_on_enter_inline_fragment(node, parent)
|
125
|
-
super
|
126
|
-
@skipping = @skip_stack.pop
|
127
|
-
call_on_leave_inline_fragment(node, parent)
|
110
|
+
check_timeout
|
111
|
+
object_type = if node.type
|
112
|
+
@types.type(node.type.name)
|
113
|
+
else
|
114
|
+
@object_types.last
|
128
115
|
end
|
116
|
+
@object_types.push(object_type)
|
117
|
+
@path.push("...#{node.type ? " on #{node.type.name}" : ""}")
|
118
|
+
@skipping = @skip_stack.last || skip?(node)
|
119
|
+
@skip_stack << @skipping
|
120
|
+
call_on_enter_inline_fragment(node, parent)
|
121
|
+
super
|
122
|
+
@skipping = @skip_stack.pop
|
123
|
+
call_on_leave_inline_fragment(node, parent)
|
124
|
+
@object_types.pop
|
125
|
+
@path.pop
|
129
126
|
end
|
130
127
|
|
131
128
|
def on_field(node, parent)
|
129
|
+
check_timeout
|
132
130
|
@response_path.push(node.alias || node.name)
|
133
131
|
parent_type = @object_types.last
|
134
132
|
# This could be nil if the previous field wasn't found:
|
@@ -156,6 +154,7 @@ module GraphQL
|
|
156
154
|
end
|
157
155
|
|
158
156
|
def on_directive(node, parent)
|
157
|
+
check_timeout
|
159
158
|
directive_defn = @schema.directives[node.name]
|
160
159
|
@directive_definitions.push(directive_defn)
|
161
160
|
call_on_enter_directive(node, parent)
|
@@ -165,6 +164,7 @@ module GraphQL
|
|
165
164
|
end
|
166
165
|
|
167
166
|
def on_argument(node, parent)
|
167
|
+
check_timeout
|
168
168
|
argument_defn = if (arg = @argument_definitions.last)
|
169
169
|
arg_type = arg.type.unwrap
|
170
170
|
if arg_type.kind.input_object?
|
@@ -190,6 +190,7 @@ module GraphQL
|
|
190
190
|
end
|
191
191
|
|
192
192
|
def on_fragment_spread(node, parent)
|
193
|
+
check_timeout
|
193
194
|
@path.push("... #{node.name}")
|
194
195
|
@skipping = @skip_stack.last || skip?(node)
|
195
196
|
@skip_stack << @skipping
|
@@ -267,16 +268,10 @@ module GraphQL
|
|
267
268
|
!dir.empty? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
|
268
269
|
end
|
269
270
|
|
270
|
-
def
|
271
|
-
|
272
|
-
|
273
|
-
else
|
274
|
-
@object_types.last
|
271
|
+
def check_timeout
|
272
|
+
if Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) > @timeout_time
|
273
|
+
raise GraphQL::Analysis::TimeoutError
|
275
274
|
end
|
276
|
-
@object_types.push(object_type)
|
277
|
-
yield(node)
|
278
|
-
@object_types.pop
|
279
|
-
@path.pop
|
280
275
|
end
|
281
276
|
end
|
282
277
|
end
|
data/lib/graphql/analysis.rb
CHANGED
@@ -6,11 +6,16 @@ require "graphql/analysis/query_complexity"
|
|
6
6
|
require "graphql/analysis/max_query_complexity"
|
7
7
|
require "graphql/analysis/query_depth"
|
8
8
|
require "graphql/analysis/max_query_depth"
|
9
|
-
require "timeout"
|
10
|
-
|
11
9
|
module GraphQL
|
12
10
|
module Analysis
|
13
11
|
AST = self
|
12
|
+
|
13
|
+
class TimeoutError < AnalysisError
|
14
|
+
def initialize(...)
|
15
|
+
super("Timeout on validation of query")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
14
19
|
module_function
|
15
20
|
# Analyze a multiplex, and all queries within.
|
16
21
|
# Multiplex analyzers are ran for all queries, keeping state.
|
@@ -61,13 +66,11 @@ module GraphQL
|
|
61
66
|
if !analyzers_to_run.empty?
|
62
67
|
visitor = GraphQL::Analysis::Visitor.new(
|
63
68
|
query: query,
|
64
|
-
analyzers: analyzers_to_run
|
69
|
+
analyzers: analyzers_to_run,
|
70
|
+
timeout: query.validate_timeout_remaining,
|
65
71
|
)
|
66
72
|
|
67
|
-
|
68
|
-
Timeout::timeout(query.validate_timeout_remaining) do
|
69
|
-
visitor.visit
|
70
|
-
end
|
73
|
+
visitor.visit
|
71
74
|
|
72
75
|
if !visitor.rescued_errors.empty?
|
73
76
|
return visitor.rescued_errors
|
@@ -79,8 +82,8 @@ module GraphQL
|
|
79
82
|
[]
|
80
83
|
end
|
81
84
|
end
|
82
|
-
rescue
|
83
|
-
[
|
85
|
+
rescue TimeoutError => err
|
86
|
+
[err]
|
84
87
|
rescue GraphQL::UnauthorizedError, GraphQL::ExecutionError
|
85
88
|
# This error was raised during analysis and will be returned the client before execution
|
86
89
|
[]
|
@@ -473,7 +473,7 @@ module GraphQL
|
|
473
473
|
# When this comes from a list item, use the parent object:
|
474
474
|
parent_type = selection_result.is_a?(GraphQLResultArray) ? selection_result.graphql_parent.graphql_result_type : selection_result.graphql_result_type
|
475
475
|
# This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
|
476
|
-
err = parent_type::InvalidNullError.new(parent_type, field,
|
476
|
+
err = parent_type::InvalidNullError.new(parent_type, field, ast_node)
|
477
477
|
schema.type_error(err, context)
|
478
478
|
end
|
479
479
|
else
|
@@ -9,16 +9,12 @@ module GraphQL
|
|
9
9
|
# @return [GraphQL::Field] The field which failed to return a value
|
10
10
|
attr_reader :field
|
11
11
|
|
12
|
-
# @return [nil, GraphQL::ExecutionError] The invalid value for this field
|
13
|
-
attr_reader :value
|
14
|
-
|
15
12
|
# @return [GraphQL::Language::Nodes::Field] the field where the error occurred
|
16
13
|
attr_reader :ast_node
|
17
14
|
|
18
|
-
def initialize(parent_type, field,
|
15
|
+
def initialize(parent_type, field, ast_node)
|
19
16
|
@parent_type = parent_type
|
20
17
|
@field = field
|
21
|
-
@value = value
|
22
18
|
@ast_node = ast_node
|
23
19
|
super("Cannot return null for non-nullable field #{@parent_type.graphql_name}.#{@field.graphql_name}")
|
24
20
|
end
|
@@ -13,17 +13,21 @@ module GraphQL
|
|
13
13
|
@pos = nil
|
14
14
|
@max_tokens = max_tokens || Float::INFINITY
|
15
15
|
@tokens_count = 0
|
16
|
+
@finished = false
|
16
17
|
end
|
17
18
|
|
18
|
-
def
|
19
|
-
@
|
19
|
+
def finished?
|
20
|
+
@finished
|
20
21
|
end
|
21
22
|
|
22
23
|
attr_reader :pos, :tokens_count
|
23
24
|
|
24
25
|
def advance
|
25
26
|
@scanner.skip(IGNORE_REGEXP)
|
26
|
-
|
27
|
+
if @scanner.eos?
|
28
|
+
@finished = true
|
29
|
+
return false
|
30
|
+
end
|
27
31
|
@tokens_count += 1
|
28
32
|
if @tokens_count > @max_tokens
|
29
33
|
raise_parse_error("This query is too large to execute.")
|
@@ -110,7 +110,7 @@ module GraphQL
|
|
110
110
|
# Only ignored characters is not a valid document
|
111
111
|
raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @graphql_str)
|
112
112
|
end
|
113
|
-
while !@lexer.
|
113
|
+
while !@lexer.finished?
|
114
114
|
defns << definition
|
115
115
|
end
|
116
116
|
Document.new(pos: 0, definitions: defns, filename: @filename, source: self)
|
@@ -22,7 +22,6 @@ module GraphQL
|
|
22
22
|
include Schema::Member::GraphQLTypeNames
|
23
23
|
# Really we only need description & comment from here, but:
|
24
24
|
extend Schema::Member::BaseDSLMethods
|
25
|
-
extend Member::BaseDSLMethods::ConfigurationExtension
|
26
25
|
extend GraphQL::Schema::Member::HasArguments
|
27
26
|
extend GraphQL::Schema::Member::HasValidators
|
28
27
|
include Schema::Member::HasPath
|
@@ -404,6 +403,11 @@ module GraphQL
|
|
404
403
|
end
|
405
404
|
end
|
406
405
|
|
406
|
+
def inherited(child_class)
|
407
|
+
child_class.description(description)
|
408
|
+
super
|
409
|
+
end
|
410
|
+
|
407
411
|
private
|
408
412
|
|
409
413
|
attr_reader :own_extensions
|
data/lib/graphql/schema.rb
CHANGED
@@ -821,13 +821,13 @@ module GraphQL
|
|
821
821
|
|
822
822
|
attr_writer :validate_timeout
|
823
823
|
|
824
|
-
def validate_timeout(new_validate_timeout =
|
825
|
-
if new_validate_timeout
|
824
|
+
def validate_timeout(new_validate_timeout = NOT_CONFIGURED)
|
825
|
+
if !NOT_CONFIGURED.equal?(new_validate_timeout)
|
826
826
|
@validate_timeout = new_validate_timeout
|
827
827
|
elsif defined?(@validate_timeout)
|
828
828
|
@validate_timeout
|
829
829
|
else
|
830
|
-
find_inherited_value(:validate_timeout)
|
830
|
+
find_inherited_value(:validate_timeout) || 3
|
831
831
|
end
|
832
832
|
end
|
833
833
|
|
@@ -345,7 +345,7 @@ module GraphQL
|
|
345
345
|
fields << Field.new(node, definition, owner_type, parents)
|
346
346
|
when GraphQL::Language::Nodes::InlineFragment
|
347
347
|
fragment_type = node.type ? @types.type(node.type.name) : owner_type
|
348
|
-
find_fields_and_fragments(node.selections, parents: [*parents, fragment_type], owner_type:
|
348
|
+
find_fields_and_fragments(node.selections, parents: [*parents, fragment_type], owner_type: fragment_type, fields: fields, fragment_spreads: fragment_spreads) if fragment_type
|
349
349
|
when GraphQL::Language::Nodes::FragmentSpread
|
350
350
|
fragment_spreads << FragmentSpread.new(node.name, parents)
|
351
351
|
end
|
@@ -13,16 +13,21 @@ module GraphQL
|
|
13
13
|
# # Optional, use the operation name to set the new relic transaction name:
|
14
14
|
# # trace_with GraphQL::Tracing::NewRelicTrace, set_transaction_name: true
|
15
15
|
# end
|
16
|
+
#
|
17
|
+
# @example Installing without trace events for `authorized?` or `resolve_type` calls
|
18
|
+
# trace_with GraphQL::Tracing::NewRelicTrace, trace_authorized: false, trace_resolve_type: false
|
16
19
|
module NewRelicTrace
|
17
20
|
# @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
|
18
21
|
# This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
|
19
22
|
# It can also be specified per-query with `context[:set_new_relic_transaction_name]`.
|
20
23
|
# @param trace_authorized [Boolean] If `false`, skip tracing `authorized?` calls
|
21
24
|
# @param trace_resolve_type [Boolean] If `false`, skip tracing `resolve_type?` calls
|
22
|
-
|
25
|
+
# @param trace_scalars [Boolean] If `true`, Enum and Scalar fields will be traced by default
|
26
|
+
def initialize(set_transaction_name: false, trace_authorized: true, trace_resolve_type: true, trace_scalars: false, **_rest)
|
23
27
|
@set_transaction_name = set_transaction_name
|
24
28
|
@trace_authorized = trace_authorized
|
25
29
|
@trace_resolve_type = trace_resolve_type
|
30
|
+
@trace_scalars = trace_scalars
|
26
31
|
@nr_field_names = Hash.new do |h, field|
|
27
32
|
h[field] = "GraphQL/#{field.owner.graphql_name}/#{field.graphql_name}"
|
28
33
|
end.compare_by_identity
|
@@ -89,78 +94,88 @@ module GraphQL
|
|
89
94
|
end
|
90
95
|
|
91
96
|
def begin_execute_field(field, object, arguments, query)
|
92
|
-
|
97
|
+
return_type = field.type.unwrap
|
98
|
+
trace_field = if return_type.kind.scalar? || return_type.kind.enum?
|
99
|
+
(field.trace.nil? && @trace_scalars) || field.trace
|
100
|
+
else
|
101
|
+
true
|
102
|
+
end
|
103
|
+
if trace_field
|
104
|
+
start_segment(partial_name: @nr_field_names[field], category: :web)
|
105
|
+
end
|
93
106
|
super
|
94
107
|
end
|
95
108
|
|
96
109
|
def end_execute_field(field, objects, arguments, query, result)
|
97
|
-
|
110
|
+
finish_segment
|
98
111
|
super
|
99
112
|
end
|
100
113
|
|
101
114
|
def begin_authorized(type, obj, ctx)
|
102
115
|
if @trace_authorized
|
103
|
-
|
116
|
+
start_segment(partial_name: @nr_authorized_names[type], category: :web)
|
104
117
|
end
|
105
118
|
super
|
106
119
|
end
|
107
120
|
|
108
121
|
def end_authorized(type, obj, ctx, is_authed)
|
109
122
|
if @trace_authorized
|
110
|
-
|
123
|
+
finish_segment
|
111
124
|
end
|
112
125
|
super
|
113
126
|
end
|
114
127
|
|
115
128
|
def begin_resolve_type(type, value, context)
|
116
129
|
if @trace_resolve_type
|
117
|
-
|
130
|
+
start_segment(partial_name: @nr_resolve_type_names[type], category: :web)
|
118
131
|
end
|
119
132
|
super
|
120
133
|
end
|
121
134
|
|
122
135
|
def end_resolve_type(type, value, context, resolved_type)
|
123
136
|
if @trace_resolve_type
|
124
|
-
|
137
|
+
finish_segment
|
125
138
|
end
|
126
139
|
super
|
127
140
|
end
|
128
141
|
|
129
|
-
def begin_dataloader(dl)
|
130
|
-
super
|
131
|
-
end
|
132
|
-
|
133
|
-
def end_dataloader(dl)
|
134
|
-
super
|
135
|
-
end
|
136
|
-
|
137
142
|
def begin_dataloader_source(source)
|
138
|
-
|
143
|
+
start_segment(partial_name: @nr_source_names[source], category: :web)
|
139
144
|
super
|
140
145
|
end
|
141
146
|
|
142
147
|
def end_dataloader_source(source)
|
143
|
-
|
148
|
+
finish_segment
|
144
149
|
super
|
145
150
|
end
|
146
151
|
|
147
152
|
def dataloader_fiber_yield(source)
|
148
|
-
|
149
|
-
|
153
|
+
prev_segment = finish_segment
|
154
|
+
Fiber[:graphql_nr_previous_segment] = prev_segment
|
150
155
|
super
|
151
156
|
end
|
152
157
|
|
153
158
|
def dataloader_fiber_resume(source)
|
154
|
-
prev_segment =
|
159
|
+
prev_segment = Fiber[:graphql_nr_previous_segment]
|
160
|
+
Fiber[:graphql_nr_previous_segment] = nil
|
155
161
|
seg_partial_name = prev_segment.name.sub(/^.*(GraphQL.*)$/, '\1')
|
156
|
-
|
162
|
+
start_segment(partial_name: seg_partial_name, category: :web)
|
157
163
|
super
|
158
164
|
end
|
159
165
|
|
160
166
|
private
|
161
167
|
|
162
|
-
def
|
163
|
-
Fiber[:
|
168
|
+
def start_segment(...)
|
169
|
+
Fiber[:graphql_nr_segment] = NewRelic::Agent::Tracer.start_transaction_or_segment(...)
|
170
|
+
end
|
171
|
+
|
172
|
+
def finish_segment
|
173
|
+
segment = Fiber[:graphql_nr_segment]
|
174
|
+
if segment
|
175
|
+
segment.finish
|
176
|
+
Fiber[:graphql_nr_segment] = nil
|
177
|
+
segment
|
178
|
+
end
|
164
179
|
end
|
165
180
|
|
166
181
|
def transaction_name(query)
|
data/lib/graphql/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.4.
|
4
|
+
version: 2.4.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-03-11 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: base64
|