graphql 2.0.23 → 2.0.25

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5aade8e5f88eb3dd8e8d6148aa7e8e00b0a351422b090e02a911158f425f1905
4
- data.tar.gz: 1ee1e4529f0b1f68596ce73f2b171624637999f0a1d6fc03f377c46fc7756a78
3
+ metadata.gz: 289b06689b36a3673c053d13d2ce0835a089e92db60e3e24e8af791fdd934d81
4
+ data.tar.gz: 3c5257c0732d0729b97ac92376427ccba26ef66397f9b9d07c18e9792ea03251
5
5
  SHA512:
6
- metadata.gz: 8b9cd861e35c529674bdacb6e1a34864bca6852c030022215ecc028d2b36b20da120be4ee45bb5ac5fb906b856e78dc4cfbbcf55609ad47fc47e6d68422a26b1
7
- data.tar.gz: b4afc463a0c9058396f274c9224f5a6b0bca73fefc68c1e6f10e53eea24b95b89ef6d9ddd3e22929d90be69b7c46bd81e6ed134ddab2550cbfc4408aee29b8ef
6
+ metadata.gz: 72ad503ed008aa168a63c1a40ac9e44dbb9b2d6a7871f1cd6191a692d6275506ce17f0df3135ae0a2ef95fe695a457d76239a2623f477cf7cd7f27cc124888c7
7
+ data.tar.gz: 90976b77a4230bf8a7d3fe5470deae70082e6caac1d3b30c799e958621cd2177283d0f83eb06128081e7e2179e33e718a1ba9353ab388fac83553e0988e7b4bc
@@ -6,7 +6,7 @@ module Graphql
6
6
  # TODO: What other options should be supported?
7
7
  #
8
8
  # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name
9
- # rails g graphql:mutation CreatePostMutation
9
+ # rails g graphql:mutation DeletePostMutation
10
10
  class MutationDeleteGenerator < OrmMutationsBase
11
11
 
12
12
  desc "Scaffold a Relay Classic ORM delete mutation for the given model class"
@@ -6,7 +6,7 @@ module Graphql
6
6
  # TODO: What other options should be supported?
7
7
  #
8
8
  # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name
9
- # rails g graphql:mutation CreatePostMutation
9
+ # rails g graphql:mutation UpdatePostMutation
10
10
  class MutationUpdateGenerator < OrmMutationsBase
11
11
 
12
12
  desc "Scaffold a Relay Classic ORM update mutation for the given model class"
@@ -7,9 +7,9 @@ module GraphQL
7
7
  # @api private
8
8
  def setup(dataloader)
9
9
  # These keys have been requested but haven't been fetched yet
10
- @pending_keys = []
10
+ @pending = {}
11
11
  # These keys have been passed to `fetch` but haven't been finished yet
12
- @fetching_keys = []
12
+ @fetching = {}
13
13
  # { key => result }
14
14
  @results = {}
15
15
  @dataloader = dataloader
@@ -18,42 +18,66 @@ module GraphQL
18
18
  attr_reader :dataloader
19
19
 
20
20
  # @return [Dataloader::Request] a pending request for a value from `key`. Call `.load` on that object to wait for the result.
21
- def request(key)
22
- if !@results.key?(key)
23
- @pending_keys << key
21
+ def request(value)
22
+ res_key = result_key_for(value)
23
+ if !@results.key?(res_key)
24
+ @pending[res_key] ||= value
24
25
  end
25
- Dataloader::Request.new(self, key)
26
+ Dataloader::Request.new(self, value)
27
+ end
28
+
29
+ # Implement this method to return a stable identifier if different
30
+ # key objects should load the same data value.
31
+ #
32
+ # @param value [Object] A value passed to `.request` or `.load`, for which a value will be loaded
33
+ # @return [Object] The key for tracking this pending data
34
+ def result_key_for(value)
35
+ value
26
36
  end
27
37
 
28
38
  # @return [Dataloader::Request] a pending request for a values from `keys`. Call `.load` on that object to wait for the results.
29
- def request_all(keys)
30
- pending_keys = keys.select { |k| !@results.key?(k) }
31
- @pending_keys.concat(pending_keys)
32
- Dataloader::RequestAll.new(self, keys)
39
+ def request_all(values)
40
+ values.each do |v|
41
+ res_key = result_key_for(v)
42
+ if !@results.key?(res_key)
43
+ @pending[res_key] ||= v
44
+ end
45
+ end
46
+ Dataloader::RequestAll.new(self, values)
33
47
  end
34
48
 
35
- # @param key [Object] A loading key which will be passed to {#fetch} if it isn't already in the internal cache.
49
+ # @param value [Object] A loading value which will be passed to {#fetch} if it isn't already in the internal cache.
36
50
  # @return [Object] The result from {#fetch} for `key`. If `key` hasn't been loaded yet, the Fiber will yield until it's loaded.
37
- def load(key)
38
- if @results.key?(key)
39
- result_for(key)
51
+ def load(value)
52
+ result_key = result_key_for(value)
53
+ if @results.key?(result_key)
54
+ result_for(result_key)
40
55
  else
41
- @pending_keys << key
42
- sync
43
- result_for(key)
56
+ @pending[result_key] ||= value
57
+ sync([result_key])
58
+ result_for(result_key)
44
59
  end
