graphql 2.1.0 → 2.1.1

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  3. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  4. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  7. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  8. data/lib/generators/graphql/templates/base_field.erb +2 -0
  9. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  10. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  11. data/lib/generators/graphql/templates/base_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  13. data/lib/generators/graphql/templates/base_union.erb +2 -0
  14. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  15. data/lib/generators/graphql/templates/loader.erb +2 -0
  16. data/lib/generators/graphql/templates/mutation.erb +2 -0
  17. data/lib/generators/graphql/templates/node_type.erb +2 -0
  18. data/lib/generators/graphql/templates/query_type.erb +2 -0
  19. data/lib/generators/graphql/templates/schema.erb +2 -0
  20. data/lib/graphql/analysis/ast/analyzer.rb +7 -0
  21. data/lib/graphql/analysis/ast/visitor.rb +2 -2
  22. data/lib/graphql/analysis/ast.rb +15 -11
  23. data/lib/graphql/dataloader/source.rb +7 -0
  24. data/lib/graphql/dataloader.rb +9 -0
  25. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
  26. data/lib/graphql/execution/interpreter/runtime.rb +90 -251
  27. data/lib/graphql/execution/interpreter.rb +0 -6
  28. data/lib/graphql/execution/lookahead.rb +1 -1
  29. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  30. data/lib/graphql/introspection/entry_points.rb +2 -2
  31. data/lib/graphql/language/block_string.rb +28 -16
  32. data/lib/graphql/language/definition_slice.rb +1 -1
  33. data/lib/graphql/language/document_from_schema_definition.rb +30 -19
  34. data/lib/graphql/language/nodes.rb +1 -1
  35. data/lib/graphql/language/printer.rb +88 -27
  36. data/lib/graphql/language/sanitized_printer.rb +6 -1
  37. data/lib/graphql/language/static_visitor.rb +167 -0
  38. data/lib/graphql/language/visitor.rb +2 -0
  39. data/lib/graphql/language.rb +1 -0
  40. data/lib/graphql/query/context/scoped_context.rb +101 -0
  41. data/lib/graphql/query/context.rb +32 -98
  42. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  43. data/lib/graphql/schema/field/connection_extension.rb +1 -15
  44. data/lib/graphql/schema/field.rb +6 -3
  45. data/lib/graphql/schema/has_single_input_argument.rb +156 -0
  46. data/lib/graphql/schema/introspection_system.rb +2 -0
  47. data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
  48. data/lib/graphql/schema/member/has_arguments.rb +14 -2
  49. data/lib/graphql/schema/member/has_fields.rb +4 -1
  50. data/lib/graphql/schema/member/has_interfaces.rb +21 -7
  51. data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
  52. data/lib/graphql/schema/resolver.rb +4 -0
  53. data/lib/graphql/schema/scalar.rb +3 -3
  54. data/lib/graphql/schema/warden.rb +20 -3
  55. data/lib/graphql/schema.rb +19 -2
  56. data/lib/graphql/static_validation/all_rules.rb +1 -1
  57. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  58. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  59. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  60. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  61. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  62. data/lib/graphql/static_validation/validation_context.rb +5 -2
  63. data/lib/graphql/tracing/appoptics_trace.rb +2 -2
  64. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  65. data/lib/graphql/version.rb +1 -1
  66. data/lib/graphql.rb +1 -1
  67. metadata +7 -2
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Query
4
+ class Context
5
+ class ScopedContext
6
+ def initialize(query_context)
7
+ @query_context = query_context
8
+ @scoped_contexts = nil
9
+ @all_keys = nil
10
+ end
11
+
12
+ def merged_context
13
+ if @scoped_contexts.nil?
14
+ GraphQL::EmptyObjects::EMPTY_HASH
15
+ else
16
+ merged_ctx = {}
17
+ each_present_path_ctx do |path_ctx|
18
+ merged_ctx = path_ctx.merge(merged_ctx)
19
+ end
20
+ merged_ctx
21
+ end
22
+ end
23
+
24
+ def merge!(hash, at: current_path)
25
+ @all_keys ||= Set.new
26
+ @all_keys.merge(hash.keys)
27
+ ctx = @scoped_contexts ||= {}
28
+ at.each do |path_part|
29
+ ctx = ctx[path_part] ||= { parent: ctx }
30
+ end
31
+ this_scoped_ctx = ctx[:scoped_context] ||= {}
32
+ this_scoped_ctx.merge!(hash)
33
+ end
34
+
35
+ def key?(key)
36
+ if @all_keys && @all_keys.include?(key)
37
+ each_present_path_ctx do |path_ctx|
38
+ if path_ctx.key?(key)
39
+ return true
40
+ end
41
+ end
42
+ end
43
+ false
44
+ end
45
+
46
+ def [](key)
47
+ each_present_path_ctx do |path_ctx|
48
+ if path_ctx.key?(key)
49
+ return path_ctx[key]
50
+ end
51
+ end
52
+ nil
53
+ end
54
+
55
+ def current_path
56
+ @query_context.current_path || GraphQL::EmptyObjects::EMPTY_ARRAY
57
+ end
58
+
59
+ def dig(key, *other_keys)
60
+ each_present_path_ctx do |path_ctx|
61
+ if path_ctx.key?(key)
62
+ found_value = path_ctx[key]
63
+ if other_keys.any?
64
+ return found_value.dig(*other_keys)
65
+ else
66
+ return found_value
67
+ end
68
+ end
69
+ end
70
+ nil
71
+ end
72
+
73
+ private
74
+
75
+ # Start at the current location,
76
+ # but look up the tree for previously-assigned scoped values
77
+ def each_present_path_ctx
78
+ ctx = @scoped_contexts
79
+ if ctx.nil?
80
+ # no-op
81
+ else
82
+ current_path.each do |path_part|
83
+ if ctx.key?(path_part)
84
+ ctx = ctx[path_part]
85
+ else
86
+ break
87
+ end
88
+ end
89
+
90
+ while ctx
91
+ if (scoped_ctx = ctx[:scoped_context])
92
+ yield(scoped_ctx)
93
+ end
94
+ ctx = ctx[:parent]
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require "graphql/query/context/scoped_context"
3
+
2
4
  module GraphQL
