graphql 2.1.2 → 2.1.4

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: c2b3d848591e918d34df3851a973b6510912d6472f5847f493b5b0bfed4df997
4
- data.tar.gz: 3fcf17d2bc7259ef01cbe6d85aa23b31ccbed2321700c0474124e5eb6f0792d2
3
+ metadata.gz: 6c20f3924267df0e3f7b2a0b65a642572677d6f5cb041c37a098f8bb0cfaa32e
4
+ data.tar.gz: 8dacd2476d5471297a82348551ea8e34429d14edef9dbb1c2c8f34073c18abfc
5
5
  SHA512:
6
- metadata.gz: 8b2b258c108210008131e610ff0e486817ceebea1782d4f329f1f776ac8c70bdc03177f22a746c2f918d9a1c15e3b6e3971f2569cd3d2c15c6dd714e5a5d38d7
7
- data.tar.gz: 28e7f3c992987507e74e42320133b710aa6ca6af593dd2b6300359e30728ad415ff27e7671b5e8cfedb4b1a44324a06bc9fdd4ab95535c1f99e142aafebc3ea5
6
+ metadata.gz: f1fb9466a19755ce3e7f0ee9693428d1b2e5c8017287909e624975e49727376729ec5fda28318953ec6fda606a693f842c1caad5419dc410938a59506c994fae
7
+ data.tar.gz: d048fc02aece1781f4d15a700bc2cde8c1baf929917f35a9ae23567d5f720dabd5f4d18e47e3b8af2d9d6ecc6dde146f750a08b28de9fef769a960dc60ac0444
@@ -52,13 +52,6 @@ module GraphQL
52
52
  # { Class => Boolean }
53
53
  @lazy_cache = {}
54
54
  @lazy_cache.compare_by_identity
55
-
56
- @gathered_selections_cache = Hash.new { |h, k|
57
- cache = {}
58
- cache.compare_by_identity
59
- h[k] = cache
60
- }
61
- @gathered_selections_cache.compare_by_identity
62
55
  end
63
56
 
64
57
  def final_result
@@ -98,7 +91,7 @@ module GraphQL
98
91
  @response = nil
99
92
  else
100
93
  call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
101
- gathered_selections = gather_selections(runtime_object, root_type, nil, root_operation.selections)
94
+ gathered_selections = gather_selections(runtime_object, root_type, root_operation.selections)
102
95
  # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
103
96
  # require isolation during execution (because of runtime directives). In that case,
104
97
  # make a new, isolated result hash for writing the result into. (That isolated response
@@ -143,18 +136,11 @@ module GraphQL
143
136
  nil
144
137
  end
145
138
 
146
- def gather_selections(owner_object, owner_type, ast_node_for_caching, selections, selections_to_run = nil, selections_by_name = nil)
147
- if ast_node_for_caching && (cached_selections = @gathered_selections_cache[ast_node_for_caching][owner_type])
148
- return cached_selections
149
- end
150
- selections_by_name ||= {} # allocate this default here so we check the cache first
151
-
152
- should_cache = true
139
+ def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
153
140
 
154
141
  selections.each do |node|
155
142
  # Skip gathering this if the directive says so
156
143
  if !directives_include?(node, owner_object, owner_type)
157
- should_cache = false
158
144
  next
159
145
  end
160
146
 
@@ -179,7 +165,6 @@ module GraphQL
179
165
  if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
180
166
  next_selections = {}
181
167
  next_selections[:graphql_directives] = node.directives
182
- should_cache = false
183
168
  if selections_to_run
184
169
  selections_to_run << next_selections
185
170
  else
@@ -197,28 +182,24 @@ module GraphQL
197
182
  type_defn = schema.get_type(node.type.name, context)
198
183
 
199
184
  if query.warden.possible_types(type_defn).include?(owner_type)
200
- gather_selections(owner_object, owner_type, nil, node.selections, selections_to_run, next_selections)
185
+ gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
201
186
  end
202
187
  else
203
188
  # it's an untyped fragment, definitely continue
204
- gather_selections(owner_object, owner_type, nil, node.selections, selections_to_run, next_selections)
189
+ gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
205
190
  end
206
191
  when GraphQL::Language::Nodes::FragmentSpread
207
192
  fragment_def = query.fragments[node.name]
208
193
  type_defn = query.get_type(fragment_def.type.name)
209
194
  if query.warden.possible_types(type_defn).include?(owner_type)
210
- gather_selections(owner_object, owner_type, nil, fragment_def.selections, selections_to_run, next_selections)
195
+ gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
211
196
  end
212
197
  else
213
198
  raise "Invariant: unexpected selection class: #{node.class}"
214
199
  end
215
200
  end
216
201
  end
217
- result = selections_to_run || selections_by_name
218
- if should_cache
219
- @gathered_selections_cache[ast_node_for_caching][owner_type] = result
220
- end
221
- result
202
+ selections_to_run || selections_by_name
222
203
  end
223
204
 
224
205
  NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
@@ -619,7 +600,7 @@ module GraphQL
619
600
  response_hash = GraphQLResultHash.new(result_name, selection_result, is_non_null)
620
601
  set_result(selection_result, result_name, response_hash, true, is_non_null)
621
602
 
622
- gathered_selections = gather_selections(continue_value, current_type, ast_node, next_selections)
603
+ gathered_selections = gather_selections(continue_value, current_type, next_selections)
623
604
  # There are two possibilities for `gathered_selections`:
624
605
  # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
625
606
  # This case is handled below, and the result can be written right into the main `response_hash` above.
@@ -80,6 +80,22 @@ module GraphQL
80
80
  selection(field_name, selected_type: selected_type, arguments: arguments).selected?