45
60
  end
46
61
 
47
- # @param keys [Array<Object>] Loading keys which will be passed to `#fetch` (or read from the internal cache).
62
+ # @param values [Array<Object>] Loading keys which will be passed to `#fetch` (or read from the internal cache).
48
63
  # @return [Object] The result from {#fetch} for `keys`. If `keys` haven't been loaded yet, the Fiber will yield until they're loaded.
49
- def load_all(keys)
50
- if keys.any? { |k| !@results.key?(k) }
51
- pending_keys = keys.select { |k| !@results.key?(k) }
52
- @pending_keys.concat(pending_keys)
53
- sync
64
+ def load_all(values)
65
+ result_keys = []
66
+ pending_keys = []
67
+ values.each { |v|
68
+ k = result_key_for(v)
69
+ result_keys << k
70
+ if !@results.key?(k)
71
+ @pending[k] ||= v
72
+ pending_keys << k
73
+ end
74
+ }
75
+
76
+ if pending_keys.any?
77
+ sync(pending_keys)
54
78
  end
55
79
 
56
- keys.map { |k| result_for(k) }
80
+ result_keys.map { |k| result_for(k) }
57
81
  end
58
82
 
59
83
  # Subclasses must implement this method to return a value for each of `keys`
@@ -67,14 +91,13 @@ module GraphQL
67
91
  # Wait for a batch, if there's anything to batch.
68
92
  # Then run the batch and update the cache.
69
93
  # @return [void]
70
- def sync
71
- pending_keys = @pending_keys.dup
94
+ def sync(pending_result_keys)
72
95
  @dataloader.yield
73
96
  iterations = 0
74
- while pending_keys.any? { |k| !@results.key?(k) }
97
+ while pending_result_keys.any? { |key| !@results.key?(key) }
75
98
  iterations += 1
76
99
  if iterations > 1000
77
- raise "#{self.class}#sync tried 1000 times to load pending keys (#{pending_keys}), but they still weren't loaded. There is likely a circular dependency."
100
+ raise "#{self.class}#sync tried 1000 times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency."
78
101
  end
79
102
  @dataloader.yield
80
103
  end
@@ -83,15 +106,18 @@ module GraphQL
83
106
 
84
107
  # @return [Boolean] True if this source has any pending requests for data.
85
108
  def pending?
86
- !@pending_keys.empty?
109
+ !@pending.empty?
87
110
  end
88
111
 
89
112
  # Add these key-value pairs to this source's cache
90
113
  # (future loads will use these merged values).
91
- # @param results [Hash<Object => Object>] key-value pairs to cache in this source
114
+ # @param new_results [Hash<Object => Object>] key-value pairs to cache in this source
92
115
  # @return [void]
93
- def merge(results)
94
- @results.merge!(results)
116
+ def merge(new_results)
117
+ new_results.each do |new_k, new_v|
118
+ key = result_key_for(new_k)
119
+ @results[key] = new_v
120
+ end
95
121
  nil
96
122
  end
97
123
 
@@ -99,24 +125,22 @@ module GraphQL
99
125
  # @api private
100
126
  # @return [void]
101
127
  def run_pending_keys
102
- if !@fetching_keys.empty?
103
- @pending_keys -= @fetching_keys
128
+ if !@fetching.empty?
129
+ @fetching.each_key { |k| @pending.delete(k) }
104
130
  end
105
- return if @pending_keys.empty?
106
- fetch_keys = @pending_keys.uniq
107
- @fetching_keys.concat(fetch_keys)
108
- @pending_keys = []
109
- results = fetch(fetch_keys)
110
- fetch_keys.each_with_index do |key, idx|
131
+ return if @pending.empty?
132
+ fetch_h = @pending
133
+ @pending = {}
134
+ @fetching.merge!(fetch_h)
135
+ results = fetch(fetch_h.values)
136
+ fetch_h.each_with_index do |(key, _value), idx|
111
137
  @results[key] = results[idx]
112
138
  end
113
139
  nil
114
140
  rescue StandardError => error
115
- fetch_keys.each { |key| @results[key] = error }
141
+ fetch_h.each_key { |key| @results[key] = error }
116
142
  ensure
117
- if fetch_keys
118
- @fetching_keys -= fetch_keys
119
- end
143
+ fetch_h && fetch_h.each_key { |k| @fetching.delete(k) }
120
144
  end
121
145
 
122
146
  # These arguments are given to `dataloader.with(source_class, ...)`. The object
@@ -137,7 +161,7 @@ module GraphQL
137
161
  [*batch_args, **batch_kwargs]
138
162
  end
139
163
 
140
- attr_reader :pending_keys
164
+ attr_reader :pending
141
165
 
142
166
  private
143
167
 
@@ -111,8 +111,8 @@ module GraphQL
111
111
  @source_cache.each do |source_class, batched_sources|
112
112
  batched_sources.each do |batch_args, batched_source_instance|
113
113
  if batched_source_instance.pending?
114
- prev_pending_keys[batched_source_instance] = batched_source_instance.pending_keys.dup
115
- batched_source_instance.pending_keys.clear
114
+ prev_pending_keys[batched_source_instance] = batched_source_instance.pending.dup
115
+ batched_source_instance.pending.clear
116
116
  end