3
5
  class Query
4
6
  # Expose some query-specific info to field resolve functions.
@@ -90,104 +92,6 @@ module GraphQL
90
92
  @scoped_context = ScopedContext.new(self)
91
93
  end
92
94
 
93
- class ScopedContext
94
- NO_PATH = GraphQL::EmptyObjects::EMPTY_ARRAY
95
- NO_CONTEXT = GraphQL::EmptyObjects::EMPTY_HASH
96
-
97
- def initialize(query_context)
98
- @query_context = query_context
99
- @scoped_contexts = nil
100
- @all_keys = nil
101
- end
102
-
103
- def merged_context
104
- if @scoped_contexts.nil?
105
- NO_CONTEXT
106
- else
107
- merged_ctx = {}
108
- each_present_path_ctx do |path_ctx|
109
- merged_ctx = path_ctx.merge(merged_ctx)
110
- end
111
- merged_ctx
112
- end
113
- end
114
-
115
- def merge!(hash)
116
- @all_keys ||= Set.new
117
- @all_keys.merge(hash.keys)
118
- ctx = @scoped_contexts ||= {}
119
- current_path.each do |path_part|
120
- ctx = ctx[path_part] ||= { parent: ctx }
121
- end
122
- this_scoped_ctx = ctx[:scoped_context] ||= {}
123
- this_scoped_ctx.merge!(hash)
124
- end
125
-
126
- def key?(key)
127
- if @all_keys && @all_keys.include?(key)
128
- each_present_path_ctx do |path_ctx|
129
- if path_ctx.key?(key)
130
- return true
131
- end
132
- end
133
- end
134
- false
135
- end
136
-
137
- def [](key)
138
- each_present_path_ctx do |path_ctx|
139
- if path_ctx.key?(key)
140
- return path_ctx[key]
141
- end
142
- end
143
- nil
144
- end
145
-
146
- def current_path
147
- @query_context.current_path || NO_PATH
148
- end
149
-
150
- def dig(key, *other_keys)
151
- each_present_path_ctx do |path_ctx|
152
- if path_ctx.key?(key)
153
- found_value = path_ctx[key]
154
- if other_keys.any?
155
- return found_value.dig(*other_keys)
156
- else
157
- return found_value
158
- end
159
- end
160
- end
161
- nil
162
- end
163
-
164
- private
165
-
166
- # Start at the current location,
167
- # but look up the tree for previously-assigned scoped values
168
- def each_present_path_ctx
169
- ctx = @scoped_contexts
170
- if ctx.nil?
171
- # no-op
172
- else
173
- current_path.each do |path_part|
174
- if ctx.key?(path_part)
175
- ctx = ctx[path_part]
176
- else
177
- break
178
- end
179
- end
180
-
181
- while ctx
182
- if (scoped_ctx = ctx[:scoped_context])
183
- yield(scoped_ctx)
184
- end
185
- ctx = ctx[:parent]
186
- end
187
- end
188
- end
189
- end
190
-
191
95
  # @return [Hash] A hash that will be added verbatim to the result hash, as `"extensions" => { ... }`
