graphql 1.12.16 → 1.13.2

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 (151) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -1
  3. data/lib/generators/graphql/install_generator.rb +9 -2
  4. data/lib/generators/graphql/mutation_generator.rb +1 -1
  5. data/lib/generators/graphql/object_generator.rb +2 -1
  6. data/lib/generators/graphql/relay.rb +19 -11
  7. data/lib/generators/graphql/templates/schema.erb +14 -2
  8. data/lib/generators/graphql/type_generator.rb +0 -1
  9. data/lib/graphql/analysis/ast/field_usage.rb +3 -3
  10. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  11. data/lib/graphql/analysis/ast/visitor.rb +4 -4
  12. data/lib/graphql/backtrace/table.rb +1 -1
  13. data/lib/graphql/base_type.rb +4 -2
  14. data/lib/graphql/boolean_type.rb +1 -1
  15. data/lib/graphql/dataloader/source.rb +50 -2
  16. data/lib/graphql/dataloader.rb +93 -37
  17. data/lib/graphql/define/instance_definable.rb +1 -1
  18. data/lib/graphql/deprecated_dsl.rb +11 -3
  19. data/lib/graphql/deprecation.rb +1 -5
  20. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  21. data/lib/graphql/directive/include_directive.rb +1 -1
  22. data/lib/graphql/directive/skip_directive.rb +1 -1
  23. data/lib/graphql/directive.rb +0 -4
  24. data/lib/graphql/enum_type.rb +5 -1
  25. data/lib/graphql/execution/errors.rb +1 -0
  26. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  27. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
  28. data/lib/graphql/execution/interpreter/runtime.rb +39 -23
  29. data/lib/graphql/execution/lookahead.rb +2 -2
  30. data/lib/graphql/execution/multiplex.rb +4 -1
  31. data/lib/graphql/float_type.rb +1 -1
  32. data/lib/graphql/id_type.rb +1 -1
  33. data/lib/graphql/int_type.rb +1 -1
  34. data/lib/graphql/integer_encoding_error.rb +18 -2
  35. data/lib/graphql/introspection/directive_type.rb +1 -1
  36. data/lib/graphql/introspection/entry_points.rb +2 -2
  37. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  38. data/lib/graphql/introspection/field_type.rb +2 -2
  39. data/lib/graphql/introspection/input_value_type.rb +10 -4
  40. data/lib/graphql/introspection/schema_type.rb +2 -2
  41. data/lib/graphql/introspection/type_type.rb +10 -10
  42. data/lib/graphql/language/block_string.rb +2 -6
  43. data/lib/graphql/language/document_from_schema_definition.rb +4 -2
  44. data/lib/graphql/language/lexer.rb +0 -3
  45. data/lib/graphql/language/lexer.rl +0 -4
  46. data/lib/graphql/language/nodes.rb +12 -2
  47. data/lib/graphql/language/parser.rb +442 -434
  48. data/lib/graphql/language/parser.y +5 -4
  49. data/lib/graphql/language/printer.rb +6 -1
  50. data/lib/graphql/language/sanitized_printer.rb +5 -5
  51. data/lib/graphql/language/token.rb +0 -4
  52. data/lib/graphql/name_validator.rb +0 -4
  53. data/lib/graphql/pagination/connections.rb +35 -16
  54. data/lib/graphql/query/arguments.rb +1 -1
  55. data/lib/graphql/query/arguments_cache.rb +1 -1
  56. data/lib/graphql/query/context.rb +15 -2
  57. data/lib/graphql/query/literal_input.rb +1 -1
  58. data/lib/graphql/query/null_context.rb +12 -7
  59. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  60. data/lib/graphql/query/validation_pipeline.rb +1 -1
  61. data/lib/graphql/query/variables.rb +5 -1
  62. data/lib/graphql/query.rb +4 -0
  63. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  64. data/lib/graphql/relay/global_id_resolve.rb +1 -1
  65. data/lib/graphql/relay/page_info.rb +1 -1
  66. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  67. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  68. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  69. data/lib/graphql/rubocop.rb +4 -0
  70. data/lib/graphql/schema/addition.rb +37 -28
  71. data/lib/graphql/schema/argument.rb +79 -34
  72. data/lib/graphql/schema/build_from_definition.rb +5 -5
  73. data/lib/graphql/schema/directive/feature.rb +1 -1
  74. data/lib/graphql/schema/directive/flagged.rb +2 -2
  75. data/lib/graphql/schema/directive/include.rb +1 -1
  76. data/lib/graphql/schema/directive/skip.rb +1 -1
  77. data/lib/graphql/schema/directive/transform.rb +1 -1
  78. data/lib/graphql/schema/directive.rb +7 -3
  79. data/lib/graphql/schema/enum.rb +60 -10
  80. data/lib/graphql/schema/enum_value.rb +6 -0
  81. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  82. data/lib/graphql/schema/field.rb +140 -42
  83. data/lib/graphql/schema/field_extension.rb +52 -2
  84. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  85. data/lib/graphql/schema/finder.rb +5 -5
  86. data/lib/graphql/schema/input_object.rb +13 -14
  87. data/lib/graphql/schema/interface.rb +11 -20
  88. data/lib/graphql/schema/introspection_system.rb +1 -1
  89. data/lib/graphql/schema/list.rb +3 -1
  90. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  91. data/lib/graphql/schema/member/build_type.rb +0 -4
  92. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  93. data/lib/graphql/schema/member/has_arguments.rb +145 -57
  94. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  95. data/lib/graphql/schema/member/has_fields.rb +76 -18
  96. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  97. data/lib/graphql/schema/member.rb +1 -0
  98. data/lib/graphql/schema/non_null.rb +3 -1
  99. data/lib/graphql/schema/object.rb +10 -75
  100. data/lib/graphql/schema/printer.rb +1 -1
  101. data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
  102. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  103. data/lib/graphql/schema/resolver.rb +49 -64
  104. data/lib/graphql/schema/scalar.rb +2 -0
  105. data/lib/graphql/schema/subscription.rb +17 -9
  106. data/lib/graphql/schema/traversal.rb +1 -1
  107. data/lib/graphql/schema/type_expression.rb +1 -1
  108. data/lib/graphql/schema/type_membership.rb +18 -4
  109. data/lib/graphql/schema/union.rb +8 -1
  110. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  111. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  112. data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
  113. data/lib/graphql/schema/validator/format_validator.rb +4 -5
  114. data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
  115. data/lib/graphql/schema/validator/length_validator.rb +5 -3
  116. data/lib/graphql/schema/validator/numericality_validator.rb +13 -2
  117. data/lib/graphql/schema/validator.rb +33 -25
  118. data/lib/graphql/schema/warden.rb +116 -52
  119. data/lib/graphql/schema.rb +124 -27
  120. data/lib/graphql/static_validation/base_visitor.rb +8 -5
  121. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  122. data/lib/graphql/static_validation/error.rb +3 -1
  123. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  124. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  125. data/lib/graphql/static_validation/rules/fields_will_merge.rb +52 -26
  126. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  127. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  128. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
  129. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  130. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -7
  131. data/lib/graphql/static_validation/validation_context.rb +8 -2
  132. data/lib/graphql/static_validation/validator.rb +15 -12
  133. data/lib/graphql/string_encoding_error.rb +13 -3
  134. data/lib/graphql/string_type.rb +1 -1
  135. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +15 -5
  136. data/lib/graphql/subscriptions/event.rb +66 -13
  137. data/lib/graphql/subscriptions/serialize.rb +1 -1
  138. data/lib/graphql/subscriptions.rb +17 -19
  139. data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
  140. data/lib/graphql/types/int.rb +1 -1
  141. data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
  142. data/lib/graphql/types/relay/default_relay.rb +5 -1
  143. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  144. data/lib/graphql/types/relay/has_node_field.rb +1 -1
  145. data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
  146. data/lib/graphql/types/string.rb +1 -1
  147. data/lib/graphql/unauthorized_error.rb +1 -1
  148. data/lib/graphql/version.rb +1 -1
  149. data/lib/graphql.rb +10 -32
  150. data/readme.md +1 -1
  151. metadata +13 -6
