graphql 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/graphql.rb +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
|