graphql 1.5.15 → 1.6.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 +4 -19
- data/lib/graphql/analysis/analyze_query.rb +27 -2
- data/lib/graphql/analysis/query_complexity.rb +10 -11
- data/lib/graphql/argument.rb +7 -6
- data/lib/graphql/backwards_compatibility.rb +47 -0
- data/lib/graphql/compatibility/execution_specification.rb +14 -0
- data/lib/graphql/compatibility/execution_specification/specification_schema.rb +6 -1
- data/lib/graphql/compatibility/lazy_execution_specification.rb +19 -0
- data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +15 -6
- data/lib/graphql/directive.rb +1 -6
- data/lib/graphql/execution.rb +1 -0
- data/lib/graphql/execution/execute.rb +174 -160
- data/lib/graphql/execution/field_result.rb +5 -1
- data/lib/graphql/execution/lazy.rb +2 -2
- data/lib/graphql/execution/lazy/resolve.rb +8 -11
- data/lib/graphql/execution/multiplex.rb +134 -0
- data/lib/graphql/execution/selection_result.rb +5 -0
- data/lib/graphql/field.rb +1 -8
- data/lib/graphql/filter.rb +53 -0
- data/lib/graphql/internal_representation/node.rb +11 -6
- data/lib/graphql/internal_representation/rewrite.rb +3 -3
- data/lib/graphql/query.rb +160 -78
- data/lib/graphql/query/arguments.rb +14 -25
- data/lib/graphql/query/arguments_cache.rb +6 -13
- data/lib/graphql/query/context.rb +28 -10
- data/lib/graphql/query/executor.rb +1 -0
- data/lib/graphql/query/literal_input.rb +10 -4
- data/lib/graphql/query/null_context.rb +1 -1
- data/lib/graphql/query/serial_execution/field_resolution.rb +5 -1
- data/lib/graphql/query/validation_pipeline.rb +12 -7
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/rake_task.rb +140 -0
- data/lib/graphql/relay/array_connection.rb +29 -48
- data/lib/graphql/relay/base_connection.rb +9 -7
- data/lib/graphql/relay/mutation.rb +0 -11
- data/lib/graphql/relay/mutation/instrumentation.rb +2 -2
- data/lib/graphql/relay/mutation/resolve.rb +7 -10
- data/lib/graphql/relay/relation_connection.rb +98 -61
- data/lib/graphql/scalar_type.rb +1 -15
- data/lib/graphql/schema.rb +90 -25
- data/lib/graphql/schema/build_from_definition.rb +22 -23
- data/lib/graphql/schema/build_from_definition/resolve_map.rb +70 -0
- data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +47 -0
- data/lib/graphql/schema/middleware_chain.rb +1 -1
- data/lib/graphql/schema/printer.rb +2 -1
- data/lib/graphql/schema/timeout_middleware.rb +6 -6
- data/lib/graphql/schema/type_map.rb +1 -1
- data/lib/graphql/schema/warden.rb +5 -9
- data/lib/graphql/static_validation/definition_dependencies.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/analysis/analyze_query_spec.rb +2 -2
- data/spec/graphql/analysis/max_query_complexity_spec.rb +28 -0
- data/spec/graphql/argument_spec.rb +3 -3
- data/spec/graphql/execution/lazy_spec.rb +8 -114
- data/spec/graphql/execution/multiplex_spec.rb +131 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +10 -0
- data/spec/graphql/query/arguments_spec.rb +14 -16
- data/spec/graphql/query/context_spec.rb +14 -1
- data/spec/graphql/query/literal_input_spec.rb +19 -13
- data/spec/graphql/query/variables_spec.rb +1 -1
- data/spec/graphql/query_spec.rb +12 -1
- data/spec/graphql/rake_task_spec.rb +57 -0
- data/spec/graphql/relay/array_connection_spec.rb +24 -3
- data/spec/graphql/relay/connection_instrumentation_spec.rb +23 -0
- data/spec/graphql/relay/mutation_spec.rb +2 -10
- data/spec/graphql/relay/page_info_spec.rb +2 -2
- data/spec/graphql/relay/relation_connection_spec.rb +167 -3
- data/spec/graphql/schema/build_from_definition_spec.rb +93 -19
- data/spec/graphql/schema/warden_spec.rb +80 -0
- data/spec/graphql/schema_spec.rb +26 -2
- data/spec/spec_helper.rb +4 -2
- data/spec/support/lazy_helpers.rb +152 -0
- data/spec/support/star_wars/schema.rb +23 -0
- metadata +28 -3
- data/lib/graphql/schema/mask.rb +0 -55
@@ -1,4 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require "graphql/schema/build_from_definition/resolve_map"
|
3
|
+
|
2
4
|
module GraphQL
|
3
5
|
class Schema
|
4
6
|
module BuildFromDefinition
|
@@ -23,24 +25,6 @@ module GraphQL
|
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
26
|
-
# @api private
|
27
|
-
class ResolveMap
|
28
|
-
def initialize(resolve_hash)
|
29
|
-
@resolve_hash = resolve_hash
|
30
|
-
end
|
31
|
-
|
32
|
-
def call(type, field, obj, args, ctx)
|
33
|
-
type_hash = @resolve_hash[type.name]
|
34
|
-
type_hash && (resolver = type_hash[field.name])
|
35
|
-
|
36
|
-
if resolver.nil?
|
37
|
-
raise(KeyError, "resolver not found for #{type.name}.#{field.name}")
|
38
|
-
else
|
39
|
-
resolver.call(obj, args, ctx)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
28
|
# @api private
|
45
29
|
module Builder
|
46
30
|
extend self
|
@@ -72,7 +56,7 @@ module GraphQL
|
|
72
56
|
when GraphQL::Language::Nodes::UnionTypeDefinition
|
73
57
|
types[definition.name] = build_union_type(definition, type_resolver)
|
74
58
|
when GraphQL::Language::Nodes::ScalarTypeDefinition
|
75
|
-
types[definition.name] = build_scalar_type(definition, type_resolver)
|
59
|
+
types[definition.name] = build_scalar_type(definition, type_resolver, default_resolve: default_resolve)
|
76
60
|
when GraphQL::Language::Nodes::InputObjectTypeDefinition
|
77
61
|
types[definition.name] = build_input_object_type(definition, type_resolver)
|
78
62
|
when GraphQL::Language::Nodes::DirectiveDefinition
|
@@ -107,17 +91,23 @@ module GraphQL
|
|
107
91
|
|
108
92
|
raise InvalidDocumentError.new('Must provide schema definition with query type or a type named Query.') unless query_root_type
|
109
93
|
|
110
|
-
Schema.define do
|
94
|
+
schema = Schema.define do
|
111
95
|
raise_definition_error true
|
112
96
|
|
113
97
|
query query_root_type
|
114
98
|
mutation mutation_root_type
|
115
99
|
subscription subscription_root_type
|
116
100
|
orphan_types types.values
|
117
|
-
resolve_type
|
101
|
+
if default_resolve.respond_to?(:resolve_type)
|
102
|
+
resolve_type(default_resolve.method(:resolve_type))
|
103
|
+
else
|
104
|
+
resolve_type(NullResolveType)
|
105
|
+
end
|
118
106
|
|
119
107
|
directives directives.values
|
120
108
|
end
|
109
|
+
|
110
|
+
schema
|
121
111
|
end
|
122
112
|
|
123
113
|
NullResolveType = ->(obj, ctx) {
|
@@ -151,12 +141,21 @@ module GraphQL
|
|
151
141
|
reason.value
|
152
142
|
end
|
153
143
|
|
154
|
-
def build_scalar_type(scalar_type_definition, type_resolver)
|
155
|
-
GraphQL::ScalarType.define(
|
144
|
+
def build_scalar_type(scalar_type_definition, type_resolver, default_resolve:)
|
145
|
+
scalar_type = GraphQL::ScalarType.define(
|
156
146
|
name: scalar_type_definition.name,
|
157
147
|
description: scalar_type_definition.description,
|
158
148
|
coerce: NullScalarCoerce,
|
159
149
|
)
|
150
|
+
|
151
|
+
if default_resolve.respond_to?(:coerce_input)
|
152
|
+
scalar_type = scalar_type.redefine(
|
153
|
+
coerce_input: ->(val, ctx) { default_resolve.coerce_input(scalar_type, val, ctx) },
|
154
|
+
coerce_result: ->(val, ctx) { default_resolve.coerce_result(scalar_type, val, ctx) },
|
155
|
+
)
|
156
|
+
end
|
157
|
+
|
158
|
+
scalar_type
|
160
159
|
end
|
161
160
|
|
162
161
|
def build_union_type(union_type_definition, type_resolver)
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "graphql/schema/build_from_definition/resolve_map/default_resolve"
|
3
|
+
|
4
|
+
module GraphQL
|
5
|
+
class Schema
|
6
|
+
module BuildFromDefinition
|
7
|
+
# Wrap a user-provided hash of resolution behavior for easy access at runtime.
|
8
|
+
#
|
9
|
+
# Coerce scalar values by:
|
10
|
+
# - Checking for a function in the map like `{ Date: { coerce_input: ->(val, ctx) { ... }, coerce_result: ->(val, ctx) { ... } } }`
|
11
|
+
# - Falling back to a passthrough
|
12
|
+
#
|
13
|
+
# Interface/union resolution can be provided as a `resolve_type:` key.
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
class ResolveMap
|
17
|
+
def initialize(user_resolve_hash)
|
18
|
+
@resolve_hash = Hash.new do |h, k|
|
19
|
+
# For each type name, provide a new hash if one wasn't given:
|
20
|
+
h[k] = Hash.new do |h2, k2|
|
21
|
+
if k2 == "coerce_input" || k2 == "coerce_result"
|
22
|
+
# This isn't an object field, it's a scalar coerce function.
|
23
|
+
# Use a passthrough
|
24
|
+
Builder::NullScalarCoerce
|
25
|
+
else
|
26
|
+
# For each field, provide a resolver that will
|
27
|
+
# make runtime checks & replace itself
|
28
|
+
h2[k2] = DefaultResolve.new(h2, k2)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@user_resolve_hash = user_resolve_hash
|
33
|
+
# User-provided resolve functions take priority over the default:
|
34
|
+
@user_resolve_hash.each do |type_name, fields|
|
35
|
+
type_name_s = type_name.to_s
|
36
|
+
case fields
|
37
|
+
when Hash
|
38
|
+
fields.each do |field_name, resolve_fn|
|
39
|
+
@resolve_hash[type_name_s][field_name.to_s] = resolve_fn
|
40
|
+
end
|
41
|
+
when Proc
|
42
|
+
# for example, __resolve_type
|
43
|
+
@resolve_hash[type_name_s] = fields
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Check the normalized hash, not the user input:
|
48
|
+
if @resolve_hash.key?("resolve_type")
|
49
|
+
define_singleton_method :resolve_type do |type, ctx|
|
50
|
+
@resolve_hash.fetch("resolve_type").call(type, ctx)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def call(type, field, obj, args, ctx)
|
56
|
+
resolver = @resolve_hash[type.name][field.name]
|
57
|
+
resolver.call(obj, args, ctx)
|
58
|
+
end
|
59
|
+
|
60
|
+
def coerce_input(type, value, ctx)
|
61
|
+
@resolve_hash[type.name]["coerce_input"].call(value, ctx)
|
62
|
+
end
|
63
|
+
|
64
|
+
def coerce_result(type, value, ctx)
|
65
|
+
@resolve_hash[type.name]["coerce_result"].call(value, ctx)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Schema
|
4
|
+
module BuildFromDefinition
|
5
|
+
class ResolveMap
|
6
|
+
class DefaultResolve
|
7
|
+
def initialize(field_map, field_name)
|
8
|
+
@field_map = field_map
|
9
|
+
@field_name = field_name
|
10
|
+
end
|
11
|
+
|
12
|
+
# Make some runtime checks about
|
13
|
+
# how `obj` implements the `field_name`.
|
14
|
+
#
|
15
|
+
# Create a new resolve function according to that implementation, then:
|
16
|
+
# - update `field_map` with this implementation
|
17
|
+
# - call the implementation now (to satisfy this field execution)
|
18
|
+
#
|
19
|
+
# If `obj` doesn't implement `field_name`, raise an error.
|
20
|
+
def call(obj, args, ctx)
|
21
|
+
method_name = @field_name
|
22
|
+
if !obj.respond_to?(method_name)
|
23
|
+
raise KeyError, "Can't resolve field #{method_name} on #{obj}"
|
24
|
+
else
|
25
|
+
method_arity = obj.method(method_name).arity
|
26
|
+
resolver = case method_arity
|
27
|
+
when 0, -1
|
28
|
+
# -1 Handles method_missing, eg openstruct
|
29
|
+
->(o, a, c) { o.public_send(method_name) }
|
30
|
+
when 1
|
31
|
+
->(o, a, c) { o.public_send(method_name, a) }
|
32
|
+
when 2
|
33
|
+
->(o, a, c) { o.public_send(method_name, a, c) }
|
34
|
+
else
|
35
|
+
raise "Unexpected resolve arity: #{method_arity}. Must be 0, 1, 2"
|
36
|
+
end
|
37
|
+
# Call the resolver directly next time
|
38
|
+
@field_map[method_name] = resolver
|
39
|
+
# Call through this time
|
40
|
+
resolver.call(obj, args, ctx)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -5,7 +5,7 @@ module GraphQL
|
|
5
5
|
#
|
6
6
|
# Steps should call `next_step.call` to continue the chain, or _not_ call it to stop the chain.
|
7
7
|
class MiddlewareChain
|
8
|
-
extend
|
8
|
+
extend Forwardable
|
9
9
|
|
10
10
|
# @return [Array<#call(*args)>] Steps in this chain, will be called with arguments and `next_middleware`
|
11
11
|
attr_reader :steps, :final_step
|
@@ -49,7 +49,8 @@ module GraphQL
|
|
49
49
|
@context = context
|
50
50
|
|
51
51
|
blacklist = build_blacklist(only, except, introspection: introspection)
|
52
|
-
|
52
|
+
filter = GraphQL::Filter.new(except: blacklist)
|
53
|
+
@warden = GraphQL::Schema::Warden.new(filter, schema: @schema, context: @context)
|
53
54
|
end
|
54
55
|
|
55
56
|
# Return the GraphQL schema string for the introspection type system
|
@@ -24,18 +24,18 @@ module GraphQL
|
|
24
24
|
# end
|
25
25
|
#
|
26
26
|
class TimeoutMiddleware
|
27
|
-
# This key is used for storing timeout data in the {Query::Context} instance
|
28
|
-
DEFAULT_CONTEXT_KEY = :__timeout_at__
|
29
27
|
# @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields
|
30
|
-
|
31
|
-
def initialize(max_seconds:, context_key: DEFAULT_CONTEXT_KEY, &block)
|
28
|
+
def initialize(max_seconds:, context_key: nil, &block)
|
32
29
|
@max_seconds = max_seconds
|
33
|
-
|
30
|
+
if context_key
|
31
|
+
warn("TimeoutMiddleware's `context_key` is ignored, timeout data is now stored in isolated storage")
|
32
|
+
end
|
34
33
|
@error_handler = block
|
35
34
|
end
|
36
35
|
|
37
36
|
def call(parent_type, parent_object, field_definition, field_args, query_context)
|
38
|
-
|
37
|
+
ns = query_context.namespace(TimeoutMiddleware)
|
38
|
+
timeout_at = ns[:timeout_at] ||= Time.now + @max_seconds
|
39
39
|
|
40
40
|
if timeout_at < Time.now
|
41
41
|
on_timeout(parent_type, parent_object, field_definition, field_args, query_context)
|
@@ -8,7 +8,7 @@ module GraphQL
|
|
8
8
|
#
|
9
9
|
# If you want a type, but want to handle the undefined case, use {#fetch}.
|
10
10
|
class TypeMap
|
11
|
-
extend
|
11
|
+
extend Forwardable
|
12
12
|
def_delegators :@storage, :key?, :keys, :values, :to_h, :fetch, :each, :each_value
|
13
13
|
|
14
14
|
def initialize
|
@@ -1,21 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GraphQL
|
3
3
|
class Schema
|
4
|
-
# Restrict access to a {GraphQL::Schema} with a user-defined
|
5
|
-
#
|
6
|
-
# The mask is object that responds to `#visible?(schema_member)`.
|
4
|
+
# Restrict access to a {GraphQL::Schema} with a user-defined filter.
|
7
5
|
#
|
8
6
|
# When validating and executing a query, all access to schema members
|
9
7
|
# should go through a warden. If you access the schema directly,
|
10
8
|
# you may show a client something that it shouldn't be allowed to see.
|
11
9
|
#
|
12
|
-
# Masks can be provided in {Schema#execute} (or {Query#initialize}) with the `mask:` keyword.
|
13
|
-
#
|
14
10
|
# @example Hidding private fields
|
15
11
|
# private_members = -> (member, ctx) { member.metadata[:private] }
|
16
12
|
# result = Schema.execute(query_string, except: private_members)
|
17
13
|
#
|
18
|
-
# @example Custom
|
14
|
+
# @example Custom filter implementation
|
19
15
|
# # It must respond to `#call(member)`.
|
20
16
|
# class MissingRequiredFlags
|
21
17
|
# def initialize(user)
|
@@ -38,13 +34,13 @@ module GraphQL
|
|
38
34
|
#
|
39
35
|
# @api private
|
40
36
|
class Warden
|
41
|
-
# @param
|
37
|
+
# @param filter [<#call(member)>] Objects are hidden when `.call(member, ctx)` returns true
|
42
38
|
# @param context [GraphQL::Query::Context]
|
43
39
|
# @param schema [GraphQL::Schema]
|
44
40
|
# @param deep_check [Boolean]
|
45
|
-
def initialize(
|
41
|
+
def initialize(filter, context:, schema:)
|
46
42
|
@schema = schema
|
47
|
-
@visibility_cache = read_through { |m|
|
43
|
+
@visibility_cache = read_through { |m| filter.call(m, context) }
|
48
44
|
end
|
49
45
|
|
50
46
|
# @return [Array<GraphQL::BaseType>] Visible types in the schema
|
data/lib/graphql/version.rb
CHANGED
@@ -9,7 +9,7 @@ describe GraphQL::Analysis do
|
|
9
9
|
|
10
10
|
def call(memo, visit_type, irep_node)
|
11
11
|
if visit_type == :enter
|
12
|
-
memo + [irep_node.return_type]
|
12
|
+
memo + [irep_node.return_type.unwrap]
|
13
13
|
else
|
14
14
|
memo
|
15
15
|
end
|
@@ -83,7 +83,7 @@ describe GraphQL::Analysis do
|
|
83
83
|
memo ||= Hash.new { |h,k| h[k] = 0 }
|
84
84
|
if visit_type == :enter
|
85
85
|
if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
|
86
|
-
if irep_node.definition.
|
86
|
+
if irep_node.definition.connection?
|
87
87
|
memo[:connection] ||= 0
|
88
88
|
memo[:connection] += 1
|
89
89
|
else
|
@@ -60,4 +60,32 @@ describe GraphQL::Analysis::MaxQueryComplexity do
|
|
60
60
|
assert_equal "Query has complexity of 10, which exceeds max complexity of 7", result["errors"][0]["message"]
|
61
61
|
end
|
62
62
|
end
|
63
|
+
|
64
|
+
describe "across a multiplex" do
|
65
|
+
before do
|
66
|
+
Dummy::Schema.max_complexity = 9
|
67
|
+
end
|
68
|
+
|
69
|
+
let(:queries) { 5.times.map { |n| { query: "{ cheese(id: #{n}) { id } }" } } }
|
70
|
+
|
71
|
+
it "returns errors for all queries" do
|
72
|
+
results = Dummy::Schema.multiplex(queries)
|
73
|
+
assert_equal 5, results.length
|
74
|
+
err_msg = "Query has complexity of 10, which exceeds max complexity of 9"
|
75
|
+
results.each do |res|
|
76
|
+
assert_equal err_msg, res["errors"][0]["message"]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "with a local override" do
|
81
|
+
it "uses the override" do
|
82
|
+
results = Dummy::Schema.multiplex(queries, max_complexity: 10)
|
83
|
+
assert_equal 5, results.length
|
84
|
+
results.each do |res|
|
85
|
+
assert_equal true, res.key?("data")
|
86
|
+
assert_equal false, res.key?("errors")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
63
91
|
end
|
@@ -60,14 +60,14 @@ describe GraphQL::Argument do
|
|
60
60
|
|
61
61
|
describe "prepare" do
|
62
62
|
it "accepts a prepare proc and calls it to generate the prepared value" do
|
63
|
-
prepare_proc = Proc.new { |arg| arg +
|
63
|
+
prepare_proc = Proc.new { |arg, ctx| arg + ctx[:val] }
|
64
64
|
argument = GraphQL::Argument.define(name: :plusOne, type: GraphQL::INT_TYPE, prepare: prepare_proc)
|
65
|
-
assert_equal argument.prepare(1), 2
|
65
|
+
assert_equal argument.prepare(1, {val: 1}), 2
|
66
66
|
end
|
67
67
|
|
68
68
|
it "returns the value itself if no prepare proc is provided" do
|
69
69
|
argument = GraphQL::Argument.define(name: :someNumber, type: GraphQL::INT_TYPE)
|
70
|
-
assert_equal argument.prepare(1), 1
|
70
|
+
assert_equal argument.prepare(1, nil), 1
|
71
71
|
end
|
72
72
|
end
|
73
73
|
end
|
@@ -2,117 +2,11 @@
|
|
2
2
|
require "spec_helper"
|
3
3
|
|
4
4
|
describe GraphQL::Execution::Lazy do
|
5
|
-
|
6
|
-
def initialize(item = nil, &block)
|
7
|
-
if block
|
8
|
-
@block = block
|
9
|
-
else
|
10
|
-
@item = item
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def item
|
15
|
-
if @block
|
16
|
-
@item = @block.call()
|
17
|
-
@block = nil
|
18
|
-
end
|
19
|
-
@item
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class SumAll
|
24
|
-
attr_reader :own_value
|
25
|
-
attr_accessor :value
|
26
|
-
|
27
|
-
def initialize(ctx, own_value)
|
28
|
-
@own_value = own_value
|
29
|
-
@all = ctx[:__sum_all__] ||= []
|
30
|
-
@all << self
|
31
|
-
end
|
32
|
-
|
33
|
-
def value
|
34
|
-
@value ||= begin
|
35
|
-
total_value = @all.map(&:own_value).reduce(&:+)
|
36
|
-
@all.each { |v| v.value = total_value}
|
37
|
-
@all.clear
|
38
|
-
total_value
|
39
|
-
end
|
40
|
-
@value
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
LazySum = GraphQL::ObjectType.define do
|
45
|
-
name "LazySum"
|
46
|
-
field :value, types.Int do
|
47
|
-
resolve ->(o, a, c) { o == 13 ? nil : o }
|
48
|
-
end
|
49
|
-
field :nestedSum, !LazySum do
|
50
|
-
argument :value, !types.Int
|
51
|
-
resolve ->(o, args, c) {
|
52
|
-
if args[:value] == 13
|
53
|
-
Wrapper.new(nil)
|
54
|
-
else
|
55
|
-
SumAll.new(c, o + args[:value])
|
56
|
-
end
|
57
|
-
}
|
58
|
-
end
|
59
|
-
|
60
|
-
field :nullableNestedSum, LazySum do
|
61
|
-
argument :value, types.Int
|
62
|
-
resolve ->(o, args, c) {
|
63
|
-
if args[:value] == 13
|
64
|
-
Wrapper.new(nil)
|
65
|
-
else
|
66
|
-
SumAll.new(c, o + args[:value])
|
67
|
-
end
|
68
|
-
}
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
LazyQuery = GraphQL::ObjectType.define do
|
73
|
-
name "Query"
|
74
|
-
field :int, !types.Int do
|
75
|
-
argument :value, !types.Int
|
76
|
-
argument :plus, types.Int, default_value: 0
|
77
|
-
resolve ->(o, a, c) { Wrapper.new(a[:value] + a[:plus])}
|
78
|
-
end
|
79
|
-
|
80
|
-
field :nestedSum, !LazySum do
|
81
|
-
argument :value, !types.Int
|
82
|
-
resolve ->(o, args, c) { SumAll.new(c, args[:value]) }
|
83
|
-
end
|
84
|
-
|
85
|
-
field :nullableNestedSum, LazySum do
|
86
|
-
argument :value, types.Int
|
87
|
-
resolve ->(o, args, c) {
|
88
|
-
if args[:value] == 13
|
89
|
-
Wrapper.new { raise GraphQL::ExecutionError.new("13 is unlucky") }
|
90
|
-
else
|
91
|
-
SumAll.new(c, args[:value])
|
92
|
-
end
|
93
|
-
}
|
94
|
-
end
|
95
|
-
|
96
|
-
field :listSum, types[LazySum] do
|
97
|
-
argument :values, types[types.Int]
|
98
|
-
resolve ->(o, args, c) { args[:values] }
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
LazySchema = GraphQL::Schema.define do
|
103
|
-
query(LazyQuery)
|
104
|
-
mutation(LazyQuery)
|
105
|
-
lazy_resolve(Wrapper, :item)
|
106
|
-
lazy_resolve(SumAll, :value)
|
107
|
-
end
|
108
|
-
|
109
|
-
def run_query(query_str)
|
110
|
-
LazySchema.execute(query_str)
|
111
|
-
end
|
5
|
+
include LazyHelpers
|
112
6
|
|
113
7
|
describe "resolving" do
|
114
8
|
it "calls value handlers" do
|
115
|
-
res = run_query('{ int(value: 2, plus: 1)}')
|
9
|
+
res = run_query('{ int(value: 2, plus: 1) }')
|
116
10
|
assert_equal 3, res["data"]["int"]
|
117
11
|
end
|
118
12
|
|
@@ -234,16 +128,16 @@ describe GraphQL::Execution::Lazy do
|
|
234
128
|
end
|
235
129
|
|
236
130
|
describe "LazyMethodMap" do
|
237
|
-
class SubWrapper < Wrapper; end
|
131
|
+
class SubWrapper < LazyHelpers::Wrapper; end
|
238
132
|
|
239
133
|
let(:map) { GraphQL::Execution::Lazy::LazyMethodMap.new }
|
240
134
|
|
241
135
|
it "finds methods for classes and subclasses" do
|
242
|
-
map.set(Wrapper, :item)
|
243
|
-
map.set(SumAll, :value)
|
244
|
-
b = Wrapper.new(1)
|
245
|
-
sub_b = Wrapper.new(2)
|
246
|
-
s = SumAll.new({}, 3)
|
136
|
+
map.set(LazyHelpers::Wrapper, :item)
|
137
|
+
map.set(LazyHelpers::SumAll, :value)
|
138
|
+
b = LazyHelpers::Wrapper.new(1)
|
139
|
+
sub_b = LazyHelpers::Wrapper.new(2)
|
140
|
+
s = LazyHelpers::SumAll.new({}, 3)
|
247
141
|
assert_equal(:item, map.get(b))
|
248
142
|
assert_equal(:item, map.get(sub_b))
|
249
143
|
assert_equal(:value, map.get(s))
|