192
96
  def response_extensions
193
97
  namespace(:__query_result_extensions__)
@@ -345,6 +249,36 @@ module GraphQL
345
249
  scoped_merge!(key => value)
346
250
  nil
347
251
  end
252
+
253
+ # Use this when you need to do a scoped set _inside_ a lazy-loaded (or batch-loaded)
254
+ # block of code.
255
+ #
256
+ # @example using scoped context inside a promise
257
+ # scoped_ctx = context.scoped
258
+ # SomeBatchLoader.load(...).then do |thing|
259
+ # # use a scoped_ctx which was created _before_ dataloading:
260
+ # scoped_ctx.set!(:thing, thing)
261
+ # end
262
+ # @return [Context::Scoped]
263
+ def scoped
264
+ Scoped.new(@scoped_context, current_path)
265
+ end
266
+
267
+ class Scoped
268
+ def initialize(scoped_context, path)
269
+ @path = path
270
+ @scoped_context = scoped_context
271
+ end
272
+
273
+ def merge!(hash)
274
+ @scoped_context.merge!(hash, at: @path)
275
+ end
276
+
277
+ def set!(key, value)
278
+ @scoped_context.merge!({ key => value }, at: @path)
279
+ nil
280
+ end
281
+ end
348
282
  end
349
283
  end
350
284
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Directive < GraphQL::Schema::Member
5
+ class SpecifiedBy < GraphQL::Schema::Directive
6
+ description "Exposes a URL that specifies the behavior of this scalar."
7
+ locations(GraphQL::Schema::Directive::SCALAR)
8
+ default_directive true
9
+
10
+ argument :url, String, description: "The URL that specifies the behavior of this scalar."
11
+ end
12
+ end
13
+ end
14
+ end
@@ -54,23 +54,9 @@ module GraphQL
54
54
  value.edge_class = custom_t
55
55
  end
56
56
  value
57
- elsif context.schema.new_connections?
57
+ else
58
58
  context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
59
59
  context.schema.connections.wrap(field, object.object, value, original_arguments, context)
60
- else
61
- if object.is_a?(GraphQL::Schema::Object)
62
- object = object.object
63
- end
64
- connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(value)
65
- connection_class.new(
66
- value,
67
- original_arguments,
68
- field: field,
69
- max_page_size: field.max_page_size,
70
- default_page_size: field.default_page_size,
71
- parent: object,
72
- context: context,
73
- )
74
60
  end
75
61
  end
76
62
  end
@@ -218,8 +218,8 @@ module GraphQL
218
218
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
219
219
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
220
220
  # @param validates [Array<Hash>] Configurations for validating this field
