Skip to content

Commit 4051d7b

Browse files
committed
feat(GraphQL::Execution::Execute) propagate nulls; add path and location to invalid null errors
1 parent fe04c50 commit 4051d7b

File tree

15 files changed

+446
-336
lines changed

15 files changed

+446
-336
lines changed

lib/graphql/compatibility/execution_specification.rb

Lines changed: 12 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
require "graphql/compatibility/execution_specification/counter_schema"
2+
require "graphql/compatibility/execution_specification/specification_schema"
3+
14
module GraphQL
25
module Compatibility
36
# Test an execution strategy. This spec is not meant as a development aid.
@@ -22,164 +25,22 @@ module Compatibility
2225
# - Relay features
2326
#
2427
module ExecutionSpecification
25-
DATA = {
26-
"1001" => OpenStruct.new({
27-
name: "Fannie Lou Hamer",
28-
birthdate: Time.new(1917, 10, 6),
29-
organization_ids: [],
30-
}),
31-
"1002" => OpenStruct.new({
32-
name: "John Lewis",
33-
birthdate: Time.new(1940, 2, 21),
34-
organization_ids: ["2001"],
35-
}),
36-
"1003" => OpenStruct.new({
37-
name: "Diane Nash",
38-
birthdate: Time.new(1938, 5, 15),
39-
organization_ids: ["2001", "2002"],
40-
}),
41-
"1004" => OpenStruct.new({
42-
name: "Ralph Abernathy",
43-
birthdate: Time.new(1926, 3, 11),
44-
organization_ids: ["2002"],
45-
}),
46-
"2001" => OpenStruct.new({
47-
name: "SNCC",
48-
leader_id: nil, # fail on purpose
49-
}),
50-
"2002" => OpenStruct.new({
51-
name: "SCLC",
52-
leader_id: "1004",
53-
}),
54-
}
55-
5628
# Make a minitest suite for this execution strategy, making sure it
5729
# fulfills all the requirements of this library.
5830
# @param execution_strategy [<#new, #execute>] An execution strategy class
5931
# @return [Class<Minitest::Test>] A test suite for this execution strategy
6032
def self.build_suite(execution_strategy)
6133
Class.new(Minitest::Test) do
62-
def self.build_schema(execution_strategy)
63-
organization_type = nil
64-
65-
timestamp_type = GraphQL::ScalarType.define do
66-
name "Timestamp"
67-
coerce_input ->(value) { Time.at(value.to_i) }
68-
coerce_result ->(value) { value.to_i }
69-
end
70-
71-
named_entity_interface_type = GraphQL::InterfaceType.define do
72-
name "NamedEntity"
73-
field :name, !types.String
74-
end
75-
76-
person_type = GraphQL::ObjectType.define do
77-
name "Person"
78-
interfaces [named_entity_interface_type]
79-
field :name, !types.String
80-
field :birthdate, timestamp_type
81-
field :age, types.Int do
82-
argument :on, !timestamp_type
83-
resolve ->(obj, args, ctx) {
84-
if obj.birthdate.nil?
85-
nil
86-
else
87-
age_on = args[:on]
88-
age_years = age_on.year - obj.birthdate.year
89-
this_year_birthday = Time.new(age_on.year, obj.birthdate.month, obj.birthdate.day)
90-
if this_year_birthday > age_on
91-
age_years -= 1
92-
end
93-
end
94-
age_years
95-
}
96-
end
97-
field :organizations, types[organization_type] do
98-
resolve ->(obj, args, ctx) {
99-
obj.organization_ids.map { |id| DATA[id] }
100-
}
101-
end
102-
field :first_organization, !organization_type do
103-
resolve ->(obj, args, ctx) {
104-
DATA[obj.organization_ids.first]
105-
}
106-
end
107-
end
108-
109-
organization_type = GraphQL::ObjectType.define do
110-
name "Organization"
111-
interfaces [named_entity_interface_type]
112-
field :name, !types.String
113-
field :leader, !person_type do
114-
resolve ->(obj, args, ctx) {
115-
DATA[obj.leader_id] || (ctx[:return_error] ? ExecutionError.new("Error on Nullable") : nil)
116-
}
117-
end
118-
field :returnedError, types.String do
119-
resolve ->(o, a, c) {
120-
GraphQL::ExecutionError.new("This error was returned")
121-
}
122-
end
123-
field :raisedError, types.String do
124-
resolve ->(o, a, c) {
125-
raise GraphQL::ExecutionError.new("This error was raised")
126-
}
127-
end
128-
129-
field :nodePresence, !types[!types.Boolean] do
130-
resolve ->(o, a, ctx) {
131-
[
132-
ctx.irep_node.is_a?(GraphQL::InternalRepresentation::Node),
133-
ctx.ast_node.is_a?(GraphQL::Language::Nodes::AbstractNode),
134-
false, # just testing
135-
]
136-
}
137-
end
138-
end
139-
140-
node_union_type = GraphQL::UnionType.define do
141-
name "Node"
142-
possible_types [person_type, organization_type]
143-
end
144-
145-
query_type = GraphQL::ObjectType.define do
146-
name "Query"
147-
field :node, node_union_type do
148-
argument :id, !types.ID
149-
resolve ->(obj, args, ctx) {
150-
obj[args[:id]]
151-
}
152-
end
153-
154-
field :organization, !organization_type do
155-
argument :id, !types.ID
156-
resolve ->(obj, args, ctx) {
157-
args[:id].start_with?("2") && obj[args[:id]]
158-
}
159-
end
160-
161-
field :organizations, types[organization_type] do
162-
resolve ->(obj, args, ctx) {
163-
[obj["2001"], obj["2002"]]
164-
}
165-
end
166-
end
167-
168-
GraphQL::Schema.define do
169-
query_execution_strategy execution_strategy
170-
query query_type
171-
172-
resolve_type ->(obj, ctx) {
173-
obj.respond_to?(:birthdate) ? person_type : organization_type
174-
}
175-
end
34+
class << self
35+
attr_accessor :counter_schema, :specification_schema
17636
end
17737