81
81
  end
82
82
 
83
+ # True if this node has a selection with alias matching `alias_name`.
84
+ # If `alias_name` is a String, it is treated as a GraphQL-style (camelized)
85
+ # field name and used verbatim. If `alias_name` is a Symbol, it is
86
+ # treated as a Ruby-style (underscored) name and camelized before comparing.
87
+ #
88
+ # If `arguments:` is provided, each provided key/value will be matched
89
+ # against the arguments in the next selection. This method will return false
90
+ # if any of the given `arguments:` are not present and matching in the next selection.
91
+ # (But, the next selection may contain _more_ than the given arguments.)
92
+ # @param alias_name [String, Symbol]
93
+ # @param arguments [Hash] Arguments which must match in the selection
94
+ # @return [Boolean]
95
+ def selects_alias?(alias_name, arguments: nil)
96
+ alias_selection(alias_name, arguments: arguments).selected?
97
+ end
98
+
83
99
  # @return [Boolean] True if this lookahead represents a field that was requested
84
100
  def selected?
85
101
  true
@@ -105,6 +121,7 @@ module GraphQL
105
121
  .tap(&:flatten!)
106
122
  end
107
123
 
124
+
108
125
  if (match_by_orig_name = all_fields.find { |f| f.original_name == field_name })
109
126
  match_by_orig_name
110
127
  else
@@ -114,23 +131,29 @@ module GraphQL
114
131
  @query.get_field(selected_type, guessed_name)
115
132
  end
116
133
  end
134
+ lookahead_for_selection(next_field_defn, selected_type, arguments)
135
+ end
117
136
 
118
- if next_field_defn
119
- next_nodes = []
120
- @ast_nodes.each do |ast_node|
121
- ast_node.selections.each do |selection|
122
- find_selected_nodes(selection, next_field_defn, arguments: arguments, matches: next_nodes)
123
- end
124
- end
137
+ # Like {#selection}, but for aliases.
138
+ # It returns a null object (check with {#selected?})
139
+ # @return [GraphQL::Execution::Lookahead]
140
+ def alias_selection(alias_name, selected_type: @selected_type, arguments: nil)
141
+ alias_cache_key = [alias_name, arguments]
142
+ return alias_selections[key] if alias_selections.key?(alias_name)
125
143
 
126
- if next_nodes.any?
127
- Lookahead.new(query: @query, ast_nodes: next_nodes, field: next_field_defn, owner_type: selected_type)
128
- else
129
- NULL_LOOKAHEAD
130
- end
131
- else
132
- NULL_LOOKAHEAD
144
+ alias_node = lookup_alias_node(ast_nodes, alias_name)
145
+ return NULL_LOOKAHEAD unless alias_node
146
+
147
+ next_field_defn = @query.get_field(selected_type, alias_node.name)
148
+
149
+ alias_arguments = @query.arguments_for(alias_node, next_field_defn)
150
+ if alias_arguments.is_a?(::GraphQL::Execution::Interpreter::Arguments)
151
+ alias_arguments = alias_arguments.keyword_arguments
133
152
  end
153
+
154
+ return NULL_LOOKAHEAD if arguments && arguments != alias_arguments
155
+
156
+ alias_selections[alias_cache_key] = lookahead_for_selection(next_field_defn, selected_type, alias_arguments, alias_name)
134
157
  end
135
158
 
136
159
  # Like {#selection}, but for all nodes.
@@ -258,7 +281,7 @@ module GraphQL
258
281
  end
259
282
  find_selections(subselections_by_type, subselections_on_type, on_type, ast_selection.selections, arguments)
260
283
  when GraphQL::Language::Nodes::FragmentSpread
261
- frag_defn = @query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})")
284
+ frag_defn = lookup_fragment(ast_selection)
262
285
  # Again, assuming a valid AST
263
286
  on_type = @query.get_type(frag_defn.type.name)
264
287
  subselections_on_type = subselections_by_type[on_type] ||= {}
@@ -271,11 +294,11 @@ module GraphQL
271
294
 
272
295
  # If a selection on `node` matches `field_name` (which is backed by `field_defn`)
273
296
  # and matches the `arguments:` constraints, then add that node to `matches`
274
- def find_selected_nodes(node, field_defn, arguments:, matches:)
297
+ def find_selected_nodes(node, field_name, field_defn, arguments:, matches:, alias_name: NOT_CONFIGURED)
275
298
  return if skipped_by_directive?(node)
276
299
  case node
277
300
  when GraphQL::Language::Nodes::Field
278
- if node.name == field_defn.graphql_name
301
+ if node.name == field_name && (NOT_CONFIGURED.equal?(alias_name) || node.alias == alias_name)
279
302
  if arguments.nil? || arguments.empty?
280
303
  # No constraint applied
281
304
  matches << node
@@ -284,10 +307,10 @@ module GraphQL
284
307
  end
285
308
  end
286
309
  when GraphQL::Language::Nodes::InlineFragment
287
- node.selections.each { |s| find_selected_nodes(s, field_defn, arguments: arguments, matches: matches) }
310
+ node.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name) }
288
311
  when GraphQL::Language::Nodes::FragmentSpread
289
- frag_defn = @query.fragments[node.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})")
290
- frag_defn.selections.each { |s| find_selected_nodes(s, field_defn, arguments: arguments, matches: matches) }
312
+ frag_defn = lookup_fragment(node)
313
+ frag_defn.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name) }
291
314
  else
292
315
  raise "Unexpected selection comparison on #{node.class.name} (#{node})"
293
316
  end
