graphql 0.6.2 → 0.7.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/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
|