graphql 1.5.15 → 1.6.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 +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))
|