@@ -11,8 +11,24 @@ module GraphQL
11
11
  # A cached result of {.to_graphql}.
12
12
  # It's cached here so that user-overridden {.to_graphql} implementations
13
13
  # are also cached
14
- def graphql_definition
15
- @graphql_definition ||= to_graphql
14
+ def graphql_definition(silence_deprecation_warning: false)
15
+ @graphql_definition ||= begin
16
+ unless silence_deprecation_warning
17
+ message = "Legacy `.graphql_definition` objects are deprecated and will be removed in GraphQL-Ruby 2.0. Remove `.graphql_definition` to use a class-based definition instead."
18
+ caller_message = "\n\nCalled on #{self.inspect} from:\n #{caller(1, 25).map { |l| " #{l}" }.join("\n")}"
19
+ GraphQL::Deprecation.warn(message + caller_message)
20
+ end
21
+ deprecated_to_graphql
22
+ end
23
+ end
24
+
25
+ def deprecated_to_graphql
26
+ case method(:to_graphql).arity
27
+ when 0
28
+ to_graphql
29
+ else
30
+ to_graphql(silence_deprecation_warning: true)
31
+ end
16
32
  end
17
33
 
18
34
  # This is for a common interface with .define-based types
@@ -25,6 +41,17 @@ module GraphQL
25
41
  super
