graphql 0.10.9 → 0.11.0
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/graphql.rb +8 -5
- data/lib/graphql/definition_helpers/defined_by_config.rb +2 -1
- data/lib/graphql/field.rb +34 -7
- data/lib/graphql/input_object_type.rb +4 -3
- data/lib/graphql/invalid_null_error.rb +22 -0
- data/lib/graphql/language/nodes.rb +165 -58
- data/lib/graphql/language/transform.rb +5 -6
- data/lib/graphql/non_null_type.rb +0 -4
- data/lib/graphql/query.rb +1 -2
- data/lib/graphql/query/arguments.rb +39 -7
- data/lib/graphql/query/literal_input.rb +1 -1
- data/lib/graphql/query/serial_execution.rb +30 -1
- data/lib/graphql/query/serial_execution/execution_context.rb +30 -0
- data/lib/graphql/query/serial_execution/field_resolution.rb +27 -21
- data/lib/graphql/query/serial_execution/operation_resolution.rb +9 -6
- data/lib/graphql/query/serial_execution/selection_resolution.rb +49 -56
- data/lib/graphql/query/{base_execution → serial_execution}/value_resolution.rb +35 -24
- data/lib/graphql/static_validation/complexity_validator.rb +27 -0
- data/lib/graphql/static_validation/literal_validator.rb +4 -4
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -0
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
- data/lib/graphql/static_validation/validator.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/readme.md +8 -4
- data/spec/graphql/execution_error_spec.rb +7 -1
- data/spec/graphql/field_spec.rb +53 -0
- data/spec/graphql/input_object_type_spec.rb +8 -1
- data/spec/graphql/introspection/schema_type_spec.rb +2 -1
- data/spec/graphql/language/transform_spec.rb +14 -14
- data/spec/graphql/object_type_spec.rb +7 -0
- data/spec/graphql/query/arguments_spec.rb +5 -5
- data/spec/graphql/query/executor_spec.rb +31 -0
- data/spec/graphql/query/serial_execution/execution_context_spec.rb +55 -0
- data/spec/graphql/query/{base_execution → serial_execution}/value_resolution_spec.rb +1 -1
- data/spec/graphql/query/variables_spec.rb +1 -1
- data/spec/graphql/static_validation/complexity_validator.rb +15 -0
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +2 -1
- data/spec/graphql/static_validation/validator_spec.rb +1 -1
- data/spec/support/dairy_app.rb +15 -0
- data/spec/support/minimum_input_object.rb +13 -0
- metadata +14 -6
- data/lib/graphql/query/base_execution.rb +0 -32
data/lib/graphql/query.rb
CHANGED
@@ -44,7 +44,7 @@ class GraphQL::Query
|
|
44
44
|
@operations = {}
|
45
45
|
@provided_variables = variables
|
46
46
|
@document = GraphQL.parse(query_string)
|
47
|
-
@document.
|
47
|
+
@document.definitions.each do |part|
|
48
48
|
if part.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
|
49
49
|
@fragments[part.name] = part
|
50
50
|
elsif part.is_a?(GraphQL::Language::Nodes::OperationDefinition)
|
@@ -104,7 +104,6 @@ class GraphQL::Query
|
|
104
104
|
end
|
105
105
|
|
106
106
|
require 'graphql/query/arguments'
|
107
|
-
require 'graphql/query/base_execution'
|
108
107
|
require 'graphql/query/context'
|
109
108
|
require 'graphql/query/directive_chain'
|
110
109
|
require 'graphql/query/executor'
|
@@ -7,8 +7,8 @@ module GraphQL
|
|
7
7
|
extend Forwardable
|
8
8
|
|
9
9
|
def initialize(values)
|
10
|
-
@
|
11
|
-
@
|
10
|
+
@original_values = values
|
11
|
+
@argument_values = values.inject({}) do |memo, (inner_key, inner_value)|
|
12
12
|
memo[inner_key.to_s] = wrap_value(inner_value)
|
13
13
|
memo
|
14
14
|
end
|
@@ -17,28 +17,60 @@ module GraphQL
|
|
17
17
|
# @param [String, Symbol] name or index of value to access
|
18
18
|
# @return [Object] the argument at that key
|
19
19
|
def [](key)
|
20
|
-
@
|
20
|
+
@argument_values[key.to_s]
|
21
21
|
end
|
22
22
|
|
23
23
|
# Get the original Ruby hash
|
24
24
|
# @return [Hash] the original values hash
|
25
25
|
def to_h
|
26
|
-
@
|
26
|
+
@unwrapped_values ||= unwrap_value(@original_values)
|
27
27
|
end
|
28
28
|
|
29
|
-
def_delegators
|
29
|
+
def_delegators :string_key_values, :keys, :values, :each
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
33
|
def wrap_value(value)
|
34
|
-
|
34
|
+
case value
|
35
|
+
when Array
|
35
36
|
value.map { |item| wrap_value(item) }
|
36
|
-
|
37
|
+
when Hash
|
37
38
|
self.class.new(value)
|
38
39
|
else
|
39
40
|
value
|
40
41
|
end
|
41
42
|
end
|
43
|
+
|
44
|
+
def unwrap_value(value)
|
45
|
+
case value
|
46
|
+
when Array
|
47
|
+
value.map { |item| unwrap_value(item) }
|
48
|
+
when Hash
|
49
|
+
value.inject({}) do |memo, (key, value)|
|
50
|
+
memo[key] = unwrap_value(value)
|
51
|
+
memo
|
52
|
+
end
|
53
|
+
when GraphQL::Query::Arguments
|
54
|
+
value.to_h
|
55
|
+
else
|
56
|
+
value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def string_key_values
|
61
|
+
@string_key_values ||= stringify_keys(to_h)
|
62
|
+
end
|
63
|
+
|
64
|
+
def stringify_keys(value)
|
65
|
+
case value
|
66
|
+
when Hash
|
67
|
+
value.inject({}) { |memo, (k, v)| memo[k.to_s] = stringify_keys(v); memo }
|
68
|
+
when Array
|
69
|
+
value.map { |v| stringify_keys(v) }
|
70
|
+
else
|
71
|
+
value
|
72
|
+
end
|
73
|
+
end
|
42
74
|
end
|
43
75
|
end
|
44
76
|
end
|
@@ -48,7 +48,7 @@ module GraphQL
|
|
48
48
|
module InputObjectLiteral
|
49
49
|
def self.coerce(value, type, variables)
|
50
50
|
hash = {}
|
51
|
-
value.
|
51
|
+
value.arguments.each do |arg|
|
52
52
|
field_type = type.input_fields[arg.name].type
|
53
53
|
hash[arg.name] = LiteralInput.coerce(field_type, arg.value, variables)
|
54
54
|
end
|
@@ -1,10 +1,39 @@
|
|
1
1
|
module GraphQL
|
2
2
|
class Query
|
3
|
-
class SerialExecution
|
3
|
+
class SerialExecution
|
4
|
+
# This is the only required method for an Execution strategy.
|
5
|
+
# You could create a custom execution strategy and configure your schema to
|
6
|
+
# use that custom strategy instead.
|
7
|
+
#
|
8
|
+
# @param ast_operation [GraphQL::Language::Nodes::OperationDefinition] The operation definition to run
|
9
|
+
# @param root_type [GraphQL::ObjectType] either the query type or the mutation type
|
10
|
+
# @param query_obj [GraphQL::Query] the query object for this execution
|
11
|
+
# @return [Hash] a spec-compliant GraphQL result, as a hash
|
12
|
+
def execute(ast_operation, root_type, query_obj)
|
13
|
+
operation_resolution.new(
|
14
|
+
ast_operation,
|
15
|
+
root_type,
|
16
|
+
ExecutionContext.new(query_obj, self)
|
17
|
+
).result
|
18
|
+
end
|
19
|
+
|
20
|
+
def field_resolution
|
21
|
+
self.class::FieldResolution
|
22
|
+
end
|
23
|
+
|
24
|
+
def operation_resolution
|
25
|
+
self.class::OperationResolution
|
26
|
+
end
|
27
|
+
|
28
|
+
def selection_resolution
|
29
|
+
self.class::SelectionResolution
|
30
|
+
end
|
4
31
|
end
|
5
32
|
end
|
6
33
|
end
|
7
34
|
|
35
|
+
require 'graphql/query/serial_execution/execution_context'
|
36
|
+
require 'graphql/query/serial_execution/value_resolution'
|
8
37
|
require 'graphql/query/serial_execution/field_resolution'
|
9
38
|
require 'graphql/query/serial_execution/operation_resolution'
|
10
39
|
require 'graphql/query/serial_execution/selection_resolution'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Query
|
3
|
+
class SerialExecution
|
4
|
+
class ExecutionContext
|
5
|
+
attr_reader :query, :strategy
|
6
|
+
|
7
|
+
def initialize(query, strategy)
|
8
|
+
@query = query
|
9
|
+
@strategy = strategy
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_type(type)
|
13
|
+
@query.schema.types[type]
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_fragment(name)
|
17
|
+
@query.fragments[name]
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_field(type, name)
|
21
|
+
@query.schema.get_field(type, name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_error(err)
|
25
|
+
@query.context.errors << err
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -2,22 +2,30 @@ module GraphQL
|
|
2
2
|
class Query
|
3
3
|
class SerialExecution
|
4
4
|
class FieldResolution
|
5
|
-
attr_reader :ast_node, :parent_type, :target, :
|
5
|
+
attr_reader :ast_node, :parent_type, :target, :execution_context, :field, :arguments
|
6
6
|
|
7
|
-
def initialize(ast_node, parent_type, target,
|
7
|
+
def initialize(ast_node, parent_type, target, execution_context)
|
8
8
|
@ast_node = ast_node
|
9
9
|
@parent_type = parent_type
|
10
10
|
@target = target
|
11
|
-
@
|
12
|
-
@
|
13
|
-
|
14
|
-
@arguments = GraphQL::Query::LiteralInput.from_arguments(
|
11
|
+
@execution_context = execution_context
|
12
|
+
@field = execution_context.get_field(parent_type, ast_node.name)
|
13
|
+
raise("No field found on #{parent_type.name} '#{parent_type}' for '#{ast_node.name}'") unless field
|
14
|
+
@arguments = GraphQL::Query::LiteralInput.from_arguments(
|
15
|
+
ast_node.arguments,
|
16
|
+
field.arguments,
|
17
|
+
execution_context.query.variables
|
18
|
+
)
|
15
19
|
end
|
16
20
|
|
17
21
|
def result
|
18
22
|
result_name = ast_node.alias || ast_node.name
|
19
|
-
|
20
|
-
|
23
|
+
raw_value = begin
|
24
|
+
get_raw_value
|
25
|
+
rescue GraphQL::ExecutionError => err
|
26
|
+
err
|
27
|
+
end
|
28
|
+
{ result_name => get_finished_value(raw_value) }
|
21
29
|
end
|
22
30
|
|
23
31
|
private
|
@@ -25,18 +33,14 @@ module GraphQL
|
|
25
33
|
# After getting the value from the field's resolve method,
|
26
34
|
# continue by "finishing" the value, eg. executing sub-fields or coercing values
|
27
35
|
def get_finished_value(raw_value)
|
28
|
-
if raw_value.
|
29
|
-
nil
|
30
|
-
elsif raw_value.is_a?(GraphQL::ExecutionError)
|
36
|
+
if raw_value.is_a?(GraphQL::ExecutionError)
|
31
37
|
raw_value.ast_node = ast_node
|
32
|
-
|
33
|
-
nil
|
34
|
-
else
|
35
|
-
resolved_type = field.type.resolve_type(raw_value)
|
36
|
-
strategy_class = GraphQL::Query::BaseExecution::ValueResolution.get_strategy_for_kind(resolved_type.kind)
|
37
|
-
result_strategy = strategy_class.new(raw_value, resolved_type, target, parent_type, ast_node, query, execution_strategy)
|
38
|
-
result_strategy.result
|
38
|
+
execution_context.add_error(raw_value)
|
39
39
|
end
|
40
|
+
|
41
|
+
strategy_class = GraphQL::Query::SerialExecution::ValueResolution.get_strategy_for_kind(field.type.kind)
|
42
|
+
result_strategy = strategy_class.new(raw_value, field.type, target, parent_type, ast_node, execution_context)
|
43
|
+
result_strategy.result
|
40
44
|
end
|
41
45
|
|
42
46
|
|
@@ -44,12 +48,14 @@ module GraphQL
|
|
44
48
|
# - Any middleware on this schema
|
45
49
|
# - The field's resolve method
|
46
50
|
def get_raw_value
|
47
|
-
steps = query.schema.middleware + [get_middleware_proc_from_field_resolve]
|
51
|
+
steps = execution_context.query.schema.middleware + [get_middleware_proc_from_field_resolve]
|
48
52
|
chain = GraphQL::Schema::MiddlewareChain.new(
|
49
53
|
steps: steps,
|
50
|
-
arguments: [parent_type, target, field, arguments, query.context]
|
54
|
+
arguments: [parent_type, target, field, arguments, execution_context.query.context]
|
51
55
|
)
|
52
|
-
chain.call
|
56
|
+
value = chain.call
|
57
|
+
raise value if value.instance_of?(GraphQL::ExecutionError)
|
58
|
+
value
|
53
59
|
end
|
54
60
|
|
55
61
|
|
@@ -2,19 +2,22 @@ module GraphQL
|
|
2
2
|
class Query
|
3
3
|
class SerialExecution
|
4
4
|
class OperationResolution
|
5
|
-
attr_reader :query, :target, :ast_operation_definition, :
|
5
|
+
attr_reader :query, :target, :ast_operation_definition, :execution_context
|
6
6
|
|
7
|
-
def initialize(ast_operation_definition, target,
|
7
|
+
def initialize(ast_operation_definition, target, execution_context)
|
8
8
|
@ast_operation_definition = ast_operation_definition
|
9
|
-
@query = query
|
10
9
|
@target = target
|
11
|
-
@
|
10
|
+
@execution_context = execution_context
|
12
11
|
end
|
13
12
|
|
14
13
|
def result
|
15
14
|
selections = ast_operation_definition.selections
|
16
|
-
|
17
|
-
|
15
|
+
execution_context.strategy.selection_resolution.new(
|
16
|
+
nil,
|
17
|
+
target,
|
18
|
+
selections,
|
19
|
+
execution_context
|
20
|
+
).result
|
18
21
|
end
|
19
22
|
end
|
20
23
|
end
|
@@ -2,33 +2,24 @@ module GraphQL
|
|
2
2
|
class Query
|
3
3
|
class SerialExecution
|
4
4
|
class SelectionResolution
|
5
|
-
attr_reader :target, :type, :selections, :
|
5
|
+
attr_reader :target, :type, :selections, :execution_context
|
6
6
|
|
7
|
-
def initialize(target, type, selections,
|
7
|
+
def initialize(target, type, selections, execution_context)
|
8
8
|
@target = target
|
9
9
|
@type = type
|
10
10
|
@selections = selections
|
11
|
-
@
|
12
|
-
@execution_strategy = execution_strategy
|
11
|
+
@execution_context = execution_context
|
13
12
|
end
|
14
13
|
|
15
14
|
def result
|
16
|
-
|
17
|
-
# any fragments - this prevents us from resolving the same fields
|
18
|
-
# more than one time in cases where fragments repeat fields.
|
19
|
-
# Then, In a second pass, we resolve the flattened set of fields
|
20
|
-
selections
|
21
|
-
.reduce({}){|memo, ast_node|
|
22
|
-
flattened_selections = flatten_selection(ast_node)
|
23
|
-
flattened_selections.each do |name, selection|
|
24
|
-
merge_into_result(memo, selection)
|
25
|
-
end
|
26
|
-
memo
|
27
|
-
}
|
15
|
+
flatten_and_merge_selections(selections)
|
28
16
|
.values
|
29
|
-
.reduce({}){|
|
30
|
-
|
17
|
+
.reduce({}) { |result, ast_node|
|
18
|
+
result.merge(resolve_field(ast_node))
|
31
19
|
}
|
20
|
+
rescue GraphQL::InvalidNullError => err
|
21
|
+
execution_context.add_error(err) unless err.parent_error?
|
22
|
+
nil
|
32
23
|
end
|
33
24
|
|
34
25
|
private
|
@@ -46,67 +37,59 @@ module GraphQL
|
|
46
37
|
|
47
38
|
def flatten_field(ast_node)
|
48
39
|
result_name = ast_node.alias || ast_node.name
|
49
|
-
|
40
|
+
{ result_name => ast_node }
|
50
41
|
end
|
51
42
|
|
52
43
|
def flatten_inline_fragment(ast_node)
|
53
|
-
chain = GraphQL::Query::DirectiveChain.new(ast_node, query) {
|
44
|
+
chain = GraphQL::Query::DirectiveChain.new(ast_node, execution_context.query) {
|
54
45
|
flatten_fragment(ast_node)
|
55
46
|
}
|
56
47
|
chain.result
|
57
48
|
end
|
58
49
|
|
59
50
|
def flatten_fragment_spread(ast_node)
|
60
|
-
ast_fragment_defn =
|
61
|
-
chain = GraphQL::Query::DirectiveChain.new(ast_node, query) {
|
51
|
+
ast_fragment_defn = execution_context.get_fragment(ast_node.name)
|
52
|
+
chain = GraphQL::Query::DirectiveChain.new(ast_node, execution_context.query) {
|
62
53
|
flatten_fragment(ast_fragment_defn)
|
63
54
|
}
|
64
55
|
chain.result
|
65
56
|
end
|
66
57
|
|
67
58
|
def flatten_fragment(ast_fragment)
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
flat_result = {}
|
73
|
-
ast_fragment.selections.each do |selection|
|
74
|
-
flat_selection = flatten_selection(selection)
|
75
|
-
flat_selection.each do |name, selection|
|
76
|
-
merge_into_result(flat_result, selection)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
flat_result
|
59
|
+
return {} unless fragment_type_can_apply?(ast_fragment)
|
60
|
+
flatten_and_merge_selections(ast_fragment.selections)
|
80
61
|
end
|
81
62
|
|
82
63
|
def fragment_type_can_apply?(ast_fragment)
|
83
|
-
child_type =
|
64
|
+
child_type = execution_context.get_type(ast_fragment.type)
|
84
65
|
resolved_type = GraphQL::Query::TypeResolver.new(target, child_type, type).type
|
85
66
|
!resolved_type.nil?
|
86
67
|
end
|
87
68
|
|
88
69
|
def merge_fields(field1, field2)
|
89
|
-
field_type =
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
else
|
103
|
-
field2
|
104
|
-
end
|
70
|
+
field_type = execution_context.get_field(type, field2.name).type.unwrap
|
71
|
+
return field2 unless field_type.kind.fields?
|
72
|
+
|
73
|
+
# create a new ast field node merging selections from each field.
|
74
|
+
# Because of static validation, we can assume that name, alias,
|
75
|
+
# arguments, and directives are exactly the same for fields 1 and 2.
|
76
|
+
GraphQL::Language::Nodes::Field.new(
|
77
|
+
name: field2.name,
|
78
|
+
alias: field2.alias,
|
79
|
+
arguments: field2.arguments,
|
80
|
+
directives: field2.directives,
|
81
|
+
selections: field1.selections + field2.selections
|
82
|
+
)
|
105
83
|
end
|
106
84
|
|
107
85
|
def resolve_field(ast_node)
|
108
|
-
chain = GraphQL::Query::DirectiveChain.new(ast_node, query) {
|
109
|
-
|
86
|
+
chain = GraphQL::Query::DirectiveChain.new(ast_node, execution_context.query) {
|
87
|
+
execution_context.strategy.field_resolution.new(
|
88
|
+
ast_node,
|
89
|
+
type,
|
90
|
+
target,
|
91
|
+
execution_context
|
92
|
+
).result
|
110
93
|
}
|
111
94
|
chain.result
|
112
95
|
end
|
@@ -118,10 +101,20 @@ module GraphQL
|
|
118
101
|
selection.name
|
119
102
|
end
|
120
103
|
|
121
|
-
if memo.has_key?(name)
|
122
|
-
|
104
|
+
memo[name] = if memo.has_key?(name)
|
105
|
+
merge_fields(memo[name], selection)
|
123
106
|
else
|
124
|
-
|
107
|
+
selection
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def flatten_and_merge_selections(selections)
|
112
|
+
selections.reduce({}) do |result, ast_node|
|
113
|
+
flattened_selections = flatten_selection(ast_node)
|
114
|
+
flattened_selections.each do |name, selection|
|
115
|
+
merge_into_result(result, selection)
|
116
|
+
end
|
117
|
+
result
|
125
118
|
end
|
126
119
|
end
|
127
120
|
end
|