@@ -306,6 +329,50 @@ module GraphQL
306
329
  query_kwargs.key?(arg_name_sym) && query_kwargs[arg_name_sym] == arg_value
307
330
  end
308
331
  end
332
+
333
+ def lookahead_for_selection(field_defn, selected_type, arguments, alias_name = NOT_CONFIGURED)
334
+ return NULL_LOOKAHEAD unless field_defn
335
+
336
+ next_nodes = []
337
+ field_name = field_defn.name
338
+ @ast_nodes.each do |ast_node|
339
+ ast_node.selections.each do |selection|
340
+ find_selected_nodes(selection, field_name, field_defn, arguments: arguments, matches: next_nodes, alias_name: alias_name)
341
+ end
342
+ end
343
+
344
+ return NULL_LOOKAHEAD if next_nodes.empty?
345
+
346
+ Lookahead.new(query: @query, ast_nodes: next_nodes, field: field_defn, owner_type: selected_type)
347
+ end
348
+
349
+ def alias_selections
350
+ return @alias_selections if defined?(@alias_selections)
351
+ @alias_selections ||= {}
352
+ end
353
+
354
+ def lookup_alias_node(nodes, name)
355
+ return if nodes.empty?
356
+
357
+ nodes.flat_map(&:children)
358
+ .flat_map { |child| unwrap_fragments(child) }
359
+ .find { |child| child.is_a?(GraphQL::Language::Nodes::Field) && child.alias == name }
360
+ end
361
+
362
+ def unwrap_fragments(node)
363
+ case node
364
+ when GraphQL::Language::Nodes::InlineFragment
365
+ node.children
366
+ when GraphQL::Language::Nodes::FragmentSpread
367
+ lookup_fragment(node).children
368
+ else
369
+ [node]
370
+ end
371
+ end
372
+
373
+ def lookup_fragment(ast_selection)
374
+ @query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})")
375
+ end
309
376
  end
310
377
  end
311
378
  end
@@ -12,10 +12,14 @@ module GraphQL
12
12
  attr_reader :id
13
13
  # @return [Object] The value found with this ID
14
14
  attr_reader :object
15
- def initialize(argument:, id:, object:)
15
+ # @return [GraphQL::Query::Context]
16
+ attr_reader :context
17
+
18
+ def initialize(argument:, id:, object:, context:)
16
19
  @id = id
17
20
  @argument = argument
18
21
  @object = object
22
+ @context = context
19
23
  super("No object found for `#{argument.graphql_name}: #{id.inspect}`")
20
24
  end
21
25
  end
@@ -22,10 +22,11 @@ module GraphQL
22
22
  attr_reader :context
23
23
 
24
24
  def context=(new_ctx)
25
- current_runtime_state = Thread.current[:__graphql_runtime_info]
26
- query_runtime_state = current_runtime_state[new_ctx.query]
27
- @was_authorized_by_scope_items = query_runtime_state.was_authorized_by_scope_items
28
25
  @context = new_ctx
26
+ if @was_authorized_by_scope_items.nil?
27
+ @was_authorized_by_scope_items = detect_was_authorized_by_scope_items
28
+ end
29
+ @context
29
30
  end
30
31
 
31
32
  # @return [Object] the object this collection belongs to
@@ -90,13 +91,7 @@ module GraphQL
90
91
  else
91
92
  default_page_size
92
93
  end
93
- @was_authorized_by_scope_items = if @context
94
- current_runtime_state = Thread.current[:__graphql_runtime_info]
95
- query_runtime_state = current_runtime_state[@context.query]
96
- query_runtime_state.was_authorized_by_scope_items
97
- else
98
- nil
99
- end
94
+ @was_authorized_by_scope_items = detect_was_authorized_by_scope_items
100
95
  end
101
96
 
102
97
  def was_authorized_by_scope_items?
@@ -226,6 +221,16 @@ module GraphQL
226
221
 
227
222
  private
228
223
 