117
117
  end
118
118
  end
@@ -127,8 +127,8 @@ module GraphQL
127
127
  res
128
128
  ensure
129
129
  @pending_jobs = prev_queue
130
- prev_pending_keys.each do |source_instance, pending_keys|
131
- source_instance.pending_keys.concat(pending_keys)
130
+ prev_pending_keys.each do |source_instance, pending|
131
+ source_instance.pending.merge!(pending)
132
132
  end
133
133
  end
134
134
 
@@ -247,15 +247,15 @@ module GraphQL
247
247
  st = get_current_runtime_state
248
248
  st.current_object = query.root_value
249
249
  st.current_result = @response
250
- object_proxy = authorized_new(root_type, query.root_value, context)
251
- object_proxy = schema.sync_lazy(object_proxy)
250
+ runtime_object = root_type.wrap(query.root_value, context)
251
+ runtime_object = schema.sync_lazy(runtime_object)
252
252
 
253
- if object_proxy.nil?
253
+ if runtime_object.nil?
254
254
  # Root .authorized? returned false.
255
255
  @response = nil
256
256
  else
257
- call_method_on_directives(:resolve, object_proxy, root_operation.directives) do # execute query level directives
258
- gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
257
+ call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
258
+ gathered_selections = gather_selections(runtime_object, root_type, root_operation.selections)
259
259
  # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
260
260
  # require isolation during execution (because of runtime directives). In that case,
261
261
  # make a new, isolated result hash for writing the result into. (That isolated response
@@ -280,9 +280,9 @@ module GraphQL
280
280
  if (directives = selections[:graphql_directives])
281
281
  selections.delete(:graphql_directives)
282
282
  end
283
- call_method_on_directives(:resolve, object_proxy, directives) do
283
+ call_method_on_directives(:resolve, runtime_object, directives) do
284
284
  evaluate_selections(
285
- object_proxy,
285
+ runtime_object,
286
286
  root_type,
287
287
  root_op_type == "mutation",
288
288
  selections,
@@ -438,10 +438,8 @@ module GraphQL
438
438
  st.current_result = selections_result
439
439
  st.current_result_name = result_name
440
440
 
441
- object = owner_object
442
-
443
441
  if is_introspection
444
- object = authorized_new(field_defn.owner, object, context)
442
+ owner_object = field_defn.owner.wrap(owner_object, context)
445
443
  end
446
444
 
447
445
  total_args_count = field_defn.arguments(context).size
@@ -449,14 +447,14 @@ module GraphQL
449
447
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
450
448
  if field_defn.extras.size == 0
451
449
  evaluate_selection_with_resolved_keyword_args(
452
- NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null
450
+ NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null
453
451
  )
454
452
  else
455
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
453
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
456
454
  end
457
455
  else
458
- @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
459
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
456
+ @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
457
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
460
458
  end
461
459
  end
462
460
  end
@@ -774,7 +772,7 @@ module GraphQL
774
772
  end
775
773
  when "OBJECT"
776
774
  object_proxy = begin
777
- authorized_new(current_type, value, context)
775
+ current_type.wrap(value, context)
778
776
  rescue GraphQL::ExecutionError => err
779
777
  err
780
778
  end
@@ -1047,10 +1045,6 @@ module GraphQL
1047
1045
  end
1048
1046
  end
1049
1047
 
1050
- def authorized_new(type, value, context)
1051
- type.authorized_new(value, context)
1052
- end
1053
-
1054
1048
  def lazy?(object)
1055
1049
  obj_class = object.class
1056
1050
  is_lazy = @lazy_cache[obj_class]
@@ -11,7 +11,7 @@ module GraphQL
11
11
  # Apply wrapping manually since this field isn't wrapped by instrumentation
12
12
  schema = @context.query.schema
13
13
  schema_type = schema.introspection_system.types["__Schema"]
14
- schema_type.authorized_new(schema, @context)
14
+ schema_type.wrap(schema, @context)
15
15
  end
16
16
 
17
17
  def __type(name:)
@@ -68,7 +68,8 @@ module GraphQL
68
68
  elsif @operation_name_error
69
69
  @validation_errors << @operation_name_error
70
70
  else
71
- validation_result = @schema.static_validator.validate(@query, validate: @query.validate, timeout: @schema.validate_timeout, max_errors: @schema.validate_max_errors)
71
+ validator = @query.static_validator || @schema.static_validator
72
+ validation_result = validator.validate(@query, validate: @query.validate, timeout: @schema.validate_timeout, max_errors: @schema.validate_max_errors)
72
73
  @validation_errors.concat(validation_result[:errors])
73
74
 
74
75
  if @validation_errors.empty?
data/lib/graphql/query.rb CHANGED
@@ -45,6 +45,20 @@ module GraphQL
45
45
  end
46
46
  end
47
47
 
48
+ # @return [GraphQL::StaticValidation::Validator] if present, the query will validate with these rules.
49
+ attr_reader :static_validator
50
+
51
+ # @param new_validate [GraphQL::StaticValidation::Validator] if present, the query will validate with these rules. This can't be reasssigned after validation.
52
+ def static_validator=(new_validator)
53
+ if defined?(@validation_pipeline) && @validation_pipeline && @validation_pipeline.has_validated?
54
+ raise ArgumentError, "Can't reassign Query#static_validator= after validation has run, remove this assignment."
55
+ elsif !new_validator.is_a?(GraphQL::StaticValidation::Validator)
56
+ raise ArgumentError, "Expected a `GraphQL::StaticValidation::Validator` instance."
57
+ else
58
+ @static_validator = new_validator
59
+ end
60
+ end
61
+
48
62
  attr_writer :query_string
49
63
 
50
64
  # @return [GraphQL::Language::Nodes::Document]
@@ -83,7 +97,7 @@ module GraphQL
83
97
  # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
84
98
  # @param except [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns truthy
85
99
  # @param only [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns false
86
- def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, except: nil, only: nil, warden: nil)
100
+ def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, except: nil, only: nil, warden: nil)
87
101
  # Even if `variables: nil` is passed, use an empty hash for simpler logic