221
- # @fallback_value [Object] A fallback value if the method is not defined
222
- def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, &definition_block)
221
+ # @param fallback_value [Object] A fallback value if the method is not defined
222
+ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
223
223
  if name.nil?
224
224
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
225
225
  end
@@ -267,6 +267,7 @@ module GraphQL
267
267
  @method_sym = method_name.to_sym
268
268
  @resolver_method = (resolver_method || name_s).to_sym
269
269
  @complexity = complexity
270
+ @dynamic_introspection = dynamic_introspection
270
271
  @return_type_expr = type
271
272
  @return_type_null = if !null.nil?
272
273
  null
@@ -351,6 +352,8 @@ module GraphQL
351
352
  @call_after_define = true
352
353
  end
353
354
 
355
+ attr_accessor :dynamic_introspection
356
+
354
357
  # If true, subscription updates with this field can be shared between viewers
355
358
  # @return [Boolean, nil]
356
359
  # @see GraphQL::Subscriptions::BroadcastAnalyzer
@@ -659,7 +662,7 @@ module GraphQL
659
662
  method_to_call = nil
660
663
  method_args = nil
661
664
 
662
- Schema::Validator.validate!(validators, application_object, query_ctx, args)
665
+ @own_validators && Schema::Validator.validate!(validators, application_object, query_ctx, args)
663
666
 
664
667
  query_ctx.query.after_lazy(self.authorized?(application_object, args, query_ctx)) do |is_authorized|