224
+ def detect_was_authorized_by_scope_items
225
+ if @context &&
226
+ (current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
227
+ (query_runtime_state = current_runtime_state[@context.query])
228
+ query_runtime_state.was_authorized_by_scope_items
229
+ else
230
+ nil
231
+ end
232
+ end
233
+
229
234
  # @param argument [nil, Integer] `first` or `last`, as provided by the client
230
235
  # @param max_page_size [nil, Integer]
231
236
  # @return [nil, Integer] `nil` if the input was `nil`, otherwise a value between `0` and `max_page_size`
@@ -13,8 +13,7 @@ module GraphQL
13
13
  end
14
14
 
15
15
  def relation_count(relation)
16
- # Mongo's `.count` doesn't apply limit or skip, which we need. So we have to load _everything_!
17
- relation.to_a.count
16
+ relation.all.count(relation.options.slice(:limit, :skip))
18
17
  end
19
18
 
20
19
  def null_relation(relation)
@@ -237,6 +237,10 @@ module GraphQL
237
237
  @storage.key?(ns)
238
238
  end
239
239
 
240
+ def logger
241
+ @query && @query.logger
242
+ end
243
+
240
244
  def inspect
241
245
  "#<Query::Context ...>"
242
246
  end
@@ -3,6 +3,8 @@ module GraphQL
3
3
  class Query
4
4
  # This object can be `ctx` in places where there is no query
5
5
  class NullContext
6
+ include Singleton
7
+
6
8
  class NullQuery
7
9
  def after_lazy(value)
8
10
  yield(value)
@@ -27,16 +29,6 @@ module GraphQL
27
29
  def interpreter?
28
30
  true
29
31
  end
30
-
31
- class << self
32
- extend Forwardable
33
-
34
- def instance
35
- @instance ||= self.new
36
- end
37
-
38
- def_delegators :instance, :query, :warden, :schema, :interpreter?, :dataloader, :[], :fetch, :dig, :key?
39
- end
40
32
  end
41
33
  end
42
34
  end
data/lib/graphql/query.rb CHANGED
@@ -162,6 +162,14 @@ module GraphQL
162
162
 
163
163
  @result_values = nil
164
164
  @executed = false
165
+
166
+ @logger = if context && context[:logger] == false
167
+ Logger.new(IO::NULL)
168
+ elsif context && (l = context[:logger])
169
+ l
170
+ else
171
+ schema.default_logger
172
+ end
165
173
  end
166
174
 
167
175
  # If a document was provided to `GraphQL::Schema#execute` instead of the raw query string, we will need to get it from the document
@@ -369,6 +377,8 @@ module GraphQL
369
377
  end
370
378
  end
371
379
 
380
+ attr_reader :logger
381
+
372
382
  private
373
383
 
374
384
  def find_operation(operations, operation_name)
@@ -432,14 +432,12 @@ module GraphQL
432
432
  builder = self
433
433
 
434
434
  field_definitions.each do |field_definition|
435
- type_name = resolve_type_name(field_definition.type)
436
435
  resolve_method_name = -"resolve_field_#{field_definition.name}"
437
436
  schema_field_defn = owner.field(
438
437
  field_definition.name,
439
438
  description: field_definition.description,
440
439
  type: type_resolver.call(field_definition.type),
441
440
  null: true,
442
- connection: type_name.end_with?("Connection"),
443
441
  connection_extension: nil,
444
442
  deprecation_reason: build_deprecation_reason(field_definition.directives),
445
443
  ast_node: field_definition,
@@ -487,15 +485,6 @@ module GraphQL
487
485
  }
488
486
  resolve_type_proc
489
487
  end
490
-
491
- def resolve_type_name(type)
492
- case type
493
- when GraphQL::Language::Nodes::TypeName
494
- return type.name
495
- else
496
- resolve_type_name(type.of_type)
497
- end
498
- end
499
488
  end
500
489
 
501
490
  private_constant :Builder
@@ -6,6 +6,18 @@ module GraphQL
6
6
  description "Requires that exactly one field must be supplied and that field must not be `null`."
7
7
  locations(GraphQL::Schema::Directive::INPUT_OBJECT)
8
8
  default_directive true
9
+
10
+ def initialize(...)
11
+ super
12
+
13
+ owner.extend(IsOneOf)
14
+ end
15
+
16
+ module IsOneOf
17
+ def one_of?
18
+ true
19
+ end
20
+ end
9
21
  end
10
22
  end
11
23
  end
@@ -119,7 +119,7 @@ module GraphQL
119
119
  # - lazy resolution
120
120
  # Probably, those won't be needed here, since these are configuration arguments,
121
121
  # not runtime arguments.
122
- @arguments = self.class.coerce_arguments(nil, arguments, Query::NullContext)
122
+ @arguments = self.class.coerce_arguments(nil, arguments, Query::NullContext.instance)
123
123
  end
124
124
 
125
125
  def graphql_name
@@ -68,7 +68,7 @@ module GraphQL
68
68
  end
69
69
 
70
70
  # @return [Array<GraphQL::Schema::EnumValue>] Possible values of this enum
71
- def enum_values(context = GraphQL::Query::NullContext)
71
+ def enum_values(context = GraphQL::Query::NullContext.instance)
72
72
  inherited_values = superclass.respond_to?(:enum_values) ? superclass.enum_values(context) : nil
73
73
  visible_values = []
74
74
  warden = Warden.from_context(context)
@@ -110,7 +110,7 @@ module GraphQL
110
110
  end
111
111
 
112
112
  # @return [Hash<String => GraphQL::Schema::EnumValue>] Possible values of this enum, keyed by name.
113
- def values(context = GraphQL::Query::NullContext)
113
+ def values(context = GraphQL::Query::NullContext.instance)
114
114
  enum_values(context).each_with_object({}) { |val, obj| obj[val.graphql_name] = val }
115
115
  end
116
116
 
@@ -12,9 +12,10 @@ module GraphQL
12
12
  if ret_type.respond_to?(:scope_items)
13
13
  scoped_items = ret_type.scope_items(value, context)
14
14
  if !scoped_items.equal?(value) && !ret_type.reauthorize_scoped_objects
15
- current_runtime_state = Thread.current[:__graphql_runtime_info]
16
- query_runtime_state = current_runtime_state[context.query]
17
- query_runtime_state.was_authorized_by_scope_items = true
15
+ if (current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
16
+ (query_runtime_state = current_runtime_state[context.query])
17
+ query_runtime_state.was_authorized_by_scope_items = true
18
+ end
18
19
  end
19
20
  scoped_items
20
21
  else
@@ -138,7 +138,7 @@ module GraphQL
138
138
  # As a last ditch, try to force loading the return type:
139
139
  type.unwrap.name
140
140
  end
141
- @connection = return_type_name.end_with?("Connection")
141
+ @connection = return_type_name.end_with?("Connection") && return_type_name != "Connection"
142
142
  else
143
143
  @connection
144
144
  end
@@ -54,11 +54,11 @@ module GraphQL
54
54
  end
55
55
  end
56
56
 
57
- def field_arguments(context = GraphQL::Query::NullContext)
57
+ def field_arguments(context = GraphQL::Query::NullContext.instance)
58
58
  dummy.arguments(context)
59
59
  end
60
60
 
61
- def get_field_argument(name, context = GraphQL::Query::NullContext)
61
+ def get_field_argument(name, context = GraphQL::Query::NullContext.instance)
62
62
  dummy.get_argument(name, context)
63
63
  end
64
64
 
@@ -79,7 +79,7 @@ module GraphQL
79
79
  end
80
80
 
81
81
  def self.one_of?
82
- directives.any? { |d| d.is_a?(GraphQL::Schema::Directive::OneOf) }
82
+ false # Re-defined when `OneOf` is added
83
83
  end
84
84
 
85
85
  def unwrap_value(value)
@@ -20,6 +20,15 @@ module GraphQL
20
20
  # - Added as class methods to this interface
21
21
  # - Added as class methods to all child interfaces
22
22
  def definition_methods(&block)
23
+ # Use an instance variable to tell whether it's been included previously or not;
24
+ # You can't use constant detection because constants are brought into scope
25
+ # by `include`, which has already happened at this point.
26
+ if !defined?(@_definition_methods)
27
+ defn_methods_module = Module.new
28
+ @_definition_methods = defn_methods_module
29
+ const_set(:DefinitionMethods, defn_methods_module)
30
+ extend(self::DefinitionMethods)
31
+ end
23
32
  self::DefinitionMethods.module_eval(&block)
24
33
  end
25
34
 
@@ -47,20 +56,11 @@ module GraphQL
47
56
 
48
57
  child_class.type_membership_class(self.type_membership_class)
49
58
  child_class.ancestors.reverse_each do |ancestor|
50
- if ancestor.const_defined?(:DefinitionMethods)
59
+ if ancestor.const_defined?(:DefinitionMethods) && ancestor != child_class
51
60
  child_class.extend(ancestor::DefinitionMethods)
52
61
  end
53
62
  end
54
63
 
55
- # Use an instance variable to tell whether it's been included previously or not;
56
- # You can't use constant detection because constants are brought into scope
57
- # by `include`, which has already happened at this point.
58
- if !child_class.instance_variable_defined?(:@_definition_methods)
59
- defn_methods_module = Module.new
60
- child_class.instance_variable_set(:@_definition_methods, defn_methods_module)
61
- child_class.const_set(:DefinitionMethods, defn_methods_module)
62
- child_class.extend(child_class::DefinitionMethods)
63
- end
64
64
  child_class.introspection(introspection)
65
65
  child_class.description(description)
66
66
  # If interfaces are mixed into each other, only define this class once
@@ -176,7 +176,6 @@ module GraphQL
176
176
  while (of_type = unwrapped_field_hash["ofType"])
177
177
  unwrapped_field_hash = of_type
178
178
  end
179
- type_name = unwrapped_field_hash["name"]
180
179
 
181
180
  type_defn.field(
182
181
  field_hash["name"],
@@ -186,7 +185,6 @@ module GraphQL
186
185
  null: true,
187
186
  camelize: false,
188
187
  connection_extension: nil,
189
- connection: type_name.end_with?("Connection"),
190
188
  ) do
191
189
  if field_hash["args"].any?
192
190
  loader.build_arguments(self, field_hash["args"], type_resolver)
@@ -109,7 +109,7 @@ module GraphQL
109
109
  end
110
110
 
111
111
  # @return [Hash<String => GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions
112
- def arguments(context = GraphQL::Query::NullContext)
112
+ def arguments(context = GraphQL::Query::NullContext.instance)
113
113
  if own_arguments.any?
114
114
  own_arguments_that_apply = {}
115
115
  own_arguments.each do |name, args_entry|
@@ -133,7 +133,7 @@ module GraphQL
133
133
  end
134
134
 
135
135
  module InheritedArguments
136
- def arguments(context = GraphQL::Query::NullContext)
136
+ def arguments(context = GraphQL::Query::NullContext.instance)
137
137
  own_arguments = super
138
138
  inherited_arguments = superclass.arguments(context)
139
139
 
@@ -166,7 +166,7 @@ module GraphQL
166
166
  end
167
167
 
168
168
 
169
- def get_argument(argument_name, context = GraphQL::Query::NullContext)
169
+ def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
170
170
  warden = Warden.from_context(context)
171
171
  for ancestor in ancestors
172
172
  if ancestor.respond_to?(:own_arguments) &&
@@ -181,7 +181,7 @@ module GraphQL
181
181
  end
182
182
 
183
183
  module FieldConfigured
184
- def arguments(context = GraphQL::Query::NullContext)
184
+ def arguments(context = GraphQL::Query::NullContext.instance)
185
185
  own_arguments = super
186
186
  if @resolver_class
187
187
  inherited_arguments = @resolver_class.field_arguments(context)
@@ -236,7 +236,7 @@ module GraphQL
236
236
  end
237
237
 
238
238
  # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
239
- def get_argument(argument_name, context = GraphQL::Query::NullContext)
239
+ def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
240
240
  warden = Warden.from_context(context)
241
241
  if (arg_config = own_arguments[argument_name]) && (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))
242
242
  visible_arg
@@ -379,44 +379,52 @@ module GraphQL
379
379
  def authorize_application_object(argument, id, context, loaded_application_object)
380
380
  context.query.after_lazy(loaded_application_object) do |application_object|
381
381
  if application_object.nil?
382
- err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
383
- load_application_object_failed(err)
382
+ err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object)
383
+ application_object = load_application_object_failed(err)
384
384
  end