88
102
  variables ||= {}
89
103
  @schema = schema
@@ -97,6 +111,7 @@ module GraphQL
97
111
  @fragments = nil
98
112
  @operations = nil
99
113
  @validate = validate
114
+ self.static_validator = static_validator if static_validator
100
115
  context_tracers = (context ? context.fetch(:tracers, []) : [])
101
116
  @tracers = schema.tracers + context_tracers
102
117
 
@@ -40,14 +40,21 @@ module GraphQL
40
40
  end
41
41
 
42
42
  def add_directives_from(owner)
43
- dirs = owner.directives.map(&:class)
44
- @directives.merge(dirs)
45
- add_type_and_traverse(dirs)
43
+ if (dir_instances = owner.directives).any?
44
+ dirs = dir_instances.map(&:class)
45
+ @directives.merge(dirs)
46
+ add_type_and_traverse(dirs)
47
+ end
46
48
  end
47
49
 
48
50
  def add_type_and_traverse(new_types)
49
51
  late_types = []
50
- new_types.each { |t| add_type(t, owner: nil, late_types: late_types, path: [t.graphql_name]) }
52
+ path = []
53
+ new_types.each do |t|
54
+ path.push(t.graphql_name)
55
+ add_type(t, owner: nil, late_types: late_types, path: path)
56
+ path.pop
57
+ end
51
58
  missed_late_types = 0
52
59
  while (late_type_vals = late_types.shift)
53
60
  type_owner, lt = late_type_vals
@@ -158,7 +165,9 @@ module GraphQL
158
165
  type.all_argument_definitions.each do |arg|
159
166
  arg_type = arg.type.unwrap
160
167
  references_to(arg_type, from: arg)
161
- add_type(arg_type, owner: arg, late_types: late_types, path: path + [arg.graphql_name])
168
+ path.push(arg.graphql_name)
169
+ add_type(arg_type, owner: arg, late_types: late_types, path: path)
170
+ path.pop
162
171
  if arg.default_value?
163
172
  @arguments_with_default_values << arg
164
173
  end
@@ -179,18 +188,21 @@ module GraphQL
179
188
  name = field.graphql_name
180
189
  field_type = field.type.unwrap
181
190
  references_to(field_type, from: field)
182
- field_path = path + [name]
183
- add_type(field_type, owner: field, late_types: late_types, path: field_path)
191
+ path.push(name)
192
+ add_type(field_type, owner: field, late_types: late_types, path: path)
184
193
  add_directives_from(field)
185
194
  field.all_argument_definitions.each do |arg|
186
195
  add_directives_from(arg)
187
196
  arg_type = arg.type.unwrap
188
197
  references_to(arg_type, from: arg)
189
- add_type(arg_type, owner: arg, late_types: late_types, path: field_path + [arg.graphql_name])
198
+ path.push(arg.graphql_name)
199
+ add_type(arg_type, owner: arg, late_types: late_types, path: path)
200
+ path.pop
190
201
  if arg.default_value?
191
202
  @arguments_with_default_values << arg
192
203
  end
193
204
  end
205
+ path.pop
194
206
  end
195
207
  end
196
208
  if type.kind.input_object?
@@ -198,7 +210,9 @@ module GraphQL
198
210
  add_directives_from(arg)
199
211
  arg_type = arg.type.unwrap
200
212
  references_to(arg_type, from: arg)
201
- add_type(arg_type, owner: arg, late_types: late_types, path: path + [arg.graphql_name])
213
+ path.push(arg.graphql_name)
214
+ add_type(arg_type, owner: arg, late_types: late_types, path: path)
215
+ path.pop
202
216
  if arg.default_value?
203
217
  @arguments_with_default_values << arg
204
218
  end
@@ -206,14 +220,18 @@ module GraphQL
206
220
  end
207
221
  if type.kind.union?
208
222
  @possible_types[type.graphql_name] = type.all_possible_types
223
+ path.push("possible_types")
209
224
  type.all_possible_types.each do |t|
210
- add_type(t, owner: type, late_types: late_types, path: path + ["possible_types"])
225
+ add_type(t, owner: type, late_types: late_types, path: path)
211
226
  end
227
+ path.pop
212
228
  end
213
229
  if type.kind.interface?
230
+ path.push("orphan_types")
214
231
  type.orphan_types.each do |t|