26
42
  @graphql_definition = nil
27
43
  end
44
+
45
+ module DeprecatedToGraphQL
46
+ def to_graphql(silence_deprecation_warning: false)
47
+ unless silence_deprecation_warning
48
+ message = "Legacy `.to_graphql` objects are deprecated and will be removed in GraphQL-Ruby 2.0. Remove `.to_graphql` to use a class-based definition instead."
49
+ caller_message = "\n\nCalled on #{self.inspect} from:\n #{caller(1, 25).map { |l| " #{l}" }.join("\n")}"
50
+ GraphQL::Deprecation.warn(message + caller_message)
51
+ end
52
+ super()
53
+ end
54
+ end
28
55
  end
29
56
  end
30
57
  end
@@ -37,6 +37,40 @@ module GraphQL
37
37
  end
38
38
  arg_defn = self.argument_class.new(*args, **kwargs, &block)
39
39
  add_argument(arg_defn)
40
+
41
+ if self.is_a?(Class) && !method_defined?(:"load_#{arg_defn.keyword}")
42
+ method_owner = if self < GraphQL::Schema::InputObject || self < GraphQL::Schema::Directive
43
+ "self."
44
+ elsif self < GraphQL::Schema::Resolver
45
+ ""
46
+ else
47
+ raise "Unexpected argument owner: #{self}"
48
+ end
49
+ if loads && arg_defn.type.list?
50
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
51
+ def #{method_owner}load_#{arg_defn.keyword}(values, context = nil)
52
+ argument = get_argument("#{arg_defn.graphql_name}")
53
+ (context || self.context).schema.after_lazy(values) do |values2|
54
+ GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, value, context || self.context) })
55
+ end
56
+ end
57
+ RUBY
58
+ elsif loads
59
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
60
+ def #{method_owner}load_#{arg_defn.keyword}(value, context = nil)
61
+ argument = get_argument("#{arg_defn.graphql_name}")
62
+ load_application_object(argument, value, context || self.context)
63
+ end
64
+ RUBY
65
+ else
66
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
67
+ def #{method_owner}load_#{arg_defn.keyword}(value, _context = nil)
68
+ value
69
+ end
70
+ RUBY
71
+ end
72
+ end
73
+ arg_defn
40
74
  end
41
75
 
42
76
  # Register this argument with the class.