385
385
  # Double-check that the located object is actually of this type
386
386
  # (Don't want to allow arbitrary access to objects this way)
387
- maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context)
388
- context.query.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result|
389
- if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2
390
- application_object_type, application_object = resolve_type_result
391
- else
392
- application_object_type = resolve_type_result
393
- # application_object is already assigned
394
- end
387
+ if application_object.nil?
388
+ nil
389
+ else
390
+ maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context)
391
+ context.query.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result|
392
+ if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2
393
+ application_object_type, application_object = resolve_type_result
394
+ else
395
+ application_object_type = resolve_type_result
396
+ # application_object is already assigned
397
+ end
395
398
 
396
- if !(
397
- context.warden.possible_types(argument.loads).include?(application_object_type) ||
398
- context.warden.loadable?(argument.loads, context)
399
- )
400
- err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
401
- load_application_object_failed(err)
402
- else
403
- # This object was loaded successfully
404
- # and resolved to the right type,
405
- # now apply the `.authorized?` class method if there is one
406
- context.query.after_lazy(application_object_type.authorized?(application_object, context)) do |authed|
407
- if authed
408
- application_object
409
- else
410
- err = GraphQL::UnauthorizedError.new(
411
- object: application_object,
412
- type: application_object_type,
413
- context: context,
414
- )
415
- if self.respond_to?(:unauthorized_object)
416
- err.set_backtrace(caller)
417
- unauthorized_object(err)
399
+ if !(
400
+ context.warden.possible_types(argument.loads).include?(application_object_type) ||
401
+ context.warden.loadable?(argument.loads, context)
402
+ )
403
+ err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object)
404
+ application_object = load_application_object_failed(err)
405
+ end
406
+
407
+ if application_object.nil?
408
+ nil
409
+ else
410
+ # This object was loaded successfully
411
+ # and resolved to the right type,
412
+ # now apply the `.authorized?` class method if there is one
413
+ context.query.after_lazy(application_object_type.authorized?(application_object, context)) do |authed|
414
+ if authed
415
+ application_object
418
416
  else