665
668
  if is_authorized
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ module HasSingleInputArgument
6
+ def resolve_with_support(**inputs)
7
+ if inputs[:input].is_a?(InputObject)
8
+ input = inputs[:input].to_kwargs
9
+ else
10
+ input = inputs[:input]
11
+ end
12
+
13
+ new_extras = field ? field.extras : []
14
+ all_extras = self.class.extras + new_extras
15
+
16
+ # Transfer these from the top-level hash to the
17
+ # shortcutted `input:` object
18
+ all_extras.each do |ext|
19
+ # It's possible that the `extra` was not passed along by this point,
20
+ # don't re-add it if it wasn't given here.
21
+ if inputs.key?(ext)
22
+ input[ext] = inputs[ext]
23
+ end
24
+ end
25
+
26
+ if input
27
+ # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
28
+ input_kwargs = input.to_h
29
+ else
30
+ # Relay Classic Mutations with no `argument`s
31
+ # don't require `input:`
32
+ input_kwargs = {}
33
+ end
34
+
35
+ if input_kwargs.any?
36
+ super(**input_kwargs)
37
+ else
38
+ super()
39
+ end
40
+ end
41
+
42
+ def self.included(base)
43
+ base.extend(ClassMethods)
44
+ end
45
+
46
+ module ClassMethods
47
+ def dummy
48
+ @dummy ||= begin
49
+ d = Class.new(GraphQL::Schema::Resolver)
50
+ d.argument_class(self.argument_class)
51
+ # TODO make this lazier?
52
+ d.argument(:input, input_type, description: "Parameters for #{self.graphql_name}")
53
+ d
54
+ end
55
+ end
56
+
57
+ def field_arguments(context = GraphQL::Query::NullContext)
58
+ dummy.arguments(context)
59
+ end
60
+
61
+ def get_field_argument(name, context = GraphQL::Query::NullContext)
62
+ dummy.get_argument(name, context)
63
+ end
64
+
65
+ def own_field_arguments
66
+ dummy.own_arguments
67
+ end
68
+
69
+ def any_field_arguments?
70
+ dummy.any_arguments?
71
+ end
72
+
73
+ def all_field_argument_definitions
74
+ dummy.all_argument_definitions
75
+ end
76
+
77
+ # Also apply this argument to the input type:
78
+ def argument(*args, own_argument: false, **kwargs, &block)
79
+ it = input_type # make sure any inherited arguments are already added to it
80
+ arg = super(*args, **kwargs, &block)
81
+
82
+ # This definition might be overriding something inherited;
83
+ # if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions
84
+ prev_args = it.own_arguments[arg.graphql_name]
85
+ case prev_args
86
+ when GraphQL::Schema::Argument
87
+ if prev_args.owner != self
88
+ it.own_arguments.delete(arg.graphql_name)
89
+ end
90
+ when Array
91
+ prev_args.reject! { |a| a.owner != self }
92
+ if prev_args.empty?
93
+ it.own_arguments.delete(arg.graphql_name)
94
+ end
95
+ end
96
+
97
+ it.add_argument(arg)
98
+ arg
99
+ end
100
+
101
+ # The base class for generated input object types
102
+ # @param new_class [Class] The base class to use for generating input object definitions
103
+ # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
104
+ def input_object_class(new_class = nil)
105
+ if new_class
106
+ @input_object_class = new_class
107
+ end
108
+ @input_object_class || (superclass.respond_to?(:input_object_class) ? superclass.input_object_class : GraphQL::Schema::InputObject)
109
+ end
110
+
111
+ # @param new_input_type [Class, nil] If provided, it configures this mutation to accept `new_input_type` instead of generating an input type
112
+ # @return [Class] The generated {Schema::InputObject} class for this mutation's `input`
113
+ def input_type(new_input_type = nil)
114
+ if new_input_type
115
+ @input_type = new_input_type
116
+ end
117
+ @input_type ||= generate_input_type
118
+ end
119
+
120
+ private
121
+
122
+ # Generate the input type for the `input:` argument
123
+ # To customize how input objects are generated, override this method
124
+ # @return [Class] a subclass of {.input_object_class}
125
+ def generate_input_type
126
+ mutation_args = all_argument_definitions
127
+ mutation_class = self
128
+ Class.new(input_object_class) do
129
+ class << self
130
+ def default_graphql_name
131
+ "#{self.mutation.graphql_name}Input"
132
+ end
133
+
134
+ def description(new_desc = nil)
135
+ super || "Autogenerated input type of #{self.mutation.graphql_name}"
136
+ end
137
+ end
138
+ mutation(mutation_class)
139
+ # these might be inherited:
140
+ mutation_args.each do |arg|
141
+ add_argument(arg)
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def authorize_arguments(args, values)
150
+ # remove the `input` wrapper to match values
151
+ input_args = args["input"].type.unwrap.arguments(context)
152
+ super(input_args, values)
153
+ end
154
+ end
155
+ end
156
+ end
@@ -39,7 +39,9 @@ module GraphQL
39
39
  entry_point_fields.delete('__type') if schema.disable_type_introspection_entry_point?
40
40
  entry_point_fields
41
41
  end
42
+ @entry_point_fields.each { |k, v| v.dynamic_introspection = true }
42
43
  @dynamic_fields = get_fields_from_class(class_sym: :DynamicFields)
44
+ @dynamic_fields.each { |k, v| v.dynamic_introspection = true }
43
45
  end
44
46
 
45
47
  def entry_points
@@ -102,7 +102,8 @@ module GraphQL
102
102
  def default_graphql_name
103
103
  @default_graphql_name ||= begin
104
104
  raise GraphQL::RequiredImplementationMissingError, 'Anonymous class should declare a `graphql_name`' if name.nil?
105
- -name.split("::").last.sub(/Type\Z/, "")
105
+ g_name = -name.split("::").last
106
+ g_name.end_with?("Type") ? g_name.sub(/Type\Z/, "") : g_name
106
107
  end
107
108
  end
108
109
 
@@ -122,6 +122,10 @@ module GraphQL
122
122
  own_arguments_that_apply || own_arguments
123
123
  end
124
124
 
125
+ def any_arguments?
126
+ own_arguments.any?
127
+ end
128
+
125
129
  module ClassConfigured
126
130
  def inherited(child_class)
127
131
  super
@@ -145,6 +149,10 @@ module GraphQL
145
149
  end
146
150
  end
147
151
 