215
- add_type(t, owner: type, late_types: late_types, path: path + ["orphan_types"])
232
+ add_type(t, owner: type, late_types: late_types, path: path)
216
233
  end
234
+ path.pop
217
235
  end
218
236
  if type.kind.object?
219
237
  possible_types_for_this_name = @possible_types[type.graphql_name] ||= []
@@ -221,6 +239,7 @@ module GraphQL
221
239
  end
222
240
 
223
241
  if type.kind.object? || type.kind.interface?
242
+ path.push("implements")
224
243
  type.interface_type_memberships.each do |interface_type_membership|
225
244
  case interface_type_membership
226
245
  when Schema::TypeMembership
@@ -235,8 +254,9 @@ module GraphQL
235
254
  else
236
255
  raise ArgumentError, "Invariant: unexpected type membership for #{type.graphql_name}: #{interface_type_membership.class} (#{interface_type_membership.inspect})"
237
256
  end
238
- add_type(interface_type, owner: type, late_types: late_types, path: path + ["implements"])
257
+ add_type(interface_type, owner: type, late_types: late_types, path: path)
239
258
  end
259
+ path.pop
240
260
  end
241
261
  end
242
262
  end
@@ -339,7 +339,7 @@ module GraphQL
339
339
  self.validates(validates)
340
340
  end
341
341
 
342
- if definition_block
342
+ if block_given?
343
343
  if definition_block.arity == 1
344
344
  yield self
345
345
  else
@@ -155,7 +155,7 @@ module GraphQL
155
155
  if obj.is_a?(GraphQL::Schema::Object)
156
156
  obj = obj.object
157
157
  end
158
- wrapped_object = @object_class.authorized_new(obj, query_ctx)
158
+ wrapped_object = @object_class.wrap(obj, query_ctx)
159
159
  @inner_resolve.call(wrapped_object, args, ctx)
160
160
  end
161
161
  end
@@ -109,7 +109,14 @@ module GraphQL
109
109
  to_type_name(something.name)
110
110
  end
111
111
  when String
112
- something.gsub(/\]\[\!/, "").split("::").last
112
+ if something.include?("]") ||
113
+ something.include?("[") ||
114
+ something.include?("!") ||
115
+ something.include?("::")
116
+ something.gsub(/\]\[\!/, "").split("::").last
117
+ else
118
+ something
119
+ end
113
120
  when GraphQL::Schema::NonNull, GraphQL::Schema::List
114
121
  to_type_name(something.unwrap)
115
122
  else
@@ -122,7 +129,8 @@ module GraphQL
122
129
  return string unless string.include?("_")
123
130
  camelized = string.split('_').each(&:capitalize!).join
124
131
  camelized[0] = camelized[0].downcase
125
- if (match_data = string.match(/\A(_+)/))
132
+ if string.start_with?("_")
133
+ match_data = string.match(/\A(_+)/)
126
134
  camelized = "#{match_data[0]}#{camelized}"
127
135
  end
128
136
  camelized
@@ -30,6 +30,11 @@ module GraphQL
30
30
  # @see authorized_new to make instances
31
31
  protected :new
32
32
 
33
+ # This is called by the runtime to return an object to call methods on.
34
+ def wrap(object, context)
35
+ authorized_new(object, context)
36
+ end
37
+
33
38
  # Make a new instance of this type _if_ the auth check passes,
34
39
  # otherwise, raise an error.
35
40
  #
@@ -158,16 +158,24 @@ module GraphQL
158
158
  def trace_class_for(mode)
159
159
  @trace_modes ||= {}
160
160
  @trace_modes[mode] ||= begin
161
- if mode == :default_backtrace
161
+ case mode
162
+ when :default
163
+ superclass_base_class = if superclass.respond_to?(:trace_class_for)
164
+ superclass.trace_class_for(mode)
165
+ else
166
+ GraphQL::Tracing::Trace
167
+ end
168
+ Class.new(superclass_base_class)
169
+ when :default_backtrace
162
170
  schema_base_class = trace_class_for(:default)
163
171
  Class.new(schema_base_class) do
164
172
  include(GraphQL::Backtrace::Trace)
165
173
  end
166
- elsif superclass.respond_to?(:trace_class_for)
167
- superclass_base_class = superclass.trace_class_for(mode)
168
- Class.new(superclass_base_class)
169
174
  else
170
- Class.new(GraphQL::Tracing::Trace)
175
+ mods = trace_modules_for(mode)
176
+ Class.new(trace_class_for(:default)) do
177
+ mods.any? && include(*mods)
178
+ end
171
179
  end
172
180
  end
173
181
  end
@@ -183,6 +191,19 @@ module GraphQL
183
191
  nil
184
192
  end
185
193
 
194
+ def own_trace_modules
195
+ @own_trace_modules ||= Hash.new { |h, k| h[k] = [] }
196
+ end
197
+
198
+ # @return [Array<Module>] Modules added for tracing in `trace_mode`, including inherited ones
199
+ def trace_modules_for(trace_mode)
200
+ modules = own_trace_modules[trace_mode]
201
+ if superclass.respond_to?(:trace_modules_for)
202
+ modules += superclass.trace_modules_for(trace_mode)
203
+ end
204
+ modules
205
+ end
206
+
186
207
 