419
- raise err
417
+ err = GraphQL::UnauthorizedError.new(
418
+ object: application_object,
419
+ type: application_object_type,
420
+ context: context,
421
+ )
422
+ if self.respond_to?(:unauthorized_object)
423
+ err.set_backtrace(caller)
424
+ unauthorized_object(err)
425
+ else
426
+ raise err
427
+ end
420
428
  end
421
429
  end
422
430
  end
@@ -97,7 +97,7 @@ module GraphQL
97
97
  end
98
98
 
99
99
  module InterfaceMethods
100
- def get_field(field_name, context = GraphQL::Query::NullContext)
100
+ def get_field(field_name, context = GraphQL::Query::NullContext.instance)
101
101
  warden = Warden.from_context(context)
102
102
  for ancestor in ancestors
103
103
  if ancestor.respond_to?(:own_fields) &&
@@ -110,7 +110,7 @@ module GraphQL
110
110
  end
111
111
 
112
112
  # @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
113
- def fields(context = GraphQL::Query::NullContext)
113
+ def fields(context = GraphQL::Query::NullContext.instance)
114
114
  warden = Warden.from_context(context)
115
115
  # Local overrides take precedence over inherited fields
116
116
  visible_fields = {}
@@ -130,7 +130,7 @@ module GraphQL
130
130
  end
131
131
 
132
132
  module ObjectMethods
133
- def get_field(field_name, context = GraphQL::Query::NullContext)
133
+ def get_field(field_name, context = GraphQL::Query::NullContext.instance)
134
134
  # Objects need to check that the interface implementation is visible, too
135
135
  warden = Warden.from_context(context)
136
136
  ancs = ancestors
@@ -148,7 +148,7 @@ module GraphQL
148
148
  end
149
149
 
150
150
  # @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
151
- def fields(context = GraphQL::Query::NullContext)
151
+ def fields(context = GraphQL::Query::NullContext.instance)
152
152
  # Objects need to check that the interface implementation is visible, too
153
153
  warden = Warden.from_context(context)
154
154
  # Local overrides take precedence over inherited fields
@@ -70,7 +70,7 @@ module GraphQL
70
70
  end
71
71
 
72
72
  module InheritedInterfaces
73
- def interfaces(context = GraphQL::Query::NullContext)
73
+ def interfaces(context = GraphQL::Query::NullContext.instance)
74
74
  visible_interfaces = super
75
75
  inherited_interfaces = superclass.interfaces(context)
76
76
  if visible_interfaces.any?
@@ -99,7 +99,7 @@ module GraphQL
99
99
  end
100
100
 
101
101
  # param context [Query::Context] If omitted, skip filtering.
102
- def interfaces(context = GraphQL::Query::NullContext)
102
+ def interfaces(context = GraphQL::Query::NullContext.instance)
103
103
  warden = Warden.from_context(context)
104
104
  visible_interfaces = nil
105
105
  own_interface_type_memberships.each do |type_membership|
@@ -17,15 +17,15 @@ module GraphQL
17
17
  end
18
18
 
19
19
  def valid_isolated_input?(v)
20
- valid_input?(v, GraphQL::Query::NullContext)
20
+ valid_input?(v, GraphQL::Query::NullContext.instance)
21
21
  end
22
22
 
23
23
  def coerce_isolated_input(v)
24
- coerce_input(v, GraphQL::Query::NullContext)
24
+ coerce_input(v, GraphQL::Query::NullContext.instance)
25
25
  end
26
26
 
27
27
  def coerce_isolated_result(v)
28
- coerce_result(v, GraphQL::Query::NullContext)
28
+ coerce_result(v, GraphQL::Query::NullContext.instance)
29
29
  end
30
30
  end
31
31
  end
@@ -205,12 +205,12 @@ module GraphQL
205
205
  end
206
206
  end
207
207
 
