graphql 2.0.11 → 2.0.14
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.
- checksums.yaml +4 -4
- data/lib/generators/graphql/templates/schema.erb +3 -0
- data/lib/graphql/dataloader/source.rb +9 -0
- data/lib/graphql/execution/interpreter/runtime.rb +10 -8
- data/lib/graphql/execution/interpreter.rb +185 -59
- data/lib/graphql/execution/lookahead.rb +26 -26
- data/lib/graphql/execution/multiplex.rb +1 -116
- data/lib/graphql/execution.rb +0 -1
- data/lib/graphql/introspection/type_type.rb +7 -0
- data/lib/graphql/introspection.rb +2 -1
- data/lib/graphql/language/printer.rb +8 -5
- data/lib/graphql/query/validation_pipeline.rb +4 -0
- data/lib/graphql/query/variable_validation_error.rb +2 -2
- data/lib/graphql/query/variables.rb +13 -3
- data/lib/graphql/query.rb +11 -2
- data/lib/graphql/rake_task/validate.rb +1 -1
- data/lib/graphql/schema/argument.rb +25 -13
- data/lib/graphql/schema/build_from_definition.rb +1 -2
- data/lib/graphql/schema/directive/one_of.rb +12 -0
- data/lib/graphql/schema/enum.rb +1 -1
- data/lib/graphql/schema/field.rb +12 -11
- data/lib/graphql/schema/input_object.rb +36 -1
- data/lib/graphql/schema/late_bound_type.rb +4 -0
- data/lib/graphql/schema/list.rb +19 -5
- data/lib/graphql/schema/member/has_arguments.rb +8 -2
- data/lib/graphql/schema/member/validates_input.rb +2 -2
- data/lib/graphql/schema/non_null.rb +2 -2
- data/lib/graphql/schema/scalar.rb +1 -1
- data/lib/graphql/schema.rb +13 -3
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/error.rb +2 -2
- data/lib/graphql/static_validation/literal_validator.rb +4 -0
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +11 -5
- data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
- data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +12 -6
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +1 -1
- data/lib/graphql/subscriptions.rb +2 -5
- data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
- data/lib/graphql/version.rb +1 -1
- metadata +6 -4
- data/lib/graphql/execution/instrumentation.rb +0 -92
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a3bc6610f88b6689ee6991a6ad3543580f9bdf1b3cb9f2f7e3f78862d76072f7
|
|
4
|
+
data.tar.gz: ce52505d6c43e330aa8e5def38719f62bdfe067274a86530c889350c64994bd0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8f3aac93aa1013c257c84521b54285719d6edb8c212a83140123902a06f3cffabc1005d2a176b723cff5677614eab7ae1617b71ce7eb403e721fcbafbe07c1e1
|
|
7
|
+
data.tar.gz: a84bf37728b5c0ff8795070fd682ac7af83e8888d47cadff5103a76ee333788c30bf78f505528fee4f868288b99dfc11d82f7c604710e13b4a7d9690eb2efe36
|
|
@@ -23,5 +23,8 @@ class <%= schema_name %> < GraphQL::Schema
|
|
|
23
23
|
# to return the correct GraphQL object type for `obj`
|
|
24
24
|
raise(GraphQL::RequiredImplementationMissingError)
|
|
25
25
|
end
|
|
26
|
+
|
|
27
|
+
# Stop validating when it encounters this many errors:
|
|
28
|
+
validate_max_errors(100)
|
|
26
29
|
end
|
|
27
30
|
<% end -%>
|
|
@@ -86,6 +86,15 @@ module GraphQL
|
|
|
86
86
|
!@pending_keys.empty?
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
+
# Add these key-value pairs to this source's cache
|
|
90
|
+
# (future loads will use these merged values).
|
|
91
|
+
# @param results [Hash<Object => Object>] key-value pairs to cache in this source
|
|
92
|
+
# @return [void]
|
|
93
|
+
def merge(results)
|
|
94
|
+
@results.merge!(results)
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
89
98
|
# Called by {GraphQL::Dataloader} to resolve and pending requests to this source.
|
|
90
99
|
# @api private
|
|
91
100
|
# @return [void]
|
|
@@ -401,8 +401,7 @@ module GraphQL
|
|
|
401
401
|
end
|
|
402
402
|
return_type = field_defn.type
|
|
403
403
|
|
|
404
|
-
next_path = path
|
|
405
|
-
next_path << result_name
|
|
404
|
+
next_path = path + [result_name]
|
|
406
405
|
next_path.freeze
|
|
407
406
|
|
|
408
407
|
# This seems janky, but we need to know
|
|
@@ -685,12 +684,16 @@ module GraphQL
|
|
|
685
684
|
set_result(selection_result, result_name, r)
|
|
686
685
|
r
|
|
687
686
|
when "UNION", "INTERFACE"
|
|
688
|
-
resolved_type_or_lazy
|
|
689
|
-
|
|
687
|
+
resolved_type_or_lazy = resolve_type(current_type, value, path)
|
|
688
|
+
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_result|
|
|
689
|
+
if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
|
|
690
|
+
resolved_type, resolved_value = resolved_type_result
|
|
691
|
+
else
|
|
692
|
+
resolved_type = resolved_type_result
|
|
693
|
+
resolved_value = value
|
|
694
|
+
end
|
|
690
695
|
|
|
691
|
-
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
696
|
possible_types = query.possible_types(current_type)
|
|
693
|
-
|
|
694
697
|
if !possible_types.include?(resolved_type)
|
|
695
698
|
parent_type = field.owner_type
|
|
696
699
|
err_class = current_type::UnresolvedTypeError
|
|
@@ -764,8 +767,7 @@ module GraphQL
|
|
|
764
767
|
set_result(selection_result, result_name, response_list)
|
|
765
768
|
result_was_set = true
|
|
766
769
|
end
|
|
767
|
-
next_path = path
|
|
768
|
-
next_path << idx
|
|
770
|
+
next_path = path + [idx]
|
|
769
771
|
this_idx = idx
|
|
770
772
|
next_path.freeze
|
|
771
773
|
idx += 1
|
|
@@ -11,76 +11,202 @@ require "graphql/execution/interpreter/handles_raw_value"
|
|
|
11
11
|
module GraphQL
|
|
12
12
|
module Execution
|
|
13
13
|
class Interpreter
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
end
|
|
14
|
+
class << self
|
|
15
|
+
# Used internally to signal that the query shouldn't be executed
|
|
16
|
+
# @api private
|
|
17
|
+
NO_OPERATION = {}.freeze
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
# @param schema [GraphQL::Schema]
|
|
20
|
+
# @param queries [Array<GraphQL::Query, Hash>]
|
|
21
|
+
# @param context [Hash]
|
|
22
|
+
# @param max_complexity [Integer, nil]
|
|
23
|
+
# @return [Array<Hash>] One result per query
|
|
24
|
+
def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
|
|
25
|
+
queries = query_options.map do |opts|
|
|
26
|
+
case opts
|
|
27
|
+
when Hash
|
|
28
|
+
GraphQL::Query.new(schema, nil, **opts)
|
|
29
|
+
when GraphQL::Query
|
|
30
|
+
opts
|
|
31
|
+
else
|
|
32
|
+
raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
|
|
37
|
+
multiplex.trace("execute_multiplex", { multiplex: multiplex }) do
|
|
38
|
+
schema = multiplex.schema
|
|
39
|
+
queries = multiplex.queries
|
|
40
|
+
query_instrumenters = schema.instrumenters[:query]
|
|
41
|
+
multiplex_instrumenters = schema.instrumenters[:multiplex]
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
# First, run multiplex instrumentation, then query instrumentation for each query
|
|
44
|
+
call_hooks(multiplex_instrumenters, multiplex, :before_multiplex, :after_multiplex) do
|
|
45
|
+
each_query_call_hooks(query_instrumenters, queries) do
|
|
46
|
+
schema = multiplex.schema
|
|
47
|
+
multiplex_analyzers = schema.multiplex_analyzers
|
|
48
|
+
queries = multiplex.queries
|
|
49
|
+
if multiplex.max_complexity
|
|
50
|
+
multiplex_analyzers += [GraphQL::Analysis::AST::MaxQueryComplexity]
|
|
51
|
+
end
|
|
40
52
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
|
|
54
|
+
begin
|
|
55
|
+
# Since this is basically the batching context,
|
|
56
|
+
# share it for a whole multiplex
|
|
57
|
+
multiplex.context[:interpreter_instance] ||= multiplex.schema.query_execution_strategy.new
|
|
58
|
+
# Do as much eager evaluation of the query as possible
|
|
59
|
+
results = []
|
|
60
|
+
queries.each_with_index do |query, idx|
|
|
61
|
+
multiplex.dataloader.append_job {
|
|
62
|
+
operation = query.selected_operation
|
|
63
|
+
result = if operation.nil? || !query.valid? || query.context.errors.any?
|
|
64
|
+
NO_OPERATION
|
|
65
|
+
else
|
|
66
|
+
begin
|
|
67
|
+
# Although queries in a multiplex _share_ an Interpreter instance,
|
|
68
|
+
# they also have another item of state, which is private to that query
|
|
69
|
+
# in particular, assign it here:
|
|
70
|
+
runtime = Runtime.new(query: query)
|
|
71
|
+
query.context.namespace(:interpreter)[:runtime] = runtime
|
|
53
72
|
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
query.trace("execute_query", {query: query}) do
|
|
74
|
+
runtime.run_eager
|
|
75
|
+
end
|
|
76
|
+
rescue GraphQL::ExecutionError => err
|
|
77
|
+
query.context.errors << err
|
|
78
|
+
NO_OPERATION
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
results[idx] = result
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
multiplex.dataloader.run
|
|
86
|
+
|
|
87
|
+
# Then, work through lazy results in a breadth-first way
|
|
88
|
+
multiplex.dataloader.append_job {
|
|
89
|
+
tracer = multiplex
|
|
90
|
+
query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
|
|
91
|
+
queries = multiplex ? multiplex.queries : [query]
|
|
92
|
+
final_values = queries.map do |query|
|
|
93
|
+
runtime = query.context.namespace(:interpreter)[:runtime]
|
|
94
|
+
# it might not be present if the query has an error
|
|
95
|
+
runtime ? runtime.final_result : nil
|
|
96
|
+
end
|
|
97
|
+
final_values.compact!
|
|
98
|
+
tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
|
|
99
|
+
Interpreter::Resolve.resolve_all(final_values, multiplex.dataloader)
|
|
100
|
+
end
|
|
101
|
+
queries.each do |query|
|
|
102
|
+
runtime = query.context.namespace(:interpreter)[:runtime]
|
|
103
|
+
if runtime
|
|
104
|
+
runtime.delete_interpreter_context(:current_path)
|
|
105
|
+
runtime.delete_interpreter_context(:current_field)
|
|
106
|
+
runtime.delete_interpreter_context(:current_object)
|
|
107
|
+
runtime.delete_interpreter_context(:current_arguments)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
}
|
|
111
|
+
multiplex.dataloader.run
|
|
112
|
+
|
|
113
|
+
# Then, find all errors and assign the result to the query object
|
|
114
|
+
results.each_with_index do |data_result, idx|
|
|
115
|
+
query = queries[idx]
|
|
116
|
+
# Assign the result so that it can be accessed in instrumentation
|
|
117
|
+
query.result_values = if data_result.equal?(NO_OPERATION)
|
|
118
|
+
if !query.valid? || query.context.errors.any?
|
|
119
|
+
# A bit weird, but `Query#static_errors` _includes_ `query.context.errors`
|
|
120
|
+
{ "errors" => query.static_errors.map(&:to_h) }
|
|
121
|
+
else
|
|
122
|
+
data_result
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
result = {
|
|
126
|
+
"data" => query.context.namespace(:interpreter)[:runtime].final_result
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if query.context.errors.any?
|
|
130
|
+
error_result = query.context.errors.map(&:to_h)
|
|
131
|
+
result["errors"] = error_result
|
|
132
|
+
end
|
|
56
133
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
134
|
+
result
|
|
135
|
+
end
|
|
136
|
+
if query.context.namespace?(:__query_result_extensions__)
|
|
137
|
+
query.result_values["extensions"] = query.context.namespace(:__query_result_extensions__)
|
|
138
|
+
end
|
|
139
|
+
# Get the Query::Result, not the Hash
|
|
140
|
+
results[idx] = query.result
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
results
|
|
144
|
+
rescue Exception
|
|
145
|
+
# TODO rescue at a higher level so it will catch errors in analysis, too
|
|
146
|
+
# Assign values here so that the query's `@executed` becomes true
|
|
147
|
+
queries.map { |q| q.result_values ||= {} }
|
|
148
|
+
raise
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
63
153
|
end
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
154
|
+
|
|
155
|
+
private
|
|
156
|
+
|
|
157
|
+
# Call the before_ hooks of each query,
|
|
158
|
+
# Then yield if no errors.
|
|
159
|
+
# `call_hooks` takes care of appropriate cleanup.
|
|
160
|
+
def each_query_call_hooks(instrumenters, queries, i = 0)
|
|
161
|
+
if i >= queries.length
|
|
162
|
+
yield
|
|
163
|
+
else
|
|
164
|
+
query = queries[i]
|
|
165
|
+
call_hooks(instrumenters, query, :before_query, :after_query) {
|
|
166
|
+
each_query_call_hooks(instrumenters, queries, i + 1) {
|
|
167
|
+
yield
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
end
|
|
69
171
|
end
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
172
|
+
|
|
173
|
+
# Call each before hook, and if they all succeed, yield.
|
|
174
|
+
# If they don't all succeed, call after_ for each one that succeeded.
|
|
175
|
+
def call_hooks(instrumenters, object, before_hook_name, after_hook_name)
|
|
176
|
+
begin
|
|
177
|
+
successful = []
|
|
178
|
+
instrumenters.each do |instrumenter|
|
|
179
|
+
instrumenter.public_send(before_hook_name, object)
|
|
180
|
+
successful << instrumenter
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# if any before hooks raise an exception, quit calling before hooks,
|
|
184
|
+
# but call the after hooks on anything that succeeded but also
|
|
185
|
+
# raise the exception that came from the before hook.
|
|
186
|
+
rescue GraphQL::ExecutionError => err
|
|
187
|
+
object.context.errors << err
|
|
188
|
+
rescue => e
|
|
189
|
+
raise call_after_hooks(successful, object, after_hook_name, e)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
begin
|
|
193
|
+
yield # Call the user code
|
|
194
|
+
ensure
|
|
195
|
+
ex = call_after_hooks(successful, object, after_hook_name, nil)
|
|
196
|
+
raise ex if ex
|
|
197
|
+
end
|
|
73
198
|
end
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
199
|
+
|
|
200
|
+
def call_after_hooks(instrumenters, object, after_hook_name, ex)
|
|
201
|
+
instrumenters.reverse_each do |instrumenter|
|
|
202
|
+
begin
|
|
203
|
+
instrumenter.public_send(after_hook_name, object)
|
|
204
|
+
rescue => e
|
|
205
|
+
ex = e
|
|
206
|
+
end
|
|
81
207
|
end
|
|
208
|
+
ex
|
|
82
209
|
end
|
|
83
|
-
nil
|
|
84
210
|
end
|
|
85
211
|
|
|
86
212
|
class ListResultFailedError < GraphQL::Error
|
|
@@ -87,16 +87,28 @@ module GraphQL
|
|
|
87
87
|
|
|
88
88
|
# Like {#selects?}, but can be used for chaining.
|
|
89
89
|
# It returns a null object (check with {#selected?})
|
|
90
|
+
# @param field_name [String, Symbol]
|
|
90
91
|
# @return [GraphQL::Execution::Lookahead]
|
|
91
92
|
def selection(field_name, selected_type: @selected_type, arguments: nil)
|
|
92
|
-
|
|
93
|
+
next_field_defn = case field_name
|
|
94
|
+
when String
|
|
95
|
+
@query.get_field(selected_type, field_name)
|
|
96
|
+
when Symbol
|
|
97
|
+
# Try to avoid the `.to_s` below, if possible
|
|
98
|
+
all_fields = @query.warden.fields(selected_type)
|
|
99
|
+
if (match_by_orig_name = all_fields.find { |f| f.original_name == field_name })
|
|
100
|
+
match_by_orig_name
|
|
101
|
+
else
|
|
102
|
+
guessed_name = Schema::Member::BuildType.camelize(field_name.to_s)
|
|
103
|
+
@query.get_field(selected_type, guessed_name)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
93
106
|
|
|
94
|
-
next_field_defn = @query.get_field(selected_type, next_field_name)
|
|
95
107
|
if next_field_defn
|
|
96
108
|
next_nodes = []
|
|
97
109
|
@ast_nodes.each do |ast_node|
|
|
98
110
|
ast_node.selections.each do |selection|
|
|
99
|
-
find_selected_nodes(selection,
|
|
111
|
+
find_selected_nodes(selection, next_field_defn, arguments: arguments, matches: next_nodes)
|
|
100
112
|
end
|
|
101
113
|
end
|
|
102
114
|
|
|
@@ -196,23 +208,6 @@ module GraphQL
|
|
|
196
208
|
|
|
197
209
|
private
|
|
198
210
|
|
|
199
|
-
# If it's a symbol, stringify and camelize it
|
|
200
|
-
def normalize_name(name)
|
|
201
|
-
if name.is_a?(Symbol)
|
|
202
|
-
Schema::Member::BuildType.camelize(name.to_s)
|
|
203
|
-
else
|
|
204
|
-
name
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def normalize_keyword(keyword)
|
|
209
|
-
if keyword.is_a?(String)
|
|
210
|
-
Schema::Member::BuildType.underscore(keyword).to_sym
|
|
211
|
-
else
|
|
212
|
-
keyword
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
|
|
216
211
|
def skipped_by_directive?(ast_selection)
|
|
217
212
|
ast_selection.directives.each do |directive|
|
|
218
213
|
dir_defn = @query.schema.directives.fetch(directive.name)
|
|
@@ -265,11 +260,11 @@ module GraphQL
|
|
|
265
260
|
|
|
266
261
|
# If a selection on `node` matches `field_name` (which is backed by `field_defn`)
|
|
267
262
|
# and matches the `arguments:` constraints, then add that node to `matches`
|
|
268
|
-
def find_selected_nodes(node,
|
|
263
|
+
def find_selected_nodes(node, field_defn, arguments:, matches:)
|
|
269
264
|
return if skipped_by_directive?(node)
|
|
270
265
|
case node
|
|
271
266
|
when GraphQL::Language::Nodes::Field
|
|
272
|
-
if node.name ==
|
|
267
|
+
if node.name == field_defn.graphql_name
|
|
273
268
|
if arguments.nil? || arguments.empty?
|
|
274
269
|
# No constraint applied
|
|
275
270
|
matches << node
|
|
@@ -278,10 +273,10 @@ module GraphQL
|
|
|
278
273
|
end
|
|
279
274
|
end
|
|
280
275
|
when GraphQL::Language::Nodes::InlineFragment
|
|
281
|
-
node.selections.each { |s| find_selected_nodes(s,
|
|
276
|
+
node.selections.each { |s| find_selected_nodes(s, field_defn, arguments: arguments, matches: matches) }
|
|
282
277
|
when GraphQL::Language::Nodes::FragmentSpread
|
|
283
278
|
frag_defn = @query.fragments[node.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})")
|
|
284
|
-
frag_defn.selections.each { |s| find_selected_nodes(s,
|
|
279
|
+
frag_defn.selections.each { |s| find_selected_nodes(s, field_defn, arguments: arguments, matches: matches) }
|
|
285
280
|
else
|
|
286
281
|
raise "Unexpected selection comparison on #{node.class.name} (#{node})"
|
|
287
282
|
end
|
|
@@ -290,9 +285,14 @@ module GraphQL
|
|
|
290
285
|
def arguments_match?(arguments, field_defn, field_node)
|
|
291
286
|
query_kwargs = @query.arguments_for(field_node, field_defn)
|
|
292
287
|
arguments.all? do |arg_name, arg_value|
|
|
293
|
-
|
|
288
|
+
arg_name_sym = if arg_name.is_a?(String)
|
|
289
|
+
Schema::Member::BuildType.underscore(arg_name).to_sym
|
|
290
|
+
else
|
|
291
|
+
arg_name
|
|
292
|
+
end
|
|
293
|
+
|
|
294
294
|
# Make sure the constraint is present with a matching value
|
|
295
|
-
query_kwargs.key?(
|
|
295
|
+
query_kwargs.key?(arg_name_sym) && query_kwargs[arg_name_sym] == arg_value
|
|
296
296
|
end
|
|
297
297
|
end
|
|
298
298
|
end
|
|
@@ -23,13 +23,10 @@ module GraphQL
|
|
|
23
23
|
# @see {Schema#multiplex} for public API
|
|
24
24
|
# @api private
|
|
25
25
|
class Multiplex
|
|
26
|
-
# Used internally to signal that the query shouldn't be executed
|
|
27
|
-
# @api private
|
|
28
|
-
NO_OPERATION = {}.freeze
|
|
29
|
-
|
|
30
26
|
include Tracing::Traceable
|
|
31
27
|
|
|
32
28
|
attr_reader :context, :queries, :schema, :max_complexity, :dataloader
|
|
29
|
+
|
|
33
30
|
def initialize(schema:, queries:, context:, max_complexity:)
|
|
34
31
|
@schema = schema
|
|
35
32
|
@queries = queries
|
|
@@ -43,118 +40,6 @@ module GraphQL
|
|
|
43
40
|
end
|
|
44
41
|
@max_complexity = max_complexity
|
|
45
42
|
end
|
|
46
|
-
|
|
47
|
-
class << self
|
|
48
|
-
# @param schema [GraphQL::Schema]
|
|
49
|
-
# @param queries [Array<GraphQL::Query, Hash>]
|
|
50
|
-
# @param context [Hash]
|
|
51
|
-
# @param max_complexity [Integer, nil]
|
|
52
|
-
# @return [Array<Hash>] One result per query
|
|
53
|
-
def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
|
|
54
|
-
queries = query_options.map do |opts|
|
|
55
|
-
case opts
|
|
56
|
-
when Hash
|
|
57
|
-
GraphQL::Query.new(schema, nil, **opts)
|
|
58
|
-
when GraphQL::Query
|
|
59
|
-
opts
|
|
60
|
-
else
|
|
61
|
-
raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
multiplex = self.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
|
|
66
|
-
multiplex.trace("execute_multiplex", { multiplex: multiplex }) do
|
|
67
|
-
GraphQL::Execution::Instrumentation.apply_instrumenters(multiplex) do
|
|
68
|
-
schema = multiplex.schema
|
|
69
|
-
multiplex_analyzers = schema.multiplex_analyzers
|
|
70
|
-
if multiplex.max_complexity
|
|
71
|
-
multiplex_analyzers += [GraphQL::Analysis::AST::MaxQueryComplexity]
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
|
|
75
|
-
|
|
76
|
-
begin
|
|
77
|
-
multiplex.schema.query_execution_strategy.begin_multiplex(multiplex)
|
|
78
|
-
# Do as much eager evaluation of the query as possible
|
|
79
|
-
results = []
|
|
80
|
-
queries.each_with_index do |query, idx|
|
|
81
|
-
multiplex.dataloader.append_job { begin_query(results, idx, query, multiplex) }
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
multiplex.dataloader.run
|
|
85
|
-
|
|
86
|
-
# Then, work through lazy results in a breadth-first way
|
|
87
|
-
multiplex.dataloader.append_job {
|
|
88
|
-
multiplex.schema.query_execution_strategy.finish_multiplex(results, multiplex)
|
|
89
|
-
}
|
|
90
|
-
multiplex.dataloader.run
|
|
91
|
-
|
|
92
|
-
# Then, find all errors and assign the result to the query object
|
|
93
|
-
results.each_with_index do |data_result, idx|
|
|
94
|
-
query = queries[idx]
|
|
95
|
-
finish_query(data_result, query, multiplex)
|
|
96
|
-
# Get the Query::Result, not the Hash
|
|
97
|
-
results[idx] = query.result
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
results
|
|
101
|
-
rescue Exception
|
|
102
|
-
# TODO rescue at a higher level so it will catch errors in analysis, too
|
|
103
|
-
# Assign values here so that the query's `@executed` becomes true
|
|
104
|
-
queries.map { |q| q.result_values ||= {} }
|
|
105
|
-
raise
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# @param query [GraphQL::Query]
|
|
112
|
-
def begin_query(results, idx, query, multiplex)
|
|
113
|
-
operation = query.selected_operation
|
|
114
|
-
result = if operation.nil? || !query.valid? || query.context.errors.any?
|
|
115
|
-
NO_OPERATION
|
|
116
|
-
else
|
|
117
|
-
begin
|
|
118
|
-
query.schema.query_execution_strategy.begin_query(query, multiplex)
|
|
119
|
-
rescue GraphQL::ExecutionError => err
|
|
120
|
-
query.context.errors << err
|
|
121
|
-
NO_OPERATION
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
results[idx] = result
|
|
125
|
-
nil
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
private
|
|
129
|
-
|
|
130
|
-
# @param data_result [Hash] The result for the "data" key, if any
|
|
131
|
-
# @param query [GraphQL::Query] The query which was run
|
|
132
|
-
# @return [Hash] final result of this query, including all values and errors
|
|
133
|
-
def finish_query(data_result, query, multiplex)
|
|
134
|
-
# Assign the result so that it can be accessed in instrumentation
|
|
135
|
-
query.result_values = if data_result.equal?(NO_OPERATION)
|
|
136
|
-
if !query.valid? || query.context.errors.any?
|
|
137
|
-
# A bit weird, but `Query#static_errors` _includes_ `query.context.errors`
|
|
138
|
-
{ "errors" => query.static_errors.map(&:to_h) }
|
|
139
|
-
else
|
|
140
|
-
data_result
|
|
141
|
-
end
|
|
142
|
-
else
|
|
143
|
-
# Use `context.value` which was assigned during execution
|
|
144
|
-
result = query.schema.query_execution_strategy.finish_query(query, multiplex)
|
|
145
|
-
|
|
146
|
-
if query.context.errors.any?
|
|
147
|
-
error_result = query.context.errors.map(&:to_h)
|
|
148
|
-
result["errors"] = error_result
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
result
|
|
152
|
-
end
|
|
153
|
-
if query.context.namespace?(:__query_result_extensions__)
|
|
154
|
-
query.result_values["extensions"] = query.context.namespace(:__query_result_extensions__)
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
43
|
end
|
|
159
44
|
end
|
|
160
45
|
end
|
data/lib/graphql/execution.rb
CHANGED
|
@@ -29,6 +29,13 @@ module GraphQL
|
|
|
29
29
|
|
|
30
30
|
field :specifiedByURL, String, resolver_method: :specified_by_url
|
|
31
31
|
|
|
32
|
+
field :is_one_of, Boolean, null: false
|
|
33
|
+
|
|
34
|
+
def is_one_of
|
|
35
|
+
object.kind.input_object? &&
|
|
36
|
+
object.directives.any? { |d| d.graphql_name == "oneOf" }
|
|
37
|
+
end
|
|
38
|
+
|
|
32
39
|
def specified_by_url
|
|
33
40
|
if object.kind.scalar?
|
|
34
41
|
object.specified_by_url
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module GraphQL
|
|
3
3
|
module Introspection
|
|
4
|
-
def self.query(include_deprecated_args: false, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false)
|
|
4
|
+
def self.query(include_deprecated_args: false, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false)
|
|
5
5
|
# The introspection query to end all introspection queries, copied from
|
|
6
6
|
# https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js
|
|
7
7
|
<<-QUERY
|
|
@@ -30,6 +30,7 @@ fragment FullType on __Type {
|
|
|
30
30
|
name
|
|
31
31
|
description
|
|
32
32
|
#{include_specified_by_url ? "specifiedByURL" : ""}
|
|
33
|
+
#{include_is_one_of ? "isOneOf" : ""}
|
|
33
34
|
fields(includeDeprecated: true) {
|
|
34
35
|
name
|
|
35
36
|
description
|
|
@@ -236,12 +236,15 @@ module GraphQL
|
|
|
236
236
|
out = print_description(input_object_type)
|
|
237
237
|
out << "input #{input_object_type.name}"
|
|
238
238
|
out << print_directives(input_object_type.directives)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
239
|
+
if !input_object_type.fields.empty?
|
|
240
|
+
out << " {\n"
|
|
241
|
+
input_object_type.fields.each.with_index do |field, i|
|
|
242
|
+
out << print_description(field, indent: ' ', first_in_block: i == 0)
|
|
243
|
+
out << " #{print_input_value_definition(field)}\n"
|
|
244
|
+
end
|
|
245
|
+
out << "}"
|
|
243
246
|
end
|
|
244
|
-
out
|
|
247
|
+
out
|
|
245
248
|
end
|
|
246
249
|
|
|
247
250
|
def print_directive_definition(directive)
|
|
@@ -4,11 +4,11 @@ module GraphQL
|
|
|
4
4
|
class VariableValidationError < GraphQL::ExecutionError
|
|
5
5
|
attr_accessor :value, :validation_result
|
|
6
6
|
|
|
7
|
-
def initialize(variable_ast, type, value, validation_result)
|
|
7
|
+
def initialize(variable_ast, type, value, validation_result, msg: nil)
|
|
8
8
|
@value = value
|
|
9
9
|
@validation_result = validation_result
|
|
10
10
|
|
|
11
|
-
msg
|
|
11
|
+
msg ||= "Variable $#{variable_ast.name} of type #{type.to_type_signature} was provided invalid value"
|
|
12
12
|
|
|
13
13
|
if problem_fields.any?
|
|
14
14
|
msg += " for #{problem_fields.join(", ")}"
|