graphql 1.5.15 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql.rb +4 -19
- data/lib/graphql/analysis/analyze_query.rb +27 -2
- data/lib/graphql/analysis/query_complexity.rb +10 -11
- data/lib/graphql/argument.rb +7 -6
- data/lib/graphql/backwards_compatibility.rb +47 -0
- data/lib/graphql/compatibility/execution_specification.rb +14 -0
- data/lib/graphql/compatibility/execution_specification/specification_schema.rb +6 -1
- data/lib/graphql/compatibility/lazy_execution_specification.rb +19 -0
- data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +15 -6
- data/lib/graphql/directive.rb +1 -6
- data/lib/graphql/execution.rb +1 -0
- data/lib/graphql/execution/execute.rb +174 -160
- data/lib/graphql/execution/field_result.rb +5 -1
- data/lib/graphql/execution/lazy.rb +2 -2
- data/lib/graphql/execution/lazy/resolve.rb +8 -11
- data/lib/graphql/execution/multiplex.rb +134 -0
- data/lib/graphql/execution/selection_result.rb +5 -0
- data/lib/graphql/field.rb +1 -8
- data/lib/graphql/filter.rb +53 -0
- data/lib/graphql/internal_representation/node.rb +11 -6
- data/lib/graphql/internal_representation/rewrite.rb +3 -3
- data/lib/graphql/query.rb +160 -78
- data/lib/graphql/query/arguments.rb +14 -25
- data/lib/graphql/query/arguments_cache.rb +6 -13
- data/lib/graphql/query/context.rb +28 -10
- data/lib/graphql/query/executor.rb +1 -0
- data/lib/graphql/query/literal_input.rb +10 -4
- data/lib/graphql/query/null_context.rb +1 -1
- data/lib/graphql/query/serial_execution/field_resolution.rb +5 -1
- data/lib/graphql/query/validation_pipeline.rb +12 -7
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/rake_task.rb +140 -0
- data/lib/graphql/relay/array_connection.rb +29 -48
- data/lib/graphql/relay/base_connection.rb +9 -7
- data/lib/graphql/relay/mutation.rb +0 -11
- data/lib/graphql/relay/mutation/instrumentation.rb +2 -2
- data/lib/graphql/relay/mutation/resolve.rb +7 -10
- data/lib/graphql/relay/relation_connection.rb +98 -61
- data/lib/graphql/scalar_type.rb +1 -15
- data/lib/graphql/schema.rb +90 -25
- data/lib/graphql/schema/build_from_definition.rb +22 -23
- data/lib/graphql/schema/build_from_definition/resolve_map.rb +70 -0
- data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +47 -0
- data/lib/graphql/schema/middleware_chain.rb +1 -1
- data/lib/graphql/schema/printer.rb +2 -1
- data/lib/graphql/schema/timeout_middleware.rb +6 -6
- data/lib/graphql/schema/type_map.rb +1 -1
- data/lib/graphql/schema/warden.rb +5 -9
- data/lib/graphql/static_validation/definition_dependencies.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/analysis/analyze_query_spec.rb +2 -2
- data/spec/graphql/analysis/max_query_complexity_spec.rb +28 -0
- data/spec/graphql/argument_spec.rb +3 -3
- data/spec/graphql/execution/lazy_spec.rb +8 -114
- data/spec/graphql/execution/multiplex_spec.rb +131 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +10 -0
- data/spec/graphql/query/arguments_spec.rb +14 -16
- data/spec/graphql/query/context_spec.rb +14 -1
- data/spec/graphql/query/literal_input_spec.rb +19 -13
- data/spec/graphql/query/variables_spec.rb +1 -1
- data/spec/graphql/query_spec.rb +12 -1
- data/spec/graphql/rake_task_spec.rb +57 -0
- data/spec/graphql/relay/array_connection_spec.rb +24 -3
- data/spec/graphql/relay/connection_instrumentation_spec.rb +23 -0
- data/spec/graphql/relay/mutation_spec.rb +2 -10
- data/spec/graphql/relay/page_info_spec.rb +2 -2
- data/spec/graphql/relay/relation_connection_spec.rb +167 -3
- data/spec/graphql/schema/build_from_definition_spec.rb +93 -19
- data/spec/graphql/schema/warden_spec.rb +80 -0
- data/spec/graphql/schema_spec.rb +26 -2
- data/spec/spec_helper.rb +4 -2
- data/spec/support/lazy_helpers.rb +152 -0
- data/spec/support/star_wars/schema.rb +23 -0
- metadata +28 -3
- data/lib/graphql/schema/mask.rb +0 -55
@@ -21,6 +21,7 @@ module GraphQL
|
|
21
21
|
# If it is {Execute::PROPAGATE_NULL}, tell the owner to propagate null.
|
22
22
|
# If the value is a {SelectionResult}, make a link with it, and if it's already null,
|
23
23
|
# propagate the null as needed.
|
24
|
+
# If it's {Execute::Execution::SKIP}, remove this field result from its parent
|
24
25
|
# @param new_value [Any] The GraphQL-ready value
|
25
26
|
def value=(new_value)
|
26
27
|
if new_value.is_a?(SelectionResult)
|
@@ -31,12 +32,15 @@ module GraphQL
|
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
34
|
-
|
35
|
+
case new_value
|
36
|
+
when GraphQL::Execution::Execute::PROPAGATE_NULL
|
35
37
|
if @type.kind.non_null?
|
36
38
|
@owner.propagate_null
|
37
39
|
else
|
38
40
|
@value = nil
|
39
41
|
end
|
42
|
+
when GraphQL::Execution::Execute::SKIP
|
43
|
+
@owner.delete(self)
|
40
44
|
else
|
41
45
|
@value = new_value
|
42
46
|
end
|
@@ -14,10 +14,8 @@ module GraphQL
|
|
14
14
|
|
15
15
|
def self.resolve_in_place(value)
|
16
16
|
lazies = []
|
17
|
-
acc = []
|
18
|
-
each_lazy(acc, value)
|
19
17
|
|
20
|
-
|
18
|
+
each_lazy(value) do |field_result|
|
21
19
|
inner_lazy = field_result.value.then do |inner_v|
|
22
20
|
field_result.value = inner_v
|
23
21
|
resolve_in_place(inner_v)
|
@@ -28,26 +26,25 @@ module GraphQL
|
|
28
26
|
Lazy.new { lazies.map(&:value) }
|
29
27
|
end
|
30
28
|
|
31
|
-
# If `value` is a collection,
|
32
|
-
#
|
33
|
-
# to `acc`
|
29
|
+
# If `value` is a collection, call `block`
|
30
|
+
# with any {Lazy} instances in the collection
|
34
31
|
# @return [void]
|
35
|
-
def self.each_lazy(
|
32
|
+
def self.each_lazy(value, &block)
|
36
33
|
case value
|
37
34
|
when SelectionResult
|
38
35
|
value.each do |key, field_result|
|
39
|
-
each_lazy(
|
36
|
+
each_lazy(field_result, &block)
|
40
37
|
end
|
41
38
|
when Array
|
42
39
|
value.each do |field_result|
|
43
|
-
each_lazy(
|
40
|
+
each_lazy(field_result, &block)
|
44
41
|
end
|
45
42
|
when FieldResult
|
46
43
|
field_value = value.value
|
47
44
|
if field_value.is_a?(Lazy)
|
48
|
-
|
45
|
+
yield(value)
|
49
46
|
else
|
50
|
-
each_lazy(
|
47
|
+
each_lazy(field_value, &block)
|
51
48
|
end
|
52
49
|
end
|
53
50
|
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module Execution
|
4
|
+
# Execute multiple queries under the same multiplex "umbrella".
|
5
|
+
# They can share a batching context and reduce redundant database hits.
|
6
|
+
#
|
7
|
+
# The flow is:
|
8
|
+
#
|
9
|
+
# - Multiplex instrumentation setup
|
10
|
+
# - Query instrumentation setup
|
11
|
+
# - Analyze the multiplex + each query
|
12
|
+
# - Begin each query
|
13
|
+
# - Resolve lazy values, breadth-first across all queries
|
14
|
+
# - Finish each query (eg, get errors)
|
15
|
+
# - Query instrumentation teardown
|
16
|
+
# - Multiplex instrumentation teardown
|
17
|
+
#
|
18
|
+
# If one query raises an application error, all queries will be in undefined states.
|
19
|
+
#
|
20
|
+
# Validation errors and {GraphQL::ExecutionError}s are handled in isolation:
|
21
|
+
# one of these errors in one query will not affect the other queries.
|
22
|
+
#
|
23
|
+
# @see {Schema#multiplex} for public API
|
24
|
+
# @api private
|
25
|
+
class Multiplex
|
26
|
+
# Used internally to signal that the query shouldn't be executed
|
27
|
+
# @api private
|
28
|
+
NO_OPERATION = {}.freeze
|
29
|
+
|
30
|
+
attr_reader :context, :queries, :schema
|
31
|
+
def initialize(schema:, queries:, context:)
|
32
|
+
@schema = schema
|
33
|
+
@queries = queries
|
34
|
+
@context = context
|
35
|
+
end
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def run_all(schema, query_options, *rest)
|
39
|
+
queries = query_options.map { |opts| GraphQL::Query.new(schema, nil, opts) }
|
40
|
+
run_queries(schema, queries, *rest)
|
41
|
+
end
|
42
|
+
|
43
|
+
def run_queries(schema, queries, context: {}, max_complexity: nil)
|
44
|
+
query_instrumenters = schema.instrumenters[:query]
|
45
|
+
multiplex_instrumenters = schema.instrumenters[:multiplex]
|
46
|
+
multiplex = self.new(schema: schema, queries: queries, context: context)
|
47
|
+
|
48
|
+
# First, run multiplex instrumentation, then query instrumentation for each query
|
49
|
+
multiplex_instrumenters.each { |i| i.before_multiplex(multiplex) }
|
50
|
+
queries.each do |query|
|
51
|
+
query_instrumenters.each { |i| i.before_query(query) }
|
52
|
+
end
|
53
|
+
|
54
|
+
multiplex_analyzers = schema.multiplex_analyzers
|
55
|
+
if max_complexity ||= schema.max_complexity
|
56
|
+
multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity.new(max_complexity)]
|
57
|
+
end
|
58
|
+
|
59
|
+
GraphQL::Analysis.analyze_multiplex(multiplex, multiplex_analyzers)
|
60
|
+
|
61
|
+
# Then, do as much eager evaluation of the query as possible
|
62
|
+
results = queries.map do |query|
|
63
|
+
begin_query(query)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Then, work through lazy results in a breadth-first way
|
67
|
+
GraphQL::Execution::Lazy.resolve(results)
|
68
|
+
|
69
|
+
# Then, find all errors and assign the result to the query object
|
70
|
+
results.each_with_index.map do |data_result, idx|
|
71
|
+
query = queries[idx]
|
72
|
+
finish_query(data_result, query)
|
73
|
+
end
|
74
|
+
ensure
|
75
|
+
# Finally, run teardown instrumentation for each query + the multiplex
|
76
|
+
# Use `reverse_each` so instrumenters are treated like a stack
|
77
|
+
queries.each do |query|
|
78
|
+
query_instrumenters.reverse_each { |i| i.after_query(query) }
|
79
|
+
end
|
80
|
+
multiplex_instrumenters.reverse_each { |i| i.after_multiplex(multiplex) }
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# @param query [GraphQL::Query]
|
86
|
+
# @return [Hash] The initial result (may not be finished if there are lazy values)
|
87
|
+
def begin_query(query)
|
88
|
+
operation = query.selected_operation
|
89
|
+
if operation.nil? || !query.valid?
|
90
|
+
NO_OPERATION
|
91
|
+
else
|
92
|
+
begin
|
93
|
+
op_type = operation.operation_type
|
94
|
+
root_type = query.root_type_for_operation(op_type)
|
95
|
+
GraphQL::Execution::Execute::ExecutionFunctions.resolve_selection(
|
96
|
+
query.root_value,
|
97
|
+
root_type,
|
98
|
+
query.irep_selection,
|
99
|
+
query.context,
|
100
|
+
mutation: query.mutation?
|
101
|
+
)
|
102
|
+
rescue GraphQL::ExecutionError => err
|
103
|
+
query.context.errors << err
|
104
|
+
{}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# @param data_result [Hash] The result for the "data" key, if any
|
110
|
+
# @param query [GraphQL::Query] The query which was run
|
111
|
+
# @return [Hash] final result of this query, including all values and errors
|
112
|
+
def finish_query(data_result, query)
|
113
|
+
# Assign the result so that it can be accessed in instrumentation
|
114
|
+
query.result = if data_result.equal?(NO_OPERATION)
|
115
|
+
if !query.valid?
|
116
|
+
{ "errors" => query.static_errors.map(&:to_h) }
|
117
|
+
else
|
118
|
+
{}
|
119
|
+
end
|
120
|
+
else
|
121
|
+
result = { "data" => data_result.to_h }
|
122
|
+
error_result = query.context.errors.map(&:to_h)
|
123
|
+
|
124
|
+
if error_result.any?
|
125
|
+
result["errors"] = error_result
|
126
|
+
end
|
127
|
+
|
128
|
+
result
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -38,6 +38,11 @@ module GraphQL
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
+
# TODO this should delete by key, ya dummy
|
42
|
+
def delete(field_result)
|
43
|
+
@storage.delete_if { |k, v| v == field_result }
|
44
|
+
end
|
45
|
+
|
41
46
|
# A field has been unexpectedly nullified.
|
42
47
|
# Tell the owner {FieldResult} if it is present.
|
43
48
|
# Record {#invalid_null} in case an owner is added later.
|
data/lib/graphql/field.rb
CHANGED
@@ -135,7 +135,7 @@ module GraphQL
|
|
135
135
|
:mutation, :arguments, :complexity, :function,
|
136
136
|
:resolve, :resolve=, :lazy_resolve, :lazy_resolve=, :lazy_resolve_proc, :resolve_proc,
|
137
137
|
:type, :type=, :name=, :property=, :hash_key=,
|
138
|
-
:relay_node_field, :relay_nodes_field
|
138
|
+
:relay_node_field, :relay_nodes_field
|
139
139
|
)
|
140
140
|
|
141
141
|
# @return [Boolean] True if this is the Relay find-by-id field
|
@@ -193,7 +193,6 @@ module GraphQL
|
|
193
193
|
@resolve_proc = build_default_resolver
|
194
194
|
@lazy_resolve_proc = DefaultLazyResolve
|
195
195
|
@relay_node_field = false
|
196
|
-
@default_arguments = nil
|
197
196
|
@connection = false
|
198
197
|
@connection_max_page_size = nil
|
199
198
|
end
|
@@ -201,7 +200,6 @@ module GraphQL
|
|
201
200
|
def initialize_copy(other)
|
202
201
|
super
|
203
202
|
@arguments = other.arguments.dup
|
204
|
-
@default_arguments = nil
|
205
203
|
end
|
206
204
|
|
207
205
|
# Get a value for this field
|
@@ -282,11 +280,6 @@ module GraphQL
|
|
282
280
|
}
|
283
281
|
end
|
284
282
|
|
285
|
-
# @return [GraphQL::Query::Arguments] Arguments to use when no args are provided in the query
|
286
|
-
def default_arguments
|
287
|
-
@default_arguments ||= GraphQL::Query::LiteralInput.defaults_for(self.arguments)
|
288
|
-
end
|
289
|
-
|
290
283
|
private
|
291
284
|
|
292
285
|
def build_default_resolver
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
# @api private
|
4
|
+
class Filter
|
5
|
+
def initialize(only: nil, except: nil)
|
6
|
+
@only = only
|
7
|
+
@except = except
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns true if `member, ctx` passes this filter
|
11
|
+
def call(member, ctx)
|
12
|
+
(@only ? @only.call(member, ctx) : true) &&
|
13
|
+
(@except ? !@except.call(member, ctx) : true)
|
14
|
+
end
|
15
|
+
|
16
|
+
def merge(only: nil, except: nil)
|
17
|
+
onlies = [self].concat(Array(only))
|
18
|
+
merged_only = MergedOnly.build(onlies)
|
19
|
+
merged_except = MergedExcept.build(Array(except))
|
20
|
+
self.class.new(only: merged_only, except: merged_except)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
class MergedOnly
|
26
|
+
def initialize(first, second)
|
27
|
+
@first = first
|
28
|
+
@second = second
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(member, ctx)
|
32
|
+
@first.call(member, ctx) && @second.call(member, ctx)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.build(onlies)
|
36
|
+
case onlies
|
37
|
+
when 0
|
38
|
+
nil
|
39
|
+
when 1
|
40
|
+
onlies[0]
|
41
|
+
else
|
42
|
+
onlies.reduce { |memo, only| self.new(memo, only) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class MergedExcept < MergedOnly
|
48
|
+
def call(member, ctx)
|
49
|
+
@first.call(member, ctx) || @second.call(member, ctx)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -2,8 +2,6 @@
|
|
2
2
|
module GraphQL
|
3
3
|
module InternalRepresentation
|
4
4
|
class Node
|
5
|
-
# @api private
|
6
|
-
DEFAULT_TYPED_CHILDREN = Proc.new { |h, k| h[k] = {} }
|
7
5
|
# @return [String] the name this node has in the response
|
8
6
|
attr_reader :name
|
9
7
|
|
@@ -17,13 +15,13 @@ module GraphQL
|
|
17
15
|
# @return [Hash<GraphQL::ObjectType, Hash<String => Node>>]
|
18
16
|
def typed_children
|
19
17
|
@typed_childen ||= begin
|
20
|
-
new_tc = Hash.new
|
18
|
+
new_tc = Hash.new { |h, k| h[k] = {} }
|
21
19
|
if @scoped_children.any?
|
22
20
|
all_object_types = Set.new
|
23
21
|
scoped_children.each_key { |t| all_object_types.merge(@query.possible_types(t)) }
|
24
22
|
# Remove any scoped children which don't follow this return type
|
25
23
|
# (This can happen with fragment merging where lexical scope is lost)
|
26
|
-
all_object_types &= @query.possible_types(@return_type)
|
24
|
+
all_object_types &= @query.possible_types(@return_type.unwrap)
|
27
25
|
all_object_types.each do |t|
|
28
26
|
new_tc[t] = get_typed_children(t)
|
29
27
|
end
|
@@ -47,7 +45,7 @@ module GraphQL
|
|
47
45
|
# @return [Array<GraphQL::Field>] Field definitions for this node (there should only be one!)
|
48
46
|
attr_reader :definitions
|
49
47
|
|
50
|
-
# @return [GraphQL::BaseType]
|
48
|
+
# @return [GraphQL::BaseType] The expected wrapped type this node must return.
|
51
49
|
attr_reader :return_type
|
52
50
|
|
53
51
|
# @return [InternalRepresentation::Node, nil]
|
@@ -97,6 +95,10 @@ module GraphQL
|
|
97
95
|
definition && definition.name
|
98
96
|
end
|
99
97
|
|
98
|
+
def arguments
|
99
|
+
@query.arguments_for(self, definition)
|
100
|
+
end
|
101
|
+
|
100
102
|
def definition
|
101
103
|
@definition ||= begin
|
102
104
|
first_def = @definitions.first
|
@@ -121,7 +123,7 @@ module GraphQL
|
|
121
123
|
@ast_nodes |= new_parent.ast_nodes
|
122
124
|
@definitions |= new_parent.definitions
|
123
125
|
end
|
124
|
-
scope ||= Scope.new(@query, @return_type)
|
126
|
+
scope ||= Scope.new(@query, @return_type.unwrap)
|
125
127
|
new_parent.scoped_children.each do |obj_type, new_fields|
|
126
128
|
inner_scope = scope.enter(obj_type)
|
127
129
|
inner_scope.each do |scoped_type|
|
@@ -138,6 +140,9 @@ module GraphQL
|
|
138
140
|
end
|
139
141
|
end
|
140
142
|
|
143
|
+
# @return [GraphQL::Query]
|
144
|
+
attr_reader :query
|
145
|
+
|
141
146
|
protected
|
142
147
|
|
143
148
|
attr_writer :owner_type, :parent
|
@@ -93,7 +93,7 @@ module GraphQL
|
|
93
93
|
# It's a non-existent field
|
94
94
|
new_scope = nil
|
95
95
|
else
|
96
|
-
field_return_type = field_defn.type
|
96
|
+
field_return_type = field_defn.type
|
97
97
|
scopes_stack.last.each do |scope_type|
|
98
98
|
parent_nodes.each do |parent_node|
|
99
99
|
node = parent_node.scoped_children[scope_type][node_name] ||= Node.new(
|
@@ -108,7 +108,7 @@ module GraphQL
|
|
108
108
|
next_nodes << node
|
109
109
|
end
|
110
110
|
end
|
111
|
-
new_scope = Scope.new(query, field_return_type)
|
111
|
+
new_scope = Scope.new(query, field_return_type.unwrap)
|
112
112
|
end
|
113
113
|
|
114
114
|
nodes_stack.push(next_nodes)
|
@@ -181,7 +181,7 @@ module GraphQL
|
|
181
181
|
owner_type: owner_type,
|
182
182
|
query: @query,
|
183
183
|
ast_nodes: [ast_node],
|
184
|
-
return_type:
|
184
|
+
return_type: @context.type_definition,
|
185
185
|
)
|
186
186
|
|
187
187
|
@definitions[defn_name] = node
|
data/lib/graphql/query.rb
CHANGED
@@ -14,7 +14,7 @@ require "graphql/query/validation_pipeline"
|
|
14
14
|
module GraphQL
|
15
15
|
# A combination of query string and {Schema} instance which can be reduced to a {#result}.
|
16
16
|
class Query
|
17
|
-
extend
|
17
|
+
extend Forwardable
|
18
18
|
|
19
19
|
class OperationNameMissingError < GraphQL::ExecutionError
|
20
20
|
def initialize(name)
|
@@ -27,7 +27,19 @@ module GraphQL
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
attr_reader :schema, :
|
30
|
+
attr_reader :schema, :context, :root_value, :warden, :provided_variables
|
31
|
+
|
32
|
+
attr_accessor :query_string
|
33
|
+
|
34
|
+
# @return [GraphQL::Language::Nodes::Document]
|
35
|
+
def document
|
36
|
+
with_prepared_ast { @document }
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String, nil] The name of the operation to run (may be inferred)
|
40
|
+
def operation_name
|
41
|
+
with_prepared_ast { @operation_name }
|
42
|
+
end
|
31
43
|
|
32
44
|
# Prepare query `query_string` on `schema`
|
33
45
|
# @param schema [GraphQL::Schema]
|
@@ -40,39 +52,23 @@ module GraphQL
|
|
40
52
|
# @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
|
41
53
|
# @param except [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns truthy
|
42
54
|
# @param only [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns false
|
43
|
-
def initialize(schema, query_string = nil, document: nil, context: nil, variables: {}, validate: true, operation_name: nil, root_value: nil, max_depth: nil, max_complexity: nil, except: nil, only: nil)
|
44
|
-
fail ArgumentError, "a query string or document is required" unless query_string || document
|
45
|
-
|
55
|
+
def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: {}, validate: true, operation_name: nil, root_value: nil, max_depth: nil, max_complexity: nil, except: nil, only: nil)
|
46
56
|
@schema = schema
|
47
|
-
|
57
|
+
@filter = schema.default_filter.merge(except: except, only: only)
|
48
58
|
@context = Context.new(query: self, values: context)
|
49
|
-
@warden = GraphQL::Schema::Warden.new(mask, schema: @schema, context: @context)
|
50
59
|
@root_value = root_value
|
51
|
-
@fragments =
|
52
|
-
@operations =
|
60
|
+
@fragments = nil
|
61
|
+
@operations = nil
|
62
|
+
|
63
|
+
@analysis_errors = []
|
53
64
|
if variables.is_a?(String)
|
54
65
|
raise ArgumentError, "Query variables should be a Hash, not a String. Try JSON.parse to prepare variables."
|
55
66
|
else
|
56
67
|
@provided_variables = variables
|
57
68
|
end
|
58
|
-
@query_string = query_string
|
59
|
-
parse_error = nil
|
60
|
-
@document = document || begin
|
61
|
-
GraphQL.parse(query_string)
|
62
|
-
rescue GraphQL::ParseError => err
|
63
|
-
parse_error = err
|
64
|
-
@schema.parse_error(err, @context)
|
65
|
-
nil
|
66
|
-
end
|
67
69
|
|
68
|
-
@
|
69
|
-
|
70
|
-
when GraphQL::Language::Nodes::FragmentDefinition
|
71
|
-
@fragments[part.name] = part
|
72
|
-
when GraphQL::Language::Nodes::OperationDefinition
|
73
|
-
@operations[part.name] = part
|
74
|
-
end
|
75
|
-
end
|
70
|
+
@query_string = query_string || query
|
71
|
+
@document = document
|
76
72
|
|
77
73
|
@resolved_types_cache = Hash.new { |h, k| h[k] = @schema.resolve_type(k, @context) }
|
78
74
|
|
@@ -82,64 +78,56 @@ module GraphQL
|
|
82
78
|
# with no operations returns an empty hash
|
83
79
|
@ast_variables = []
|
84
80
|
@mutation = false
|
85
|
-
|
86
|
-
@
|
81
|
+
@operation_name = operation_name
|
82
|
+
@prepared_ast = false
|
87
83
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
operation_name_error = GraphQL::Query::OperationNameMissingError.new(operation_name)
|
92
|
-
else
|
93
|
-
@operation_name = selected_operation.name
|
94
|
-
@ast_variables = selected_operation.variables
|
95
|
-
@mutation = selected_operation.operation_type == "mutation"
|
96
|
-
@selected_operation = selected_operation
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
@validation_pipeline = GraphQL::Query::ValidationPipeline.new(
|
101
|
-
query: self,
|
102
|
-
parse_error: parse_error,
|
103
|
-
operation_name_error: operation_name_error,
|
104
|
-
max_depth: max_depth || schema.max_depth,
|
105
|
-
max_complexity: max_complexity || schema.max_complexity,
|
106
|
-
)
|
84
|
+
@validation_pipeline = nil
|
85
|
+
@max_depth = max_depth || schema.max_depth
|
86
|
+
@max_complexity = max_complexity || schema.max_complexity
|
107
87
|
|
108
88
|
@result = nil
|
109
89
|
@executed = false
|
110
90
|
end
|
111
91
|
|
112
|
-
#
|
113
|
-
|
114
|
-
def result
|
92
|
+
# @api private
|
93
|
+
def result=(result_hash)
|
115
94
|
if @executed
|
116
|
-
|
95
|
+
raise "Invariant: Can't reassign result"
|
117
96
|
else
|
118
97
|
@executed = true
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
98
|
+
@result = result_hash
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def fragments
|
103
|
+
with_prepared_ast { @fragments }
|
104
|
+
end
|
105
|
+
|
106
|
+
def operations
|
107
|
+
with_prepared_ast { @operations }
|
108
|
+
end
|
109
|
+
|
110
|
+
# Get the result for this query, executing it once
|
111
|
+
# @return [Hash] A GraphQL response, with `"data"` and/or `"errors"` keys
|
112
|
+
def result
|
113
|
+
if !@executed
|
114
|
+
with_prepared_ast {
|
115
|
+
Execution::Multiplex.run_queries(@schema, [self])
|
116
|
+
}
|
135
117
|
end
|
118
|
+
@result
|
136
119
|
end
|
137
120
|
|
121
|
+
def static_errors
|
122
|
+
validation_errors + analysis_errors + context.errors
|
123
|
+
end
|
138
124
|
|
139
125
|
# This is the operation to run for this query.
|
140
126
|
# If more than one operation is present, it must be named at runtime.
|
141
127
|
# @return [GraphQL::Language::Nodes::OperationDefinition, nil]
|
142
|
-
|
128
|
+
def selected_operation
|
129
|
+
with_prepared_ast { @selected_operation }
|
130
|
+
end
|
143
131
|
|
144
132
|
# Determine the values for variables of this query, using default values
|
145
133
|
# if a value isn't provided at runtime.
|
@@ -149,12 +137,13 @@ module GraphQL
|
|
149
137
|
# @return [GraphQL::Query::Variables] Variables to apply to this query
|
150
138
|
def variables
|
151
139
|
@variables ||= begin
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
140
|
+
with_prepared_ast {
|
141
|
+
GraphQL::Query::Variables.new(
|
142
|
+
@context,
|
143
|
+
@ast_variables,
|
144
|
+
@provided_variables,
|
145
|
+
)
|
146
|
+
}
|
158
147
|
end
|
159
148
|
end
|
160
149
|
|
@@ -169,12 +158,27 @@ module GraphQL
|
|
169
158
|
@arguments_cache[irep_or_ast_node][definition]
|
170
159
|
end
|
171
160
|
|
172
|
-
# @return [GraphQL::Language::Nodes::
|
173
|
-
|
161
|
+
# @return [GraphQL::Language::Nodes::OperationDefinition, nil]
|
162
|
+
def selected_operation
|
163
|
+
with_prepared_ast { @selected_operation }
|
164
|
+
end
|
174
165
|
|
175
|
-
|
166
|
+
def validation_pipeline
|
167
|
+
with_prepared_ast { @validation_pipeline }
|
168
|
+
end
|
169
|
+
|
170
|
+
def_delegators :validation_pipeline, :validation_errors, :internal_representation, :analyzers
|
171
|
+
|
172
|
+
attr_accessor :analysis_errors
|
173
|
+
def valid?
|
174
|
+
validation_pipeline.valid? && analysis_errors.none?
|
175
|
+
end
|
176
|
+
|
177
|
+
def warden
|
178
|
+
with_prepared_ast { @warden }
|
179
|
+
end
|
176
180
|
|
177
|
-
def_delegators
|
181
|
+
def_delegators :warden, :get_type, :get_field, :possible_types, :root_type_for_operation
|
178
182
|
|
179
183
|
# @param value [Object] Any runtime value
|
180
184
|
# @return [GraphQL::ObjectType, nil] The runtime type of `value` from {Schema#resolve_type}
|
@@ -187,6 +191,16 @@ module GraphQL
|
|
187
191
|
@mutation
|
188
192
|
end
|
189
193
|
|
194
|
+
# @return [void]
|
195
|
+
def merge_filters(only: nil, except: nil)
|
196
|
+
if @prepared_ast
|
197
|
+
raise "Can't add filters after preparing the query"
|
198
|
+
else
|
199
|
+
@filter = @filter.merge(only: only, except: except)
|
200
|
+
end
|
201
|
+
nil
|
202
|
+
end
|
203
|
+
|
190
204
|
private
|
191
205
|
|
192
206
|
def find_operation(operations, operation_name)
|
@@ -198,5 +212,73 @@ module GraphQL
|
|
198
212
|
operations.fetch(operation_name)
|
199
213
|
end
|
200
214
|
end
|
215
|
+
|
216
|
+
def prepare_ast
|
217
|
+
@prepared_ast = true
|
218
|
+
@warden = GraphQL::Schema::Warden.new(@filter, schema: @schema, context: @context)
|
219
|
+
|
220
|
+
parse_error = nil
|
221
|
+
@document ||= begin
|
222
|
+
if query_string
|
223
|
+
GraphQL.parse(query_string)
|
224
|
+
end
|
225
|
+
rescue GraphQL::ParseError => err
|
226
|
+
parse_error = err
|
227
|
+
@schema.parse_error(err, @context)
|
228
|
+
nil
|
229
|
+
end
|
230
|
+
|
231
|
+
@fragments = {}
|
232
|
+
@operations = {}
|
233
|
+
if @document
|
234
|
+
@document.definitions.each do |part|
|
235
|
+
case part
|
236
|
+
when GraphQL::Language::Nodes::FragmentDefinition
|
237
|
+
@fragments[part.name] = part
|
238
|
+
when GraphQL::Language::Nodes::OperationDefinition
|
239
|
+
@operations[part.name] = part
|
240
|
+
end
|
241
|
+
end
|
242
|
+
elsif parse_error
|
243
|
+
# This will be handled later
|
244
|
+
else
|
245
|
+
raise ArgumentError, "a query string or document is required"
|
246
|
+
end
|
247
|
+
|
248
|
+
# Trying to execute a document
|
249
|
+
# with no operations returns an empty hash
|
250
|
+
@ast_variables = []
|
251
|
+
@mutation = false
|
252
|
+
operation_name_error = nil
|
253
|
+
if @operations.any?
|
254
|
+
@selected_operation = find_operation(@operations, @operation_name)
|
255
|
+
if @selected_operation.nil?
|
256
|
+
operation_name_error = GraphQL::Query::OperationNameMissingError.new(@operation_name)
|
257
|
+
else
|
258
|
+
if @operation_name.nil?
|
259
|
+
@operation_name = @selected_operation.name
|
260
|
+
end
|
261
|
+
@ast_variables = @selected_operation.variables
|
262
|
+
@mutation = @selected_operation.operation_type == "mutation"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
@validation_pipeline = GraphQL::Query::ValidationPipeline.new(
|
267
|
+
query: self,
|
268
|
+
parse_error: parse_error,
|
269
|
+
operation_name_error: operation_name_error,
|
270
|
+
max_depth: @max_depth,
|
271
|
+
max_complexity: @max_complexity || schema.max_complexity,
|
272
|
+
)
|
273
|
+
end
|
274
|
+
|
275
|
+
# Since the query string is processed at the last possible moment,
|
276
|
+
# any internal values which depend on it should be accessed within this wrapper.
|
277
|
+
def with_prepared_ast
|
278
|
+
if !@prepared_ast
|
279
|
+
prepare_ast
|
280
|
+
end
|
281
|
+
yield
|
282
|
+
end
|
201
283
|
end
|
202
284
|
end
|