208
- def get_argument(name, context = GraphQL::Query::NullContext)
208
+ def get_argument(name, context = GraphQL::Query::NullContext.instance)
209
209
  self.class.get_argument(name, context)
210
210
  end
211
211
 
212
212
  class << self
213
- def field_arguments(context = GraphQL::Query::NullContext)
213
+ def field_arguments(context = GraphQL::Query::NullContext.instance)
214
214
  arguments(context)
215
215
  end
216
216
 
@@ -218,7 +218,7 @@ module GraphQL
218
218
  any_arguments?
219
219
  end
220
220
 
221
- def get_field_argument(name, context = GraphQL::Query::NullContext)
221
+ def get_field_argument(name, context = GraphQL::Query::NullContext.instance)
222
222
  get_argument(name, context)
223
223
  end
224
224
 
@@ -10,7 +10,7 @@ module GraphQL
10
10
  super
11
11
  end
12
12
 
13
- def possible_types(*types, context: GraphQL::Query::NullContext, **options)
13
+ def possible_types(*types, context: GraphQL::Query::NullContext.instance, **options)
14
14
  if types.any?
15
15
  types.each do |t|
16
16
  assert_valid_union_member(t)
@@ -106,7 +106,7 @@ module GraphQL
106
106
  @types = @visible_types = @reachable_types = @visible_parent_fields =
107
107
  @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
108
108
  @visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
109
- @visible_and_reachable_type = @unions = @unfiltered_interfaces = @references_to =
109
+ @visible_and_reachable_type = @unions = @unfiltered_interfaces =
110
110
  @reachable_type_set =
111
111
  nil
112
112
  end
@@ -291,7 +291,14 @@ module GraphQL
291
291
  if type_defn.kind.union?
292
292
  possible_types(type_defn).any? && (referenced?(type_defn) || orphan_type?(type_defn))
293
293
  elsif type_defn.kind.interface?
294
- possible_types(type_defn).any?
294
+ if possible_types(type_defn).any?
295
+ true
296
+ else
297
+ if @context.respond_to?(:logger) && (logger = @context.logger)
298
+ logger.debug { "Interface `#{type_defn.graphql_name}` hidden because it has no visible implementors" }
299
+ end
300
+ false
301
+ end
295
302
  else
296
303
  if referenced?(type_defn)
297
304
  true
@@ -356,14 +363,11 @@ module GraphQL
356
363
  end
357
364
 
358
365
  def referenced?(type_defn)
359
- @references_to ||= @schema.references_to
360
366
  graphql_name = type_defn.unwrap.graphql_name
361
- members = @references_to[graphql_name] || NO_REFERENCES
367
+ members = @schema.references_to(graphql_name)
362
368
  members.any? { |m| visible?(m) }
363
369
  end
364
370
 
365
- NO_REFERENCES = [].freeze
366
-
367
371
  def orphan_type?(type_defn)
368
372
  @schema.orphan_types.include?(type_defn)
369
373
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "logger"
2
3
  require "graphql/schema/addition"
3
4
  require "graphql/schema/always_visible"
4
5
  require "graphql/schema/base_64_encoder"
@@ -170,9 +171,9 @@ module GraphQL
170
171
  end
171
172
 
172
173
  # @return [Class] Return the trace class to use for this mode, looking one up on the superclass if this Schema doesn't have one defined.
173
- def trace_class_for(mode, build: false)
174
+ def trace_class_for(mode)
174
175
  own_trace_modes[mode] ||
175
- (superclass.respond_to?(:trace_class_for) ? superclass.trace_class_for(mode) : nil)
176
+ (superclass.respond_to?(:trace_class_for) ? superclass.trace_class_for(mode) : (own_trace_modes[mode] = build_trace_mode(mode)))
176
177
  end
177
178
 
178
179
  # Configure `trace_class` to be used whenever `context: { trace_mode: mode_name }` is requested.
@@ -325,7 +326,7 @@ module GraphQL
325
326
  # Build a map of `{ name => type }` and return it
326
327
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
327
328
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
328
- def types(context = GraphQL::Query::NullContext)
329
+ def types(context = GraphQL::Query::NullContext.instance)
329
330
  all_types = non_introspection_types.merge(introspection_system.types)
330
331
  visible_types = {}
331
332
  all_types.each do |k, v|
@@ -352,7 +353,7 @@ module GraphQL
352
353
 
353
354
  # @param type_name [String]
354
355
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
355
- def get_type(type_name, context = GraphQL::Query::NullContext)
356
+ def get_type(type_name, context = GraphQL::Query::NullContext.instance)
356
357
  local_entry = own_types[type_name]
357
358
  type_defn = case local_entry
358
359
  when nil
@@ -483,7 +484,7 @@ module GraphQL
483
484
  # @param type [Module] The type definition whose possible types you want to see
484
485
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
485
486
  # @return [Array<Module>] Possible types for `type`, if it's given.
486
- def possible_types(type = nil, context = GraphQL::Query::NullContext)
487
+ def possible_types(type = nil, context = GraphQL::Query::NullContext.instance)
487
488
  if type
488
489
  # TODO duck-typing `.possible_types` would probably be nicer here
489
490
  if type.kind.union?
@@ -536,18 +537,17 @@ module GraphQL
536
537
  attr_writer :dataloader_class
537
538
 
538
539
  def references_to(to_type = nil, from: nil)
539
- @own_references_to ||= Hash.new { |h, k| h[k] = [] }
540
+ @own_references_to ||= {}
540
541
  if to_type
541
542
  if !to_type.is_a?(String)
542
543
  to_type = to_type.graphql_name
543
544
  end
544
545
 
545
546
  if from
546
- @own_references_to[to_type] << from
547
+ refs = @own_references_to[to_type] ||= []
548
+ refs << from
547
549
  else