187
208
  # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
188
209
  # @see {#as_json}
@@ -992,26 +1013,58 @@ module GraphQL
992
1013
  # will be called at runtime.
993
1014
  #
994
1015
  # @param trace_mod [Module] A module that implements tracing methods
1016
+ # @param mode [Symbol] Trace module will only be used for this trade mode
995
1017
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
996
1018
  # @return [void]
997
- def trace_with(trace_mod, **options)
998
- trace_options.merge!(options)
999
- trace_class.include(trace_mod)
1019
+ def trace_with(trace_mod, mode: :default, **options)
1020
+ if mode.is_a?(Array)
1021
+ mode.each { |m| trace_with(trace_mod, mode: m, **options) }
1022
+ else
1023
+ tc = trace_class_for(mode)
1024
+ tc.include(trace_mod)
1025
+ if mode != :default
1026
+ own_trace_modules[mode] << trace_mod
1027
+ end
1028
+ t_opts = trace_options_for(mode)
1029
+ t_opts.merge!(options)
1030
+ end
1031
+ nil
1000
1032
  end
1001
1033
 
1002
- def trace_options
1003
- @trace_options ||= superclass.respond_to?(:trace_options) ? superclass.trace_options.dup : {}
1034
+ # The options hash for this trace mode
1035
+ # @return [Hash]
1036
+ def trace_options_for(mode)
1037
+ @trace_options_for_mode ||= {}
1038
+ @trace_options_for_mode[mode] ||= begin
1039
+ if superclass.respond_to?(:trace_options_for)
1040
+ superclass.trace_options_for(mode).dup
1041
+ else
1042
+ {}
1043
+ end
1044
+ end
1004
1045
  end
1005
1046
 
1006
- def new_trace(**options)
1007
- options = trace_options.merge(options)
1008
- trace_mode = if (target = options[:query] || options[:multiplex]) && target.context[:backtrace]
1047
+ # Create a trace instance which will include the trace modules specified for the optional mode.
1048
+ #
1049
+ # @param mode [Symbol] Trace modules for this trade mode will be included
1050
+ # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1051
+ # @return [Tracing::Trace]
1052
+ def new_trace(mode: nil, **options)
1053
+ target = options[:query] || options[:multiplex]
1054
+ mode ||= target && target.context[:trace_mode]
1055
+
1056
+ trace_mode = if mode
1057
+ mode
1058
+ elsif target && target.context[:backtrace]
1009
1059
  :default_backtrace
1010
1060
  else
1011
1061
  :default
1012
1062
  end
1013
- trace = trace_class_for(trace_mode).new(**options)
1014
- trace
1063
+
1064
+ base_trace_options = trace_options_for(trace_mode)
1065
+ trace_options = base_trace_options.merge(options)
1066
+ trace_class_for_mode = trace_class_for(trace_mode)
1067
+ trace_class_for_mode.new(**trace_options)
1015
1068
  end
1016
1069
 
1017
1070
  def query_analyzer(new_analyzer)
@@ -1052,6 +1105,7 @@ module GraphQL
1052
1105
  tracers: ctx[:tracers],
1053
1106
  trace: ctx[:trace],
1054
1107
  dataloader: ctx[:dataloader],
1108
+ trace_mode: ctx[:trace_mode],
1055
1109
  }
1056
1110
  else
1057
1111
  {}
@@ -55,20 +55,39 @@ module GraphQL
55
55
  RUBY
56
56
  end
57
57
 
58
- def platform_execute_field(platform_key, data)
59
- return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
60
- layer = platform_key
61
- kvs = metadata(data, layer)
62
-
63
- ::AppOpticsAPM::SDK.trace(layer, kvs) do
64
- kvs.clear # we don't have to send them twice
65
- yield
58
+ def execute_field(query:, field:, ast_node:, arguments:, object:)
59
+ return_type = field.type.unwrap
60
+ trace_field = if return_type.kind.scalar? || return_type.kind.enum?
61
+ (field.trace.nil? && @trace_scalars) || field.trace
62
+ else
63
+ true
64
+ end
65
+ platform_key = if trace_field
66
+ @platform_key_cache[AppOpticsTrace].platform_field_key_cache[field]
67
+ else
68
+ nil
69
+ end
70
+ if platform_key && trace_field
71
+ return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
72
+ layer = platform_key
73
+ kvs = metadata({query: query, field: field, ast_node: ast_node, arguments: arguments, object: object}, layer)
74
+
75
+ ::AppOpticsAPM::SDK.trace(layer, kvs) do
76
+ kvs.clear # we don't have to send them twice
77
+ super
78
+ end
79
+ else
80
+ super
66
81
  end
67
82
  end
68
83
 
