graphql 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql.rb +2 -0
- data/lib/graphql/base_type.rb +79 -0
- data/lib/graphql/enum_type.rb +1 -3
- data/lib/graphql/input_object_type.rb +2 -2
- data/lib/graphql/interface_type.rb +3 -22
- data/lib/graphql/list_type.rb +5 -3
- data/lib/graphql/non_null_type.rb +4 -2
- data/lib/graphql/object_type.rb +1 -34
- data/lib/graphql/query.rb +10 -5
- data/lib/graphql/query/arguments.rb +1 -1
- data/lib/graphql/query/base_execution.rb +21 -2
- data/lib/graphql/query/base_execution/value_resolution.rb +82 -0
- data/lib/graphql/query/executor.rb +7 -5
- data/lib/graphql/query/parallel_execution.rb +101 -0
- data/lib/graphql/query/serial_execution/field_resolution.rb +18 -9
- data/lib/graphql/scalar_type.rb +3 -3
- data/lib/graphql/schema.rb +4 -0
- data/lib/graphql/schema/type_map.rb +34 -0
- data/lib/graphql/schema/type_reducer.rb +14 -16
- data/lib/graphql/static_validation/arguments_validator.rb +8 -8
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +6 -8
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +7 -11
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +1 -1
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +25 -4
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
- data/lib/graphql/static_validation/type_stack.rb +3 -3
- data/lib/graphql/type_kinds.rb +0 -19
- data/lib/graphql/union_type.rb +3 -13
- data/lib/graphql/version.rb +1 -1
- data/readme.md +0 -11
- data/spec/graphql/base_type_spec.rb +24 -0
- data/spec/graphql/object_type_spec.rb +0 -20
- data/spec/graphql/query/parallel_execution_spec.rb +29 -0
- data/spec/graphql/query_spec.rb +23 -6
- data/spec/graphql/schema/type_reducer_spec.rb +25 -0
- data/spec/graphql/static_validation/validator_spec.rb +4 -3
- data/spec/spec_helper.rb +1 -1
- data/spec/support/dairy_app.rb +6 -2
- data/spec/support/parallel_schema.rb +31 -0
- metadata +26 -3
- data/lib/graphql/query/value_resolution.rb +0 -76
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9893aa36baed8d8eef998b94e2a7e1e160f6bc30
|
4
|
+
data.tar.gz: 838caa42e67801cf03761deae983200af1eccb71
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b54e1f57376d148e3c8f6df3043bb4cde33f27374b02c3022ce96ebd39d5fc3bda70e60737501c29963ec320e510c1c2f593b904f651dfcd1603e24c074aa3a6
|
7
|
+
data.tar.gz: f3086c08018736a123143ae7221fc4b7ea7df30430eabd43d154bace39364235ad1383ca55022389ec8b115fe6944e6d416340e9dfb88c2e25bf13d5a1b06850
|
data/lib/graphql.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "celluloid/current"
|
1
2
|
require "json"
|
2
3
|
require "parslet"
|
3
4
|
require "singleton"
|
@@ -20,6 +21,7 @@ end
|
|
20
21
|
# Order matters for these:
|
21
22
|
|
22
23
|
require 'graphql/definition_helpers'
|
24
|
+
require 'graphql/base_type'
|
23
25
|
require 'graphql/object_type'
|
24
26
|
|
25
27
|
require 'graphql/enum_type'
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module GraphQL
|
2
|
+
# The parent for all type classes.
|
3
|
+
class BaseType
|
4
|
+
include GraphQL::DefinitionHelpers::NonNullWithBang
|
5
|
+
include GraphQL::DefinitionHelpers::DefinedByConfig
|
6
|
+
|
7
|
+
# @param other [GraphQL::BaseType] compare to this object
|
8
|
+
# @return [Boolean] are these types equivalent? (incl. non-null, list)
|
9
|
+
def ==(other)
|
10
|
+
if other.is_a?(GraphQL::BaseType)
|
11
|
+
self.to_s == other.to_s
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# If this type is modifying an underlying type,
|
18
|
+
# return the underlying type. (Otherwise, return `self`.)
|
19
|
+
def unwrap
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
module ModifiesAnotherType
|
24
|
+
def unwrap
|
25
|
+
self.of_type.unwrap
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Find out which possible type to use for `value`.
|
30
|
+
# Returns self if there are no possible types (ie, not Union or Interface)
|
31
|
+
def resolve_type(value)
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
module HasPossibleTypes
|
36
|
+
# Return the implementing type for `object`.
|
37
|
+
# The default implementation assumes that there's a type with the same name as `object.class.name`.
|
38
|
+
# Maybe you'll need to override this in your own interfaces!
|
39
|
+
#
|
40
|
+
# @param object [Object] the object which needs a type to expose it
|
41
|
+
# @return [GraphQL::ObjectType] the type which should expose `object`
|
42
|
+
def resolve_type(object)
|
43
|
+
instance_exec(object, &@resolve_type_proc)
|
44
|
+
end
|
45
|
+
|
46
|
+
# The default implementation of {#resolve_type} gets `object.class.name`
|
47
|
+
# and finds a type with the same name
|
48
|
+
DEFAULT_RESOLVE_TYPE = -> (object) {
|
49
|
+
type_name = object.class.name
|
50
|
+
possible_types.find {|t| t.name == type_name}
|
51
|
+
}
|
52
|
+
|
53
|
+
def resolve_type=(new_proc)
|
54
|
+
@resolve_type_proc = new_proc || DEFAULT_RESOLVE_TYPE
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Print the human-readable name of this type
|
59
|
+
def to_s
|
60
|
+
Printer.instance.print(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
alias :inspect :to_s
|
64
|
+
|
65
|
+
# Print a type, using the query-style naming pattern
|
66
|
+
class Printer
|
67
|
+
include Singleton
|
68
|
+
def print(type)
|
69
|
+
if type.kind.non_null?
|
70
|
+
"#{print(type.of_type)}!"
|
71
|
+
elsif type.kind.list?
|
72
|
+
"[#{print(type.of_type)}]"
|
73
|
+
else
|
74
|
+
type.name
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/graphql/enum_type.rb
CHANGED
@@ -10,9 +10,7 @@
|
|
10
10
|
# value("RUBY", "A very dynamic language aimed at programmer happiness")
|
11
11
|
# value("JAVASCRIPT", "Accidental lingua franca of the web")
|
12
12
|
# end
|
13
|
-
class GraphQL::EnumType
|
14
|
-
include GraphQL::DefinitionHelpers::NonNullWithBang
|
15
|
-
include GraphQL::DefinitionHelpers::DefinedByConfig
|
13
|
+
class GraphQL::EnumType < GraphQL::BaseType
|
16
14
|
attr_accessor :name, :description, :values
|
17
15
|
defined_by_config :name, :description, :values
|
18
16
|
|
@@ -7,8 +7,8 @@
|
|
7
7
|
# input_field :number, !types.Int
|
8
8
|
# end
|
9
9
|
#
|
10
|
-
class GraphQL::InputObjectType < GraphQL::
|
11
|
-
attr_accessor :input_fields
|
10
|
+
class GraphQL::InputObjectType < GraphQL::BaseType
|
11
|
+
attr_accessor :name, :description, :input_fields
|
12
12
|
defined_by_config :name, :description, :input_fields
|
13
13
|
|
14
14
|
def input_fields=(new_fields)
|
@@ -10,15 +10,10 @@
|
|
10
10
|
# field :release_year, types.Int
|
11
11
|
# end
|
12
12
|
#
|
13
|
-
class GraphQL::InterfaceType < GraphQL::
|
13
|
+
class GraphQL::InterfaceType < GraphQL::BaseType
|
14
|
+
include GraphQL::BaseType::HasPossibleTypes
|
14
15
|
defined_by_config :name, :description, :fields, :resolve_type
|
15
|
-
|
16
|
-
# The default implementation of {#resolve_type} gets `object.class.name`
|
17
|
-
# and finds a type with the same name
|
18
|
-
DEFAULT_RESOLVE_TYPE = -> (object) {
|
19
|
-
type_name = object.class.name
|
20
|
-
possible_types.find {|t| t.name == type_name}
|
21
|
-
}
|
16
|
+
attr_accessor :name, :description, :fields
|
22
17
|
|
23
18
|
def kind
|
24
19
|
GraphQL::TypeKinds::INTERFACE
|
@@ -28,18 +23,4 @@ class GraphQL::InterfaceType < GraphQL::ObjectType
|
|
28
23
|
def possible_types
|
29
24
|
@possible_types ||= []
|
30
25
|
end
|
31
|
-
|
32
|
-
# Return the implementing type for `object`.
|
33
|
-
# The default implementation assumes that there's a type with the same name as `object.class.name`.
|
34
|
-
# Maybe you'll need to override this in your own interfaces!
|
35
|
-
#
|
36
|
-
# @param object [Object] the object which needs a type to expose it
|
37
|
-
# @return [GraphQL::ObjectType] the type which should expose `object`
|
38
|
-
def resolve_type(object)
|
39
|
-
instance_exec(object, &@resolve_type_proc)
|
40
|
-
end
|
41
|
-
|
42
|
-
def resolve_type=(new_proc)
|
43
|
-
@resolve_type_proc = new_proc || DEFAULT_RESOLVE_TYPE
|
44
|
-
end
|
45
26
|
end
|
data/lib/graphql/list_type.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# A list type wraps another type.
|
2
2
|
#
|
3
|
-
#
|
4
|
-
class GraphQL::ListType < GraphQL::
|
5
|
-
|
3
|
+
# Get the underlying type with {#unwrap}
|
4
|
+
class GraphQL::ListType < GraphQL::BaseType
|
5
|
+
include GraphQL::BaseType::ModifiesAnotherType
|
6
|
+
attr_reader :of_type, :name
|
6
7
|
def initialize(of_type:)
|
7
8
|
@name = "List"
|
8
9
|
@of_type = of_type
|
9
10
|
end
|
11
|
+
|
10
12
|
def kind
|
11
13
|
GraphQL::TypeKinds::LIST
|
12
14
|
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
# A non-null type wraps another type.
|
2
2
|
#
|
3
|
-
#
|
4
|
-
class GraphQL::NonNullType < GraphQL::
|
3
|
+
# Get the underlying type with {#unwrap}
|
4
|
+
class GraphQL::NonNullType < GraphQL::BaseType
|
5
|
+
include GraphQL::BaseType::ModifiesAnotherType
|
6
|
+
|
5
7
|
attr_reader :of_type
|
6
8
|
def initialize(of_type:)
|
7
9
|
@of_type = of_type
|
data/lib/graphql/object_type.rb
CHANGED
@@ -19,9 +19,7 @@
|
|
19
19
|
# end
|
20
20
|
# end
|
21
21
|
#
|
22
|
-
class GraphQL::ObjectType
|
23
|
-
include GraphQL::DefinitionHelpers::NonNullWithBang
|
24
|
-
include GraphQL::DefinitionHelpers::DefinedByConfig
|
22
|
+
class GraphQL::ObjectType < GraphQL::BaseType
|
25
23
|
defined_by_config :name, :description, :interfaces, :fields
|
26
24
|
attr_accessor :name, :description, :interfaces, :fields
|
27
25
|
|
@@ -43,35 +41,4 @@ class GraphQL::ObjectType
|
|
43
41
|
def kind
|
44
42
|
GraphQL::TypeKinds::OBJECT
|
45
43
|
end
|
46
|
-
|
47
|
-
# Print the human-readable name of this type
|
48
|
-
def to_s
|
49
|
-
Printer.instance.print(self)
|
50
|
-
end
|
51
|
-
|
52
|
-
alias :inspect :to_s
|
53
|
-
|
54
|
-
# @param other [GraphQL::ObjectType] compare to this object
|
55
|
-
# @return [Boolean] are these types equivalent? (incl. non-null, list)
|
56
|
-
def ==(other)
|
57
|
-
if other.is_a?(GraphQL::ObjectType)
|
58
|
-
self.to_s == other.to_s
|
59
|
-
else
|
60
|
-
super
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# Print a type, using the query-style naming pattern
|
65
|
-
class Printer
|
66
|
-
include Singleton
|
67
|
-
def print(type)
|
68
|
-
if type.kind.non_null?
|
69
|
-
"#{print(type.of_type)}!"
|
70
|
-
elsif type.kind.list?
|
71
|
-
"[#{print(type.of_type)}]"
|
72
|
-
else
|
73
|
-
type.name
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
44
|
end
|
data/lib/graphql/query.rb
CHANGED
@@ -16,7 +16,7 @@ class GraphQL::Query
|
|
16
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
|
-
@context = Context.new(context)
|
19
|
+
@context = Context.new(values: context)
|
20
20
|
|
21
21
|
@variables = variables
|
22
22
|
@validate = validate
|
@@ -52,12 +52,17 @@ class GraphQL::Query
|
|
52
52
|
# Expose some query-specific info to field resolve functions.
|
53
53
|
# It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
|
54
54
|
class Context
|
55
|
-
|
56
|
-
|
55
|
+
attr_accessor :execution_strategy, :ast_node
|
56
|
+
def initialize(values:)
|
57
|
+
@values = values
|
57
58
|
end
|
58
59
|
|
59
60
|
def [](key)
|
60
|
-
@
|
61
|
+
@values[key]
|
62
|
+
end
|
63
|
+
|
64
|
+
def async(&block)
|
65
|
+
execution_strategy.async(block)
|
61
66
|
end
|
62
67
|
end
|
63
68
|
end
|
@@ -65,7 +70,7 @@ end
|
|
65
70
|
require 'graphql/query/arguments'
|
66
71
|
require 'graphql/query/base_execution'
|
67
72
|
require 'graphql/query/serial_execution'
|
68
|
-
require 'graphql/query/
|
73
|
+
require 'graphql/query/parallel_execution'
|
69
74
|
require 'graphql/query/type_resolver'
|
70
75
|
require 'graphql/query/directive_chain'
|
71
76
|
require 'graphql/query/executor'
|
@@ -27,7 +27,7 @@ class GraphQL::Query::Arguments
|
|
27
27
|
elsif value.is_a?(GraphQL::Language::Nodes::Enum)
|
28
28
|
value = arg_defn.type.coerce(value.name)
|
29
29
|
elsif value.is_a?(GraphQL::Language::Nodes::InputObject)
|
30
|
-
wrapped_type = arg_defn.type.
|
30
|
+
wrapped_type = arg_defn.type.unwrap
|
31
31
|
value = self.class.new(value.pairs, wrapped_type.input_fields, variables)
|
32
32
|
else
|
33
33
|
value
|
@@ -1,6 +1,21 @@
|
|
1
|
+
require 'graphql/query/base_execution/selected_object_resolution'
|
2
|
+
require 'graphql/query/base_execution/value_resolution'
|
3
|
+
|
1
4
|
module GraphQL
|
2
5
|
class Query
|
3
6
|
class BaseExecution
|
7
|
+
# This is the only required method for an Execution strategy.
|
8
|
+
# You could create a custom execution strategy and configure your schema to
|
9
|
+
# use that custom strategy instead.
|
10
|
+
#
|
11
|
+
# @param ast_operation [GraphQL::Language::Nodes::OperationDefinition] The operation definition to run
|
12
|
+
# @param root_type [GraphQL::ObjectType] either the query type or the mutation type
|
13
|
+
# @param query_obj [GraphQL::Query] the query object for this execution
|
14
|
+
# @return [Hash] a spec-compliant GraphQL result, as a hash
|
15
|
+
def execute(ast_operation, root_type, query_obj)
|
16
|
+
resolver = operation_resolution.new(ast_operation, root_type, query_obj, self)
|
17
|
+
resolver.result
|
18
|
+
end
|
4
19
|
|
5
20
|
def field_resolution
|
6
21
|
get_class :FieldResolution
|
@@ -22,6 +37,12 @@ module GraphQL
|
|
22
37
|
get_class :SelectionResolution
|
23
38
|
end
|
24
39
|
|
40
|
+
# ParallelExecution overrides this to provide
|
41
|
+
# real async behavior
|
42
|
+
def async(&block)
|
43
|
+
block.call
|
44
|
+
end
|
45
|
+
|
25
46
|
private
|
26
47
|
|
27
48
|
def get_class(class_name)
|
@@ -30,5 +51,3 @@ module GraphQL
|
|
30
51
|
end
|
31
52
|
end
|
32
53
|
end
|
33
|
-
|
34
|
-
require 'graphql/query/base_execution/selected_object_resolution'
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Query
|
3
|
+
class BaseExecution
|
4
|
+
module ValueResolution
|
5
|
+
def self.get_strategy_for_kind(kind)
|
6
|
+
TYPE_KIND_STRATEGIES[kind] || raise("No value resolution strategy for #{kind}!")
|
7
|
+
end
|
8
|
+
|
9
|
+
class BaseResolution
|
10
|
+
attr_reader :value, :field_type, :target, :parent_type,
|
11
|
+
:ast_field, :query, :execution_strategy
|
12
|
+
def initialize(value, field_type, target, parent_type, ast_field, query, execution_strategy)
|
13
|
+
@value = value
|
14
|
+
@field_type = field_type
|
15
|
+
@target = target
|
16
|
+
@parent_type = parent_type
|
17
|
+
@ast_field = ast_field
|
18
|
+
@query = query
|
19
|
+
@execution_strategy = execution_strategy
|
20
|
+
end
|
21
|
+
|
22
|
+
def result
|
23
|
+
raise NotImplementedError, "Should return a value based on initialization params"
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_strategy_for_kind(*args)
|
27
|
+
GraphQL::Query::BaseExecution::ValueResolution.get_strategy_for_kind(*args)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ScalarResolution < BaseResolution
|
32
|
+
def result
|
33
|
+
field_type.coerce(value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ListResolution < BaseResolution
|
38
|
+
def result
|
39
|
+
wrapped_type = field_type.of_type
|
40
|
+
value.map do |item|
|
41
|
+
resolved_type = wrapped_type.resolve_type(item)
|
42
|
+
strategy_class = get_strategy_for_kind(resolved_type.kind)
|
43
|
+
inner_strategy = strategy_class.new(item, resolved_type, target, parent_type, ast_field, query, execution_strategy)
|
44
|
+
inner_strategy.result
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class ObjectResolution < BaseResolution
|
50
|
+
def result
|
51
|
+
resolver = execution_strategy.selection_resolution.new(value, field_type, ast_field.selections, query, execution_strategy)
|
52
|
+
resolver.result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class EnumResolution < BaseResolution
|
57
|
+
def result
|
58
|
+
value.to_s
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class NonNullResolution < BaseResolution
|
63
|
+
def result
|
64
|
+
wrapped_type = field_type.of_type
|
65
|
+
resolved_type = wrapped_type.resolve_type(value)
|
66
|
+
strategy_class = get_strategy_for_kind(resolved_type.kind)
|
67
|
+
inner_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, query, execution_strategy)
|
68
|
+
inner_strategy.result
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
TYPE_KIND_STRATEGIES = {
|
73
|
+
GraphQL::TypeKinds::SCALAR => ScalarResolution,
|
74
|
+
GraphQL::TypeKinds::LIST => ListResolution,
|
75
|
+
GraphQL::TypeKinds::OBJECT => ObjectResolution,
|
76
|
+
GraphQL::TypeKinds::ENUM => EnumResolution,
|
77
|
+
GraphQL::TypeKinds::NON_NULL => NonNullResolution,
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -30,13 +30,15 @@ module GraphQL
|
|
30
30
|
return {} if query.operations.none?
|
31
31
|
operation = find_operation(operation_name, query.operations)
|
32
32
|
if operation.operation_type == "query"
|
33
|
-
|
33
|
+
root_type = query.schema.query
|
34
|
+
execution_strategy_class = query.schema.query_execution_strategy || GraphQL::Query::ParallelExecution
|
34
35
|
elsif operation.operation_type == "mutation"
|
35
|
-
|
36
|
+
root_type = query.schema.mutation
|
37
|
+
execution_strategy_class = query.schema.mutation_execution_strategy || GraphQL::Query::SerialExecution
|
36
38
|
end
|
37
|
-
execution_strategy =
|
38
|
-
|
39
|
-
|
39
|
+
execution_strategy = execution_strategy_class.new
|
40
|
+
query.context.execution_strategy = execution_strategy
|
41
|
+
result = execution_strategy.execute(operation, root_type, query)
|
40
42
|
end
|
41
43
|
|
42
44
|
def find_operation(operation_name, operations)
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Query
|
3
|
+
# Utilize Celluloid::Future to run field resolution in parallel.
|
4
|
+
class ParallelExecution < GraphQL::Query::BaseExecution
|
5
|
+
def initialize
|
6
|
+
# Why isn't `require "celluloid/current"` enough here?
|
7
|
+
Celluloid.boot unless Celluloid.running?
|
8
|
+
@has_futures = false
|
9
|
+
end
|
10
|
+
|
11
|
+
def pool
|
12
|
+
@pool ||= ExecutionPool.run!
|
13
|
+
@pool[:execution_worker_pool]
|
14
|
+
end
|
15
|
+
|
16
|
+
def async(block)
|
17
|
+
@has_futures ||= true
|
18
|
+
pool.future.resolve(block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def has_futures?
|
22
|
+
@has_futures
|
23
|
+
end
|
24
|
+
|
25
|
+
class OperationResolution < GraphQL::Query::SerialExecution::OperationResolution
|
26
|
+
def result
|
27
|
+
result_futures = super
|
28
|
+
if execution_strategy.has_futures?
|
29
|
+
finished_result = finish_all_futures(result_futures)
|
30
|
+
else
|
31
|
+
# Don't bother re-traversing the result if there are no futures.
|
32
|
+
finished_result = result_futures
|
33
|
+
end
|
34
|
+
ensure
|
35
|
+
execution_strategy.pool.terminate
|
36
|
+
finished_result
|
37
|
+
end
|
38
|
+
|
39
|
+
# Recurse over `result_object`, finding any futures and
|
40
|
+
# getting their finished values.
|
41
|
+
def finish_all_futures(result_object)
|
42
|
+
if result_object.is_a?(FutureFieldResolution)
|
43
|
+
resolved_value = finish_all_futures(result_object.result)
|
44
|
+
elsif result_object.is_a?(Hash)
|
45
|
+
result_object.each do |key, value|
|
46
|
+
result_object[key] = finish_all_futures(value)
|
47
|
+
end
|
48
|
+
resolved_value = result_object
|
49
|
+
elsif result_object.is_a?(Array)
|
50
|
+
resolved_value = result_object.map { |v| finish_all_futures(v) }
|
51
|
+
else
|
52
|
+
resolved_value = result_object
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class SelectionResolution < GraphQL::Query::SerialExecution::SelectionResolution
|
58
|
+
end
|
59
|
+
|
60
|
+
class FieldResolution < GraphQL::Query::SerialExecution::FieldResolution
|
61
|
+
def get_finished_value(raw_value)
|
62
|
+
if raw_value.is_a?(Celluloid::Future)
|
63
|
+
GraphQL::Query::ParallelExecution::FutureFieldResolution.new(field_resolution: self, future: raw_value)
|
64
|
+
else
|
65
|
+
super
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class InlineFragmentResolution < GraphQL::Query::SerialExecution::InlineFragmentResolution
|
71
|
+
end
|
72
|
+
|
73
|
+
class FragmentSpreadResolution < GraphQL::Query::SerialExecution::FragmentSpreadResolution
|
74
|
+
end
|
75
|
+
|
76
|
+
class ExecutionWorker
|
77
|
+
include Celluloid
|
78
|
+
def resolve(proc)
|
79
|
+
proc.call
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class ExecutionPool < Celluloid::Supervision::Container
|
84
|
+
pool ExecutionWorker, as: :execution_worker_pool
|
85
|
+
end
|
86
|
+
|
87
|
+
class FutureFieldResolution
|
88
|
+
def initialize(field_resolution:, future:)
|
89
|
+
@field_resolution = field_resolution
|
90
|
+
@future = future
|
91
|
+
end
|
92
|
+
|
93
|
+
def result
|
94
|
+
resolved_value = @future.value
|
95
|
+
result_value = @field_resolution.get_finished_value(resolved_value)
|
96
|
+
result_value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|