graphql 0.19.0 → 0.19.1
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 +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
|