84
+ def execute_field_lazy(query:, field:, ast_node:, arguments:, object:)
85
+ execute_field(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
86
+ end
87
+
69
88
  def authorized(**data)
70
89
  return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
71
- layer = @platform_authorized_key_cache[data[:type]]
90
+ layer = @platform_key_cache[AppOpticsTrace].platform_authorized_key_cache[data[:type]]
72
91
  kvs = metadata(data, layer)
73
92
 
74
93
  ::AppOpticsAPM::SDK.trace(layer, kvs) do
@@ -79,7 +98,7 @@ module GraphQL
79
98
 
80
99
  def authorized_lazy(**data)
81
100
  return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
82
- layer = @platform_authorized_key_cache[data[:type]]
101
+ layer = @platform_key_cache[AppOpticsTrace].platform_authorized_key_cache[data[:type]]
83
102
  kvs = metadata(data, layer)
84
103
 
85
104
  ::AppOpticsAPM::SDK.trace(layer, kvs) do
@@ -90,7 +109,8 @@ module GraphQL
90
109
 
91
110
  def resolve_type(**data)
92
111
  return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
93
- layer = @platform_resolve_type_key_cache[data[:type]]
112
+ layer = @platform_key_cache[AppOpticsTrace].platform_resolve_type_key_cache[data[:type]]
113
+
94
114
  kvs = metadata(data, layer)
95
115
 
96
116
  ::AppOpticsAPM::SDK.trace(layer, kvs) do
@@ -101,7 +121,7 @@ module GraphQL
101
121
 
102
122
  def resolve_type_lazy(**data)
103
123
  return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false
104
- layer = @platform_resolve_type_key_cache[data[:type]]
124
+ layer = @platform_key_cache[AppOpticsTrace].platform_resolve_type_key_cache[data[:type]]
105
125
  kvs = metadata(data, layer)
106
126
 
107
127
  ::AppOpticsAPM::SDK.trace(layer, kvs) do
@@ -18,6 +18,7 @@ module GraphQL
18
18
  @analytics_enabled = analytics_available && Datadog::Contrib::Analytics.enabled?(analytics_enabled)
19
19
  @analytics_sample_rate = analytics_sample_rate
20
20
  @service_name = service
21
+ @has_prepare_span = respond_to?(:prepare_span)
21
22
  super
22
23
  end
23
24
 
@@ -66,38 +67,60 @@ module GraphQL
66
67
  RUBY
67
68
  end
68
69
  }
69
- prepare_span("#{trace_method.sub("platform_", "")}", data, span)
70
+ if @has_prepare_span
71
+ prepare_span("#{trace_method.sub("platform_", "")}", data, span)
72
+ end
70
73
  super
71
74
  end
72
75
  end
73
76
  RUBY
74
77
  end
75
78
 