@@ -44,30 +78,72 @@ module GraphQL
44
78
  # @return [GraphQL::Schema::Argument]
45
79
  def add_argument(arg_defn)
46
80
  @own_arguments ||= {}
47
- own_arguments[arg_defn.name] = arg_defn
81
+ prev_defn = own_arguments[arg_defn.name]
82
+ case prev_defn
83
+ when nil
84
+ own_arguments[arg_defn.name] = arg_defn
85
+ when Array
86
+ prev_defn << arg_defn
87
+ when GraphQL::Schema::Argument
88
+ own_arguments[arg_defn.name] = [prev_defn, arg_defn]
89
+ else
90
+ raise "Invariant: unexpected `@own_arguments[#{arg_defn.name.inspect}]`: #{prev_defn.inspect}"
91
+ end
48
92
  arg_defn
49
93
  end
50
94
 
51
95
  # @return [Hash<String => GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions
52
- def arguments
53
- inherited_arguments = ((self.is_a?(Class) && superclass.respond_to?(:arguments)) ? superclass.arguments : nil)
96
+ def arguments(context = GraphQL::Query::NullContext)
97
+ inherited_arguments = ((self.is_a?(Class) && superclass.respond_to?(:arguments)) ? superclass.arguments(context) : nil)
54
98
  # Local definitions override inherited ones
99
+ if own_arguments.any?
100
+ own_arguments_that_apply = {}
101
+ own_arguments.each do |name, args_entry|
102
+ if (visible_defn = Warden.visible_entry?(:visible_argument?, args_entry, context))
103
+ own_arguments_that_apply[visible_defn.graphql_name] = visible_defn
104
+ end
105
+ end
106
+ end
107
+
55
108
  if inherited_arguments
56
- inherited_arguments.merge(own_arguments)
109
+ if own_arguments_that_apply
110
+ inherited_arguments.merge(own_arguments_that_apply)
111
+ else
112
+ inherited_arguments
113
+ end
57
114
  else
58
- own_arguments
115
+ # might be nil if there are actually no arguments
116
+ own_arguments_that_apply || own_arguments
59
117
  end
60
118
  end
61
119
 
62
- # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
63
- def get_argument(argument_name)
64
- a = own_arguments[argument_name]
120
+ def all_argument_definitions
121
+ if self.is_a?(Class)
122
+ all_defns = {}
123
+ ancestors.reverse_each do |ancestor|
124
+ if ancestor.respond_to?(:own_arguments)
125
+ all_defns.merge!(ancestor.own_arguments)
126
+ end
127
+ end
128
+ else
129
+ all_defns = own_arguments
130
+ end
131
+ all_defns = all_defns.values
132
+ all_defns.flatten!
133
+ all_defns
134
+ end
65
135
 
66
- if a || !self.is_a?(Class)
67
- a
136
+ # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
137
+ def get_argument(argument_name, context = GraphQL::Query::NullContext)
138
+ warden = Warden.from_context(context)
139
+ if !self.is_a?(Class)
140
+ a = own_arguments[argument_name]
141
+ a && Warden.visible_entry?(:visible_argument?, a, context, warden)
68
142
  else
69
143
  for ancestor in ancestors
70
- if ancestor.respond_to?(:own_arguments) && a = ancestor.own_arguments[argument_name]
144
+ if ancestor.respond_to?(:own_arguments) &&
145
+ (a = ancestor.own_arguments[argument_name]) &&
146
+ (a = Warden.visible_entry?(:visible_argument?, a, context, warden))
71
147
  return a
72
148
  end
73
149
  end
@@ -91,57 +167,55 @@ module GraphQL
91
167
  # @return [Interpreter::Arguments, Execution::Lazy<Interpeter::Arguments>]
92
168
  def coerce_arguments(parent_object, values, context, &block)
93
169
  # Cache this hash to avoid re-merging it
94
- arg_defns = self.arguments
170
+ arg_defns = self.arguments(context)
95
171
  total_args_count = arg_defns.size
96
172
 