152
+ def any_arguments?
153
+ super || superclass.any_arguments?
154
+ end
155
+
148
156
  def all_argument_definitions
149
157
  all_defns = {}
150
158
  ancestors.reverse_each do |ancestor|
@@ -175,7 +183,7 @@ module GraphQL
175
183
  module FieldConfigured
176
184
  def arguments(context = GraphQL::Query::NullContext)
177
185
  own_arguments = super
178
- if defined?(@resolver_class) && @resolver_class
186
+ if @resolver_class
179
187
  inherited_arguments = @resolver_class.field_arguments(context)
180
188
  if own_arguments.any?
181
189
  if inherited_arguments.any?
@@ -191,8 +199,12 @@ module GraphQL
191
199
  end
192
200
  end
193
201
 
202
+ def any_arguments?
203
+ super || (@resolver_class && @resolver_class.any_field_arguments?)
204
+ end
205
+
194
206
  def all_argument_definitions
195
- if defined?(@resolver_class) && @resolver_class
207
+ if @resolver_class
196
208
  all_defns = {}
197
209
  @resolver_class.all_field_argument_definitions.each do |arg_defn|
198
210
  key = arg_defn.graphql_name
@@ -133,13 +133,16 @@ module GraphQL
133
133
  def get_field(field_name, context = GraphQL::Query::NullContext)
134
134
  # Objects need to check that the interface implementation is visible, too
135
135
  warden = Warden.from_context(context)
136
- for ancestor in ancestors
136
+ ancs = ancestors
137
+ i = 0
138
+ while (ancestor = ancs[i])
137
139
  if ancestor.respond_to?(:own_fields) &&
138
140
  visible_interface_implementation?(ancestor, context, warden) &&
139
141
  (f_entry = ancestor.own_fields[field_name]) &&
140
142
  (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
141
143
  return f
142
144
  end
145
+ i += 1
143
146
  end
144
147
  nil
145
148
  end
@@ -72,9 +72,18 @@ module GraphQL
72
72
  module InheritedInterfaces
73
73
  def interfaces(context = GraphQL::Query::NullContext)
74
74
  visible_interfaces = super
75
- visible_interfaces.concat(superclass.interfaces(context))
76
- visible_interfaces.uniq!
77
- visible_interfaces
75
+ inherited_interfaces = superclass.interfaces(context)
76
+ if visible_interfaces.any?
77
+ if inherited_interfaces.any?
78
+ visible_interfaces.concat(inherited_interfaces)
79
+ visible_interfaces.uniq!
80
+ end
81
+ visible_interfaces
82
+ elsif inherited_interfaces.any?
83
+ inherited_interfaces
84
+ else
85
+ EmptyObjects::EMPTY_ARRAY
86
+ end
78
87
  end
79
88
 
80
89
  def interface_type_memberships
@@ -92,23 +101,28 @@ module GraphQL
92
101
  # param context [Query::Context] If omitted, skip filtering.
93
102
  def interfaces(context = GraphQL::Query::NullContext)
94
103
  warden = Warden.from_context(context)
95
- visible_interfaces = []
104
+ visible_interfaces = nil
96
105
  own_interface_type_memberships.each do |type_membership|
97
106
  case type_membership
98
107
  when Schema::TypeMembership
99
108
  if warden.visible_type_membership?(type_membership, context)
109
+ visible_interfaces ||= []
100
110
  visible_interfaces << type_membership.abstract_type
101
111
  end
102
112
  when String, Schema::LateBoundType
103
113
  # During initialization, `type_memberships` can hold late-bound types
114
+ visible_interfaces ||= []
104
115
  visible_interfaces << type_membership
105
116
  else
106
117
  raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}"
107
118
  end
108
119
  end
109
- visible_interfaces.uniq!
110
-
111
- visible_interfaces
120
+ if visible_interfaces
121
+ visible_interfaces.uniq!
122
+ visible_interfaces
123
+ else
124
+ EmptyObjects::EMPTY_ARRAY
125
+ end
112
126
  end
113
127
 
114
128
  private