graphql 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c54b2619d5d111575839ba3e5d34794d993cb37a0b9aee4d41347551bb6be846
4
- data.tar.gz: f6d53678c268767478a2a86ec34c045ab8744f4ea6f97deeebed3708df634340
3
+ metadata.gz: f37881627f4e4ddcdb6f2f83f395982d624396949b7e26c1c7fa395f34b6c37a
4
+ data.tar.gz: c4840087ffff471824df26453876279b2e44014cdf99f9778b35481c917e18fa
5
5
  SHA512:
6
- metadata.gz: c7ccf09ae0a94e0cdce47f66555681391ac29af9cb544b662e8f9cd127905cda5cf5923dc64500dcab15899c7c51d5d2458ae78b95cb40ca47b1ce792e35f52c
7
- data.tar.gz: ba6d04f4fc107fe9e2daaf70bd06288f85a6ead011764ab1b975e1796e97fa8c289cc08186bbf23c6020670a585c1a48c22d2acbd28ed10c6ec09b97f07a2a47
6
+ metadata.gz: 34999652dad1730d8cf51a46b213dc62d34a00e429b734da857aadbe5d5497c1a70c5d6ebc9d0a316db65a2e8e40075ab56d8bb59eb1cdc45b88c9549149ff92
7
+ data.tar.gz: 8ff253848a6fcdaaaa831c87377b4b9d4ca494704bba0844336dc3c500b125df5f888f076f77cdf4fae61c03b2d8cb9383e18406bbf8b6ab2d7bd399dd54bf7d
@@ -11,7 +11,9 @@ module GraphQL
11
11
  # executed sychronously.
12
12
  def run; end
13
13
  def run_isolated; yield; end
14
- def yield; end
14
+ def yield
15
+ raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
16
+ end
15
17
 
16
18
  def append_job
17
19
  yield
@@ -230,7 +230,6 @@ module GraphQL
230
230
  call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
