graphql 1.1.0 → 1.2.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/analysis/analyze_query.rb +4 -2
- data/lib/graphql/analysis/field_usage.rb +4 -4
- data/lib/graphql/analysis/query_complexity.rb +16 -21
- data/lib/graphql/argument.rb +13 -6
- data/lib/graphql/base_type.rb +2 -1
- data/lib/graphql/compatibility/execution_specification.rb +76 -0
- data/lib/graphql/compatibility/query_parser_specification.rb +16 -2
- data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +0 -5
- data/lib/graphql/compatibility/schema_parser_specification.rb +6 -0
- data/lib/graphql/define/assign_argument.rb +8 -2
- data/lib/graphql/define/instance_definable.rb +12 -15
- data/lib/graphql/directive.rb +2 -1
- data/lib/graphql/enum_type.rb +5 -7
- data/lib/graphql/field.rb +6 -11
- data/lib/graphql/field/resolve.rb +1 -0
- data/lib/graphql/input_object_type.rb +9 -9
- data/lib/graphql/interface_type.rb +2 -1
- data/lib/graphql/internal_representation.rb +1 -0
- data/lib/graphql/internal_representation/node.rb +31 -9
- data/lib/graphql/internal_representation/rewrite.rb +26 -26
- data/lib/graphql/internal_representation/selections.rb +41 -0
- data/lib/graphql/introspection/input_value_type.rb +6 -2
- data/lib/graphql/language/generation.rb +2 -0
- data/lib/graphql/language/lexer.rl +4 -0
- data/lib/graphql/language/nodes.rb +3 -0
- data/lib/graphql/language/parser.rb +525 -509
- data/lib/graphql/language/parser.y +2 -0
- data/lib/graphql/object_type.rb +2 -2
- data/lib/graphql/query.rb +21 -0
- data/lib/graphql/query/context.rb +52 -4
- data/lib/graphql/query/serial_execution.rb +3 -4
- data/lib/graphql/query/serial_execution/field_resolution.rb +35 -36
- data/lib/graphql/query/serial_execution/operation_resolution.rb +9 -15
- data/lib/graphql/query/serial_execution/selection_resolution.rb +14 -11
- data/lib/graphql/query/serial_execution/value_resolution.rb +18 -17
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/relay/mutation.rb +5 -8
- data/lib/graphql/scalar_type.rb +1 -2
- data/lib/graphql/schema.rb +2 -13
- data/lib/graphql/schema/build_from_definition.rb +28 -13
- data/lib/graphql/schema/loader.rb +4 -1
- data/lib/graphql/schema/printer.rb +10 -3
- data/lib/graphql/schema/timeout_middleware.rb +18 -2
- data/lib/graphql/schema/unique_within_type.rb +6 -3
- data/lib/graphql/static_validation/literal_validator.rb +3 -1
- data/lib/graphql/union_type.rb +1 -2
- data/lib/graphql/version.rb +1 -1
- data/readme.md +1 -0
- data/spec/graphql/analysis/analyze_query_spec.rb +6 -8
- data/spec/graphql/argument_spec.rb +18 -0
- data/spec/graphql/define/assign_argument_spec.rb +48 -0
- data/spec/graphql/define/instance_definable_spec.rb +4 -2
- data/spec/graphql/execution_error_spec.rb +66 -0
- data/spec/graphql/input_object_type_spec.rb +81 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +104 -21
- data/spec/graphql/introspection/input_value_type_spec.rb +43 -6
- data/spec/graphql/introspection/schema_type_spec.rb +1 -0
- data/spec/graphql/introspection/type_type_spec.rb +2 -0
- data/spec/graphql/language/generation_spec.rb +3 -2
- data/spec/graphql/query/arguments_spec.rb +17 -4
- data/spec/graphql/query/context_spec.rb +23 -0
- data/spec/graphql/query/variables_spec.rb +15 -1
- data/spec/graphql/relay/mutation_spec.rb +42 -2
- data/spec/graphql/schema/build_from_definition_spec.rb +4 -2
- data/spec/graphql/schema/loader_spec.rb +59 -1
- data/spec/graphql/schema/printer_spec.rb +2 -0
- data/spec/graphql/schema/reduce_types_spec.rb +1 -1
- data/spec/graphql/schema/timeout_middleware_spec.rb +2 -2
- data/spec/graphql/schema/unique_within_type_spec.rb +9 -0
- data/spec/graphql/schema/validation_spec.rb +15 -3
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +122 -0
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +78 -0
- data/spec/support/dairy_app.rb +9 -0
- data/spec/support/minimum_input_object.rb +4 -0
- data/spec/support/star_wars_schema.rb +1 -1
- metadata +5 -5
- data/lib/graphql/query/serial_execution/execution_context.rb +0 -37
- data/spec/graphql/query/serial_execution/execution_context_spec.rb +0 -54
@@ -172,11 +172,13 @@ rule
|
|
172
172
|
| STRING { return val[0].to_s }
|
173
173
|
| TRUE { return true }
|
174
174
|
| FALSE { return false }
|
175
|
+
| null_value
|
175
176
|
| variable
|
176
177
|
| list_value
|
177
178
|
| object_value
|
178
179
|
| enum_value
|
179
180
|
|
181
|
+
null_value: NULL { return make_node(:NullValue, name: val[0], position_source: val[0]) }
|
180
182
|
variable: VAR_SIGN name { return make_node(:VariableIdentifier, name: val[1], position_source: val[0]) }
|
181
183
|
|
182
184
|
list_value:
|
data/lib/graphql/object_type.rb
CHANGED
@@ -23,7 +23,8 @@ module GraphQL
|
|
23
23
|
class ObjectType < GraphQL::BaseType
|
24
24
|
accepts_definitions :interfaces, :fields, :mutation, field: GraphQL::Define::AssignObjectField
|
25
25
|
|
26
|
-
|
26
|
+
attr_accessor :fields, :mutation
|
27
|
+
ensure_defined(:fields, :mutation, :interfaces)
|
27
28
|
|
28
29
|
# @!attribute fields
|
29
30
|
# @return [Hash<String => GraphQL::Field>] Map String fieldnames to their {GraphQL::Field} implementations
|
@@ -45,7 +46,6 @@ module GraphQL
|
|
45
46
|
|
46
47
|
def interfaces
|
47
48
|
@clean_interfaces ||= begin
|
48
|
-
ensure_defined
|
49
49
|
if @dirty_interfaces.respond_to?(:map)
|
50
50
|
@dirty_interfaces.map { |i_type| GraphQL::BaseType.resolve_related_type(i_type) }
|
51
51
|
else
|
data/lib/graphql/query.rb
CHANGED
@@ -167,6 +167,27 @@ module GraphQL
|
|
167
167
|
@valid
|
168
168
|
end
|
169
169
|
|
170
|
+
def selections(nodes, type)
|
171
|
+
@selections ||= Hash.new { |h, k| h[k] = GraphQL::InternalRepresentation::Selections.build(self, k) }
|
172
|
+
@selections[nodes][type]
|
173
|
+
end
|
174
|
+
|
175
|
+
def get_type(type_name)
|
176
|
+
@warden.get_type(type_name)
|
177
|
+
end
|
178
|
+
|
179
|
+
def get_field(type, name)
|
180
|
+
@warden.get_field(type, name)
|
181
|
+
end
|
182
|
+
|
183
|
+
def possible_types(type)
|
184
|
+
@warden.possible_types(type)
|
185
|
+
end
|
186
|
+
|
187
|
+
def resolve_type(type)
|
188
|
+
@schema.resolve_type(type, @context)
|
189
|
+
end
|
190
|
+
|
170
191
|
private
|
171
192
|
|
172
193
|
# Assert that the passed-in query string is internally consistent
|
@@ -3,16 +3,23 @@ module GraphQL
|
|
3
3
|
# Expose some query-specific info to field resolve functions.
|
4
4
|
# It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
|
5
5
|
class Context
|
6
|
-
|
6
|
+
attr_reader :execution_strategy
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
def execution_strategy=(new_strategy)
|
9
|
+
# GraphQL::Batch re-assigns this value but it was previously not used
|
10
|
+
# (ExecutionContext#strategy was used instead)
|
11
|
+
# now it _is_ used, but it breaks GraphQL::Batch tests
|
12
|
+
@execution_strategy ||= new_strategy
|
11
13
|
end
|
12
14
|
|
13
15
|
# @return [GraphQL::InternalRepresentation::Node] The internal representation for this query node
|
14
16
|
attr_accessor :irep_node
|
15
17
|
|
18
|
+
# @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field
|
19
|
+
def ast_node
|
20
|
+
@irep_node.ast_node
|
21
|
+
end
|
22
|
+
|
16
23
|
# @return [Array<GraphQL::ExecutionError>] errors returned during execution
|
17
24
|
attr_reader :errors
|
18
25
|
|
@@ -25,6 +32,9 @@ module GraphQL
|
|
25
32
|
# @return [GraphQL::Schema::Mask::Warden]
|
26
33
|
attr_reader :warden
|
27
34
|
|
35
|
+
# @return [Array<String, Integer>] The current position in the result
|
36
|
+
attr_reader :path
|
37
|
+
|
28
38
|
# Make a new context which delegates key lookup to `values`
|
29
39
|
# @param query [GraphQL::Query] the query who owns this context
|
30
40
|
# @param values [Hash] A hash of arbitrary values which will be accessible at query-time
|
@@ -34,6 +44,7 @@ module GraphQL
|
|
34
44
|
@values = values || {}
|
35
45
|
@errors = []
|
36
46
|
@warden = query.warden
|
47
|
+
@path = []
|
37
48
|
end
|
38
49
|
|
39
50
|
# Lookup `key` from the hash passed to {Schema#execute} as `context:`
|
@@ -45,6 +56,43 @@ module GraphQL
|
|
45
56
|
def []=(key, value)
|
46
57
|
@values[key] = value
|
47
58
|
end
|
59
|
+
|
60
|
+
def spawn(path:, irep_node:)
|
61
|
+
FieldResolutionContext.new(context: self, path: path, irep_node: irep_node)
|
62
|
+
end
|
63
|
+
|
64
|
+
class FieldResolutionContext
|
65
|
+
extend Forwardable
|
66
|
+
|
67
|
+
attr_reader :path, :irep_node
|
68
|
+
|
69
|
+
def initialize(context:, path:, irep_node:)
|
70
|
+
@context = context
|
71
|
+
@path = path
|
72
|
+
@irep_node = irep_node
|
73
|
+
end
|
74
|
+
|
75
|
+
def_delegators :@context, :[], :[]=, :spawn, :query, :schema, :warden, :errors, :execution_strategy
|
76
|
+
|
77
|
+
# @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field
|
78
|
+
def ast_node
|
79
|
+
@irep_node.ast_node
|
80
|
+
end
|
81
|
+
|
82
|
+
# Add error to current field resolution.
|
83
|
+
# @param error [GraphQL::ExecutionError] an execution error
|
84
|
+
# @return [void]
|
85
|
+
def add_error(error)
|
86
|
+
unless error.is_a?(ExecutionError)
|
87
|
+
raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
|
88
|
+
end
|
89
|
+
|
90
|
+
error.ast_node ||= irep_node.ast_node
|
91
|
+
error.path ||= path
|
92
|
+
errors << error
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
end
|
48
96
|
end
|
49
97
|
end
|
50
98
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require "graphql/query/serial_execution/execution_context"
|
2
1
|
require "graphql/query/serial_execution/value_resolution"
|
3
2
|
require "graphql/query/serial_execution/field_resolution"
|
4
3
|
require "graphql/query/serial_execution/operation_resolution"
|
@@ -18,11 +17,11 @@ module GraphQL
|
|
18
17
|
def execute(ast_operation, root_type, query_object)
|
19
18
|
irep_root = query_object.internal_representation[ast_operation.name]
|
20
19
|
|
21
|
-
operation_resolution.
|
20
|
+
operation_resolution.resolve(
|
22
21
|
irep_root,
|
23
22
|
root_type,
|
24
|
-
|
25
|
-
)
|
23
|
+
query_object
|
24
|
+
)
|
26
25
|
end
|
27
26
|
|
28
27
|
def field_resolution
|
@@ -2,15 +2,17 @@ module GraphQL
|
|
2
2
|
class Query
|
3
3
|
class SerialExecution
|
4
4
|
class FieldResolution
|
5
|
-
attr_reader :irep_node, :parent_type, :target, :
|
5
|
+
attr_reader :irep_node, :parent_type, :target, :field, :arguments, :query
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@irep_node =
|
7
|
+
def initialize(irep_nodes, parent_type, target, query_ctx)
|
8
|
+
@irep_node = irep_nodes.first
|
9
|
+
@irep_nodes = irep_nodes
|
9
10
|
@parent_type = parent_type
|
10
11
|
@target = target
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@
|
12
|
+
@field_ctx = query_ctx.spawn(path: query_ctx.path + [irep_node.name], irep_node: irep_node)
|
13
|
+
@query = query_ctx.query
|
14
|
+
@field = @query.get_field(parent_type, irep_node.definition_name)
|
15
|
+
@arguments = @query.arguments_for(irep_node, @field)
|
14
16
|
end
|
15
17
|
|
16
18
|
def result
|
@@ -19,6 +21,11 @@ module GraphQL
|
|
19
21
|
{ result_name => get_finished_value(raw_value) }
|
20
22
|
end
|
21
23
|
|
24
|
+
# GraphQL::Batch depends on this
|
25
|
+
def execution_context
|
26
|
+
@field_ctx
|
27
|
+
end
|
28
|
+
|
22
29
|
private
|
23
30
|
|
24
31
|
# After getting the value from the field's resolve method,
|
@@ -27,15 +34,15 @@ module GraphQL
|
|
27
34
|
case raw_value
|
28
35
|
when GraphQL::ExecutionError
|
29
36
|
raw_value.ast_node = irep_node.ast_node
|
30
|
-
raw_value.path =
|
31
|
-
|
37
|
+
raw_value.path = @field_ctx.path
|
38
|
+
@query.context.errors.push(raw_value)
|
32
39
|
when Array
|
33
40
|
list_errors = raw_value.each_with_index.select { |value, _| value.is_a?(GraphQL::ExecutionError) }
|
34
41
|
if list_errors.any?
|
35
42
|
list_errors.each do |error, index|
|
36
43
|
error.ast_node = irep_node.ast_node
|
37
|
-
error.path =
|
38
|
-
|
44
|
+
error.path = @field_ctx.path + [index]
|
45
|
+
@query.context.errors.push(error)
|
39
46
|
end
|
40
47
|
end
|
41
48
|
end
|
@@ -46,14 +53,14 @@ module GraphQL
|
|
46
53
|
field,
|
47
54
|
field.type,
|
48
55
|
raw_value,
|
49
|
-
|
50
|
-
|
56
|
+
@irep_nodes,
|
57
|
+
@field_ctx,
|
51
58
|
)
|
52
59
|
rescue GraphQL::InvalidNullError => err
|
53
60
|
if field.type.kind.non_null?
|
54
61
|
raise(err)
|
55
62
|
else
|
56
|
-
err.parent_error? ||
|
63
|
+
err.parent_error? || @query.context.errors.push(err)
|
57
64
|
nil
|
58
65
|
end
|
59
66
|
end
|
@@ -65,34 +72,26 @@ module GraphQL
|
|
65
72
|
# If the middleware chain returns a GraphQL::ExecutionError, its message
|
66
73
|
# is added to the "errors" key.
|
67
74
|
def get_raw_value
|
68
|
-
middlewares =
|
69
|
-
query_context = execution_context.query.context
|
70
|
-
# setup
|
71
|
-
query_context.irep_node = @irep_node
|
75
|
+
middlewares = @query.schema.middleware
|
72
76
|
|
73
|
-
resolve_arguments = [parent_type, target, field, arguments,
|
77
|
+
resolve_arguments = [parent_type, target, field, arguments, @field_ctx]
|
74
78
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
end
|
86
|
-
rescue GraphQL::ExecutionError => err
|
87
|
-
err
|
79
|
+
begin
|
80
|
+
# only run a middleware chain if there are any middleware
|
81
|
+
if middlewares.any?
|
82
|
+
chain = GraphQL::Schema::MiddlewareChain.new(
|
83
|
+
steps: middlewares + [FieldResolveStep],
|
84
|
+
arguments: resolve_arguments
|
85
|
+
)
|
86
|
+
chain.call
|
87
|
+
else
|
88
|
+
FieldResolveStep.call(*resolve_arguments)
|
88
89
|
end
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
resolve_value
|
90
|
+
rescue GraphQL::ExecutionError => err
|
91
|
+
err
|
92
|
+
end
|
93
93
|
end
|
94
94
|
|
95
|
-
|
96
95
|
# A `.call`-able suitable to be the last step in a middleware chain
|
97
96
|
module FieldResolveStep
|
98
97
|
# Execute the field's resolve method
|
@@ -1,24 +1,18 @@
|
|
1
1
|
module GraphQL
|
2
2
|
class Query
|
3
3
|
class SerialExecution
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
@target = target
|
9
|
-
@irep_node = irep_node
|
10
|
-
@execution_context = execution_context
|
11
|
-
end
|
12
|
-
|
13
|
-
def result
|
14
|
-
execution_context.strategy.selection_resolution.resolve(
|
15
|
-
execution_context.query.root_value,
|
4
|
+
module OperationResolution
|
5
|
+
def self.resolve(irep_node, target, query)
|
6
|
+
result = query.context.execution_strategy.selection_resolution.resolve(
|
7
|
+
query.root_value,
|
16
8
|
target,
|
17
|
-
irep_node,
|
18
|
-
|
9
|
+
[irep_node],
|
10
|
+
query.context,
|
19
11
|
)
|
12
|
+
|
13
|
+
result
|
20
14
|
rescue GraphQL::InvalidNullError => err
|
21
|
-
err.parent_error? ||
|
15
|
+
err.parent_error? || query.context.errors.push(err)
|
22
16
|
nil
|
23
17
|
end
|
24
18
|
end
|
@@ -2,18 +2,21 @@ module GraphQL
|
|
2
2
|
class Query
|
3
3
|
class SerialExecution
|
4
4
|
module SelectionResolution
|
5
|
-
def self.resolve(target, current_type,
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
def self.resolve(target, current_type, irep_nodes, query_ctx)
|
6
|
+
own_selections = query_ctx.query.selections(irep_nodes, current_type)
|
7
|
+
|
8
|
+
selection_result = {}
|
9
|
+
|
10
|
+
own_selections.each do |name, child_irep_nodes|
|
11
|
+
selection_result.merge!(query_ctx.execution_strategy.field_resolution.new(
|
12
|
+
child_irep_nodes,
|
13
|
+
current_type,
|
14
|
+
target,
|
15
|
+
query_ctx
|
16
|
+
).result)
|
16
17
|
end
|
18
|
+
|
19
|
+
selection_result
|
17
20
|
end
|
18
21
|
end
|
19
22
|
end
|
@@ -2,7 +2,7 @@ module GraphQL
|
|
2
2
|
class Query
|
3
3
|
class SerialExecution
|
4
4
|
module ValueResolution
|
5
|
-
def self.resolve(parent_type, field_defn, field_type, value,
|
5
|
+
def self.resolve(parent_type, field_defn, field_type, value, irep_nodes, query_ctx)
|
6
6
|
if value.nil? || value.is_a?(GraphQL::ExecutionError)
|
7
7
|
if field_type.kind.non_null?
|
8
8
|
raise GraphQL::InvalidNullError.new(parent_type.name, field_defn.name, value)
|
@@ -14,21 +14,21 @@ module GraphQL
|
|
14
14
|
when GraphQL::TypeKinds::SCALAR
|
15
15
|
field_type.coerce_result(value)
|
16
16
|
when GraphQL::TypeKinds::ENUM
|
17
|
-
field_type.coerce_result(value,
|
17
|
+
field_type.coerce_result(value, query_ctx.query.warden)
|
18
18
|
when GraphQL::TypeKinds::LIST
|
19
19
|
wrapped_type = field_type.of_type
|
20
20
|
result = value.each_with_index.map do |inner_value, index|
|
21
|
-
|
22
|
-
resolve(
|
21
|
+
inner_ctx = query_ctx.spawn(path: query_ctx.path + [index], irep_node: query_ctx.irep_node)
|
22
|
+
inner_result = resolve(
|
23
23
|
parent_type,
|
24
24
|
field_defn,
|
25
25
|
wrapped_type,
|
26
26
|
inner_value,
|
27
|
-
|
28
|
-
|
27
|
+
irep_nodes,
|
28
|
+
inner_ctx,
|
29
29
|
)
|
30
|
+
inner_result
|
30
31
|
end
|
31
|
-
irep_node.index = nil
|
32
32
|
result
|
33
33
|
when GraphQL::TypeKinds::NON_NULL
|
34
34
|
wrapped_type = field_type.of_type
|
@@ -37,30 +37,31 @@ module GraphQL
|
|
37
37
|
field_defn,
|
38
38
|
wrapped_type,
|
39
39
|
value,
|
40
|
-
|
41
|
-
|
40
|
+
irep_nodes,
|
41
|
+
query_ctx,
|
42
42
|
)
|
43
43
|
when GraphQL::TypeKinds::OBJECT
|
44
|
-
|
44
|
+
query_ctx.execution_strategy.selection_resolution.resolve(
|
45
45
|
value,
|
46
46
|
field_type,
|
47
|
-
|
48
|
-
|
47
|
+
irep_nodes,
|
48
|
+
query_ctx
|
49
49
|
)
|
50
50
|
when GraphQL::TypeKinds::UNION, GraphQL::TypeKinds::INTERFACE
|
51
|
-
|
52
|
-
|
51
|
+
query = query_ctx.query
|
52
|
+
resolved_type = query.resolve_type(value)
|
53
|
+
possible_types = query.possible_types(field_type)
|
53
54
|
|
54
55
|
if !possible_types.include?(resolved_type)
|
55
|
-
raise GraphQL::UnresolvedTypeError.new(
|
56
|
+
raise GraphQL::UnresolvedTypeError.new(irep_nodes.first.definition_name, field_type, parent_type, resolved_type, possible_types)
|
56
57
|
else
|
57
58
|
resolve(
|
58
59
|
parent_type,
|
59
60
|
field_defn,
|
60
61
|
resolved_type,
|
61
62
|
value,
|
62
|
-
|
63
|
-
|
63
|
+
irep_nodes,
|
64
|
+
query_ctx,
|
64
65
|
)
|
65
66
|
end
|
66
67
|
else
|
@@ -39,7 +39,7 @@ module GraphQL
|
|
39
39
|
validation_result = variable_type.validate_input(provided_value, @warden)
|
40
40
|
if !validation_result.valid?
|
41
41
|
@errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result)
|
42
|
-
elsif provided_value.nil?
|
42
|
+
elsif !@provided_variables.key?(variable_name) && provided_value.nil?
|
43
43
|
GraphQL::Query::LiteralInput.coerce(variable_type, default_value, {})
|
44
44
|
else
|
45
45
|
variable_type.coerce_input(provided_value)
|