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 +4 -4
- data/lib/graphql/dataloader/null_dataloader.rb +3 -1
- data/lib/graphql/execution/interpreter/runtime.rb +15 -22
- data/lib/graphql/query/context.rb +96 -9
- data/lib/graphql/query/input_validation_result.rb +10 -1
- data/lib/graphql/query.rb +0 -1
- data/lib/graphql/schema/enum.rb +3 -5
- data/lib/graphql/schema/field.rb +67 -48
- data/lib/graphql/schema/input_object.rb +12 -12
- data/lib/graphql/schema/list.rb +2 -1
- data/lib/graphql/schema/member/has_arguments.rb +36 -6
- data/lib/graphql/schema/member/validates_input.rb +2 -2
- data/lib/graphql/schema/relay_classic_mutation.rb +32 -14
- data/lib/graphql/schema/resolver/has_payload_type.rb +1 -1
- data/lib/graphql/schema/resolver.rb +23 -45
- data/lib/graphql/schema/scalar.rb +4 -4
- data/lib/graphql/schema/subscription.rb +0 -7
- data/lib/graphql/schema/warden.rb +1 -1
- data/lib/graphql/subscriptions.rb +10 -3
- data/lib/graphql/version.rb +1 -1
- metadata +2 -3
- data/lib/graphql/query/literal_input.rb +0 -131
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f37881627f4e4ddcdb6f2f83f395982d624396949b7e26c1c7fa395f34b6c37a
|
4
|
+
data.tar.gz: c4840087ffff471824df26453876279b2e44014cdf99f9778b35481c917e18fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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:,
|
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
|
-
|
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
|
-
|
123
|
-
|
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
|
-
|
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)
|
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
|
-
|
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
|
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
data/lib/graphql/schema/enum.rb
CHANGED
@@ -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
|
-
|
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)
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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 (
|
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] =
|
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 (
|
105
|
-
# The return type should be copied from
|
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
|
123
|
-
Member::BuildType.to_type_name(
|
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,
|
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 !(
|
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
|
-
|
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
|
-
@
|
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
|
-
|
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
|
-
@
|
499
|
-
|
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
|
-
|
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?(
|
641
|
-
method_to_call =
|
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(
|
664
|
+
obj.public_send(resolver_method, **ruby_kwargs)
|
646
665
|
else
|
647
|
-
obj.public_send(
|
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}##{
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
|
data/lib/graphql/schema/list.rb
CHANGED
@@ -46,10 +46,11 @@ module GraphQL
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def validate_non_null_input(value, ctx)
|
49
|
-
result =
|
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 =
|
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
|
-
|
141
|
-
|
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
|
-
|
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
|
-
|
137
|
-
|
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|
|
@@ -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
|
261
|
-
GraphQL::Schema::Member::BuildType.parse_type(
|
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
|
-
|
58
|
+
Query::InputValidationResult.from_problem("Could not coerce value#{str_value} to #{graphql_name}")
|
60
59
|
elsif coerced_result.is_a?(GraphQL::CoercionError)
|
61
|
-
|
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
|
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
|
data/lib/graphql/version.rb
CHANGED
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.
|
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-
|
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
|