76
- def platform_execute_field(platform_key, data, span_key = "execute_field")
77
- @tracer.trace(platform_key, service: @service_name) do |span|
78
- span.span_type = 'custom'
79
- if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
80
- span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
81
- span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, span_key)
79
+ def execute_field(span_key = "execute_field", query:, field:, ast_node:, arguments:, object:)
80
+ return_type = field.type.unwrap
81
+ trace_field = if return_type.kind.scalar? || return_type.kind.enum?
82
+ (field.trace.nil? && @trace_scalars) || field.trace
83
+ else
84
+ true
85
+ end
86
+ platform_key = if trace_field
87
+ @platform_key_cache[DataDogTrace].platform_field_key_cache[field]
88
+ else
89
+ nil
90
+ end
91
+ if platform_key && trace_field
92
+ @tracer.trace(platform_key, service: @service_name) do |span|
93
+ span.span_type = 'custom'
94
+ if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
95
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
96
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, span_key)
97
+ end
98
+ if @has_prepare_span
99
+ prepare_span_data = { query: query, field: field, ast_node: ast_node, arguments: arguments, object: object }
100
+ prepare_span(span_key, prepare_span_data, span)
101
+ end
102
+ super(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
82
103
  end
83
- prepare_span(span_key, data, span)
84
- yield
104
+ else
105
+ super(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
85
106
  end
86
107
  end
87
108
 
88
- def platform_execute_field_lazy(platform_key, data, &block)
89
- platform_execute_field(platform_key, data, "execute_field_lazy", &block)
109
+ def execute_field_lazy(query:, field:, ast_node:, arguments:, object:)
110
+ execute_field("execute_field_lazy", query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
90
111
  end
91
112
 
92
113
  def authorized(object:, type:, query:, span_key: "authorized")
93
- platform_key = @platform_authorized_key_cache[type]
114
+ platform_key = @platform_key_cache[DataDogTrace].platform_authorized_key_cache[type]
94
115
  @tracer.trace(platform_key, service: @service_name) do |span|
95
116
  span.span_type = 'custom'
96
117
  if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
97
118
  span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
98
119
  span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, span_key)
99
120
  end
100
- prepare_span(span_key, {object: object, type: type, query: query}, span)
121
+ if @has_prepare_span
122
+ prepare_span(span_key, {object: object, type: type, query: query}, span)
123
+ end
101
124
  super(query: query, type: type, object: object)
102
125
  end
103
126
  end
@@ -107,14 +130,16 @@ module GraphQL
107
130
  end
108
131
 
109
132
  def resolve_type(object:, type:, query:, span_key: "resolve_type")
110
- platform_key = @platform_resolve_type_key_cache[type]
133
+ platform_key = @platform_key_cache[DataDogTrace].platform_resolve_type_key_cache[type]
111
134
  @tracer.trace(platform_key, service: @service_name) do |span|
112
135
  span.span_type = 'custom'
113
136
  if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
114
137
  span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
115
138
  span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, span_key)
116
139
  end
117
- prepare_span(span_key, {object: object, type: type, query: query}, span)
140
+ if @has_prepare_span
141
+ prepare_span(span_key, {object: object, type: type, query: query}, span)
142
+ end
118
143
  super(query: query, type: type, object: object)
119
144
  end
120
145
  end
@@ -129,8 +154,8 @@ module GraphQL
129
154
  # @param key [String] The event being traced
130
155
  # @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event)
131
156
  # @param span [Datadog::Tracing::SpanOperation] The datadog span for this event
132
- def prepare_span(key, data, span)
133
- end
157
+ # def prepare_span(key, data, span)
158
+ # end
134
159
 
135
160
  def platform_field_key(field)
136
161
  field.path
@@ -5,12 +5,22 @@ module GraphQL
5
5
  module PlatformTrace
6
6
  def initialize(trace_scalars: false, **_options)
7
7
  @trace_scalars = trace_scalars
8
- @platform_field_key_cache = Hash.new { |h, k| h[k] = platform_field_key(k) }
9
- @platform_authorized_key_cache = Hash.new { |h, k| h[k] = platform_authorized_key(k) }
10
- @platform_resolve_type_key_cache = Hash.new { |h, k| h[k] = platform_resolve_type_key(k) }
8
+
9
+ @platform_key_cache = Hash.new { |h, mod| h[mod] = mod::KeyCache.new }
11
10
  super
12
11
  end
13
12
 
13
+ module BaseKeyCache
14
+ def initialize
15
+ @platform_field_key_cache = Hash.new { |h, k| h[k] = platform_field_key(k) }
16
+ @platform_authorized_key_cache = Hash.new { |h, k| h[k] = platform_authorized_key(k) }
17
+ @platform_resolve_type_key_cache = Hash.new { |h, k| h[k] = platform_resolve_type_key(k) }
18
+ end
19
+
20
+ attr_reader :platform_field_key_cache, :platform_authorized_key_cache, :platform_resolve_type_key_cache
21
+ end
22
+
23
+
14
24
  def platform_execute_field_lazy(*args, &block)
15
25
  platform_execute_field(*args, &block)
16
26
  end
@@ -24,10 +34,11 @@ module GraphQL
24
34
  end
25
35
 
26
36
  def self.included(child_class)
27
- # Don't gather this unless necessary
28
- pass_data_to_execute_field = child_class.method_defined?(:platform_execute_field) &&
29
- child_class.instance_method(:platform_execute_field).arity != 1
30
-
37
+ key_methods_class = Class.new {
38
+ include(child_class)
39
+ include(BaseKeyCache)
40
+ }
41
+ child_class.const_set(:KeyCache, key_methods_class)
31
42
  [:execute_field, :execute_field_lazy].each do |field_trace_method|
32
43
  if !child_class.method_defined?(field_trace_method)
33
44
  child_class.module_eval <<-RUBY, __FILE__, __LINE__
@@ -39,12 +50,12 @@ module GraphQL
39
50
  true
40
51
  end
41
52
  platform_key = if trace_field
42
- @platform_field_key_cache[field]
53
+ @platform_key_cache[#{child_class}].platform_field_key_cache[field]
43
54
  else
44
55
  nil
45
56
  end
46
57
  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
58
+ platform_#{field_trace_method}(platform_key) do
48
59
  super
49
60
  end
50
61
  else
@@ -60,7 +71,7 @@ module GraphQL
60
71
  if !child_class.method_defined?(auth_trace_method)
61
72
  child_class.module_eval <<-RUBY, __FILE__, __LINE__
62
73
  def #{auth_trace_method}(type:, query:, object:)
63
- platform_key = @platform_authorized_key_cache[type]
74
+ platform_key = @platform_key_cache[#{child_class}].platform_authorized_key_cache[type]
64
75
  platform_#{auth_trace_method}(platform_key) do
65
76
  super
66
77
  end
@@ -73,7 +84,7 @@ module GraphQL
73
84
  if !child_class.method_defined?(rt_trace_method)
74
85
  child_class.module_eval <<-RUBY, __FILE__, __LINE__
75
86
  def #{rt_trace_method}(query:, type:, object:)
76
- platform_key = @platform_resolve_type_key_cache[type]
87
+ platform_key = @platform_key_cache[#{child_class}].platform_resolve_type_key_cache[type]
77
88
  platform_#{rt_trace_method}(platform_key) do
78
89
  super
79
90
  end
@@ -83,8 +94,6 @@ module GraphQL
83
94
  end
84
95
  end
85
96
 
86
-
87
-
88
97
  private
89
98
 
90
99
  # Get the transaction name based on the operation type and name if possible, or fall back to a user provided
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.0.23"
3
+ VERSION = "2.0.25"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.23
4
+ version: 2.0.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-19 00:00:00.000000000 Z
11
+ date: 2023-08-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -623,7 +623,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
623
623
  - !ruby/object:Gem::Version
624
624
  version: '0'
625
625
  requirements: []
626
- rubygems_version: 3.4.13
626
+ rubygems_version: 3.4.10
627
627
  signing_key:
628
628
  specification_version: 4
629
629
  summary: A GraphQL language and runtime for Ruby