graphql 2.0.13 → 2.0.15

Sign up to get free protection for your applications and to get access to all the features.
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
  #