231
231
  evaluate_selections(
232
232
  path,
233
- context.scoped_context,
234
233
  object_proxy,
235
234
  root_type,
236
235
  root_op_type == "mutation",
@@ -349,7 +348,7 @@ module GraphQL
349
348
  NO_ARGS = {}.freeze
350
349
 
351
350
  # @return [void]
352
- def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
351
+ def evaluate_selections(path, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
353
352
  set_all_interpreter_context(owner_object, nil, nil, path)
354
353
 
355
354
  finished_jobs = 0
@@ -357,7 +356,7 @@ module GraphQL
357
356
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
358
357
  @dataloader.append_job {
359
358
  evaluate_selection(
360
- path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result, parent_object
359
+ path, result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
361
360
  )
362
361
  finished_jobs += 1
363
362
  if target_result && finished_jobs == enqueued_jobs
@@ -372,7 +371,7 @@ module GraphQL
372
371
  attr_reader :progress_path
373
372
 
374
373
  # @return [void]
375
- def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
374
+ def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
376
375
  return if dead_result?(selections_result)
377
376
  # As a performance optimization, the hash key will be a `Node` if
378
377
  # there's only one selection of the field. But if there are multiple
@@ -414,8 +413,6 @@ module GraphQL
414
413
  end
415
414
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
416
415
  set_all_interpreter_context(nil, field_defn, nil, next_path)
417
-
418
- context.scoped_context = scoped_context
419
416
  object = owner_object
420
417
 
421
418
  if is_introspection
@@ -425,19 +422,18 @@ module GraphQL
425
422
  total_args_count = field_defn.arguments(context).size
426
423
  if total_args_count == 0
427
424
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
428
- evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
425
+ evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
429
426
  else
430
427
  # TODO remove all arguments(...) usages?
431
428
  @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
432
- evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
429
+ evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
433
430
  end
434
431
  end
435
432
  end
436
433
 
437
- def evaluate_selection_with_args(arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selection_result, parent_object) # rubocop:disable Metrics/ParameterLists
438
- context.scoped_context = scoped_context
434
+ def evaluate_selection_with_args(arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object) # rubocop:disable Metrics/ParameterLists
439
435
  return_type = field_defn.type
440
- after_lazy(arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
436
+ after_lazy(arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
441
437
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
442
438
  continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
443
439
  next
@@ -510,7 +506,7 @@ module GraphQL
510
506
  rescue GraphQL::ExecutionError => err
511
507
  err
512
508
  end
513
- after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
509
+ after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
514
510
  continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
515
511
  if HALT != continue_value
516
512
  continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
@@ -688,7 +684,7 @@ module GraphQL
688
684
  resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
689
685
  resolved_value ||= value
690
686
 
691
- after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type|
687
+ after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type|
692
688
  possible_types = query.possible_types(current_type)
693
689
 
694
690
  if !possible_types.include?(resolved_type)
@@ -708,7 +704,7 @@ module GraphQL
708
704
  rescue GraphQL::ExecutionError => err
709
705
  err
710
706
  end
711
- after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
707
+ after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
712
708
  continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
713
709
  if HALT != continue_value
714
710
  response_hash = GraphQLResultHash.new(result_name, selection_result)
@@ -734,7 +730,6 @@ module GraphQL
734
730
  call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
735
731
  evaluate_selections(
736
732
  path,
737
- context.scoped_context,
738
733
  continue_value,
739
734
  current_type,
740
735
  false,
@@ -757,7 +752,6 @@ module GraphQL
757
752
  set_result(selection_result, result_name, response_list)
758
753
 
759
754
  idx = 0
760
- scoped_context = context.scoped_context
761
755
  begin
762
756
  value.each do |inner_value|
763
757
  break if dead_result?(response_list)
@@ -768,10 +762,10 @@ module GraphQL
768
762
  idx += 1
769
763
  if use_dataloader_job
770
764
  @dataloader.append_job do
771
- resolve_list_item(inner_value, inner_type, next_path, ast_node, scoped_context, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
765
+ resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
772
766
  end
773
767
  else
774
- resolve_list_item(inner_value, inner_type, next_path, ast_node, scoped_context, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
768
+ resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
775
769
  end
776
770
  end
777
771
  rescue NoMethodError => err
@@ -791,11 +785,11 @@ module GraphQL
791
785
  end
792
786
  end
793
787
 
794
- def resolve_list_item(inner_value, inner_type, next_path, ast_node, scoped_context, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
788
+ def resolve_list_item(inner_value, inner_type, next_path, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
795
789
  set_all_interpreter_context(nil, nil, nil, next_path)
796
790
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
797
791
  # This will update `response_list` with the lazy
798
- after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
792
+ after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
799
793
  continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
800
794
  if HALT != continue_value
801
795
  continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
@@ -870,11 +864,10 @@ module GraphQL
870
864
  # @param eager [Boolean] Set to `true` for mutation root fields only
871
865
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
872
866
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
873
- def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
867
+ def after_lazy(lazy_obj, owner:, field:, path:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
874
868
  if lazy?(lazy_obj)
875
869
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
876
870
  set_all_interpreter_context(owner_object, field, arguments, path)
877
- context.scoped_context = scoped_context
878
871
  # Wrap the execution of _this_ method with tracing,
879
872
  # but don't wrap the continuation below
880
873
  inner_obj = begin
@@ -86,8 +86,83 @@ module GraphQL
86
86
  @errors = []
87
87
  @path = []
88
88
  @value = nil
89
- @context = self # for SharedMethods
90
- @scoped_context = {}
89
+ @context = self # for SharedMethods TODO delete sharedmethods
90
+ @scoped_context = ScopedContext.new(self)
91
+ end
92
+
93
+ class ScopedContext
94
+ def initialize(query_context)
95
+ @query_context = query_context
96
+ @path_contexts = {}
97
+ @no_path = [].freeze
98
+ end
99
+
100
+ def merged_context
101
+ merged_ctx = {}
102
+ each_present_path_ctx do |path_ctx|
103
+ merged_ctx = path_ctx.merge(merged_ctx)
104
+ end
105
+ merged_ctx
106
+ end
107
+
108
+ def merge!(hash)
109
+ current_ctx = @path_contexts[current_path] ||= {}
110
+ current_ctx.merge!(hash)
111
+ end
112
+
113
+ def current_path
114
+ @query_context.namespace(:interpreter)[:current_path] || @no_path
115
+ end
116
+
117
+ def key?(key)
118
+ each_present_path_ctx do |path_ctx|
119
+ if path_ctx.key?(key)
120
+ return true
121
+ end
122
+ end
123
+ false
124
+ end
125
+
126
+ def [](key)
127
+ each_present_path_ctx do |path_ctx|
128
+ if path_ctx.key?(key)
129
+ return path_ctx[key]
130
+ end
131
+ end
132
+ nil
133
+ end
134
+
135
+ def dig(key, *other_keys)
136
+ each_present_path_ctx do |path_ctx|
137
+ if path_ctx.key?(key)
138
+ found_value = path_ctx[key]
139
+ if other_keys.any?
140
+ return found_value.dig(*other_keys)
141
+ else
142
+ return found_value
143
+ end
144
+ end
145
+ end
146
+ nil
147
+ end
148
+
149
+ private
150
+
151
+ # Start at the current location,
152
+ # but look up the tree for previously-assigned scoped values
153
+ def each_present_path_ctx
154
+ search_path = current_path.dup
155
+ if (current_path_ctx = @path_contexts[search_path])
156
+ yield(current_path_ctx)
157
+ end
158
+
159
+ while search_path.size > 0
160
+ search_path.pop # look one level higher
161
+ if (search_path_ctx = @path_contexts[search_path])
162
+ yield(search_path_ctx)
163
+ end
164
+ end
165
+ end
91
166
  end
92
167
 
93
168
  # @return [Hash] A hash that will be added verbatim to the result hash, as `"extensions" => { ... }`
@@ -106,7 +181,7 @@ module GraphQL
106
181
  attr_writer :value
107
182
 
108
183
  # @api private
109
- attr_accessor :scoped_context
184
+ attr_reader :scoped_context
110
185
 
111
186
  def []=(key, value)
112
187
  @provided_values[key] = value
@@ -119,8 +194,11 @@ module GraphQL
119
194
 
120
195
  # Lookup `key` from the hash passed to {Schema#execute} as `context:`
121
196
  def [](key)
122
- return @scoped_context[key] if @scoped_context.key?(key)
123
- @provided_values[key]
197
+ if @scoped_context.key?(key)
198
+ @scoped_context[key]
199
+ else
200
+ @provided_values[key]
201
+ end
124
202
  end
125
203
 
126
204
  def delete(key)
@@ -135,7 +213,7 @@ module GraphQL
135
213
 
136
214
  def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT)
137
215
  if @scoped_context.key?(key)
138
- @scoped_context[key]
216
+ scoped_context[key]
139
217
  elsif @provided_values.key?(key)
140
218
  @provided_values[key]
141
219
  elsif default != UNSPECIFIED_FETCH_DEFAULT
@@ -148,12 +226,21 @@ module GraphQL
148
226
  end
149
227
 
150
228
  def dig(key, *other_keys)
151
- @scoped_context.key?(key) ? @scoped_context.dig(key, *other_keys) : @provided_values.dig(key, *other_keys)
229
+ if @scoped_context.key?(key)
230
+ @scoped_context.dig(key, *other_keys)
231
+ else
232
+ @provided_values.dig(key, *other_keys)
233
+ end
152
234
  end
153
235
 
154
236
  def to_h
155
- @provided_values.merge(@scoped_context)
237
+ if (current_scoped_context = @scoped_context.merged_context)
238
+ @provided_values.merge(current_scoped_context)
239
+ else
240
+ @provided_values
241
+ end
156
242
  end
243
+
157
244
  alias :to_hash :to_h
158
245
 
159
246
  def key?(key)
@@ -185,7 +272,7 @@ module GraphQL
185
272
  end
186
273
 
187
274
  def scoped_merge!(hash)
188
- @scoped_context = @scoped_context.merge(hash)
275
+ @scoped_context.merge!(hash)
189
276
  end
190
277
 
191
278
  def scoped_set!(key, value)
@@ -4,6 +4,12 @@ module GraphQL
4
4
  class InputValidationResult
5
5
  attr_accessor :problems
6
6
 
7
+ def self.from_problem(explanation, path = nil, extensions: nil, message: nil)
8
+ result = self.new
9
+ result.add_problem(explanation, path, extensions: extensions, message: message)
10
+ result
11
+ end
12
+
7
13
  def initialize(valid: true, problems: nil)
8
14
  @valid = valid
9
15
  @problems = problems
@@ -27,7 +33,7 @@ module GraphQL
27
33
  end
28
34
 
29
35
  def merge_result!(path, inner_result)
30
- return if inner_result.valid?
36
+ return if inner_result.nil? || inner_result.valid?
31
37
 
32
38
  if inner_result.problems
33
39
  inner_result.problems.each do |p|
@@ -38,6 +44,9 @@ module GraphQL
38
44
  # It could have been explicitly set on inner_result (if it had no problems)
39
45
  @valid = false
40
46
  end
47
+
48
+ VALID = self.new
49
+ VALID.freeze
41
50
  end
42
51
  end
43
52
  end
data/lib/graphql/query.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/query/context"
3
3
  require "graphql/query/fingerprint"
4
- require "graphql/query/literal_input"
5
4
  require "graphql/query/null_context"
6
5
  require "graphql/query/result"
7
6
  require "graphql/query/variables"
@@ -123,16 +123,14 @@ module GraphQL
123
123
  end
124
124
 
125
125
  def validate_non_null_input(value_name, ctx)
126
- result = GraphQL::Query::InputValidationResult.new
127
-
128
126
  allowed_values = ctx.warden.enum_values(self)
129
127
  matching_value = allowed_values.find { |v| v.graphql_name == value_name }
130
128
 
131
129
  if matching_value.nil?
132
- result.add_problem("Expected #{GraphQL::Language.serialize(value_name)} to be one of: #{allowed_values.map(&:graphql_name).join(', ')}")
130
+ GraphQL::Query::InputValidationResult.from_problem("Expected #{GraphQL::Language.serialize(value_name)} to be one of: #{allowed_values.map(&:graphql_name).join(', ')}")
131
+ else
132
+ nil
133
133
  end
134
-
135
- result
136
134
  end
137
135
 
138
136
  def coerce_result(value, ctx)
@@ -29,7 +29,13 @@ module GraphQL
29
29
  attr_reader :method_str
30
30
 
31
31
  # @return [Symbol] The method on the type to look up
32
- attr_reader :resolver_method
32
+ def resolver_method
33
+ if @resolver_class
34
+ @resolver_class.resolver_method
35
+ else
36
+ @resolver_method
37
+ end
38
+ end
33
39
 
34
40
  # @return [Class] The thing this field was defined on (type, mutation, resolver)
35
41
  attr_accessor :owner
@@ -68,7 +74,10 @@ module GraphQL
68
74
  attr_reader :trace
69
75
 
70
76
  # @return [String, nil]
71
- attr_accessor :subscription_scope
77
+ def subscription_scope
78
+ @subscription_scope || (@resolver_class.respond_to?(:subscription_scope) ? @resolver_class.subscription_scope : nil)
79
+ end
80
+ attr_writer :subscription_scope
72
81
 
73
82
  # Create a field instance from a list of arguments, keyword arguments, and a block.
74
83
  #
@@ -82,11 +91,9 @@ module GraphQL
82
91
  # @return [GraphQL::Schema:Field] an instance of `self
83
92
  # @see {.initialize} for other options
84
93
  def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
85
- if (parent_config = resolver || mutation || subscription)
86
- # Get the parent config, merge in local overrides
87
- kwargs = parent_config.field_options.merge(kwargs)
94
+ if (resolver_class = resolver || mutation || subscription)
88
95
  # Add a reference to that parent class
89
- kwargs[:resolver_class] = parent_config
96
+ kwargs[:resolver_class] = resolver_class
90
97
  end
91
98
 
92
99
  if name
@@ -101,8 +108,8 @@ module GraphQL
101
108
 
102
109
  kwargs[:description] = desc
103
110
  kwargs[:type] = type
104
- elsif (kwargs[:field] || kwargs[:function] || resolver || mutation) && type.is_a?(String)
105
- # The return type should be copied from `field` or `function`, and the second positional argument is the description
111
+ elsif (resolver || mutation) && type.is_a?(String)
112
+ # The return type should be copied from the resolver, and the second positional argument is the description
106
113
  kwargs[:description] = type
107
114
  else
108
115
  kwargs[:type] = type
@@ -119,8 +126,8 @@ module GraphQL
119
126
  def connection?
120
127
  if @connection.nil?
121
128
  # Provide default based on type name
122
- return_type_name = if (contains_type = @field || @function)
123
- Member::BuildType.to_type_name(contains_type.type)
129
+ return_type_name = if @resolver_class && @resolver_class.type
130
+ Member::BuildType.to_type_name(@resolver_class.type)
124
131
  elsif @return_type_expr
125
132
  Member::BuildType.to_type_name(@return_type_expr)
126
133
  else
@@ -181,9 +188,6 @@ module GraphQL
181
188
  # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
182
189
  # @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
183
190
  # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
184
- # @param resolve [<#call(obj, args, ctx)>] **deprecated** for compatibility with <1.8.0
185
- # @param field [GraphQL::Field, GraphQL::Schema::Field] **deprecated** for compatibility with <1.8.0
186
- # @param function [GraphQL::Function] **deprecated** for compatibility with <1.8.0
187
191
  # @param resolver_class [Class] (Private) A {Schema::Resolver} which this field was derived from. Use `resolver:` to create a field with a resolver.
188
192
  # @param arguments [{String=>GraphQL::Schema::Argument, Hash}] Arguments for this field (may be added in the block, also)
189
193
  # @param camelize [Boolean] If true, the field name will be camelized when building the schema
@@ -197,30 +201,20 @@ module GraphQL
197
201
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
198
202
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
199
203
  # @param validates [Array<Hash>] Configurations for validating this field
200
- def initialize(type: nil, name: nil, owner: nil, null: true, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, 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: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, &definition_block)
204
+ def initialize(type: nil, name: nil, owner: nil, null: true, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: :not_given, 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: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, &definition_block)
201
205
  if name.nil?
202
206
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
203
207
  end
204
- if !(field || function || resolver_class)
208
+ if !(resolver_class)
205
209
  if type.nil?
206
210
  raise ArgumentError, "missing second `type` argument or keyword `type:`"
207
211
  end
208
212
  end
209
- if (field || function || resolve) && extras.any?
210
- raise ArgumentError, "keyword `extras:` may only be used with method-based resolve and class-based field such as mutation class, please remove `field:`, `function:` or `resolve:`"
211
- end
212
213
  @original_name = name
213
214
  name_s = -name.to_s
214
215
  @underscored_name = -Member::BuildType.underscore(name_s)
215
216
  @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
216
217
  @description = description
217
- if field.is_a?(GraphQL::Schema::Field)
218
- raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field."
219
- else
220
- @field = field
221
- end
222
- @function = function
223
- @resolve = resolve
224
218
  self.deprecation_reason = deprecation_reason
225
219
 
226
220
  if method && hash_key && dig
@@ -253,7 +247,9 @@ module GraphQL
253
247
  @max_page_size = max_page_size == :not_given ? nil : max_page_size
254
248
  @introspection = introspection
255
249
  @extras = extras
256
- @broadcastable = broadcastable
250
+ if !broadcastable.nil?
251
+ @broadcastable = broadcastable
252
+ end
257
253
  @resolver_class = resolver_class
258
254
  @scope = scope
259
255
  @trace = trace
@@ -297,6 +293,10 @@ module GraphQL
297
293
  self.extensions(extensions)
298
294
  end
299
295
 
296
+ if resolver_class && resolver_class.extensions.any?
297
+ self.extensions(resolver_class.extensions)
298
+ end
299
+
300
300
  if directives.any?
301
301
  directives.each do |(dir_class, options)|
302
302
  self.directive(dir_class, **options)
@@ -321,7 +321,13 @@ module GraphQL
321
321
  # @return [Boolean, nil]
322
322
  # @see GraphQL::Subscriptions::BroadcastAnalyzer
323
323
  def broadcastable?
324
- @broadcastable
324
+ if defined?(@broadcastable)
325
+ @broadcastable
326
+ elsif @resolver_class
327
+ @resolver_class.broadcastable?
328
+ else
329
+ nil
330
+ end
325
331
  end
326
332
 
327
333
  # @param text [String]
@@ -329,6 +335,8 @@ module GraphQL
329
335
  def description(text = nil)
330
336
  if text
331
337
  @description = text
338
+ elsif @resolver_class
339
+ @description || @resolver_class.description
332
340
  else
333
341
  @description
334
342
  end
@@ -394,7 +402,12 @@ module GraphQL
394
402
  def extras(new_extras = nil)
395
403
  if new_extras.nil?
396
404
  # Read the value
397
- @extras
405
+ field_extras = @extras
406
+ if @resolver_class && @resolver_class.extras.any?
407
+ field_extras + @resolver_class.extras
408
+ else
409
+ field_extras
410
+ end
398
411
  else
399
412
  if @extras.frozen?
400
413
  @extras = @extras.dup
@@ -477,7 +490,11 @@ module GraphQL
477
490
  when Numeric
478
491
  @complexity = new_complexity
479
492
  when nil
480
- @complexity
493
+ if @resolver_class
494
+ @complexity || @resolver_class.complexity || 1
495
+ else
496
+ @complexity || 1
497
+ end
481
498
  else
482
499
  raise("Invalid complexity: #{new_complexity.inspect} on #{@name}")
483
500
  end
@@ -485,29 +502,31 @@ module GraphQL
485
502
 
486
503
  # @return [Boolean] True if this field's {#max_page_size} should override the schema default.
487
504
  def has_max_page_size?
488
- @has_max_page_size
505
+ @has_max_page_size || (@resolver_class && @resolver_class.has_max_page_size?)
489
506
  end
490
507
 
491
508
  # @return [Integer, nil] Applied to connections if {#has_max_page_size?}
492
- attr_reader :max_page_size
509
+ def max_page_size
510
+ @max_page_size || (@resolver_class && @resolver_class.max_page_size)
511
+ end
493
512
 
494
513
  class MissingReturnTypeError < GraphQL::Error; end
495
514
  attr_writer :type
496
515
 
497
516
  def type
498
- @type ||= if @function
499
- Member::BuildType.parse_type(@function.type, null: false)
500
- elsif @field
501
- Member::BuildType.parse_type(@field.type, null: false)
502
- elsif @return_type_expr.nil?
503
- # Not enough info to determine type
504
- message = "Can't determine the return type for #{self.path}"
505
- if @resolver_class
506
- message += " (it has `resolver: #{@resolver_class}`, consider configuration a `type ...` for that class)"
507
- end
508
- raise MissingReturnTypeError, message
517
+ if @resolver_class && (t = @resolver_class.type)
518
+ t
509
519
  else
510
- Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
520
+ @type ||= if @return_type_expr.nil?
521
+ # Not enough info to determine type
522
+ message = "Can't determine the return type for #{self.path}"
523
+ if @resolver_class
524
+ message += " (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)"
525
+ end
526
+ raise MissingReturnTypeError, message
527
+ else
528
+ Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
529
+ end
511
530
  end
512
531
  rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
513
532
  # Let this propagate up
@@ -637,14 +656,14 @@ module GraphQL
637
656
  # - A method on the wrapped object;
638
657
  # - Or, raise not implemented.
639
658
  #
640
- if obj.respond_to?(@resolver_method)
641
- method_to_call = @resolver_method
659
+ if obj.respond_to?(resolver_method)
660
+ method_to_call = resolver_method
642
661
  method_receiver = obj
643
662
  # Call the method with kwargs, if there are any
644
663
  if ruby_kwargs.any?
645
- obj.public_send(@resolver_method, **ruby_kwargs)
664
+ obj.public_send(resolver_method, **ruby_kwargs)
646
665
  else
647
- obj.public_send(@resolver_method)
666
+ obj.public_send(resolver_method)
648
667
  end
649
668
  elsif obj.object.is_a?(Hash)
650
669
  inner_object = obj.object
@@ -667,7 +686,7 @@ module GraphQL
667
686
  raise <<-ERR
668
687
  Failed to implement #{@owner.graphql_name}.#{@name}, tried:
669
688
 
670
- - `#{obj.class}##{@resolver_method}`, which did not exist
689
+ - `#{obj.class}##{resolver_method}`, which did not exist
671
690
  - `#{obj.object.class}##{@method_sym}`, which did not exist
672
691
  - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
673
692
 
@@ -127,19 +127,15 @@ module GraphQL
127
127
  INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object responding to `to_h` or `to_unsafe_h`."
128
128
 
129
129
  def validate_non_null_input(input, ctx)
130
- result = GraphQL::Query::InputValidationResult.new
131
-
132
130
  warden = ctx.warden
133
131
 
134
132
  if input.is_a?(Array)
135
- result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
136
- return result
133
+ return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
137
134
  end
138
135
 
139
136
  if !(input.respond_to?(:to_h) || input.respond_to?(:to_unsafe_h))
140
137
  # We're not sure it'll act like a hash, so reject it:
141
- result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
142
- return result
138
+ return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
143
139
  end
144
140
 
145
141
  # Inject missing required arguments
@@ -151,18 +147,22 @@ module GraphQL
151
147
  m
152
148
  end
153
149
 
154
-
150
+ result = nil
155
151
  [input, missing_required_inputs].each do |args_to_validate|
156
152
  args_to_validate.each do |argument_name, value|
157
153
  argument = warden.get_argument(self, argument_name)
158
154
  # Items in the input that are unexpected
159
- unless argument
155
+ if argument.nil?
156
+ result ||= Query::InputValidationResult.new
160
157
  result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
161
- next
158
+ else
159
+ # Items in the input that are expected, but have invalid values
160
+ argument_result = argument.type.validate_input(value, ctx)
161
+ result ||= Query::InputValidationResult.new
162
+ if !argument_result.valid?
163
+ result.merge_result!(argument_name, argument_result)
164
+ end
162
165
  end
163
- # Items in the input that are expected, but have invalid values
164
- argument_result = argument.type.validate_input(value, ctx)
165
- result.merge_result!(argument_name, argument_result) unless argument_result.valid?
166
166
  end
167
167
  end
168
168
 
@@ -46,10 +46,11 @@ module GraphQL
46
46
  end
47
47
 
48
48
  def validate_non_null_input(value, ctx)
49
- result = GraphQL::Query::InputValidationResult.new
49
+ result = nil
50
50
  ensure_array(value).each_with_index do |item, index|
51
51
  item_result = of_type.validate_input(item, ctx)
52
52
  if !item_result.valid?
53
+ result ||= GraphQL::Query::InputValidationResult.new
53
54
  result.merge_result!(index, item_result)
54
55
  end
55
56
  end
@@ -78,23 +78,44 @@ module GraphQL
78
78
  # @return [GraphQL::Schema::Argument]
79
79
  def add_argument(arg_defn)
80
80
  @own_arguments ||= {}
81
- prev_defn = own_arguments[arg_defn.name]
81
+ prev_defn = @own_arguments[arg_defn.name]
82
82
  case prev_defn
83
83
  when nil
84
- own_arguments[arg_defn.name] = arg_defn
84
+ @own_arguments[arg_defn.name] = arg_defn
85
85
  when Array
86
86
  prev_defn << arg_defn
87
87
  when GraphQL::Schema::Argument
88
- own_arguments[arg_defn.name] = [prev_defn, arg_defn]
88
+ @own_arguments[arg_defn.name] = [prev_defn, arg_defn]
89
89
  else
90
90
  raise "Invariant: unexpected `@own_arguments[#{arg_defn.name.inspect}]`: #{prev_defn.inspect}"
91
91
  end
92
92
  arg_defn
93
93
  end
94
94
 
95
+ def remove_argument(arg_defn)
96
+ prev_defn = @own_arguments[arg_defn.name]
97
+ case prev_defn
98
+ when nil
99
+ # done
100
+ when Array
101
+ prev_defn.delete(arg_defn)
102
+ when GraphQL::Schema::Argument
103
+ @own_arguments.delete(arg_defn.name)
104
+ else
105
+ raise "Invariant: unexpected `@own_arguments[#{arg_defn.name.inspect}]`: #{prev_defn.inspect}"
106
+ end
107
+ nil
108
+ end
109
+
95
110
  # @return [Hash<String => GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions
96
111
  def arguments(context = GraphQL::Query::NullContext)
97
- inherited_arguments = ((self.is_a?(Class) && superclass.respond_to?(:arguments)) ? superclass.arguments(context) : nil)
112
+ inherited_arguments = if self.is_a?(Class) && superclass.respond_to?(:arguments)
113
+ superclass.arguments(context)
114
+ elsif defined?(@resolver_class) && @resolver_class
115
+ @resolver_class.field_arguments(context)
116
+ else
117
+ nil
118
+ end
98
119
  # Local definitions override inherited ones
99
120
  if own_arguments.any?
100
121
  own_arguments_that_apply = {}
@@ -125,6 +146,10 @@ module GraphQL
125
146
  all_defns.merge!(ancestor.own_arguments)
126
147
  end
127
148
  end
149
+ elsif defined?(@resolver_class) && @resolver_class
150
+ all_defns = {}
151
+ all_defns.merge!(@resolver_class.own_field_arguments)
152
+ all_defns.merge!(own_arguments)
128
153
  else
129
154
  all_defns = own_arguments
130
155
  end
@@ -137,8 +162,13 @@ module GraphQL
137
162
  def get_argument(argument_name, context = GraphQL::Query::NullContext)
138
163
  warden = Warden.from_context(context)
139
164
  if !self.is_a?(Class)
140
- a = own_arguments[argument_name]
141
- a && Warden.visible_entry?(:visible_argument?, a, context, warden)
165
+ if (arg_config = own_arguments[argument_name]) && (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))
166
+ visible_arg
167
+ elsif defined?(@resolver_class) && @resolver_class
168
+ @resolver_class.get_field_argument(argument_name, context)
169
+ else
170
+ nil
171
+ end
142
172
  else
143
173
  for ancestor in ancestors
144
174
  if ancestor.respond_to?(:own_arguments) &&
@@ -10,9 +10,9 @@ module GraphQL
10
10
 
11
11
  def validate_input(val, ctx)
12
12
  if val.nil?
13
- GraphQL::Query::InputValidationResult.new
13
+ Query::InputValidationResult::VALID
14
14
  else
15
- validate_non_null_input(val, ctx)
15
+ validate_non_null_input(val, ctx) || Query::InputValidationResult::VALID
16
16
  end
17
17
  end
18
18
 
@@ -70,11 +70,32 @@ module GraphQL
70
70
  end
71
71
 
72
72
  class << self
73
+ def dummy
74
+ @dummy ||= begin
75
+ d = Class.new(GraphQL::Schema::Resolver)
76
+ d.argument_class(self.argument_class)
77
+ # TODO make this lazier?
78
+ d.argument(:input, input_type, description: "Parameters for #{self.graphql_name}")
79
+ d
80
+ end
81
+ end
82
+
83
+ def field_arguments(context = GraphQL::Query::NullContext)
84
+ dummy.arguments(context)
85
+ end
86
+
87
+ def get_field_argument(name, context = GraphQL::Query::NullContext)
88
+ dummy.get_argument(name, context)
89
+ end
90
+
91
+ def own_field_arguments
92
+ dummy.own_arguments
93
+ end
73
94
 
74
95
  # Also apply this argument to the input type:
75
- def argument(*args, **kwargs, &block)
96
+ def argument(*args, own_argument: false, **kwargs, &block)
76
97
  it = input_type # make sure any inherited arguments are already added to it
77
- arg = super
98
+ arg = super(*args, **kwargs, &block)
78
99
 
79
100
  # This definition might be overriding something inherited;
80
101
  # if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions
@@ -114,15 +135,6 @@ module GraphQL
114
135
  @input_type ||= generate_input_type
115
136
  end
116
137
 
117
- # Extend {Schema::Mutation.field_options} to add the `input` argument
118
- def field_options
119
- sig = super
120
- # Arguments were added at the root, but they should be nested
121
- sig[:arguments].clear
122
- sig[:arguments][:input] = { type: input_type, required: true, description: "Parameters for #{graphql_name}" }
123
- sig
124
- end
125
-
126
138
  private
127
139
 
128
140
  # Generate the input type for the `input:` argument
@@ -130,11 +142,17 @@ module GraphQL
130
142
  # @return [Class] a subclass of {.input_object_class}
131
143
  def generate_input_type
132
144
  mutation_args = all_argument_definitions
133
- mutation_name = graphql_name
134
145
  mutation_class = self
135
146
  Class.new(input_object_class) do
136
- graphql_name("#{mutation_name}Input")
137
- description("Autogenerated input type of #{mutation_name}")
147
+ class << self
148
+ def default_graphql_name
149
+ "#{self.mutation.graphql_name}Input"
150
+ end
151
+
152
+ def description(new_desc = nil)
153
+ super || "Autogenerated input type of #{self.mutation.graphql_name}"
154
+ end
155
+ end
138
156
  mutation(mutation_class)
139
157
  # these might be inherited:
140
158
  mutation_args.each do |arg|
@@ -20,7 +20,7 @@ module GraphQL
20
20
  @payload_type ||= generate_payload_type
21
21
  end
22
22
 
23
- alias :type :payload_type
23
+ # alias :type :payload_type
24
24
  alias :type_expr :payload_type
25
25
 
26
26
  def field_class(new_class = nil)
@@ -15,8 +15,6 @@ module GraphQL
15
15
  #
16
16
  # A resolver's configuration may be overridden with other keywords in the `field(...)` call.
17
17
  #
18
- # See the {.field_options} to see how a Resolver becomes a set of field configuration options.
19
- #
20
18
  # @see {GraphQL::Schema::Mutation} for a concrete subclass of `Resolver`.
21
19
  # @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function`
22
20
  class Resolver
@@ -210,6 +208,18 @@ module GraphQL
210
208
  end
211
209
 
212
210
  class << self
211
+ def field_arguments(context = GraphQL::Query::NullContext)
212
+ arguments(context)
213
+ end
214
+
215
+ def get_field_argument(name, context = GraphQL::Query::NullContext)
216
+ get_argument(name, context)
217
+ end
218
+
219
+ def own_field_arguments
220
+ own_arguments
221
+ end
222
+
213
223
  # Default `:resolve` set below.
214
224
  # @return [Symbol] The method to call on instances of this object to resolve the field
215
225
  def resolve_method(new_method = nil)
@@ -242,6 +252,14 @@ module GraphQL
242
252
  @null.nil? ? (superclass.respond_to?(:null) ? superclass.null : true) : @null
243
253
  end
244
254
 
255
+ def resolver_method(new_method_name = nil)
256
+ if new_method_name
257
+ @resolver_method = new_method_name
258
+ else
259
+ @resolver_method || :resolve_with_support
260
+ end
261
+ end
262
+
245
263
  # Call this method to get the return type of the field,
246
264
  # or use it as a configuration method to assign a return type
247
265
  # instead of generating one.
@@ -257,8 +275,8 @@ module GraphQL
257
275
  @type_expr = new_type
258
276
  @null = null
259
277
  else
260
- if @type_expr
261
- GraphQL::Schema::Member::BuildType.parse_type(@type_expr, null: @null)
278
+ if type_expr
279
+ GraphQL::Schema::Member::BuildType.parse_type(type_expr, null: self.null)
262
280
  elsif superclass.respond_to?(:type)
263
281
  superclass.type
264
282
  else
@@ -307,47 +325,7 @@ module GraphQL
307
325
 
308
326
  # @return [Boolean] `true` if this resolver or a superclass has an assigned `max_page_size`
309
327
  def has_max_page_size?
310
- defined?(@max_page_size) || (superclass.respond_to?(:has_max_page_size?) && superclass.has_max_page_size?)
311
- end
312
-
313
- def field_options
314
-
315
- all_args = {}
316
- all_argument_definitions.each do |arg|
317
- if (prev_entry = all_args[arg.graphql_name])
318
- if prev_entry.is_a?(Array)
319
- prev_entry << arg
320
- else
321
- all_args[arg.graphql_name] = [prev_entry, arg]
322
- end
323
- else
324
- all_args[arg.graphql_name] = arg
325
- end
326
- end
327
-
328
- field_opts = {
329
- type: type_expr,
330
- description: description,
331
- extras: extras,
332
- resolver_method: :resolve_with_support,
333
- resolver_class: self,
334
- arguments: all_args,
335
- null: null,
336
- complexity: complexity,
337
- broadcastable: broadcastable?,
338
- }
339
-
340
- # If there aren't any, then the returned array is `[].freeze`,
341
- # but passing that along breaks some user code.
342
- if (exts = extensions).any?
343
- field_opts[:extensions] = exts
344
- end
345
-
346
- if has_max_page_size?
347
- field_opts[:max_page_size] = max_page_size
348
- end
349
-
350
- field_opts
328
+ (!!defined?(@max_page_size)) || (superclass.respond_to?(:has_max_page_size?) && superclass.has_max_page_size?)
351
329
  end
352
330
 
353
331
  # A non-normalized type configuration, without `null` applied
@@ -41,7 +41,6 @@ module GraphQL
41
41
  end
42
42
 
43
43
  def validate_non_null_input(value, ctx)
44
- result = Query::InputValidationResult.new
45
44
  coerced_result = begin
46
45
  ctx.query.with_error_handling do
47
46
  coerce_input(value, ctx)
@@ -56,11 +55,12 @@ module GraphQL
56
55
  else
57
56
  " #{GraphQL::Language.serialize(value)}"
58
57
  end
59
- result.add_problem("Could not coerce value#{str_value} to #{graphql_name}")
58
+ Query::InputValidationResult.from_problem("Could not coerce value#{str_value} to #{graphql_name}")
60
59
  elsif coerced_result.is_a?(GraphQL::CoercionError)
61
- result.add_problem(coerced_result.message, message: coerced_result.message, extensions: coerced_result.extensions)
60
+ Query::InputValidationResult.from_problem(coerced_result.message, message: coerced_result.message, extensions: coerced_result.extensions)
61
+ else
62
+ nil
62
63
  end
63
- result
64
64
  end
65
65
  end
66
66
  end
@@ -143,13 +143,6 @@ module GraphQL
143
143
  def self.topic_for(arguments:, field:, scope:)
144
144
  Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments])
145
145
  end
146
-
147
- # Overriding Resolver#field_options to include subscription_scope
148
- def self.field_options
149
- super.merge(
150
- subscription_scope: subscription_scope
151
- )
152
- end
153
146
  end
154
147
  end
155
148
  end
@@ -10,7 +10,7 @@ module GraphQL
10
10
  # should go through a warden. If you access the schema directly,
11
11
  # you may show a client something that it shouldn't be allowed to see.
12
12
  #
13
- # @example Hidding private fields
13
+ # @example Hiding private fields
14
14
  # private_members = -> (member, ctx) { member.metadata[:private] }
15
15
  # result = Schema.execute(query_string, except: private_members)
16
16
  #
@@ -56,17 +56,24 @@ module GraphQL
56
56
  # @param args [Hash<String, Symbol => Object]
57
57
  # @param object [Object]
58
58
  # @param scope [Symbol, String]
59
+ # @param context [Hash]
59
60
  # @return [void]
60
- def trigger(event_name, args, object, scope: nil)
61
+ def trigger(event_name, args, object, scope: nil, context: {})
62
+ # Make something as context-like as possible, even though there isn't a current query:
63
+ context = @schema.context_class.new(
64
+ query: GraphQL::Query.new(@schema, "", validate: false),
65
+ object: nil,
66
+ values: context
67
+ )
61
68
  event_name = event_name.to_s
62
69
 
63
70
  # Try with the verbatim input first:
64
- field = @schema.get_field(@schema.subscription, event_name)
71
+ field = @schema.get_field(@schema.subscription, event_name, context)
65
72
 
66
73
  if field.nil?
67
74
  # And if it wasn't found, normalize it:
68
75
  normalized_event_name = normalize_name(event_name)
69
- field = @schema.get_field(@schema.subscription, normalized_event_name)
76
+ field = @schema.get_field(@schema.subscription, normalized_event_name, context)
70
77
  if field.nil?
71
78
  raise InvalidTriggerError, "No subscription matching trigger: #{event_name} (looked for #{@schema.subscription.graphql_name}.#{normalized_event_name})"
72
79
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.0.0"
3
+ VERSION = "2.0.1"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-09 00:00:00.000000000 Z
11
+ date: 2022-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -366,7 +366,6 @@ files:
366
366
  - lib/graphql/query/context.rb
367
367
  - lib/graphql/query/fingerprint.rb
368
368
  - lib/graphql/query/input_validation_result.rb
369
- - lib/graphql/query/literal_input.rb
370
369
  - lib/graphql/query/null_context.rb
371
370
  - lib/graphql/query/result.rb
372
371
  - lib/graphql/query/validation_pipeline.rb
@@ -1,131 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- class Query
4
- # Turn query string values into something useful for query execution
5
- class LiteralInput
6
- def self.coerce(type, ast_node, variables)
7
- case ast_node
8
- when nil
9
- nil
10
- when Language::Nodes::NullValue
11
- nil
12
- when Language::Nodes::VariableIdentifier
13
- variables[ast_node.name]
14
- else
15
- case type.kind.name
16
- when "SCALAR"
17
- # TODO smell
18
- # This gets used for plain values during subscriber.trigger
19
- if variables
20
- type.coerce_input(ast_node, variables.context)
21
- else
22
- type.coerce_isolated_input(ast_node)
23
- end
24
- when "ENUM"
25
- # TODO smell
26
- # This gets used for plain values sometimes
27
- v = ast_node.is_a?(GraphQL::Language::Nodes::Enum) ? ast_node.name : ast_node
28
- if variables
29
- type.coerce_input(v, variables.context)
30
- else
31
- type.coerce_isolated_input(v)
32
- end
33
- when "NON_NULL"
34
- LiteralInput.coerce(type.of_type, ast_node, variables)
35
- when "LIST"
36
- if ast_node.is_a?(Array)
37
- ast_node.map { |element_ast| LiteralInput.coerce(type.of_type, element_ast, variables) }
38
- else
39
- [LiteralInput.coerce(type.of_type, ast_node, variables)]
40
- end
41
- when "INPUT_OBJECT"
42
- # TODO smell: handling AST vs handling plain Ruby
43
- next_args = ast_node.is_a?(Hash) ? ast_node : ast_node.arguments
44
- from_arguments(next_args, type, variables)
45
- else
46
- raise "Invariant: unexpected type to coerce to: #{type}"
47
- end
48
- end
49
- end
50
-
51
- def self.from_arguments(ast_arguments, argument_owner, variables)
52
- context = variables ? variables.context : nil
53
- values_hash = {}
54
- defaults_used = Set.new
55
-
56
- indexed_arguments = case ast_arguments
57
- when Hash
58
- ast_arguments
59
- when Array
60
- ast_arguments.each_with_object({}) { |a, memo| memo[a.name] = a }
61
- else
62
- raise ArgumentError, "Unexpected ast_arguments: #{ast_arguments}"
63
- end
64
-
65
- argument_defns = argument_owner.arguments(context || GraphQL::Query::NullContext)
66
- argument_defns.each do |arg_name, arg_defn|
67
- ast_arg = indexed_arguments[arg_name]
68
- # First, check the argument in the AST.
69
- # If the value is a variable,
70
- # only add a value if the variable is actually present.
71
- # Otherwise, coerce the value in the AST, prepare the value and add it.
72
- #
73
- # TODO: since indexed_arguments can come from a plain Ruby hash,
74
- # have to check for `false` or `nil` as hash values. This is getting smelly :S
75
- if indexed_arguments.key?(arg_name)
76
- arg_value = ast_arg.is_a?(GraphQL::Language::Nodes::Argument) ? ast_arg.value : ast_arg
77
-
78
- value_is_a_variable = arg_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
79
-
80
- if (!value_is_a_variable || (value_is_a_variable && variables.key?(arg_value.name)))
81
-
82
- value = coerce(arg_defn.type, arg_value, variables)
83
- # Legacy `prepare` application
84
- if arg_defn.is_a?(GraphQL::Argument)
85
- value = arg_defn.prepare(value, context)
86
- end
87
-
88
- if value.is_a?(GraphQL::ExecutionError)
89
- value.ast_node = ast_arg
90
- raise value
91
- end
92
-
93
- values_hash[arg_name] = value
94
- end
95
- end
96
-
97
- # Then, the definition for a default value.
98
- # If the definition has a default value and
99
- # a value wasn't provided from the AST,
100
- # then add the default value.
101
- if arg_defn.default_value? && !values_hash.key?(arg_name)
102
- value = arg_defn.default_value
103
- defaults_used << arg_name
104
- # `context` isn't present when pre-calculating defaults
105
- if context
106
- if arg_defn.is_a?(GraphQL::Argument)
107
- value = arg_defn.prepare(value, context)
108
- end
109
- if value.is_a?(GraphQL::ExecutionError)
110
- value.ast_node = ast_arg
111
- raise value
112
- end
113
- end
114
- values_hash[arg_name] = value
115
- end
116
- end
117
-
118
- # A Schema::InputObject, Schema::GraphQL::Field, Schema::Directive, logic from Interpreter::Arguments
119
- ruby_kwargs = {}
120
- values_hash.each do |key, value|
121
- ruby_kwargs[Schema::Member::BuildType.underscore(key).to_sym] = value
122
- end
123
- if argument_owner.is_a?(Class) && argument_owner < GraphQL::Schema::InputObject
124
- argument_owner.new(ruby_kwargs: ruby_kwargs, context: context, defaults_used: defaults_used)
125
- else
126
- ruby_kwargs
127
- end
128
- end
129
- end
130
- end
131
- end