graphql 2.0.13 → 2.0.15

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/templates/schema.erb +3 -0
  3. data/lib/graphql/backtrace/table.rb +2 -2
  4. data/lib/graphql/dataloader/source.rb +9 -0
  5. data/lib/graphql/dataloader.rb +4 -1
  6. data/lib/graphql/execution/interpreter/runtime.rb +23 -10
  7. data/lib/graphql/execution/interpreter.rb +185 -59
  8. data/lib/graphql/execution/lookahead.rb +39 -28
  9. data/lib/graphql/execution/multiplex.rb +1 -116
  10. data/lib/graphql/execution.rb +0 -1
  11. data/lib/graphql/introspection/type_type.rb +7 -0
  12. data/lib/graphql/introspection.rb +3 -2
  13. data/lib/graphql/language/document_from_schema_definition.rb +18 -18
  14. data/lib/graphql/language/printer.rb +20 -11
  15. data/lib/graphql/query/context.rb +19 -5
  16. data/lib/graphql/query.rb +1 -1
  17. data/lib/graphql/schema/build_from_definition.rb +32 -17
  18. data/lib/graphql/schema/directive/one_of.rb +12 -0
  19. data/lib/graphql/schema/directive/transform.rb +1 -1
  20. data/lib/graphql/schema/field.rb +3 -3
  21. data/lib/graphql/schema/input_object.rb +35 -0
  22. data/lib/graphql/schema/late_bound_type.rb +4 -0
  23. data/lib/graphql/schema/member/build_type.rb +1 -1
  24. data/lib/graphql/schema/member/has_directives.rb +71 -56
  25. data/lib/graphql/schema/resolver/has_payload_type.rb +1 -1
  26. data/lib/graphql/schema/type_membership.rb +3 -0
  27. data/lib/graphql/schema.rb +19 -7
  28. data/lib/graphql/static_validation/all_rules.rb +1 -0
  29. data/lib/graphql/static_validation/literal_validator.rb +4 -0
  30. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
  31. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
  32. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +1 -1
  33. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  34. data/lib/graphql/tracing/instrumentation_tracing.rb +83 -0
  35. data/lib/graphql/types/relay/node_behaviors.rb +1 -1
  36. data/lib/graphql/version.rb +1 -1
  37. metadata +7 -4
  38. data/lib/graphql/execution/instrumentation.rb +0 -92
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/execution/directive_checks"
3
- require "graphql/execution/instrumentation"
4
3
  require "graphql/execution/interpreter"
5
4
  require "graphql/execution/lazy"
6
5
  require "graphql/execution/lookahead"
@@ -29,6 +29,13 @@ module GraphQL
29
29
 
30
30
  field :specifiedByURL, String, resolver_method: :specified_by_url
31
31
 
32
+ field :is_one_of, Boolean, null: false
33
+
34
+ def is_one_of
35
+ object.kind.input_object? &&
36
+ object.directives.any? { |d| d.graphql_name == "oneOf" }
37
+ end
38
+
32
39
  def specified_by_url
33
40
  if object.kind.scalar?
34
41
  object.specified_by_url
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  module Introspection
4
- def self.query(include_deprecated_args: false, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false)
4
+ def self.query(include_deprecated_args: false, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false)
5
5
  # The introspection query to end all introspection queries, copied from
6
6
  # https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js
