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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba5296cae016dea52b006bebe1515d6eef54876ec9f6437f5414bfccd7d8cf40
4
- data.tar.gz: 75bf06fbb277d4e9c400f57998e0de5d2a6e4a41d562c71b0969fc30fe2bbb64
3
+ metadata.gz: b105157510cb5779715118d5f28f49822a9b3b86b0faec0fdc86a2331904f667
4
+ data.tar.gz: 9230b70e18665505190c673579fa66b2906d04009f9e46d844990bafdba374da
5
5
  SHA512:
6
- metadata.gz: e05120bb3377cbda64651ef2128b97daedf1b58e5e4a36b90d01d8ba4435a26e0dce6e270dfbc22ae75d29abcbe4d6f50c853ad6e13225a71eafeadbaad05519
7
- data.tar.gz: 2caa24da0288b0efab47b94a441351f8bbd0d1d3cb8bac1c834102fedb2ea350787fb74e2fe1d502971b181308f2b17b1e5b5273a2bf2a868d4917b29fc27f9b
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
- begin
76
- a.on_enter_#{node_type}(node, parent, self)
77
- rescue AnalysisError => err
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
- begin
86
- a.on_leave_#{node_type}(node, parent, self)
87
- rescue AnalysisError => err
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
- on_fragment_with_type(node) do
120
- @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
121
- @skipping = @skip_stack.last || skip?(node)
122
- @skip_stack << @skipping
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 on_fragment_with_type(node)
271
- object_type = if node.type
272
- @types.type(node.type.name)
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
@@ -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
- # `nil` or `0` causes no timeout
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 Timeout::Error
83
- [GraphQL::AnalysisError.new("Timeout on validation of query")]
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, value, ast_node)
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, value, ast_node)
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 eos?
19
- @scanner.eos?
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
- return false if @scanner.eos?
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.eos?
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
@@ -821,13 +821,13 @@ module GraphQL
821
821
 
822
822
  attr_writer :validate_timeout
823
823
 
824
- def validate_timeout(new_validate_timeout = nil)
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: owner_type, fields: fields, fragment_spreads: fragment_spreads) if fragment_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
- def initialize(set_transaction_name: false, trace_authorized: true, trace_resolve_type: true, **_rest)
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
- nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_field_names[field], category: :web)
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
- nr_segment_stack.pop.finish
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
- nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_authorized_names[type], category: :web)
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
- nr_segment_stack.pop.finish
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
- nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_resolve_type_names[type], category: :web)
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
- nr_segment_stack.pop.finish
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
- nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_source_names[source], category: :web)
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
- nr_segment_stack.pop.finish
148
+ finish_segment
144
149
  super
145
150
  end
146
151
 
147
152
  def dataloader_fiber_yield(source)
148
- current_segment = nr_segment_stack.last
149
- current_segment.finish
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 = nr_segment_stack.pop
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
- nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: seg_partial_name, category: :web)
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 nr_segment_stack
163
- Fiber[:graphql_nr_segment_stack] ||= []
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)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.4.11"
3
+ VERSION = "2.4.12"
4
4
  end
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.11
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-02-28 00:00:00.000000000 Z
10
+ date: 2025-03-11 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64