graphql 0.19.0 → 0.19.1
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 +1 -0
- data/lib/graphql/base_type.rb +0 -42
- data/lib/graphql/define/instance_definable.rb +27 -14
- data/lib/graphql/interface_type.rb +1 -2
- data/lib/graphql/invalid_null_error.rb +3 -2
- data/lib/graphql/language/lexer.rl +1 -1
- data/lib/graphql/language/parser.rb +544 -531
- data/lib/graphql/language/parser.y +1 -0
- data/lib/graphql/language/parser_tests.rb +1 -1
- data/lib/graphql/object_type.rb +0 -21
- data/lib/graphql/query/serial_execution/value_resolution.rb +5 -6
- data/lib/graphql/relay/node.rb +6 -1
- data/lib/graphql/schema.rb +30 -10
- data/lib/graphql/schema/loader.rb +5 -1
- data/lib/graphql/schema/validation.rb +31 -0
- data/lib/graphql/union_type.rb +0 -1
- data/lib/graphql/unresolved_type_error.rb +16 -0
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/analysis/query_complexity_spec.rb +5 -1
- data/spec/graphql/define/instance_definable_spec.rb +11 -0
- data/spec/graphql/field_spec.rb +3 -2
- data/spec/graphql/non_null_type_spec.rb +2 -2
- data/spec/graphql/query/context_spec.rb +1 -0
- data/spec/graphql/query/executor_spec.rb +2 -2
- data/spec/graphql/query/serial_execution/value_resolution_spec.rb +49 -9
- data/spec/graphql/schema/loader_spec.rb +6 -1
- data/spec/graphql/schema/printer_spec.rb +1 -1
- data/spec/graphql/schema_spec.rb +56 -0
- data/spec/graphql/static_validation/rules/mutation_root_exists_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/subscription_root_exists_spec.rb +1 -1
- data/spec/support/dairy_app.rb +0 -5
- metadata +3 -2
data/lib/graphql/object_type.rb
CHANGED
@@ -74,26 +74,5 @@ module GraphQL
|
|
74
74
|
memo.merge!(iface.fields)
|
75
75
|
end
|
76
76
|
end
|
77
|
-
|
78
|
-
|
79
|
-
# Error raised when the value provided for a field can't be resolved to one of the possible types
|
80
|
-
# for the field.
|
81
|
-
class UnresolvedTypeError < GraphQL::Error
|
82
|
-
attr_reader :field_name, :field_type, :parent_type
|
83
|
-
|
84
|
-
def initialize(field_name, field_type, parent_type)
|
85
|
-
@field_name = field_name
|
86
|
-
@field_type = field_type
|
87
|
-
@parent_type = parent_type
|
88
|
-
super(exception_message)
|
89
|
-
end
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
def exception_message
|
94
|
-
"The value returned for field #{field_name} on #{parent_type} could not be resolved "\
|
95
|
-
"to one of the possible types for #{field_type}."
|
96
|
-
end
|
97
|
-
end
|
98
77
|
end
|
99
78
|
end
|
@@ -57,12 +57,11 @@ module GraphQL
|
|
57
57
|
|
58
58
|
class HasPossibleTypeResolution < BaseResolution
|
59
59
|
def non_null_result
|
60
|
-
|
61
|
-
|
62
|
-
resolved_type = field_type.legacy_resolve_type(value, execution_context)
|
60
|
+
resolved_type = execution_context.schema.resolve_type(value, execution_context.query.context)
|
61
|
+
possible_types = execution_context.schema.possible_types(field_type)
|
63
62
|
|
64
|
-
unless resolved_type.is_a?(GraphQL::ObjectType)
|
65
|
-
raise GraphQL::
|
63
|
+
unless resolved_type.is_a?(GraphQL::ObjectType) && possible_types.include?(resolved_type)
|
64
|
+
raise GraphQL::UnresolvedTypeError.new(irep_node.definition_name, execution_context.schema, field_type, parent_type, resolved_type)
|
66
65
|
end
|
67
66
|
|
68
67
|
strategy_class = get_strategy_for_kind(resolved_type.kind)
|
@@ -87,7 +86,7 @@ module GraphQL
|
|
87
86
|
# Get the "wrapped" type and resolve the value according to that type
|
88
87
|
def result
|
89
88
|
if value.nil? || value.is_a?(GraphQL::ExecutionError)
|
90
|
-
raise GraphQL::InvalidNullError.new(irep_node.definition_name, value)
|
89
|
+
raise GraphQL::InvalidNullError.new(parent_type.name, irep_node.definition_name, value)
|
91
90
|
else
|
92
91
|
wrapped_type = field_type.of_type
|
93
92
|
strategy_class = get_strategy_for_kind(wrapped_type.kind)
|
data/lib/graphql/relay/node.rb
CHANGED
@@ -7,12 +7,17 @@ module GraphQL
|
|
7
7
|
# We have to define it fresh each time because
|
8
8
|
# its name will be modified and its description
|
9
9
|
# _may_ be modified.
|
10
|
-
GraphQL::Field.define do
|
10
|
+
node_field = GraphQL::Field.define do
|
11
11
|
type(GraphQL::Relay::Node.interface)
|
12
12
|
description("Fetches an object given its ID")
|
13
13
|
argument(:id, !types.ID, "ID of the object")
|
14
14
|
resolve(GraphQL::Relay::Node::FindNode)
|
15
15
|
end
|
16
|
+
|
17
|
+
# This is used to identify generated fields in the schema
|
18
|
+
node_field.metadata[:relay_node_field] = true
|
19
|
+
|
20
|
+
node_field
|
16
21
|
end
|
17
22
|
|
18
23
|
# @return [GraphQL::InterfaceType] The interface which all Relay types must implement
|
data/lib/graphql/schema.rb
CHANGED
@@ -63,11 +63,8 @@ module GraphQL
|
|
63
63
|
|
64
64
|
DIRECTIVES = [GraphQL::Directive::SkipDirective, GraphQL::Directive::IncludeDirective, GraphQL::Directive::DeprecatedDirective]
|
65
65
|
DYNAMIC_FIELDS = ["__type", "__typename", "__schema"]
|
66
|
-
RESOLVE_TYPE_PROC_REQUIRED = -> (obj, ctx) { raise(NotImplementedError, "Schema.resolve_type is undefined, can't resolve type for #{obj.inspect}") }
|
67
|
-
OBJECT_FROM_ID_PROC_REQUIRED = -> (id, ctx) { raise(NotImplementedError, "Schema.object_from_id is undefined, can't fetch object for #{id.inspect}") }
|
68
|
-
ID_FROM_OBJECT_PROC_REQUIRED = -> (obj, type, ctx) { raise(NotImplementedError, "Schema.id_from_object is undefined, can't return an ID for #{obj.inspect}") }
|
69
66
|
|
70
|
-
attr_reader :directives, :static_validator, :object_from_id_proc, :id_from_object_proc
|
67
|
+
attr_reader :directives, :static_validator, :object_from_id_proc, :id_from_object_proc, :resolve_type_proc
|
71
68
|
|
72
69
|
# @!attribute [r] middleware
|
73
70
|
# @return [Array<#call>] Middlewares suitable for MiddlewareChain, applied to fields during execution
|
@@ -92,9 +89,9 @@ module GraphQL
|
|
92
89
|
@rescue_middleware = GraphQL::Schema::RescueMiddleware.new
|
93
90
|
@middleware = [@rescue_middleware]
|
94
91
|
@query_analyzers = []
|
95
|
-
@resolve_type_proc =
|
96
|
-
@object_from_id_proc =
|
97
|
-
@id_from_object_proc =
|
92
|
+
@resolve_type_proc = nil
|
93
|
+
@object_from_id_proc = nil
|
94
|
+
@id_from_object_proc = nil
|
98
95
|
# Default to the built-in execution strategy:
|
99
96
|
@query_execution_strategy = GraphQL::Query::SerialExecution
|
100
97
|
@mutation_execution_strategy = GraphQL::Query::SerialExecution
|
@@ -111,6 +108,15 @@ module GraphQL
|
|
111
108
|
@rescue_middleware.remove_handler(*args, &block)
|
112
109
|
end
|
113
110
|
|
111
|
+
def define(**kwargs, &block)
|
112
|
+
super
|
113
|
+
types
|
114
|
+
# Assert that all necessary configs are present:
|
115
|
+
validation_error = Validation.validate(self)
|
116
|
+
validation_error && raise(NotImplementedError, validation_error)
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
|
114
120
|
# @return [GraphQL::Schema::TypeMap] `{ name => type }` pairs of types in this schema
|
115
121
|
def types
|
116
122
|
@types ||= begin
|
@@ -193,6 +199,11 @@ module GraphQL
|
|
193
199
|
# @return [GraphQL::ObjectType] The type for exposing `object` in GraphQL
|
194
200
|
def resolve_type(object, ctx)
|
195
201
|
ensure_defined
|
202
|
+
|
203
|
+
if @resolve_type_proc.nil?
|
204
|
+
raise(NotImplementedError, "Can't determine GraphQL type for: #{object.inspect}, define `resolve_type (obj, ctx) -> { ... }` inside `Schema.define`.")
|
205
|
+
end
|
206
|
+
|
196
207
|
type_result = @resolve_type_proc.call(object, ctx)
|
197
208
|
if type_result.nil?
|
198
209
|
nil
|
@@ -215,7 +226,11 @@ module GraphQL
|
|
215
226
|
# @return [Any] The application object identified by `id`
|
216
227
|
def object_from_id(id, ctx)
|
217
228
|
ensure_defined
|
218
|
-
@object_from_id_proc.
|
229
|
+
if @object_from_id_proc.nil?
|
230
|
+
raise(NotImplementedError, "Can't fetch an object for id \"#{id}\" because the schema's `object_from_id (id, ctx) -> { ... }` function is not defined")
|
231
|
+
else
|
232
|
+
@object_from_id_proc.call(id, ctx)
|
233
|
+
end
|
219
234
|
end
|
220
235
|
|
221
236
|
# @param new_proc [#call] A new callable for fetching objects by ID
|
@@ -226,11 +241,16 @@ module GraphQL
|
|
226
241
|
|
227
242
|
# Get a unique identifier from this object
|
228
243
|
# @param object [Any] An application object
|
244
|
+
# @param type [GraphQL::BaseType] The current type definition
|
229
245
|
# @param ctx [GraphQL::Query::Context] the context for the current query
|
230
246
|
# @return [String] a unique identifier for `object` which clients can use to refetch it
|
231
|
-
def id_from_object(
|
247
|
+
def id_from_object(object, type, ctx)
|
232
248
|
ensure_defined
|
233
|
-
@id_from_object_proc.
|
249
|
+
if @id_from_object_proc.nil?
|
250
|
+
raise(NotImplementedError, "Can't generate an ID for #{object.inspect} of type #{type}, schema's `id_from_object` must be defined")
|
251
|
+
else
|
252
|
+
@id_from_object_proc.call(object, type, ctx)
|
253
|
+
end
|
234
254
|
end
|
235
255
|
|
236
256
|
# @param new_proc [#call] A new callable for generating unique IDs
|
@@ -22,7 +22,7 @@ module GraphQL
|
|
22
22
|
types[type_object.name] = type_object
|
23
23
|
end
|
24
24
|
|
25
|
-
kargs = { :
|
25
|
+
kargs = { orphan_types: types.values, resolve_type: NullResolveType }
|
26
26
|
[:query, :mutation, :subscription].each do |root|
|
27
27
|
type = schema["#{root}Type"]
|
28
28
|
kargs[root] = types.fetch(type.fetch("name")) if type
|
@@ -31,6 +31,10 @@ module GraphQL
|
|
31
31
|
Schema.define(**kargs)
|
32
32
|
end
|
33
33
|
|
34
|
+
NullResolveType = -> (obj, ctx) {
|
35
|
+
raise(NotImplementedError, "This schema was loaded from string, so it can't resolve types for objects")
|
36
|
+
}
|
37
|
+
|
34
38
|
class << self
|
35
39
|
private
|
36
40
|
|
@@ -125,6 +125,32 @@ module GraphQL
|
|
125
125
|
"type must be a valid input type (Scalar or InputObject), not #{outer_type.class} (#{outer_type})"
|
126
126
|
end
|
127
127
|
}
|
128
|
+
|
129
|
+
SCHEMA_CAN_RESOLVE_TYPES = -> (schema) {
|
130
|
+
if schema.types.values.any? { |type| type.kind.resolves? } && schema.resolve_type_proc.nil?
|
131
|
+
"schema contains Interfaces or Unions, so you must define a `resolve_type (obj, ctx) -> { ... }` function"
|
132
|
+
else
|
133
|
+
# :+1:
|
134
|
+
end
|
135
|
+
}
|
136
|
+
|
137
|
+
SCHEMA_CAN_FETCH_IDS = -> (schema) {
|
138
|
+
has_node_field = schema.query && schema.query.all_fields.any? { |f| f.metadata[:relay_node_field] }
|
139
|
+
if has_node_field && schema.object_from_id_proc.nil?
|
140
|
+
"schema contains `node(id:...)` field, so you must define a `object_from_id (id, ctx) -> { ... }` function"
|
141
|
+
else
|
142
|
+
# :rocket:
|
143
|
+
end
|
144
|
+
}
|
145
|
+
|
146
|
+
SCHEMA_CAN_GENERATE_IDS = -> (schema) {
|
147
|
+
has_id_field = schema.types.values.any? { |t| t.kind.fields? && t.all_fields.any? { |f| f.resolve_proc.is_a?(GraphQL::Relay::GlobalIdResolve) } }
|
148
|
+
if has_id_field && schema.id_from_object_proc.nil?
|
149
|
+
"schema contains `global_id_field`, so you must define a `id_from_object (obj, type, ctx) -> { ... }` function"
|
150
|
+
else
|
151
|
+
# :ok_hand:
|
152
|
+
end
|
153
|
+
}
|
128
154
|
end
|
129
155
|
|
130
156
|
# A mapping of `{Class => [Proc, Proc...]}` pairs.
|
@@ -165,6 +191,11 @@ module GraphQL
|
|
165
191
|
GraphQL::InterfaceType => [
|
166
192
|
Rules::FIELDS_ARE_VALID,
|
167
193
|
],
|
194
|
+
GraphQL::Schema => [
|
195
|
+
Rules::SCHEMA_CAN_RESOLVE_TYPES,
|
196
|
+
Rules::SCHEMA_CAN_FETCH_IDS,
|
197
|
+
Rules::SCHEMA_CAN_GENERATE_IDS,
|
198
|
+
],
|
168
199
|
}
|
169
200
|
end
|
170
201
|
end
|
data/lib/graphql/union_type.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
module GraphQL
|
2
|
+
# Error raised when the value provided for a field can't be resolved to one of the possible types
|
3
|
+
# for the field.
|
4
|
+
class UnresolvedTypeError < GraphQL::Error
|
5
|
+
def initialize(field_name, schema, field_type, parent_type, received_type)
|
6
|
+
possible_types = field_type.kind.resolves? ? schema.possible_types(field_type) : [field_type]
|
7
|
+
message = %|The value from "#{field_name}" on "#{parent_type}" could not be resolved to "#{field_type}". (Received: #{received_type.inspect}, Expected: [#{possible_types.map(&:inspect).join(", ")}])|
|
8
|
+
super(message)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ObjectType
|
13
|
+
# @deprecated Use {GraphQL::UnresolvedTypeError} instead
|
14
|
+
UnresolvedTypeError = GraphQL::UnresolvedTypeError
|
15
|
+
end
|
16
|
+
end
|
data/lib/graphql/version.rb
CHANGED
@@ -255,7 +255,11 @@ describe GraphQL::Analysis::QueryComplexity do
|
|
255
255
|
end
|
256
256
|
end
|
257
257
|
|
258
|
-
GraphQL::Schema.define(
|
258
|
+
GraphQL::Schema.define(
|
259
|
+
query: query_type,
|
260
|
+
orphan_types: [double_complexity_type],
|
261
|
+
resolve_type: :pass
|
262
|
+
)
|
259
263
|
}
|
260
264
|
let(:query_string) {%|
|
261
265
|
{
|
@@ -62,6 +62,17 @@ describe GraphQL::Define::InstanceDefinable do
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
+
describe "#define" do
|
66
|
+
it "applies new definitions to an object" do
|
67
|
+
okra = Garden::Vegetable.define(name: "Okra", plant_between: Date.new(2000, 5, 1)..Date.new(2000, 7, 1))
|
68
|
+
assert_equal "Okra", okra.name
|
69
|
+
okra.define(name: "Gumbo")
|
70
|
+
assert_equal "Gumbo", okra.name
|
71
|
+
okra.define { name "Okra" }
|
72
|
+
assert_equal "Okra", okra.name
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
65
76
|
describe "#metadata" do
|
66
77
|
it "gets values from definitions" do
|
67
78
|
arugula = Garden::Vegetable.define(name: "Arugula", color: :green)
|
data/spec/graphql/field_spec.rb
CHANGED
@@ -88,9 +88,10 @@ describe GraphQL::Field do
|
|
88
88
|
invalid_field.name = :symbol_name
|
89
89
|
|
90
90
|
dummy_query.fields["symbol_name"] = invalid_field
|
91
|
-
dummy_schema = GraphQL::Schema.define(query: dummy_query)
|
92
91
|
|
93
|
-
err = assert_raises(GraphQL::Schema::InvalidTypeError) {
|
92
|
+
err = assert_raises(GraphQL::Schema::InvalidTypeError) {
|
93
|
+
GraphQL::Schema.define(query: dummy_query)
|
94
|
+
}
|
94
95
|
assert_equal "QueryType is invalid: field :symbol_name name must return String, not Symbol (:symbol_name)", err.message
|
95
96
|
end
|
96
97
|
end
|
@@ -6,7 +6,7 @@ describe GraphQL::NonNullType do
|
|
6
6
|
query_string = %|{ cow { name cantBeNullButIs } }|
|
7
7
|
result = DummySchema.execute(query_string)
|
8
8
|
assert_equal({"cow" => nil }, result["data"])
|
9
|
-
assert_equal([{"message"=>"Cannot return null for non-nullable field cantBeNullButIs"}], result["errors"])
|
9
|
+
assert_equal([{"message"=>"Cannot return null for non-nullable field Cow.cantBeNullButIs"}], result["errors"])
|
10
10
|
end
|
11
11
|
|
12
12
|
it "propagates the null up to the next nullable field" do
|
@@ -25,7 +25,7 @@ describe GraphQL::NonNullType do
|
|
25
25
|
|
|
26
26
|
result = DummySchema.execute(query_string)
|
27
27
|
assert_equal(nil, result["data"])
|
28
|
-
assert_equal([{"message"=>"Cannot return null for non-nullable field nonNullInt"}], result["errors"])
|
28
|
+
assert_equal([{"message"=>"Cannot return null for non-nullable field DeepNonNull.nonNullInt"}], result["errors"])
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -85,7 +85,7 @@ describe GraphQL::Query::Executor do
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
-
GraphQL::Schema.define(query: DummyQueryType, mutation: MutationType)
|
88
|
+
GraphQL::Schema.define(query: DummyQueryType, mutation: MutationType, resolve_type: :pass, id_from_object: :pass)
|
89
89
|
}
|
90
90
|
let(:variables) { nil }
|
91
91
|
let(:query_string) { %|
|
@@ -127,7 +127,7 @@ describe GraphQL::Query::Executor do
|
|
127
127
|
"data" => { "cow" => nil },
|
128
128
|
"errors" => [
|
129
129
|
{
|
130
|
-
"message" => "Cannot return null for non-nullable field cantBeNullButIs"
|
130
|
+
"message" => "Cannot return null for non-nullable field Cow.cantBeNullButIs"
|
131
131
|
}
|
132
132
|
]
|
133
133
|
}
|
@@ -18,20 +18,41 @@ describe GraphQL::Query::SerialExecution::ValueResolution do
|
|
18
18
|
field :someField, !types.Int
|
19
19
|
end
|
20
20
|
|
21
|
+
some_object = GraphQL::ObjectType.define do
|
22
|
+
name "SomeObject"
|
23
|
+
interfaces [interface]
|
24
|
+
end
|
25
|
+
|
26
|
+
other_object = GraphQL::ObjectType.define do
|
27
|
+
name "OtherObject"
|
28
|
+
end
|
29
|
+
|
30
|
+
OtherObject = Class.new
|
31
|
+
|
21
32
|
query_root = GraphQL::ObjectType.define do
|
22
33
|
name "Query"
|
23
34
|
field :tomorrow, day_of_week_enum do
|
24
35
|
argument :today, day_of_week_enum
|
25
36
|
resolve ->(obj, args, ctx) { (args["today"] + 1) % 7 }
|
26
37
|
end
|
27
|
-
field :
|
38
|
+
field :resolvesToNilInterface, interface do
|
28
39
|
resolve ->(obj, args, ctx) { Object.new }
|
29
40
|
end
|
41
|
+
field :resolvesToWrongTypeInterface, interface do
|
42
|
+
resolve ->(obj, args, ctx) { OtherObject.new }
|
43
|
+
end
|
30
44
|
end
|
31
45
|
|
32
46
|
GraphQL::Schema.define do
|
33
47
|
query(query_root)
|
34
|
-
|
48
|
+
orphan_types [some_object]
|
49
|
+
resolve_type -> (obj, ctx) do
|
50
|
+
if obj.is_a?(OtherObject)
|
51
|
+
other_object
|
52
|
+
else
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
35
56
|
end
|
36
57
|
}
|
37
58
|
|
@@ -57,14 +78,33 @@ describe GraphQL::Query::SerialExecution::ValueResolution do
|
|
57
78
|
end
|
58
79
|
|
59
80
|
describe "interface type resolution" do
|
60
|
-
|
61
|
-
{
|
62
|
-
|
63
|
-
|
64
|
-
|
81
|
+
describe "when type can't be resolved" do
|
82
|
+
let(:query_string) { %|
|
83
|
+
{
|
84
|
+
resolvesToNilInterface { someField }
|
85
|
+
}
|
86
|
+
|}
|
65
87
|
|
66
|
-
|
67
|
-
|
88
|
+
it "raises an error" do
|
89
|
+
err = assert_raises(GraphQL::ObjectType::UnresolvedTypeError) { result }
|
90
|
+
expected_message = %|The value from "resolvesToNilInterface" on "Query" could not be resolved to "SomeInterface". (Received: nil, Expected: [SomeObject])|
|
91
|
+
assert_equal expected_message, err.message
|
92
|
+
end
|
68
93
|
end
|
94
|
+
|
95
|
+
describe "when type resolves but is not a possible type of an interface" do
|
96
|
+
let(:query_string) { %|
|
97
|
+
{
|
98
|
+
resolvesToWrongTypeInterface { someField }
|
99
|
+
}
|
100
|
+
|}
|
101
|
+
|
102
|
+
it "raises an error" do
|
103
|
+
err = assert_raises(GraphQL::ObjectType::UnresolvedTypeError) { result }
|
104
|
+
expected_message = %|The value from "resolvesToWrongTypeInterface" on "Query" could not be resolved to "SomeInterface". (Received: OtherObject, Expected: [SomeObject])|
|
105
|
+
assert_equal expected_message, err.message
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
69
109
|
end
|
70
110
|
end
|