548
- own_refs = @own_references_to[to_type]
549
- inherited_refs = find_inherited_value(:references_to, EMPTY_HASH)[to_type] || EMPTY_ARRAY
550
- own_refs + inherited_refs
550
+ get_references_to(to_type) || EMPTY_ARRAY
551
551
  end
552
552
  else
553
553
  # `@own_references_to` can be quite large for big schemas,
@@ -567,7 +567,7 @@ module GraphQL
567
567
  GraphQL::Schema::TypeExpression.build_type(type_owner, ast_node)
568
568
  end
569
569
 
570
- def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext)
570
+ def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance)
571
571
  parent_type = case type_or_name
572
572
  when LateBoundType
573
573
  get_type(type_or_name.name, context)
@@ -590,7 +590,7 @@ module GraphQL
590
590
  end
591
591
  end
592
592
 
593
- def get_fields(type, context = GraphQL::Query::NullContext)
593
+ def get_fields(type, context = GraphQL::Query::NullContext.instance)
594
594
  type.fields(context)
595
595
  end
596
596
 
@@ -836,6 +836,26 @@ module GraphQL
836
836
  end
837
837
  end
838
838
 
839
+ def default_logger(new_default_logger = NOT_CONFIGURED)
840
+ if NOT_CONFIGURED.equal?(new_default_logger)
841
+ if defined?(@default_logger)
842
+ @default_logger
843
+ elsif superclass.respond_to?(:default_logger)
844
+ superclass.default_logger
845
+ elsif defined?(Rails)
846
+ Rails.logger
847
+ else
848
+ def_logger = Logger.new($stdout)
849
+ def_logger.info! # It doesn't output debug info by default
850
+ def_logger
851
+ end
852
+ elsif new_default_logger == nil
853
+ @default_logger = Logger.new(IO::NULL)
854
+ else
855
+ @default_logger = new_default_logger
856
+ end
857
+ end
858
+
839
859
  def context_class(new_context_class = nil)
840
860
  if new_context_class
841
861
  @context_class = new_context_class
@@ -921,8 +941,8 @@ module GraphQL
921
941
 
922
942
  def inherited(child_class)
923
943
  if self == GraphQL::Schema
924
- child_class.own_trace_modes[:default] = child_class.build_trace_mode(:default)
925
944
  child_class.directives(default_directives.values)
945
+ child_class.extend(SubclassGetReferencesTo)
926
946
  end
927
947
  # Make sure the child class has these built out, so that
928
948
  # subclasses can be modified by later calls to `trace_with`
@@ -1398,6 +1418,27 @@ module GraphQL
1398
1418
  def own_multiplex_analyzers
1399
1419
  @own_multiplex_analyzers ||= []
1400
1420
  end
1421
+
1422
+ # This is overridden in subclasses to check the inheritance chain
1423
+ def get_references_to(type_name)
1424
+ @own_references_to[type_name]
1425
+ end
1426
+ end
1427
+
1428
+ module SubclassGetReferencesTo
1429
+ def get_references_to(type_name)
1430
+ own_refs = @own_references_to[type_name]
1431
+ inherited_refs = superclass.references_to(type_name)
1432
+ if inherited_refs&.any?
1433
+ if own_refs&.any?
1434
+ own_refs + inherited_refs
1435
+ else
1436
+ inherited_refs
1437
+ end
1438
+ else
1439
+ own_refs
1440
+ end
1441
+ end
1401
1442
  end
1402
1443
 
1403
1444
  # Install these here so that subclasses will also install it.
@@ -37,7 +37,7 @@ module GraphQL
37
37
  end
38
38
 
39
39
  # @return [String] an identifier for this unit of subscription
40
- def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext)
40
+ def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext.instance)
41
41
  subscription = field.resolver || GraphQL::Schema::Subscription
42
42
  normalized_args = stringify_args(field, arguments.to_h, context)
43
43
  subscription.topic_for(arguments: normalized_args, field: field, scope: scope)
@@ -62,7 +62,7 @@ module GraphQL
62
62
  # @return [void]
63
63
  def trigger(event_name, args, object, scope: nil, context: {})
64
64
  # Make something as context-like as possible, even though there isn't a current query:
65
- dummy_query = GraphQL::Query.new(@schema, "", validate: false, context: context)
65
+ dummy_query = GraphQL::Query.new(@schema, "{ __typename }", validate: false, context: context)
66
66
  context = dummy_query.context
67
67
  event_name = event_name.to_s
68
68
 
@@ -83,7 +83,7 @@ module GraphQL
83
83
 
84
84
  # Normalize symbol-keyed args to strings, try camelizing them
85
85
  # Should this accept a real context somehow?
86
- normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext)
86
+ normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext.instance)
87
87
 
88
88
  event = Subscriptions::Event.new(
89
89
  name: normalized_event_name,
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.1.2"
3
+ VERSION = "2.1.4"
4
4
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 2.1.4
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-10-11 00:00:00.000000000 Z
11
+ date: 2023-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: racc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: benchmark-ips
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -94,20 +108,6 @@ dependencies:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
110
  version: '1.0'
97
- - !ruby/object:Gem::Dependency
98
- name: racc
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '1.4'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '1.4'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rake
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -626,7 +626,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
626
626
  - !ruby/object:Gem::Version
627
627
  version: '0'
628
628
  requirements: []
629
- rubygems_version: 3.5.0.dev
629
+ rubygems_version: 3.4.10
630
630
  signing_key:
631
631
  specification_version: 4
632
632
  summary: A GraphQL language and runtime for Ruby