graphql 0.6.2 → 0.7.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/query.rb +7 -44
- data/lib/graphql/query/base_execution.rb +34 -0
- data/lib/graphql/query/base_execution/selected_object_resolution.rb +16 -0
- data/lib/graphql/query/directive_chain.rb +15 -4
- data/lib/graphql/query/executor.rb +53 -0
- data/lib/graphql/query/serial_execution.rb +12 -0
- data/lib/graphql/query/serial_execution/field_resolution.rb +42 -0
- data/lib/graphql/query/serial_execution/fragment_spread_resolution.rb +22 -0
- data/lib/graphql/query/serial_execution/inline_fragment_resolution.rb +21 -0
- data/lib/graphql/query/serial_execution/operation_resolution.rb +22 -0
- data/lib/graphql/query/serial_execution/selection_resolution.rb +42 -0
- data/lib/graphql/query/value_resolution.rb +76 -0
- data/lib/graphql/schema/type_validator.rb +2 -2
- data/lib/graphql/version.rb +1 -1
- data/readme.md +18 -12
- data/spec/graphql/directive_spec.rb +3 -0
- data/spec/graphql/introspection/schema_type_spec.rb +1 -0
- data/spec/graphql/query/executor_spec.rb +90 -0
- data/spec/graphql/query_spec.rb +65 -118
- data/spec/graphql/schema/type_validator_spec.rb +20 -7
- data/spec/support/dairy_app.rb +11 -0
- metadata +15 -10
- data/lib/graphql/query/field_resolution_strategy.rb +0 -87
- data/lib/graphql/query/fragment_spread_resolution_strategy.rb +0 -16
- data/lib/graphql/query/inline_fragment_resolution_strategy.rb +0 -14
- data/lib/graphql/query/operation_resolver.rb +0 -26
- data/lib/graphql/query/selection_resolver.rb +0 -20
- data/spec/graphql/query/operation_resolver_spec.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 597ceac7f7f1c006aaaf98ff69fb2320e80c08fb
|
4
|
+
data.tar.gz: 8145bb6df94810a05738c2dbde3753c7c8e8e26d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1127f6886b67be9890dfef2db3708210a319f292fafb35d49dd4f0092330717c783e886710f7573353b7582e8a3fa743367c70ca69e0740afbc11f5a87b77920
|
7
|
+
data.tar.gz: 2ff711f09df54c8e91c97045c72780d6921fcfefce04ebadeb8368b56452e4300680c64689f48b2acadd38686ec3e9b50c72dde5bfe6e780ff891d687367afe1
|
data/lib/graphql/query.rb
CHANGED
@@ -3,7 +3,7 @@ class GraphQL::Query
|
|
3
3
|
# The executor will send the field's name to the target object
|
4
4
|
# and use the result.
|
5
5
|
DEFAULT_RESOLVE = :__default_resolve
|
6
|
-
attr_reader :schema, :document, :context, :fragments, :variables
|
6
|
+
attr_reader :schema, :document, :context, :fragments, :variables, :operations, :debug
|
7
7
|
|
8
8
|
# Prepare query `query_string` on `schema`
|
9
9
|
# @param schema [GraphQL::Schema]
|
@@ -13,17 +13,12 @@ class GraphQL::Query
|
|
13
13
|
# @param debug [Boolean] if true, errors are raised, if false, errors are put in the `errors` key
|
14
14
|
# @param validate [Boolean] if true, `query_string` will be validated with {StaticValidation::Validator}
|
15
15
|
# @param operation_name [String] if the query string contains many operations, this is the one which should be executed
|
16
|
-
def initialize(schema, query_string, context: nil,
|
16
|
+
def initialize(schema, query_string, context: nil, variables: {}, debug: true, validate: true, operation_name: nil)
|
17
17
|
@schema = schema
|
18
18
|
@debug = debug
|
19
19
|
@context = Context.new(context)
|
20
20
|
|
21
21
|
@variables = variables
|
22
|
-
if params
|
23
|
-
warn("[GraphQL] params option is deprecated for GraphQL::Query#new, use variables instead")
|
24
|
-
@variables = params
|
25
|
-
end
|
26
|
-
|
27
22
|
@validate = validate
|
28
23
|
@operation_name = operation_name
|
29
24
|
@fragments = {}
|
@@ -45,39 +40,15 @@ class GraphQL::Query
|
|
45
40
|
return { "errors" => validation_errors }
|
46
41
|
end
|
47
42
|
|
48
|
-
@result ||=
|
49
|
-
|
50
|
-
rescue OperationNameMissingError => err
|
51
|
-
{"errors" => [{"message" => err.message}]}
|
52
|
-
rescue StandardError => err
|
53
|
-
@debug && raise(err)
|
54
|
-
message = "Something went wrong during query execution: #{err}" # \n #{err.backtrace.join("\n ")}"
|
55
|
-
{"errors" => [{"message" => message}]}
|
43
|
+
@result ||= Executor.new(self, @operation_name).result
|
56
44
|
end
|
57
45
|
|
58
46
|
private
|
59
47
|
|
60
|
-
def execute
|
61
|
-
return {} if @operations.none?
|
62
|
-
operation = find_operation(@operation_name, @operations)
|
63
|
-
resolver = OperationResolver.new(operation, self)
|
64
|
-
resolver.result
|
65
|
-
end
|
66
|
-
|
67
48
|
def validation_errors
|
68
49
|
@validation_errors ||= @schema.static_validator.validate(@document)
|
69
50
|
end
|
70
51
|
|
71
|
-
def find_operation(operation_name, operations)
|
72
|
-
if operations.length == 1
|
73
|
-
operations.values.first
|
74
|
-
elsif !operations.key?(operation_name)
|
75
|
-
raise OperationNameMissingError, operations.keys
|
76
|
-
else
|
77
|
-
operations[operation_name]
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
52
|
# Expose some query-specific info to field resolve functions.
|
82
53
|
# It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
|
83
54
|
class Context
|
@@ -89,20 +60,12 @@ class GraphQL::Query
|
|
89
60
|
@arbitrary_hash[key]
|
90
61
|
end
|
91
62
|
end
|
92
|
-
|
93
|
-
class OperationNameMissingError < StandardError
|
94
|
-
def initialize(names)
|
95
|
-
msg = "You must provide an operation name from: #{names.join(", ")}"
|
96
|
-
super(msg)
|
97
|
-
end
|
98
|
-
end
|
99
63
|
end
|
100
64
|
|
101
65
|
require 'graphql/query/arguments'
|
102
|
-
require 'graphql/query/
|
103
|
-
require 'graphql/query/
|
104
|
-
require 'graphql/query/
|
105
|
-
require 'graphql/query/operation_resolver'
|
106
|
-
require 'graphql/query/selection_resolver'
|
66
|
+
require 'graphql/query/base_execution'
|
67
|
+
require 'graphql/query/serial_execution'
|
68
|
+
require 'graphql/query/value_resolution'
|
107
69
|
require 'graphql/query/type_resolver'
|
108
70
|
require 'graphql/query/directive_chain'
|
71
|
+
require 'graphql/query/executor'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Query
|
3
|
+
class BaseExecution
|
4
|
+
|
5
|
+
def field_resolution
|
6
|
+
get_class :FieldResolution
|
7
|
+
end
|
8
|
+
|
9
|
+
def fragment_spread_resolution
|
10
|
+
get_class :FragmentSpreadResolution
|
11
|
+
end
|
12
|
+
|
13
|
+
def inline_fragment_resolution
|
14
|
+
get_class :InlineFragmentResolution
|
15
|
+
end
|
16
|
+
|
17
|
+
def operation_resolution
|
18
|
+
get_class :OperationResolution
|
19
|
+
end
|
20
|
+
|
21
|
+
def selection_resolution
|
22
|
+
get_class :SelectionResolution
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def get_class(class_name)
|
28
|
+
self.class.const_get(class_name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
require 'graphql/query/base_execution/selected_object_resolution'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Query
|
3
|
+
class BaseExecution
|
4
|
+
class SelectedObjectResolution
|
5
|
+
attr_reader :ast_node, :parent_type, :target, :query, :execution_strategy
|
6
|
+
def initialize(ast_node, parent_type, target, query, execution_strategy)
|
7
|
+
@ast_node = ast_node
|
8
|
+
@parent_type = parent_type
|
9
|
+
@target = target
|
10
|
+
@query = query
|
11
|
+
@execution_strategy = execution_strategy
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# TODO: directives on fragments: http://facebook.github.io/graphql/#sec-Fragment-Directives
|
1
2
|
class GraphQL::Query::DirectiveChain
|
2
3
|
DIRECTIVE_ON = {
|
3
4
|
GraphQL::Language::Nodes::Field => GraphQL::Directive::ON_FIELD,
|
@@ -12,10 +13,15 @@ class GraphQL::Query::DirectiveChain
|
|
12
13
|
}
|
13
14
|
|
14
15
|
attr_reader :result
|
15
|
-
def initialize(ast_node,
|
16
|
-
directives =
|
16
|
+
def initialize(ast_node, query, &block)
|
17
|
+
directives = query.schema.directives
|
17
18
|
on_what = DIRECTIVE_ON[ast_node.class]
|
18
|
-
ast_directives = GET_DIRECTIVES[ast_node.class].call(ast_node,
|
19
|
+
ast_directives = GET_DIRECTIVES[ast_node.class].call(ast_node, query.fragments)
|
20
|
+
|
21
|
+
if contains_skip?(ast_directives)
|
22
|
+
ast_directives = ast_directives.reject { |ast_directive| ast_directive.name == 'include' }
|
23
|
+
end
|
24
|
+
|
19
25
|
applicable_directives = ast_directives
|
20
26
|
.map { |ast_directive| [ast_directive, directives[ast_directive.name]] }
|
21
27
|
.select { |directive_pair| directive_pair.last.on.include?(on_what) }
|
@@ -24,10 +30,15 @@ class GraphQL::Query::DirectiveChain
|
|
24
30
|
@result = block.call
|
25
31
|
else
|
26
32
|
applicable_directives.map do |(ast_directive, directive)|
|
27
|
-
args = GraphQL::Query::Arguments.new(ast_directive.arguments, directive.arguments,
|
33
|
+
args = GraphQL::Query::Arguments.new(ast_directive.arguments, directive.arguments, query.variables)
|
28
34
|
@result = directive.resolve(args, block)
|
29
35
|
end
|
30
36
|
@result ||= {}
|
31
37
|
end
|
32
38
|
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def contains_skip?(directives)
|
42
|
+
directives.any? { |directive| directive.name == 'skip' }
|
43
|
+
end
|
33
44
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Query
|
3
|
+
class Executor
|
4
|
+
class OperationNameMissingError < StandardError
|
5
|
+
def initialize(names)
|
6
|
+
msg = "You must provide an operation name from: #{names.join(", ")}"
|
7
|
+
super(msg)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :query, :operation_name
|
12
|
+
def initialize(query, operation_name)
|
13
|
+
@query = query
|
14
|
+
@operation_name = operation_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def result
|
18
|
+
{"data" => execute }
|
19
|
+
rescue OperationNameMissingError => err
|
20
|
+
{"errors" => [{"message" => err.message}]}
|
21
|
+
rescue StandardError => err
|
22
|
+
query.debug && raise(err)
|
23
|
+
message = "Something went wrong during query execution: #{err}" # \n #{err.backtrace.join("\n ")}"
|
24
|
+
{"errors" => [{"message" => message}]}
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def execute
|
30
|
+
return {} if query.operations.none?
|
31
|
+
operation = find_operation(operation_name, query.operations)
|
32
|
+
if operation.operation_type == "query"
|
33
|
+
root = query.schema.query
|
34
|
+
elsif operation.operation_type == "mutation"
|
35
|
+
root = query.schema.mutation
|
36
|
+
end
|
37
|
+
execution_strategy = GraphQL::Query::SerialExecution.new
|
38
|
+
resolver = execution_strategy.operation_resolution.new(operation, root, query, execution_strategy)
|
39
|
+
resolver.result
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_operation(operation_name, operations)
|
43
|
+
if operations.length == 1
|
44
|
+
operations.values.first
|
45
|
+
elsif !operations.key?(operation_name)
|
46
|
+
raise OperationNameMissingError, operations.keys
|
47
|
+
else
|
48
|
+
operations[operation_name]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Query
|
3
|
+
class SerialExecution < GraphQL::Query::BaseExecution
|
4
|
+
end
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'graphql/query/serial_execution/field_resolution'
|
9
|
+
require 'graphql/query/serial_execution/fragment_spread_resolution'
|
10
|
+
require 'graphql/query/serial_execution/inline_fragment_resolution'
|
11
|
+
require 'graphql/query/serial_execution/operation_resolution'
|
12
|
+
require 'graphql/query/serial_execution/selection_resolution'
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Query
|
3
|
+
class SerialExecution
|
4
|
+
class FieldResolution < GraphQL::Query::BaseExecution::SelectedObjectResolution
|
5
|
+
attr_reader :field, :arguments
|
6
|
+
|
7
|
+
def initialize(ast_node, parent_type, target, query, execution_strategy)
|
8
|
+
super
|
9
|
+
@field = query.schema.get_field(parent_type, ast_node.name) || raise("No field found on #{parent_type.name} '#{parent_type}' for '#{ast_node.name}'")
|
10
|
+
@arguments = GraphQL::Query::Arguments.new(ast_node.arguments, field.arguments, query.variables)
|
11
|
+
end
|
12
|
+
|
13
|
+
def result
|
14
|
+
result_name = ast_node.alias || ast_node.name
|
15
|
+
{ result_name => result_value}
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def result_value
|
21
|
+
value = field.resolve(target, arguments, query.context)
|
22
|
+
|
23
|
+
if value == GraphQL::Query::DEFAULT_RESOLVE
|
24
|
+
begin
|
25
|
+
value = target.public_send(ast_node.name)
|
26
|
+
rescue NoMethodError => err
|
27
|
+
raise("Couldn't resolve field '#{ast_node.name}' to #{target.class} '#{target}' (resulted in #{err})")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
return nil if value.nil?
|
32
|
+
|
33
|
+
|
34
|
+
resolved_type = field.type.kind.resolve(field.type, value)
|
35
|
+
strategy_class = GraphQL::Query::ValueResolution.get_strategy_for_kind(resolved_type.kind)
|
36
|
+
result_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_node, query, execution_strategy)
|
37
|
+
result_strategy.result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Query
|
3
|
+
class SerialExecution
|
4
|
+
class FragmentSpreadResolution < GraphQL::Query::BaseExecution::SelectedObjectResolution
|
5
|
+
attr_reader :ast_fragment, :resolved_type
|
6
|
+
def initialize(ast_node, type, target, query, execution_strategy)
|
7
|
+
super
|
8
|
+
@ast_fragment = query.fragments[ast_node.name]
|
9
|
+
child_type = query.schema.types[ast_fragment.type]
|
10
|
+
@resolved_type = GraphQL::Query::TypeResolver.new(target, child_type, type).type
|
11
|
+
end
|
12
|
+
|
13
|
+
def result
|
14
|
+
return {} if resolved_type.nil?
|
15
|
+
selections = ast_fragment.selections
|
16
|
+
resolver = execution_strategy.selection_resolution.new(target, resolved_type, selections, query, execution_strategy)
|
17
|
+
resolver.result
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Query
|
3
|
+
class SerialExecution
|
4
|
+
class InlineFragmentResolution < GraphQL::Query::BaseExecution::SelectedObjectResolution
|
5
|
+
attr_reader :resolved_type
|
6
|
+
def initialize(ast_node, type, target, query, execution_strategy)
|
7
|
+
super
|
8
|
+
child_type = query.schema.types[ast_node.type]
|
9
|
+
@resolved_type = GraphQL::Query::TypeResolver.new(target, child_type, type).type
|
10
|
+
end
|
11
|
+
|
12
|
+
def result
|
13
|
+
return {} if resolved_type.nil?
|
14
|
+
selections = ast_node.selections
|
15
|
+
resolver = execution_strategy.selection_resolution.new(target, resolved_type, selections, query, execution_strategy)
|
16
|
+
resolver.result
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Query
|
3
|
+
class SerialExecution
|
4
|
+
class OperationResolution
|
5
|
+
attr_reader :query, :target, :ast_operation_definition, :execution_strategy
|
6
|
+
|
7
|
+
def initialize(ast_operation_definition, target, query, execution_strategy)
|
8
|
+
@ast_operation_definition = ast_operation_definition
|
9
|
+
@query = query
|
10
|
+
@target = target
|
11
|
+
@execution_strategy = execution_strategy
|
12
|
+
end
|
13
|
+
|
14
|
+
def result
|
15
|
+
selections = ast_operation_definition.selections
|
16
|
+
resolver = execution_strategy.selection_resolution.new(nil, target, selections, query, execution_strategy)
|
17
|
+
resolver.result
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Query
|
3
|
+
class SerialExecution
|
4
|
+
class SelectionResolution
|
5
|
+
attr_reader :target, :type, :selections, :query, :execution_strategy
|
6
|
+
|
7
|
+
RESOLUTION_STRATEGIES = {
|
8
|
+
GraphQL::Language::Nodes::Field => :field_resolution,
|
9
|
+
GraphQL::Language::Nodes::FragmentSpread => :fragment_spread_resolution,
|
10
|
+
GraphQL::Language::Nodes::InlineFragment => :inline_fragment_resolution,
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize(target, type, selections, query, execution_strategy)
|
14
|
+
@target = target
|
15
|
+
@type = type
|
16
|
+
@selections = selections
|
17
|
+
@query = query
|
18
|
+
@execution_strategy = execution_strategy
|
19
|
+
end
|
20
|
+
|
21
|
+
def result
|
22
|
+
selections.reduce({}) do |memo, ast_field|
|
23
|
+
field_value = resolve_field(ast_field)
|
24
|
+
memo.merge(field_value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def resolve_field(ast_field)
|
31
|
+
chain = GraphQL::Query::DirectiveChain.new(ast_field, query) {
|
32
|
+
strategy_name = RESOLUTION_STRATEGIES[ast_field.class]
|
33
|
+
strategy_class = execution_strategy.public_send(strategy_name)
|
34
|
+
strategy = strategy_class.new(ast_field, type, target, query, execution_strategy)
|
35
|
+
strategy.result
|
36
|
+
}
|
37
|
+
chain.result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Query
|
3
|
+
module ValueResolution
|
4
|
+
def self.get_strategy_for_kind(kind)
|
5
|
+
TYPE_KIND_STRATEGIES[kind] || raise("No value resolution strategy for #{kind}!")
|
6
|
+
end
|
7
|
+
|
8
|
+
class BaseResolution
|
9
|
+
attr_reader :value, :field_type, :target, :parent_type,
|
10
|
+
:ast_field, :query, :execution_strategy
|
11
|
+
def initialize(value, field_type, target, parent_type, ast_field, query, execution_strategy)
|
12
|
+
@value = value
|
13
|
+
@field_type = field_type
|
14
|
+
@target = target
|
15
|
+
@parent_type = parent_type
|
16
|
+
@ast_field = ast_field
|
17
|
+
@query = query
|
18
|
+
@execution_strategy = execution_strategy
|
19
|
+
end
|
20
|
+
|
21
|
+
def result
|
22
|
+
raise NotImplementedError, "Should return a value based on initialization params"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class ScalarResolution < BaseResolution
|
27
|
+
def result
|
28
|
+
field_type.coerce(value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class ListResolution < BaseResolution
|
33
|
+
def result
|
34
|
+
wrapped_type = field_type.of_type
|
35
|
+
value.map do |item|
|
36
|
+
resolved_type = wrapped_type.kind.resolve(wrapped_type, item)
|
37
|
+
strategy_class = GraphQL::Query::ValueResolution.get_strategy_for_kind(resolved_type.kind)
|
38
|
+
inner_strategy = strategy_class.new(item, resolved_type, target, parent_type, ast_field, query, execution_strategy)
|
39
|
+
inner_strategy.result
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class ObjectResolution < BaseResolution
|
45
|
+
def result
|
46
|
+
resolver = execution_strategy.selection_resolution.new(value, field_type, ast_field.selections, query, execution_strategy)
|
47
|
+
resolver.result
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class EnumResolution < BaseResolution
|
52
|
+
def result
|
53
|
+
value.to_s
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class NonNullResolution < BaseResolution
|
58
|
+
def result
|
59
|
+
wrapped_type = field_type.of_type
|
60
|
+
resolved_type = wrapped_type.kind.resolve(wrapped_type, value)
|
61
|
+
strategy_class = GraphQL::Query::ValueResolution.get_strategy_for_kind(resolved_type.kind)
|
62
|
+
inner_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, query, execution_strategy)
|
63
|
+
inner_strategy.result
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
TYPE_KIND_STRATEGIES = {
|
68
|
+
GraphQL::TypeKinds::SCALAR => ScalarResolution,
|
69
|
+
GraphQL::TypeKinds::LIST => ListResolution,
|
70
|
+
GraphQL::TypeKinds::OBJECT => ObjectResolution,
|
71
|
+
GraphQL::TypeKinds::ENUM => EnumResolution,
|
72
|
+
GraphQL::TypeKinds::NON_NULL => NonNullResolution,
|
73
|
+
}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -45,8 +45,8 @@ class GraphQL::Schema::TypeValidator
|
|
45
45
|
|
46
46
|
if type.kind.union?
|
47
47
|
union_types = type.possible_types
|
48
|
-
if union_types.
|
49
|
-
own_errors << "Union #{type_name} must be defined with
|
48
|
+
if union_types.none?
|
49
|
+
own_errors << "Union #{type_name} must be defined with 1 or more types, not 0!"
|
50
50
|
end
|
51
51
|
end
|
52
52
|
errors.push(*own_errors)
|
data/lib/graphql/version.rb
CHANGED
data/readme.md
CHANGED
@@ -91,28 +91,34 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
|
|
91
91
|
|
92
92
|
## To Do
|
93
93
|
|
94
|
-
- To match spec:
|
95
|
-
- Directives:
|
96
|
-
- `@skip` has precedence over `@include`
|
97
|
-
- directives on fragments: http://facebook.github.io/graphql/#sec-Fragment-Directives
|
98
|
-
- Union
|
99
|
-
- only require one type https://github.com/facebook/graphql/commit/972fd5aae01195b018d305c82e43f714f9a41679
|
100
94
|
- Field merging
|
101
95
|
- if you were to request a field, then request it in a fragment, it would get looked up twice
|
102
96
|
- https://github.com/graphql/graphql-js/issues/19#issuecomment-118515077
|
103
97
|
- Code clean-up
|
104
|
-
-
|
105
|
-
|
98
|
+
- Easier built-in type definition
|
99
|
+
- Make an object that accepts type objects, symbols, or corresponding Ruby classes and convertes them to GraphQL types
|
100
|
+
- Hook up that object to `DefinitionConfig`, so it can map from incoming values to GraphQL types
|
106
101
|
- Raise if you try to configure an attribute which doesn't suit the type
|
107
|
-
-
|
108
|
-
-
|
109
|
-
|
102
|
+
- ie, if you try to define `resolve` on an ObjectType, it should somehow raise
|
103
|
+
- Make better inheritance between types
|
104
|
+
- Implement a BaseType (?) and make all type classes extend that
|
105
|
+
- No more extending ObjectType!
|
106
|
+
- Move `TypeKind#unwrap` to BaseType & update all code
|
107
|
+
- Also move `TypeKind#resolve` ?
|
108
|
+
- Big ideas:
|
109
|
+
- Cook up some path other than "n+1s everywhere"
|
110
|
+
- See Sangria's `project` approach ([in progress](https://github.com/rmosolgo/graphql-ruby/pull/15))
|
111
|
+
- Try debounced approach?
|
112
|
+
- Write Ruby bindings for [libgraphqlparser](https://github.com/graphql/libgraphqlparser) and use that instead of Parslet
|
113
|
+
- Add instrumentation
|
114
|
+
- Some way to expose what queries are run, what types & fields are accessed, how long things are taking, etc
|
115
|
+
|
110
116
|
|
111
117
|
## Goals
|
112
118
|
|
113
119
|
- Implement the GraphQL spec & support a Relay front end
|
114
120
|
- Provide idiomatic, plain-Ruby API with similarities to reference implementation where possible
|
115
|
-
- Support
|
121
|
+
- Support Ruby on Rails and Relay
|
116
122
|
|
117
123
|
## Getting Involved
|
118
124
|
|
@@ -8,6 +8,8 @@ describe GraphQL::Directive do
|
|
8
8
|
# plain fields:
|
9
9
|
skipFlavor: flavor @skip(if: true)
|
10
10
|
dontSkipFlavor: flavor @skip(if: false)
|
11
|
+
dontSkipDontIncludeFlavor: flavor @skip(if: false), @include(if: false)
|
12
|
+
skipAndInclude: flavor @skip(if: true), @include(if: true)
|
11
13
|
includeFlavor: flavor @include(if: $t)
|
12
14
|
dontIncludeFlavor: flavor @include(if: $f)
|
13
15
|
# fields in fragments
|
@@ -25,6 +27,7 @@ describe GraphQL::Directive do
|
|
25
27
|
it 'intercepts fields' do
|
26
28
|
expected = { "data" =>{
|
27
29
|
"cheese" => {
|
30
|
+
"dontSkipDontIncludeFlavor" => "Brie", #skip has precedence over include
|
28
31
|
"dontSkipFlavor" => "Brie",
|
29
32
|
"includeFlavor" => "Brie",
|
30
33
|
"includeId" => 1,
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Query::Executor do
|
4
|
+
let(:debug) { false }
|
5
|
+
let(:operation_name) { nil }
|
6
|
+
let(:query) { GraphQL::Query.new(
|
7
|
+
DummySchema,
|
8
|
+
query_string,
|
9
|
+
variables: {"cheeseId" => 2},
|
10
|
+
debug: debug,
|
11
|
+
operation_name: operation_name,
|
12
|
+
)}
|
13
|
+
let(:result) { query.result }
|
14
|
+
|
15
|
+
describe "multiple operations" do
|
16
|
+
let(:query_string) { %|
|
17
|
+
query getCheese1 { cheese(id: 1) { flavor } }
|
18
|
+
query getCheese2 { cheese(id: 2) { flavor } }
|
19
|
+
|}
|
20
|
+
|
21
|
+
describe "when an operation is named" do
|
22
|
+
let(:operation_name) { "getCheese2" }
|
23
|
+
|
24
|
+
it "runs the named one" do
|
25
|
+
expected = {
|
26
|
+
"data" => {
|
27
|
+
"cheese" => {
|
28
|
+
"flavor" => "Gouda"
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
32
|
+
assert_equal(expected, result)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "when one is NOT named" do
|
37
|
+
it "returns an error" do
|
38
|
+
expected = {
|
39
|
+
"errors" => [
|
40
|
+
{"message" => "You must provide an operation name from: getCheese1, getCheese2"}
|
41
|
+
]
|
42
|
+
}
|
43
|
+
assert_equal(expected, result)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
describe 'execution order' do
|
50
|
+
let(:query_string) {%|
|
51
|
+
mutation setInOrder {
|
52
|
+
first: pushValue(value: 1)
|
53
|
+
second: pushValue(value: 5)
|
54
|
+
third: pushValue(value: 2)
|
55
|
+
fourth: replaceValues(input: {values: [6,5,4]})
|
56
|
+
}
|
57
|
+
|}
|
58
|
+
|
59
|
+
it 'executes mutations in order' do
|
60
|
+
expected = {"data"=>{
|
61
|
+
"first"=> [1],
|
62
|
+
"second"=>[1, 5],
|
63
|
+
"third"=> [1, 5, 2],
|
64
|
+
"fourth"=> [6, 5 ,4],
|
65
|
+
}}
|
66
|
+
assert_equal(expected, result)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
describe 'runtime errors' do
|
72
|
+
let(:query_string) {%| query noMilk { error }|}
|
73
|
+
describe 'if debug: false' do
|
74
|
+
let(:debug) { false }
|
75
|
+
it 'turns into error messages' do
|
76
|
+
expected = {"errors"=>[
|
77
|
+
{"message"=>"Something went wrong during query execution: This error was raised on purpose"}
|
78
|
+
]}
|
79
|
+
assert_equal(expected, result)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'if debug: true' do
|
84
|
+
let(:debug) { true }
|
85
|
+
it 'raises error' do
|
86
|
+
assert_raises(RuntimeError) { result }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/spec/graphql/query_spec.rb
CHANGED
@@ -1,45 +1,44 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe GraphQL::Query do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
... on Cheese { cheeseKind: flavor },
|
14
|
-
}
|
15
|
-
fromSource(source: COW) { id }
|
16
|
-
fromSheep: fromSource(source: SHEEP) { id }
|
17
|
-
firstSheep: searchDairy(product: {source: SHEEP}) {
|
18
|
-
__typename,
|
19
|
-
... dairyFields,
|
20
|
-
... milkFields
|
21
|
-
}
|
22
|
-
favoriteEdible { __typename, fatContent }
|
4
|
+
let(:query_string) { %|
|
5
|
+
query getFlavor($cheeseId: Int!) {
|
6
|
+
brie: cheese(id: 1) { ...cheeseFields, taste: flavor },
|
7
|
+
cheese(id: $cheeseId) {
|
8
|
+
__typename,
|
9
|
+
id,
|
10
|
+
...cheeseFields,
|
11
|
+
... edibleFields,
|
12
|
+
... on Cheese { cheeseKind: flavor },
|
23
13
|
}
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
14
|
+
fromSource(source: COW) { id }
|
15
|
+
fromSheep: fromSource(source: SHEEP) { id }
|
16
|
+
firstSheep: searchDairy(product: {source: SHEEP}) {
|
17
|
+
__typename,
|
18
|
+
... dairyFields,
|
19
|
+
... milkFields
|
30
20
|
}
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
21
|
+
favoriteEdible { __typename, fatContent }
|
22
|
+
}
|
23
|
+
fragment cheeseFields on Cheese { flavor }
|
24
|
+
fragment edibleFields on Edible { fatContent }
|
25
|
+
fragment milkFields on Milk { source }
|
26
|
+
fragment dairyFields on AnimalProduct {
|
27
|
+
... on Cheese { flavor }
|
28
|
+
... on Milk { source }
|
29
|
+
}
|
30
|
+
|}
|
31
|
+
let(:debug) { false }
|
32
|
+
let(:operation_name) { nil }
|
33
|
+
let(:query) { GraphQL::Query.new(
|
34
|
+
DummySchema,
|
35
|
+
query_string,
|
36
|
+
variables: {"cheeseId" => 2},
|
37
|
+
debug: debug,
|
38
|
+
operation_name: operation_name,
|
39
|
+
)}
|
40
|
+
let(:result) { query.result }
|
41
|
+
describe '#result' do
|
43
42
|
it 'returns fields on objects' do
|
44
43
|
expected = {"data"=> {
|
45
44
|
"brie" => { "flavor" => "Brie", "taste" => "Brie" },
|
@@ -58,96 +57,44 @@ describe GraphQL::Query do
|
|
58
57
|
assert_equal(expected, result)
|
59
58
|
end
|
60
59
|
|
61
|
-
it
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
]}
|
73
|
-
assert_equal(expected, result)
|
74
|
-
end
|
75
|
-
end
|
60
|
+
describe "when it hits null objects" do
|
61
|
+
let(:query_string) {%|
|
62
|
+
{
|
63
|
+
maybeNull {
|
64
|
+
cheese {
|
65
|
+
flavor,
|
66
|
+
similarCheeses(source: [SHEEP]) { flavor }
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|}
|
76
71
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
72
|
+
it "skips null objects" do
|
73
|
+
expected = {"data"=> {
|
74
|
+
"maybeNull" => { "cheese" => nil }
|
75
|
+
}}
|
76
|
+
assert_equal(expected, result)
|
82
77
|
end
|
83
78
|
end
|
79
|
+
end
|
84
80
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
it "doesn't blow up" do
|
89
|
-
assert_equal({"data"=> {}}, result)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
describe "empty string" do
|
94
|
-
let(:query_string) { "" }
|
95
|
-
it "doesn't blow up" do
|
96
|
-
assert_equal({"data"=> {}}, result)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
81
|
+
it 'exposes fragments' do
|
82
|
+
assert_equal(GraphQL::Language::Nodes::FragmentDefinition, query.fragments['cheeseFields'].class)
|
83
|
+
end
|
100
84
|
|
101
|
-
describe "multiple operations" do
|
102
|
-
let(:query_string) { %|
|
103
|
-
query getCheese1 { cheese(id: 1) { flavor } }
|
104
|
-
query getCheese2 { cheese(id: 2) { flavor } }
|
105
|
-
|}
|
106
|
-
describe "when an operation is named" do
|
107
|
-
let(:operation_name) { "getCheese2" }
|
108
|
-
it "runs the named one" do
|
109
|
-
expected = {
|
110
|
-
"data" => {
|
111
|
-
"cheese" => {
|
112
|
-
"flavor" => "Gouda"
|
113
|
-
}
|
114
|
-
}
|
115
|
-
}
|
116
|
-
assert_equal(expected, result)
|
117
|
-
end
|
118
|
-
end
|
119
85
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
]
|
126
|
-
}
|
127
|
-
assert_equal(expected, result)
|
128
|
-
end
|
86
|
+
describe "malformed queries" do
|
87
|
+
describe "whitespace-only" do
|
88
|
+
let(:query_string) { " " }
|
89
|
+
it "doesn't blow up" do
|
90
|
+
assert_equal({"data"=> {}}, result)
|
129
91
|
end
|
130
92
|
end
|
131
93
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
first: pushValue(value: 1)
|
137
|
-
second: pushValue(value: 5)
|
138
|
-
third: pushValue(value: 2)
|
139
|
-
fourth: replaceValues(input: {values: [6,5,4]})
|
140
|
-
}
|
141
|
-
|}
|
142
|
-
|
143
|
-
it 'executes mutations in order' do
|
144
|
-
expected = {"data"=>{
|
145
|
-
"first"=> [1],
|
146
|
-
"second"=>[1, 5],
|
147
|
-
"third"=> [1, 5, 2],
|
148
|
-
"fourth"=> [6, 5 ,4],
|
149
|
-
}}
|
150
|
-
assert_equal(expected, result)
|
94
|
+
describe "empty string" do
|
95
|
+
let(:query_string) { "" }
|
96
|
+
it "doesn't blow up" do
|
97
|
+
assert_equal({"data"=> {}}, result)
|
151
98
|
end
|
152
99
|
end
|
153
100
|
end
|
@@ -50,19 +50,32 @@ describe GraphQL::Schema::TypeValidator do
|
|
50
50
|
|
51
51
|
describe "when a Union isnt valid" do
|
52
52
|
let(:object) {
|
53
|
+
union_types = types
|
53
54
|
GraphQL::UnionType.define do
|
54
55
|
name "Something"
|
55
56
|
description "some union"
|
56
|
-
possible_types
|
57
|
+
possible_types union_types
|
57
58
|
end
|
58
59
|
}
|
59
60
|
let(:errors) { e = []; GraphQL::Schema::TypeValidator.new.validate(object, e); e;}
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
|
62
|
+
describe 'when non-object types' do
|
63
|
+
let(:types) { [DairyProductInputType] }
|
64
|
+
it 'must be must be only object types' do
|
65
|
+
expected = [
|
66
|
+
"Something.possible_types must be objects, but some aren't: DairyProductInput"
|
67
|
+
]
|
68
|
+
assert_equal(expected, errors)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
describe 'when no types' do
|
72
|
+
let(:types) { [] }
|
73
|
+
it 'must have a type' do
|
74
|
+
expected = [
|
75
|
+
"Union Something must be defined with 1 or more types, not 0!"
|
76
|
+
]
|
77
|
+
assert_equal(expected, errors)
|
78
|
+
end
|
66
79
|
end
|
67
80
|
end
|
68
81
|
end
|
data/spec/support/dairy_app.rb
CHANGED
@@ -62,6 +62,12 @@ MilkType = GraphQL::ObjectType.define do
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
+
MaybeNullType = GraphQL::ObjectType.define do
|
66
|
+
name "MaybeNull"
|
67
|
+
description "An object whose fields return nil"
|
68
|
+
field :cheese, CheeseType
|
69
|
+
end
|
70
|
+
|
65
71
|
DairyProductUnion = GraphQL::UnionType.define do
|
66
72
|
name "DairyProduct"
|
67
73
|
description "Kinds of food made from milk"
|
@@ -131,6 +137,11 @@ QueryType = GraphQL::ObjectType.define do
|
|
131
137
|
type GraphQL::STRING_TYPE
|
132
138
|
resolve -> (t, a, c) { raise("This error was raised on purpose") }
|
133
139
|
end
|
140
|
+
|
141
|
+
# To test possibly-null fields
|
142
|
+
field :maybeNull, MaybeNullType do
|
143
|
+
resolve -> (t, a, c) { OpenStruct.new(cheese: nil) }
|
144
|
+
end
|
134
145
|
end
|
135
146
|
|
136
147
|
GLOBAL_VALUES = []
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-08-
|
11
|
+
date: 2015-08-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parslet
|
@@ -206,13 +206,18 @@ files:
|
|
206
206
|
- lib/graphql/object_type.rb
|
207
207
|
- lib/graphql/query.rb
|
208
208
|
- lib/graphql/query/arguments.rb
|
209
|
+
- lib/graphql/query/base_execution.rb
|
210
|
+
- lib/graphql/query/base_execution/selected_object_resolution.rb
|
209
211
|
- lib/graphql/query/directive_chain.rb
|
210
|
-
- lib/graphql/query/
|
211
|
-
- lib/graphql/query/
|
212
|
-
- lib/graphql/query/
|
213
|
-
- lib/graphql/query/
|
214
|
-
- lib/graphql/query/
|
212
|
+
- lib/graphql/query/executor.rb
|
213
|
+
- lib/graphql/query/serial_execution.rb
|
214
|
+
- lib/graphql/query/serial_execution/field_resolution.rb
|
215
|
+
- lib/graphql/query/serial_execution/fragment_spread_resolution.rb
|
216
|
+
- lib/graphql/query/serial_execution/inline_fragment_resolution.rb
|
217
|
+
- lib/graphql/query/serial_execution/operation_resolution.rb
|
218
|
+
- lib/graphql/query/serial_execution/selection_resolution.rb
|
215
219
|
- lib/graphql/query/type_resolver.rb
|
220
|
+
- lib/graphql/query/value_resolution.rb
|
216
221
|
- lib/graphql/repl.rb
|
217
222
|
- lib/graphql/scalar_type.rb
|
218
223
|
- lib/graphql/schema.rb
|
@@ -263,7 +268,7 @@ files:
|
|
263
268
|
- spec/graphql/language/transform_spec.rb
|
264
269
|
- spec/graphql/language/visitor_spec.rb
|
265
270
|
- spec/graphql/object_type_spec.rb
|
266
|
-
- spec/graphql/query/
|
271
|
+
- spec/graphql/query/executor_spec.rb
|
267
272
|
- spec/graphql/query_spec.rb
|
268
273
|
- spec/graphql/schema/field_validator_spec.rb
|
269
274
|
- spec/graphql/schema/type_reducer_spec.rb
|
@@ -312,7 +317,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
312
317
|
version: '0'
|
313
318
|
requirements: []
|
314
319
|
rubyforge_project:
|
315
|
-
rubygems_version: 2.
|
320
|
+
rubygems_version: 2.2.0
|
316
321
|
signing_key:
|
317
322
|
specification_version: 4
|
318
323
|
summary: A GraphQL implementation for Ruby
|
@@ -331,7 +336,7 @@ test_files:
|
|
331
336
|
- spec/graphql/language/transform_spec.rb
|
332
337
|
- spec/graphql/language/visitor_spec.rb
|
333
338
|
- spec/graphql/object_type_spec.rb
|
334
|
-
- spec/graphql/query/
|
339
|
+
- spec/graphql/query/executor_spec.rb
|
335
340
|
- spec/graphql/query_spec.rb
|
336
341
|
- spec/graphql/schema/field_validator_spec.rb
|
337
342
|
- spec/graphql/schema/type_reducer_spec.rb
|
@@ -1,87 +0,0 @@
|
|
1
|
-
class GraphQL::Query::FieldResolutionStrategy
|
2
|
-
attr_reader :result, :result_value
|
3
|
-
|
4
|
-
def initialize(ast_field, parent_type, target, operation_resolver)
|
5
|
-
field_name = ast_field.name
|
6
|
-
field = operation_resolver.query.schema.get_field(parent_type, field_name) || raise("No field found on #{parent_type.name} '#{parent_type}' for '#{field_name}'")
|
7
|
-
arguments = GraphQL::Query::Arguments.new(ast_field.arguments, field.arguments, operation_resolver.variables)
|
8
|
-
value = field.resolve(target, arguments, operation_resolver.context)
|
9
|
-
if value.nil?
|
10
|
-
@result_value = value
|
11
|
-
else
|
12
|
-
if value == GraphQL::Query::DEFAULT_RESOLVE
|
13
|
-
begin
|
14
|
-
value = target.send(field_name)
|
15
|
-
rescue NoMethodError => err
|
16
|
-
raise("Couldn't resolve field '#{field_name}' to #{target.class} '#{target}' (resulted in #{err})")
|
17
|
-
end
|
18
|
-
end
|
19
|
-
resolved_type = field.type.kind.resolve(field.type, value)
|
20
|
-
strategy_class = self.class.get_strategy_for_kind(resolved_type.kind)
|
21
|
-
result_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, operation_resolver)
|
22
|
-
@result_value = result_strategy.result
|
23
|
-
end
|
24
|
-
result_name = ast_field.alias || ast_field.name
|
25
|
-
@result = { result_name => @result_value}
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.get_strategy_for_kind(kind)
|
29
|
-
FIELD_TYPE_KIND_STRATEGIES[kind] || raise("No strategy for #{kind}")
|
30
|
-
end
|
31
|
-
|
32
|
-
class ScalarResolutionStrategy
|
33
|
-
attr_reader :result
|
34
|
-
def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
|
35
|
-
@result = field_type.coerce(value)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class ListResolutionStrategy
|
40
|
-
attr_reader :result
|
41
|
-
def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
|
42
|
-
wrapped_type = field_type.of_type
|
43
|
-
@result = value.map do |item|
|
44
|
-
resolved_type = wrapped_type.kind.resolve(wrapped_type, item)
|
45
|
-
strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
|
46
|
-
inner_strategy = strategy_class.new(item, resolved_type, target, parent_type, ast_field, operation_resolver)
|
47
|
-
inner_strategy.result
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class ObjectResolutionStrategy
|
53
|
-
attr_reader :result
|
54
|
-
def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
|
55
|
-
resolver = GraphQL::Query::SelectionResolver.new(value, field_type, ast_field.selections, operation_resolver)
|
56
|
-
@result = resolver.result
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
class EnumResolutionStrategy
|
61
|
-
attr_reader :result
|
62
|
-
def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
|
63
|
-
@result = value.to_s
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
class NonNullResolutionStrategy
|
68
|
-
attr_reader :result
|
69
|
-
def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
|
70
|
-
wrapped_type = field_type.of_type
|
71
|
-
resolved_type = wrapped_type.kind.resolve(wrapped_type, value)
|
72
|
-
strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
|
73
|
-
inner_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, operation_resolver)
|
74
|
-
@result = inner_strategy.result
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
FIELD_TYPE_KIND_STRATEGIES = {
|
81
|
-
GraphQL::TypeKinds::SCALAR => ScalarResolutionStrategy,
|
82
|
-
GraphQL::TypeKinds::LIST => ListResolutionStrategy,
|
83
|
-
GraphQL::TypeKinds::OBJECT => ObjectResolutionStrategy,
|
84
|
-
GraphQL::TypeKinds::ENUM => EnumResolutionStrategy,
|
85
|
-
GraphQL::TypeKinds::NON_NULL => NonNullResolutionStrategy,
|
86
|
-
}
|
87
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
class GraphQL::Query::FragmentSpreadResolutionStrategy
|
2
|
-
attr_reader :result
|
3
|
-
def initialize(ast_fragment_spread, type, target, operation_resolver)
|
4
|
-
fragments = operation_resolver.query.fragments
|
5
|
-
fragment_def = fragments[ast_fragment_spread.name]
|
6
|
-
child_type = operation_resolver.query.schema.types[fragment_def.type]
|
7
|
-
resolved_type = GraphQL::Query::TypeResolver.new(target, child_type, type).type
|
8
|
-
if resolved_type.nil?
|
9
|
-
@result = {}
|
10
|
-
else
|
11
|
-
selections = fragment_def.selections
|
12
|
-
resolver = GraphQL::Query::SelectionResolver.new(target, resolved_type, selections, operation_resolver)
|
13
|
-
@result = resolver.result
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
class GraphQL::Query::InlineFragmentResolutionStrategy
|
2
|
-
attr_reader :result
|
3
|
-
def initialize(ast_inline_fragment, type, target, operation_resolver)
|
4
|
-
child_type = operation_resolver.query.schema.types[ast_inline_fragment.type]
|
5
|
-
resolved_type = GraphQL::Query::TypeResolver.new(target, child_type, type).type
|
6
|
-
if resolved_type.nil?
|
7
|
-
@result = {}
|
8
|
-
else
|
9
|
-
selections = ast_inline_fragment.selections
|
10
|
-
resolver = GraphQL::Query::SelectionResolver.new(target, resolved_type, selections, operation_resolver)
|
11
|
-
@result = resolver.result
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
class GraphQL::Query::OperationResolver
|
2
|
-
attr_reader :variables, :query, :context
|
3
|
-
|
4
|
-
def initialize(operation_definition, query)
|
5
|
-
@operation_definition = operation_definition
|
6
|
-
@variables = query.variables
|
7
|
-
@query = query
|
8
|
-
@context = query.context
|
9
|
-
end
|
10
|
-
|
11
|
-
def result
|
12
|
-
@result ||= execute(@operation_definition, query)
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def execute(op_def, query)
|
18
|
-
root = if op_def.operation_type == "query"
|
19
|
-
query.schema.query
|
20
|
-
elsif op_def.operation_type == "mutation"
|
21
|
-
query.schema.mutation
|
22
|
-
end
|
23
|
-
resolver = GraphQL::Query::SelectionResolver.new(nil, root, op_def.selections, self)
|
24
|
-
resolver.result
|
25
|
-
end
|
26
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
class GraphQL::Query::SelectionResolver
|
2
|
-
attr_reader :result
|
3
|
-
|
4
|
-
RESOLUTION_STRATEGIES = {
|
5
|
-
GraphQL::Language::Nodes::Field => GraphQL::Query::FieldResolutionStrategy,
|
6
|
-
GraphQL::Language::Nodes::FragmentSpread => GraphQL::Query::FragmentSpreadResolutionStrategy,
|
7
|
-
GraphQL::Language::Nodes::InlineFragment => GraphQL::Query::InlineFragmentResolutionStrategy,
|
8
|
-
}
|
9
|
-
|
10
|
-
def initialize(target, type, selections, operation_resolver)
|
11
|
-
@result = selections.reduce({}) do |memo, ast_field|
|
12
|
-
chain = GraphQL::Query::DirectiveChain.new(ast_field, operation_resolver) {
|
13
|
-
strategy_class = RESOLUTION_STRATEGIES[ast_field.class]
|
14
|
-
strategy = strategy_class.new(ast_field, type, target, operation_resolver)
|
15
|
-
strategy.result
|
16
|
-
}
|
17
|
-
memo.merge(chain.result)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe GraphQL::Query::OperationResolver do
|
4
|
-
let(:operation) { GraphQL.parse("query getCheese($cheeseId: Int!) { cheese(id: $cheeseId) { name }}", as: :operation_definition) }
|
5
|
-
let(:variables) { {"cheeseId" => 1}}
|
6
|
-
let(:query) { OpenStruct.new(variables: variables, context: nil) }
|
7
|
-
let(:resolver) { GraphQL::Query::OperationResolver.new(operation, query)}
|
8
|
-
|
9
|
-
describe "variables" do
|
10
|
-
it 'returns variables by name' do
|
11
|
-
assert_equal(1, resolver.variables["cheeseId"])
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|