97
- if total_args_count == 0
98
- final_args = GraphQL::Execution::Interpreter::Arguments::EMPTY
99
- if block_given?
100
- block.call(final_args)
101
- nil
173
+ finished_args = nil
174
+ prepare_finished_args = -> {
175
+ if total_args_count == 0
176
+ finished_args = GraphQL::Execution::Interpreter::Arguments::EMPTY
177
+ if block_given?
178
+ block.call(finished_args)
179
+ end
102
180
  else
103
- final_args
104
- end
105
- else
106
- finished_args = nil
107
- argument_values = {}
108
- resolved_args_count = 0
109
- raised_error = false
110
- arg_defns.each do |arg_name, arg_defn|
111
- context.dataloader.append_job do
112
- begin
113
- arg_defn.coerce_into_values(parent_object, values, context, argument_values)
114
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
115
- raised_error = true
116
- if block_given?
117
- block.call(err)
118
- else
181
+ argument_values = {}
182
+ resolved_args_count = 0
183
+ raised_error = false
184
+ arg_defns.each do |arg_name, arg_defn|
185
+ context.dataloader.append_job do
186
+ begin
187
+ arg_defn.coerce_into_values(parent_object, values, context, argument_values)
188
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
189
+ raised_error = true
119
190
  finished_args = err
191
+ if block_given?
192
+ block.call(finished_args)
193
+ end
120
194
  end
121
- end
122
195
 
123
- resolved_args_count += 1
124
- if resolved_args_count == total_args_count && !raised_error
125
- finished_args = context.schema.after_any_lazies(argument_values.values) {
126
- GraphQL::Execution::Interpreter::Arguments.new(
127
- argument_values: argument_values,
128
- )
129
- }
130
-
131
- if block_given?
132
- block.call(finished_args)
196
+ resolved_args_count += 1
197
+ if resolved_args_count == total_args_count && !raised_error
198
+ finished_args = context.schema.after_any_lazies(argument_values.values) {
199
+ GraphQL::Execution::Interpreter::Arguments.new(
200
+ argument_values: argument_values,
201
+ )
202
+ }
203
+ if block_given?
204
+ block.call(finished_args)
205
+ end
133
206
  end
134
207
  end
135
208
  end
136
209
  end
210
+ }
137
211
 
138
- if block_given?
139
- nil
140
- else
141
- # This API returns eagerly, gotta run it now
142
- context.dataloader.run
143
- finished_args
144
- end
212
+ if block_given?
213
+ prepare_finished_args.call
214
+ nil
215
+ else
216
+ # This API returns eagerly, gotta run it now
217
+ context.dataloader.run_isolated(&prepare_finished_args)
218
+ finished_args
145
219
  end
146
220
  end
147
221
 
@@ -159,7 +233,7 @@ module GraphQL
159
233
  def arguments_statically_coercible?
160
234
  return @arguments_statically_coercible if defined?(@arguments_statically_coercible)
161
235
 
162
- @arguments_statically_coercible = arguments.each_value.all?(&:statically_coercible?)
236
+ @arguments_statically_coercible = all_argument_definitions.all?(&:statically_coercible?)
163
237
  end
164
238
 
165
239
  module ArgumentClassAccessor
@@ -186,12 +260,20 @@ module GraphQL
186
260
  context.schema.object_from_id(id, context)
187
261
  end
188
262
 
189
- def load_application_object(argument, lookup_as_type, id, context)
263
+ def load_application_object(argument, id, context)
190
264
  # See if any object can be found for this ID
191
265
  if id.nil?
192
266
  return nil
193
267
  end
194
- loaded_application_object = object_from_id(lookup_as_type, id, context)
268
+ object_from_id(argument.loads, id, context)
269
+ end
270
+
271
+ def load_and_authorize_application_object(argument, id, context)
272
+ loaded_application_object = load_application_object(argument, id, context)
273
+ authorize_application_object(argument, id, context, loaded_application_object)
274
+ end
275
+
276
+ def authorize_application_object(argument, id, context, loaded_application_object)
195
277
  context.schema.after_lazy(loaded_application_object) do |application_object|
