graphql 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- 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
|