7
- <<-QUERY
7
+ <<-QUERY.gsub(/\n{2,}/, "\n")
8
8
  query IntrospectionQuery {
9
9
  __schema {
10
10
  #{include_schema_description ? "description" : ""}
@@ -30,6 +30,7 @@ fragment FullType on __Type {
30
30
  name
31
31
  description
32
32
  #{include_specified_by_url ? "specifiedByURL" : ""}
33
+ #{include_is_one_of ? "isOneOf" : ""}
33
34
  fields(includeDeprecated: true) {
34
35
  name
35
36
  description
@@ -44,16 +44,18 @@ module GraphQL
44
44
  end
45
45
 
46
46
  def build_schema_node
47
- GraphQL::Language::Nodes::SchemaDefinition.new(
48
- query: (q = warden.root_type_for_operation("query")) && q.graphql_name,
49
- mutation: (m = warden.root_type_for_operation("mutation")) && m.graphql_name,
50
- subscription: (s = warden.root_type_for_operation("subscription")) && s.graphql_name,
51
- # This only supports directives from parsing,
52
- # use a custom printer to add to this list.
53
- #
47
+ schema_options = {
54
48
  # `@schema.directives` is covered by `build_definition_nodes`
55
- directives: ast_directives(@schema),
56
- )
49
+ directives: definition_directives(@schema, :schema_directives),
50
+ }
51
+ if !schema_respects_root_name_conventions?(@schema)
52
+ schema_options.merge!({
53
+ query: (q = warden.root_type_for_operation("query")) && q.graphql_name,
54
+ mutation: (m = warden.root_type_for_operation("mutation")) && m.graphql_name,
55
+ subscription: (s = warden.root_type_for_operation("subscription")) && s.graphql_name,
56
+ })
57
+ end
58
+ GraphQL::Language::Nodes::SchemaDefinition.new(schema_options)
57
59
  end
58
60
 
59
61
  def build_object_type_node(object_type)
@@ -283,7 +285,9 @@ module GraphQL
283
285
  private
284
286
 
285
287
  def include_schema_node?
286
- always_include_schema || !schema_respects_root_name_conventions?(schema)
288
+ always_include_schema ||
289
+ !schema_respects_root_name_conventions?(schema) ||
290
+ !schema.schema_directives.empty?
287
291
  end
288
292
 
289
293
  def schema_respects_root_name_conventions?(schema)
@@ -293,14 +297,14 @@ module GraphQL
293
297
  end
294
298
 
295
299
  def directives(member)
296
- definition_directives(member)
300
+ definition_directives(member, :directives)
297
301
  end
298
302
 
299
- def definition_directives(member)
300
- dirs = if !member.respond_to?(:directives) || member.directives.empty?
303
+ def definition_directives(member, directives_method)
304
+ dirs = if !member.respond_to?(directives_method) || member.directives.empty?
301
305
  []
302
306
  else
303
- member.directives.map do |dir|
307
+ member.public_send(directives_method).map do |dir|
304
308
  args = []
305
309
  dir.arguments.argument_values.each_value do |arg_value| # rubocop:disable Development/ContextIsPassedCop -- directive instance method
306
310
  arg_defn = arg_value.definition
@@ -324,10 +328,6 @@ module GraphQL
324
328
  dirs
325
329
  end
326
330
 
327
- def ast_directives(member)
328
- member.ast_node ? member.ast_node.directives : []
329
- end
330
-
331
331
  attr_reader :schema, :warden, :always_include_schema,
332
332
  :include_introspection_types, :include_built_in_directives, :include_built_in_scalars
333
333
  end
@@ -132,10 +132,11 @@ module GraphQL
132
132
  end
133
133
 
134
134
  def print_schema_definition(schema)
135
- if (schema.query.nil? || schema.query == 'Query') &&
136
- (schema.mutation.nil? || schema.mutation == 'Mutation') &&
137
- (schema.subscription.nil? || schema.subscription == 'Subscription') &&
138
- (schema.directives.empty?)
135
+ has_conventional_names = (schema.query.nil? || schema.query == 'Query') &&
136
+ (schema.mutation.nil? || schema.mutation == 'Mutation') &&
137
+ (schema.subscription.nil? || schema.subscription == 'Subscription')
138
+
139
+ if has_conventional_names && schema.directives.empty?
139
140
  return
140
141
  end
141
142
 
@@ -145,14 +146,22 @@ module GraphQL
145
146
  out << "\n "
146
147
  out << print_node(dir)
147
148
  end
148
- out << "\n{"
149
- else
150
- out << " {\n"
149
+ if !has_conventional_names
150
+ out << "\n"
151
+ end
151
152
  end
152
- out << " query: #{schema.query}\n" if schema.query
153
- out << " mutation: #{schema.mutation}\n" if schema.mutation
154
- out << " subscription: #{schema.subscription}\n" if schema.subscription
155
- out << "}"
153
+
154
+ if !has_conventional_names
155
+ if schema.directives.empty?
156
+ out << " "
157
+ end
158
+ out << "{\n"
159
+ out << " query: #{schema.query}\n" if schema.query
160
+ out << " mutation: #{schema.mutation}\n" if schema.mutation
161
+ out << " subscription: #{schema.subscription}\n" if schema.subscription
162
+ out << "}"
163
+ end
164
+ out
156
165
  end
157
166
 
158
167
  def print_scalar_type_definition(scalar_type)
@@ -111,7 +111,7 @@ module GraphQL
111
111
  end
112
112
 
113
113
  def current_path
114
- @query_context.namespace(:interpreter)[:current_path] || @no_path
114
+ @query_context[:current_path] || @no_path
115
115
  end
116
116
 
117
117
  def key?(key)
@@ -189,12 +189,16 @@ module GraphQL
189
189
 
190
190
  def_delegators :@query, :trace, :interpreter?
191
191
 
192
+ RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
192
193
  # @!method []=(key, value)
193
194
  # Reassign `key` to the hash passed to {Schema#execute} as `context:`
194
195
 
195
196
  # Lookup `key` from the hash passed to {Schema#execute} as `context:`
196
197
  def [](key)
197
- if @scoped_context.key?(key)
198
+ if RUNTIME_METADATA_KEYS.include?(key)
199
+ thread_info = Thread.current[:__graphql_runtime_info]
200
+ thread_info && thread_info[key]
201
+ elsif @scoped_context.key?(key)
198
202
  @scoped_context[key]
199
203
  else
200
204
  @provided_values[key]
@@ -212,7 +216,10 @@ module GraphQL
212
216
  UNSPECIFIED_FETCH_DEFAULT = Object.new
213
217
 
214
218
  def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT)
215
- if @scoped_context.key?(key)
219
+ if RUNTIME_METADATA_KEYS.include?(key)
220
+ (thread_info = Thread.current[:__graphql_runtime_info]) &&
221
+ thread_info[key]
222
+ elsif @scoped_context.key?(key)
216
223
  scoped_context[key]
217
224
  elsif @provided_values.key?(key)
218
225
  @provided_values[key]
@@ -226,7 +233,10 @@ module GraphQL
226
233
  end
227
234
 
228
235
  def dig(key, *other_keys)
229
- if @scoped_context.key?(key)
236
+ if RUNTIME_METADATA_KEYS.include?(key)
237
+ (thread_info = Thread.current[:__graphql_runtime_info]).key?(key) &&
238
+ thread_info.dig(key)
239
+ elsif @scoped_context.key?(key)
230
240
  @scoped_context.dig(key, *other_keys)
231
241
  else
232
242
  @provided_values.dig(key, *other_keys)
@@ -259,7 +269,11 @@ module GraphQL
259
269
  # @param ns [Object] a usage-specific namespace identifier
260
270
  # @return [Hash] namespaced storage
261
271
  def namespace(ns)
262
- @storage[ns]
272
+ if ns == :interpreter
273
+ self
274
+ else
275
+ @storage[ns]
276
+ end
263
277
  end
264
278
 
265
279
  # @return [Boolean] true if this namespace was accessed before
data/lib/graphql/query.rb CHANGED
@@ -196,7 +196,7 @@ module GraphQL
196
196
  # @return [Hash] A GraphQL response, with `"data"` and/or `"errors"` keys
197
197
  def result
198
198
  if !@executed
199
- Execution::Multiplex.run_all(@schema, [self], context: @context)
199
+ Execution::Interpreter.run_all(@schema, [self], context: @context)
200
200
  end
201
201
  @result ||= Query::Result.new(query: self, values: @result_values)
202
202
  end
@@ -6,16 +6,16 @@ module GraphQL
6
6
  module BuildFromDefinition
7
7
  class << self
8
8
  # @see {Schema.from_definition}
9
- def from_definition(definition_string, parser: GraphQL.default_parser, **kwargs)
10
- from_document(parser.parse(definition_string), **kwargs)
9
+ def from_definition(schema_superclass, definition_string, parser: GraphQL.default_parser, **kwargs)
10
+ from_document(schema_superclass, parser.parse(definition_string), **kwargs)
11
11
  end
12
12
 
13
- def from_definition_path(definition_path, parser: GraphQL.default_parser, **kwargs)
14
- from_document(parser.parse_file(definition_path), **kwargs)
13
+ def from_definition_path(schema_superclass, definition_path, parser: GraphQL.default_parser, **kwargs)
14
+ from_document(schema_superclass, parser.parse_file(definition_path), **kwargs)
15
15
  end
16
16
 
17
- def from_document(document, default_resolve:, using: {}, relay: false)
18
- Builder.build(document, default_resolve: default_resolve || {}, relay: relay, using: using)
17
+ def from_document(schema_superclass, document, default_resolve:, using: {}, relay: false)
18
+ Builder.build(schema_superclass, document, default_resolve: default_resolve || {}, relay: relay, using: using)
19
19
  end
20
20
  end
21
21
 
@@ -23,7 +23,7 @@ module GraphQL
23
23
  module Builder
24
24
  extend self
25
25
 
26
- def build(document, default_resolve:, using: {}, relay:)
26
+ def build(schema_superclass, document, default_resolve:, using: {}, relay:)
27
27
  raise InvalidDocumentError.new('Must provide a document ast.') if !document || !document.is_a?(GraphQL::Language::Nodes::Document)
28
28
 
29
29
  if default_resolve.is_a?(Hash)
@@ -36,7 +36,7 @@ module GraphQL
36
36
  end
37
37
  schema_definition = schema_defns.first
38
38
  types = {}
39
- directives = {}
39
+ directives = schema_superclass.directives.dup
40
40
  type_resolver = build_resolve_type(types, directives, ->(type_name) { types[type_name] ||= Schema::LateBoundType.new(type_name)})
41
41
  # Make a different type resolver because we need to coerce directive arguments
42
42
  # _while_ building the schema.
@@ -55,21 +55,24 @@ module GraphQL
55
55
  end
56
56
  })
57
57
 
58
+ directives.merge!(GraphQL::Schema.default_directives)
58
59
  document.definitions.each do |definition|
59
60
  if definition.is_a?(GraphQL::Language::Nodes::DirectiveDefinition)
60
61
  directives[definition.name] = build_directive(definition, directive_type_resolver)
61
62
  end
62
63
  end
63
64
 
64
- directives = GraphQL::Schema.default_directives.merge(directives)
65
-
66
65
  # In case any directives referenced built-in types for their arguments:
67
66
  replace_late_bound_types_with_built_in(types)
68
67
 
68
+ schema_extensions = nil
69
69
  document.definitions.each do |definition|
70
70
  case definition
71
71
  when GraphQL::Language::Nodes::SchemaDefinition, GraphQL::Language::Nodes::DirectiveDefinition
72
72
  nil # already handled
73
+ when GraphQL::Language::Nodes::SchemaExtension
74
+ schema_extensions ||= []
75
+ schema_extensions << definition
73
76
  else
74
77
  # It's possible that this was already loaded by the directives
75
78
  prev_type = types[definition.name]
@@ -104,7 +107,7 @@ module GraphQL
104
107
 
105
108
  raise InvalidDocumentError.new('Must provide schema definition with query type or a type named Query.') unless query_root_type
106
109
 
107
- Class.new(GraphQL::Schema) do
110
+ schema_class = Class.new(schema_superclass) do
108
111
  begin
109
112
  # Add these first so that there's some chance of resolving late-bound types
110
113
  orphan_types types.values
@@ -158,6 +161,14 @@ module GraphQL
158
161
  child_class.definition_default_resolve = self.definition_default_resolve
159
162
  end
160
163
  end
164
+
165
+ if schema_extensions
166
+ schema_extensions.each do |ext|
167
+ build_directives(schema_class, ext, type_resolver)
168
+ end
169
+ end
170
+
171
+ schema_class
161
172
  end
162
173
 
163
174
  NullResolveType = ->(type, obj, ctx) {
@@ -197,13 +208,18 @@ module GraphQL
197
208
 
198
209
  def build_directives(definition, ast_node, type_resolver)
199
210
  dirs = prepare_directives(ast_node, type_resolver)
200
- dirs.each do |dir_class, options|
201
- definition.directive(dir_class, **options)
211
+ dirs.each do |(dir_class, options)|
212
+ if definition.respond_to?(:schema_directive)
213
+ # it's a schema
214
+ definition.schema_directive(dir_class, **options)
215
+ else
216
+ definition.directive(dir_class, **options)
217
+ end
202
218
  end
203
219
  end
204
220
 
205
221
  def prepare_directives(ast_node, type_resolver)
206
- dirs = {}
222
+ dirs = []
207
223
  ast_node.directives.each do |dir_node|
208
224
  if dir_node.name == "deprecated"
209
225
  # This is handled using `deprecation_reason`
@@ -211,10 +227,10 @@ module GraphQL
211
227
  else
212
228
  dir_class = type_resolver.call(dir_node.name)
213
229
  if dir_class.nil?
214
- raise ArgumentError, "No definition for @#{dir_node.name} on #{ast_node.name} at #{ast_node.line}:#{ast_node.col}"
230
+ raise ArgumentError, "No definition for @#{dir_node.name} #{ast_node.respond_to?(:name) ? "on #{ast_node.name} " : ""}at #{ast_node.line}:#{ast_node.col}"
215
231
  end
216
232
  options = args_to_kwargs(dir_class, dir_node)
217
- dirs[dir_class] = options
233
+ dirs << [dir_class, options]
218
234
  end
219
235
  end
220
236
  dirs
@@ -390,7 +406,6 @@ module GraphQL
390
406
  graphql_name(interface_type_definition.name)
391
407
  description(interface_type_definition.description)
392
408
  interface_type_definition.interfaces.each do |interface_name|
393
- "Implements: #{interface_type_definition} -> #{interface_name}"
394
409
  interface_defn = type_resolver.call(interface_name)
395
410
  implements(interface_defn)
396
411
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Directive < GraphQL::Schema::Member
5
+ class OneOf < GraphQL::Schema::Directive
6
+ description "Requires that exactly one field must be supplied and that field must not be `null`."
7
+ locations(GraphQL::Schema::Directive::INPUT_OBJECT)
8
+ default_directive true
9
+ end
10
+ end
11
+ end
12
+ end
@@ -39,7 +39,7 @@ module GraphQL
39
39
  transform_name = arguments[:by]
40
40
  if TRANSFORMS.include?(transform_name) && return_value.respond_to?(transform_name)
41
41
  return_value = return_value.public_send(transform_name)
42
- response = context.namespace(:interpreter)[:runtime].final_result
42
+ response = context.namespace(:interpreter_runtime)[:runtime].final_result
43
43
  *keys, last = path
44
44
  keys.each do |key|
45
45
  if response && (response = response[key])
@@ -129,10 +129,10 @@ module GraphQL
129
129
  def connection?
130
130
  if @connection.nil?
131
131
  # Provide default based on type name
132
- return_type_name = if @resolver_class && @resolver_class.type
133
- Member::BuildType.to_type_name(@resolver_class.type)
134
- elsif @return_type_expr
132
+ return_type_name = if @return_type_expr
135
133
  Member::BuildType.to_type_name(@return_type_expr)
134
+ elsif @resolver_class && @resolver_class.type
135
+ Member::BuildType.to_type_name(@resolver_class.type)
136
136
  else
137
137
  # As a last ditch, try to force loading the return type:
138
138
  type.unwrap.name
@@ -69,6 +69,19 @@ module GraphQL
69
69
  true
70
70
  end
71
71
 
72
+ def self.one_of
73
+ if !one_of?
74
+ if all_argument_definitions.any? { |arg| arg.type.non_null? }
75
+ raise ArgumentError, "`one_of` may not be used with required arguments -- add `required: false` to argument definitions to use `one_of`"
76
+ end
77
+ directive(GraphQL::Schema::Directive::OneOf)
78
+ end
79
+ end
80
+
81
+ def self.one_of?
82
+ directives.any? { |d| d.is_a?(GraphQL::Schema::Directive::OneOf) }
83
+ end
84
+
72
85
  def unwrap_value(value)
73
86
  case value
74
87
  when Array
@@ -109,6 +122,14 @@ module GraphQL
109
122
  class << self
110
123
  def argument(*args, **kwargs, &block)
111
124
  argument_defn = super(*args, **kwargs, &block)
125
+ if one_of?
126
+ if argument_defn.type.non_null?
127
+ raise ArgumentError, "Argument '#{argument_defn.path}' must be nullable because it is part of a OneOf type, add `required: false`."
128
+ end
129
+ if argument_defn.default_value?
130
+ raise ArgumentError, "Argument '#{argument_defn.path}' cannot have a default value because it is part of a OneOf type, remove `default_value: ...`."
131
+ end
132
+ end
112
133
  # Add a method access
113
134
  method_name = argument_defn.keyword
114
135
  class_eval <<-RUBY, __FILE__, __LINE__
@@ -166,6 +187,20 @@ module GraphQL
166
187
  end
167
188
  end
168
189
 
190
+ if one_of?
191
+ if input.size == 1
192
+ input.each do |name, value|
193
+ if value.nil?
194
+ result ||= Query::InputValidationResult.new
195
+ result.add_problem("'#{graphql_name}' requires exactly one argument, but '#{name}' was `null`.")
196
+ end
197
+ end
198
+ else
199
+ result ||= Query::InputValidationResult.new
200
+ result.add_problem("'#{graphql_name}' requires exactly one argument, but #{input.size} were provided.")
201
+ end
202
+ end
203
+
169
204
  result
170
205
  end
171
206
 
@@ -27,6 +27,10 @@ module GraphQL
27
27
  "#<LateBoundType @name=#{name}>"
28
28
  end
29
29
 
30
+ def non_null?
31
+ false
32
+ end
33
+
30
34
  alias :to_s :inspect
31
35
  end
32
36
  end
@@ -120,7 +120,7 @@ module GraphQL
120
120
  def camelize(string)
121
121
  return string if string == '_'
122
122
  return string unless string.include?("_")
123
- camelized = string.split('_').map(&:capitalize).join
123
+ camelized = string.split('_').each(&:capitalize!).join
124
124
  camelized[0] = camelized[0].downcase
125
125
  if (match_data = string.match(/\A(_+)/))
126
126
  camelized = "#{match_data[0]}#{camelized}"
@@ -11,8 +11,7 @@ module GraphQL
11
11
  # @return [void]
12
12
  def directive(dir_class, **options)
13
13
  @own_directives ||= []
14
- remove_directive(dir_class) unless dir_class.repeatable?
15
- @own_directives << dir_class.new(self, **options)
14
+ HasDirectives.add_directive(self, @own_directives, dir_class, options)
16
15
  nil
17
16
  end
18
17
 
@@ -20,78 +19,94 @@ module GraphQL
20
19
  # @param dir_class [Class<GraphQL::Schema::Directive>]
21
20
  # @return [viod]
22
21
  def remove_directive(dir_class)
23
- @own_directives && @own_directives.reject! { |d| d.is_a?(dir_class) }
22
+ HasDirectives.remove_directive(@own_directives, dir_class)
24
23
  nil
25
24
  end
26
25
 
27
26
  NO_DIRECTIVES = [].freeze
28
27
 
29
28
  def directives
30
- case self
31
- when Class
32
- inherited_directives = if superclass.respond_to?(:directives)
33
- superclass.directives
34
- else
35
- NO_DIRECTIVES
36
- end
37
- if inherited_directives.any? && @own_directives
38
- dirs = []
39
- merge_directives(dirs, inherited_directives)
40
- merge_directives(dirs, @own_directives)
41
- dirs
42
- elsif @own_directives
43
- @own_directives
44
- elsif inherited_directives.any?
45
- inherited_directives
46
- else
47
- NO_DIRECTIVES
48
- end
49
- when Module
50
- dirs = nil
51
- self.ancestors.reverse_each do |ancestor|
52
- if ancestor.respond_to?(:own_directives) &&
53
- (anc_dirs = ancestor.own_directives).any?
29
+ HasDirectives.get_directives(self, @own_directives, :directives)
30
+ end
31
+
32
+ class << self
33
+ def add_directive(schema_member, directives, directive_class, directive_options)
34
+ remove_directive(directives, directive_class) unless directive_class.repeatable?
35
+ directives << directive_class.new(schema_member, **directive_options)
36
+ end
37
+
38
+ def remove_directive(directives, directive_class)
39
+ directives && directives.reject! { |d| d.is_a?(directive_class) }
40
+ end
41
+
42
+ def get_directives(schema_member, directives, directives_method)
43
+ case schema_member
44
+ when Class
45
+ inherited_directives = if schema_member.superclass.respond_to?(directives_method)
46
+ get_directives(schema_member.superclass, schema_member.superclass.public_send(directives_method), directives_method)
47
+ else
48
+ NO_DIRECTIVES
49
+ end
50
+ if inherited_directives.any? && directives
51
+ dirs = []
52
+ merge_directives(dirs, inherited_directives)
53
+ merge_directives(dirs, directives)
54
+ dirs
55
+ elsif directives
56
+ directives
57
+ elsif inherited_directives.any?
58
+ inherited_directives
59
+ else
60
+ NO_DIRECTIVES
61
+ end
62
+ when Module
63
+ dirs = nil
64
+ schema_member.ancestors.reverse_each do |ancestor|
65
+ if ancestor.respond_to?(:own_directives) &&
66
+ (anc_dirs = ancestor.own_directives).any?
67
+ dirs ||= []
68
+ merge_directives(dirs, anc_dirs)
69
+ end
70
+ end
71
+ if directives
54
72
  dirs ||= []
55
- merge_directives(dirs, anc_dirs)
73
+ merge_directives(dirs, directives)
56
74
  end
75
+ dirs || NO_DIRECTIVES
76
+ when HasDirectives
77
+ directives || NO_DIRECTIVES
78
+ else
79
+ raise "Invariant: how could #{schema_member} not be a Class, Module, or instance of HasDirectives?"
57
80
  end
58
- if own_directives
59
- dirs ||= []
60
- merge_directives(dirs, own_directives)
81
+ end
82
+
83
+ private
84
+
85
+ # Modify `target` by adding items from `dirs` such that:
86
+ # - Any name conflict is overriden by the incoming member of `dirs`
87
+ # - Any other member of `dirs` is appended
88
+ # @param target [Array<GraphQL::Schema::Directive>]
89
+ # @param dirs [Array<GraphQL::Schema::Directive>]
90
+ # @return [void]
91
+ def merge_directives(target, dirs)
92
+ dirs.each do |dir|
93
+ if (idx = target.find_index { |d| d.graphql_name == dir.graphql_name })
94
+ target.slice!(idx)
95
+ target.insert(idx, dir)
96
+ else
97
+ target << dir
98
+ end
61
99
  end
62
- dirs || NO_DIRECTIVES
63
- when HasDirectives
64
- @own_directives || NO_DIRECTIVES
65
- else
66
- raise "Invariant: how could #{self} not be a Class, Module, or instance of HasDirectives?"
100
+ nil
67
101
  end
68
102
  end
69
103
 
104
+
70
105
  protected
71
106
 
72
107
  def own_directives
73
108
  @own_directives
74
109
  end
75
-
76
- private
77
-
78
- # Modify `target` by adding items from `dirs` such that:
79
- # - Any name conflict is overriden by the incoming member of `dirs`
80
- # - Any other member of `dirs` is appended
81
- # @param target [Array<GraphQL::Schema::Directive>]
82
- # @param dirs [Array<GraphQL::Schema::Directive>]
83
- # @return [void]
84
- def merge_directives(target, dirs)
85
- dirs.each do |dir|
86
- if (idx = target.find_index { |d| d.graphql_name == dir.graphql_name })
87
- target.slice!(idx)
88
- target.insert(idx, dir)
89
- else
90
- target << dir
91
- end
92
- end
93
- nil
94
- end
95
110
  end
96
111
  end
97
112
  end
@@ -91,7 +91,7 @@ module GraphQL
91
91
  resolver_fields = all_field_definitions
92
92
  Class.new(object_class) do
93
93
  graphql_name("#{resolver_name}Payload")
94
- description("Autogenerated return type of #{resolver_name}")
94
+ description("Autogenerated return type of #{resolver_name}.")
95
95
  resolver_fields.each do |f|
96
96
  # Reattach the already-defined field here
97
97
  # (The field's `.owner` will still point to the mutation, not the object type, I think)
@@ -11,6 +11,9 @@ module GraphQL
11
11
  # @return [Class<GraphQL::Schema::Union>, Module<GraphQL::Schema::Interface>]
12
12
  attr_reader :abstract_type
13
13
 
14
+ # @return [Hash]
15
+ attr_reader :options
16
+
14
17
  # Called when an object is hooked up to an abstract type, such as {Schema::Union.possible_types}
15
18
  # or {Schema::Object.implements} (for interfaces).
16
19
  #