178-
@@schema = build_schema(execution_strategy)
38+
self.specification_schema = SpecificationSchema.build(execution_strategy)
39+
self.counter_schema = CounterSchema.build(execution_strategy)
17940

18041
def execute_query(query_string, **kwargs)
181-
kwargs[:root_value] = DATA
182-
@@schema.execute(query_string, **kwargs)
42+
kwargs[:root_value] = SpecificationSchema::DATA
43+
self.class.specification_schema.execute(query_string, **kwargs)
18344
end
18445

18546
def test_it_fetches_data
@@ -409,47 +270,7 @@ def test_it_doesnt_add_errors_for_invalid_nulls_from_execution_errors
409270
end
410271

411272
def test_it_only_resolves_fields_once_on_typed_fragments
412-
count = 0
413-
counter_type = nil
414-
415-
has_count_interface = GraphQL::InterfaceType.define do
416-
name "HasCount"
417-
field :count, types.Int
418-
field :counter, ->{ counter_type }
419-
end
420-
421-
counter_type = GraphQL::ObjectType.define do
422-
name "Counter"
423-
interfaces [has_count_interface]
424-
field :count, types.Int, resolve: ->(o,a,c) { count += 1 }
425-
field :counter, has_count_interface, resolve: ->(o,a,c) { :counter }
426-
end
427-
428-
alt_counter_type = GraphQL::ObjectType.define do
429-
name "AltCounter"
430-
interfaces [has_count_interface]
431-
field :count, types.Int, resolve: ->(o,a,c) { count += 1 }
432-
field :counter, has_count_interface, resolve: ->(o,a,c) { :counter }
433-
end
434-
435-
has_counter_interface = GraphQL::InterfaceType.define do
436-
name "HasCounter"
437-
field :counter, counter_type
438-
end
439-
440-
query_type = GraphQL::ObjectType.define do
441-
name "Query"
442-
interfaces [has_counter_interface]
443-
field :counter, has_count_interface, resolve: ->(o,a,c) { :counter }
444-
end
445-
446-
schema = GraphQL::Schema.define(
447-
query: query_type,
448-
resolve_type: ->(o, c) { o == :counter ? counter_type : nil },
449-
orphan_types: [alt_counter_type],
450-
)
451-
452-
res = schema.execute("
273+
res = self.class.counter_schema.execute("
453274
{
454275
counter { count }
455276
... on HasCounter {
@@ -462,10 +283,10 @@ def test_it_only_resolves_fields_once_on_typed_fragments
462283
"counter" => { "count" => 1 }
463284
}
464285
assert_equal expected_data, res["data"]
465-
assert_equal 1, count
286+
assert_equal 1, self.class.counter_schema.metadata[:count]
466287

467288
# Deep typed children are correctly distinguished:
468-
res = schema.execute("
289+
res = self.class.counter_schema.execute("
469290
{
470291
counter {
471292
... on Counter {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
module GraphQL
2+
module Compatibility
3+
module ExecutionSpecification
4+
module CounterSchema
5+
def self.build(execution_strategy)
6+
counter_type = nil
7+
schema = nil
8+
9+
has_count_interface = GraphQL::InterfaceType.define do
10+
name "HasCount"
11+
field :count, types.Int
12+
field :counter, ->{ counter_type }
13+
end
14+
15+
counter_type = GraphQL::ObjectType.define do
16+
name "Counter"
17+
interfaces [has_count_interface]
18+
field :count, types.Int, resolve: ->(o,a,c) { schema.metadata[:count] += 1 }
19+
field :counter, has_count_interface, resolve: ->(o,a,c) { :counter }
20+
end
21+
22+
alt_counter_type = GraphQL::ObjectType.define do
23+
name "AltCounter"
24+
interfaces [has_count_interface]
25+
field :count, types.Int, resolve: ->(o,a,c) { schema.metadata[:count] += 1 }
26+
field :counter, has_count_interface, resolve: ->(o,a,c) { :counter }
27+
end
28+
29+
has_counter_interface = GraphQL::InterfaceType.define do
30+
name "HasCounter"
31+
field :counter, counter_type
32+
end
33+
34+
query_type = GraphQL::ObjectType.define do
35+
name "Query"
36+
interfaces [has_counter_interface]
37+
field :counter, has_count_interface, resolve: ->(o,a,c) { :counter }
38+
end
39+
40+
schema = GraphQL::Schema.define(
41+
query: query_type,
42+
resolve_type: ->(o, c) { o == :counter ? counter_type : nil },
43+
orphan_types: [alt_counter_type],
44+
query_execution_strategy: execution_strategy,
45+
)
46+
schema.metadata[:count] = 0
47+
schema
48+
end
49+
end
50+
end
51+
end
52+
end

0 commit comments

Comments
 (0)