196
278
  if application_object.nil?
197
279
  err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
@@ -199,9 +281,9 @@ module GraphQL
199
281
  end
200
282
  # Double-check that the located object is actually of this type
201
283
  # (Don't want to allow arbitrary access to objects this way)
202
- resolved_application_object_type = context.schema.resolve_type(lookup_as_type, application_object, context)
284
+ resolved_application_object_type = context.schema.resolve_type(argument.loads, application_object, context)
203
285
  context.schema.after_lazy(resolved_application_object_type) do |application_object_type|
204
- possible_object_types = context.warden.possible_types(lookup_as_type)
286
+ possible_object_types = context.warden.possible_types(argument.loads)
205
287
  if !possible_object_types.include?(application_object_type)
206
288
  err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
207
289
  load_application_object_failed(err)
@@ -214,11 +296,17 @@ module GraphQL
214
296
  if authed
215
297
  application_object
216
298
  else
217
- raise GraphQL::UnauthorizedError.new(
299
+ err = GraphQL::UnauthorizedError.new(
218
300
  object: application_object,
219
301
  type: class_based_type,
220
302
  context: context,
221
303
  )
304
+ if self.respond_to?(:unauthorized_object)
305
+ err.set_backtrace(caller)
306
+ unauthorized_object(err)
307
+ else
308
+ raise err
309
+ end
222
310
  end
223
311
  end
224
312
  else
@@ -7,7 +7,7 @@ module GraphQL
7
7
  # @return [String, nil] Explains why this member was deprecated (if present, this will be marked deprecated in introspection)
8
8
  def deprecation_reason
9
9
  dir = self.directives.find { |d| d.is_a?(GraphQL::Schema::Directive::Deprecated) }
10
- dir && dir.arguments[:reason]
10
+ dir && dir.arguments[:reason] # rubocop:disable Development/ContextIsPassedCop -- definition-related
11
11
  end
12
12
 
13
13
  # Set the deprecation reason for this member, or remove it by assigning `nil`
@@ -3,7 +3,7 @@
3
3
  module GraphQL
4
4
  class Schema
5
5
  class Member
6
- # Shared code for Object and Interface
6
+ # Shared code for Objects, Interfaces, Mutations, Subscriptions
7
7
  module HasFields
8
8
  # Add a field to this object or interface with the given definition
9
9
  # @see {GraphQL::Schema::Field#initialize} for method signature
@@ -15,28 +15,39 @@ module GraphQL
15
15
  end
16
16
 
17
17
  # @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
18
- def fields
18
+ def fields(context = GraphQL::Query::NullContext)
19
+ warden = Warden.from_context(context)
20
+ is_object = self.respond_to?(:kind) && self.kind.object?
19
21
  # Local overrides take precedence over inherited fields
20
- all_fields = {}
21
- ancestors.reverse_each do |ancestor|
22
- if ancestor.respond_to?(:own_fields)
23
- all_fields.merge!(ancestor.own_fields)
22
+ visible_fields = {}
23
+ for ancestor in ancestors
24
+ if ancestor.respond_to?(:own_fields) &&
25
+ (is_object ? visible_interface_implementation?(ancestor, context, warden) : true)
26
+
27
+ ancestor.own_fields.each do |field_name, fields_entry|
28
+ # Choose the most local definition that passes `.visible?` --
29
+ # stop checking for fields by name once one has been found.
30
+ if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
31
+ visible_fields[field_name] = f
32
+ end
33
+ end
24
34
  end
25
35
  end
26
- all_fields
36
+ visible_fields
27
37
  end
28
38
 
29
- def get_field(field_name)
30
- if (f = own_fields[field_name])
31
- f
32
- else
33
- for ancestor in ancestors
34
- if ancestor.respond_to?(:own_fields) && f = ancestor.own_fields[field_name]
35
- return f
36
- end
39
+ def get_field(field_name, context = GraphQL::Query::NullContext)
40
+ warden = Warden.from_context(context)
41
+ is_object = self.respond_to?(:kind) && self.kind.object?
42
+ for ancestor in ancestors
43
+ if ancestor.respond_to?(:own_fields) &&
44
+ (is_object ? visible_interface_implementation?(ancestor, context, warden) : true) &&
45
+ (f_entry = ancestor.own_fields[field_name]) &&
46
+ (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
47
+ return f
37
48
  end
38
- nil
39
49
  end
50
+ nil
40
51
  end
41
52
 
42
53
  # A list of Ruby keywords.
@@ -64,7 +75,19 @@ module GraphQL
64
75
  if method_conflict_warning && CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.original_name == field_defn.method_sym
65
76
  warn(conflict_field_name_warning(field_defn))
66
77
  end
67
- own_fields[field_defn.name] = field_defn
78
+ prev_defn = own_fields[field_defn.name]
79
+
80
+ case prev_defn
81
+ when nil
82
+ own_fields[field_defn.name] = field_defn
83
+ when Array
84
+ prev_defn << field_defn
85
+ when GraphQL::Schema::Field
86
+ own_fields[field_defn.name] = [prev_defn, field_defn]
87
+ else
88
+ raise "Invariant: unexpected previous field definition for #{field_defn.name.inspect}: #{prev_defn.inspect}"
89
+ end
90
+
68
91
  nil
69
92
  end
70
93
 
@@ -87,13 +110,48 @@ module GraphQL
87
110
  end
88
111
  end
89
112
 
90
- # @return [Array<GraphQL::Schema::Field>] Fields defined on this class _specifically_, not parent classes
113
+ # @return [Hash<String => GraphQL::Schema::Field, Array<GraphQL::Schema::Field>>] Fields defined on this class _specifically_, not parent classes
91
114
  def own_fields
92
115
  @own_fields ||= {}
93
116
  end
94
117
 
118
+ def all_field_definitions
119
+ all_fields = {}
120
+ ancestors.reverse_each do |ancestor|
121
+ if ancestor.respond_to?(:own_fields)
122
+ all_fields.merge!(ancestor.own_fields)
123
+ end
124
+ end
125
+ all_fields = all_fields.values
126
+ all_fields.flatten!
127
+ all_fields
128
+ end
129
+
95
130
  private
96
131
 
132
+ # If `type` is an interface, and `self` has a type membership for `type`, then make sure it's visible.
133
+ def visible_interface_implementation?(type, context, warden)
134
+ if type.respond_to?(:kind) && type.kind.interface?
135
+ implements_this_interface = false
136
+ implementation_is_visible = false
137
+ interface_type_memberships.each do |tm|
138
+ if tm.abstract_type == type
139
+ implements_this_interface ||= true
140
+ if warden.visible_type_membership?(tm, context)
141
+ implementation_is_visible = true
142
+ break
143
+ end
144
+ end
145
+ end
146
+ # It's possible this interface came by way of `include` in another interface which this
147
+ # object type _does_ implement, and that's ok
148
+ implements_this_interface ? implementation_is_visible : true
149
+ else
150
+ # If there's no implementation, then we're looking at Ruby-style inheritance instead
151
+ true
152
+ end
153
+ end
154
+
97
155
  # @param [GraphQL::Schema::Field]
98
156
  # @return [String] A warning to give when this field definition might conflict with a built-in method
99
157
  def conflict_field_name_warning(field_defn)
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module HasInterfaces
7
+ def implements(*new_interfaces, **options)
8
+ new_memberships = []
9
+ new_interfaces.each do |int|
10
+ if int.is_a?(Module)
11
+ unless int.include?(GraphQL::Schema::Interface)
12
+ raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
13
+ end
14
+
15
+ new_memberships << int.type_membership_class.new(int, self, **options)
16
+
17
+ # Include the methods here,
18
+ # `.fields` will use the inheritance chain
19
+ # to find inherited fields
20
+ include(int)
21
+
22
+ # If this interface has interfaces of its own, add those, too
23
+ int.interfaces.each do |next_interface|
24
+ implements(next_interface)
25
+ end
26
+ elsif int.is_a?(GraphQL::InterfaceType)
27
+ new_memberships << int.type_membership_class.new(int, self, **options)
28
+ elsif int.is_a?(String) || int.is_a?(GraphQL::Schema::LateBoundType)
29
+ if options.any?
30
+ raise ArgumentError, "`implements(...)` doesn't support options with late-loaded types yet. Remove #{options} and open an issue to request this feature."
31
+ end
32
+ new_memberships << int
33
+ else
34
+ raise ArgumentError, "Unexpected interface definition (expected module): #{int} (#{int.class})"
35
+ end
36
+ end
37
+
38
+ # Remove any String or late-bound interfaces which are being replaced
39
+ own_interface_type_memberships.reject! { |old_i_m|
40
+ if !(old_i_m.respond_to?(:abstract_type) && old_i_m.abstract_type.is_a?(Module))
41
+ old_int_type = old_i_m.respond_to?(:abstract_type) ? old_i_m.abstract_type : old_i_m
42
+ old_name = Schema::Member::BuildType.to_type_name(old_int_type)
43
+
44
+ new_memberships.any? { |new_i_m|
45
+ new_int_type = new_i_m.respond_to?(:abstract_type) ? new_i_m.abstract_type : new_i_m
46
+ new_name = Schema::Member::BuildType.to_type_name(new_int_type)
47
+
48
+ new_name == old_name
49
+ }
50
+ end
51
+ }
52
+ own_interface_type_memberships.concat(new_memberships)
53
+ end
54
+
55
+ def own_interface_type_memberships
56
+ @own_interface_type_memberships ||= []
57
+ end
58
+
59
+ def interface_type_memberships
60
+ own_interface_type_memberships + ((self.is_a?(Class) && superclass.respond_to?(:interface_type_memberships)) ? superclass.interface_type_memberships : [])
61
+ end
62
+
63
+ # param context [Query::Context] If omitted, skip filtering.
64
+ def interfaces(context = GraphQL::Query::NullContext)
65
+ warden = Warden.from_context(context)
66
+ visible_interfaces = []
67
+ own_interface_type_memberships.each do |type_membership|
68
+ # During initialization, `type_memberships` can hold late-bound types
69
+ case type_membership
70
+ when String, Schema::LateBoundType
71
+ visible_interfaces << type_membership
72
+ when Schema::TypeMembership
73
+ if warden.visible_type_membership?(type_membership, context)
74
+ visible_interfaces << type_membership.abstract_type
75
+ end
76
+ else
77
+ raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}"
78
+ end
79
+ end
80
+
81
+ if self.is_a?(Class) && superclass <= GraphQL::Schema::Object
82
+ visible_interfaces.concat(superclass.interfaces(context))
83
+ end
84
+
85
+ visible_interfaces
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -6,6 +6,7 @@ require 'graphql/schema/member/graphql_type_names'
6
6
  require 'graphql/schema/member/has_ast_node'
7
7
  require 'graphql/schema/member/has_directives'
8
8
  require 'graphql/schema/member/has_deprecation_reason'
9
+ require 'graphql/schema/member/has_interfaces'
9
10
  require 'graphql/schema/member/has_path'
10
11
  require 'graphql/schema/member/has_unresolved_type_error'
11
12
  require 'graphql/schema/member/has_validators'
@@ -8,8 +8,10 @@ module GraphQL
8
8
  class NonNull < GraphQL::Schema::Wrapper
9
9
  include Schema::Member::ValidatesInput
10
10
 
11
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
12
+
11
13
  def to_graphql
12
- @of_type.graphql_definition.to_non_null_type
14
+ @of_type.graphql_definition(silence_deprecation_warning: true).to_non_null_type
13
15
  end
14
16
 
15
17
  # @return [GraphQL::TypeKinds::NON_NULL]