Skip to content

Commit 35158dd

Browse files
author
Robert Mosolgo
authored
Merge pull request #2997 from rmosolgo/mutation-invalid-null-error
Add invalid null error for mutations
2 parents da513ac + d759092 commit 35158dd

File tree

8 files changed

+65
-7
lines changed

8 files changed

+65
-7
lines changed

lib/graphql.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,13 @@ def -@
9191
require "graphql/tracing"
9292
require "graphql/dig"
9393
require "graphql/execution"
94+
require "graphql/runtime_type_error"
95+
require "graphql/unresolved_type_error"
96+
require "graphql/invalid_null_error"
9497
require "graphql/schema"
9598
require "graphql/query"
9699
require "graphql/directive"
97100
require "graphql/execution"
98-
require "graphql/runtime_type_error"
99-
require "graphql/unresolved_type_error"
100-
require "graphql/invalid_null_error"
101101
require "graphql/types"
102102
require "graphql/relay"
103103
require "graphql/boolean_type"

lib/graphql/execution/interpreter/runtime.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,8 @@ def evaluate_selections(path, scoped_context, owner_object, owner_type, selectio
254254
def continue_value(path, value, field, is_non_null, ast_node)
255255
if value.nil?
256256
if is_non_null
257-
err = field.owner::InvalidNullError.new(field.owner, field, value)
257+
parent_type = field.owner_type
258+
err = parent_type::InvalidNullError.new(parent_type, field, value)
258259
write_invalid_null_in_response(path, err)
259260
else
260261
write_in_response(path, nil)
@@ -311,7 +312,7 @@ def continue_field(path, value, field, type, ast_node, next_selections, is_non_n
311312
possible_types = query.possible_types(type)
312313

313314
if !possible_types.include?(resolved_type)
314-
parent_type = field.owner
315+
parent_type = field.owner_type
315316
err_class = type::UnresolvedTypeError
316317
type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
317318
schema.type_error(type_error, context)

lib/graphql/invalid_null_error.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,23 @@ def to_h
2828
def parent_error?
2929
false
3030
end
31+
32+
class << self
33+
attr_accessor :parent_class
34+
35+
def subclass_for(parent_class)
36+
subclass = Class.new(self)
37+
subclass.parent_class = parent_class
38+
subclass
39+
end
40+
41+
def inspect
42+
if name.nil? && parent_class.respond_to?(:mutation) && (mutation = parent_class.mutation)
43+
"#{mutation.inspect}::#{parent_class.graphql_name}::InvalidNullError"
44+
else
45+
super
46+
end
47+
end
48+
end
3149
end
3250
end

lib/graphql/schema/field.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,18 @@ class Field
3636
# @return [Symbol] The method on the type to look up
3737
attr_reader :resolver_method
3838

39-
# @return [Class] The type that this field belongs to
39+
# @return [Class] The thing this field was defined on (type, mutation, resolver)
4040
attr_accessor :owner
4141

42+
# @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type)
43+
def owner_type
44+
@owner_type ||= if owner < GraphQL::Schema::Mutation
45+
owner.payload_type
46+
else
47+
owner
48+
end
49+
end
50+
4251
# @return [Symbol] the original name of the field, passed in by the user
4352
attr_reader :original_name
4453

lib/graphql/schema/object.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class << self
7474
# Set up a type-specific invalid null error to use when this object's non-null fields wrongly return `nil`.
7575
# It should help with debugging and bug tracker integrations.
7676
def inherited(child_class)
77-
child_class.const_set(:InvalidNullError, Class.new(GraphQL::InvalidNullError))
77+
child_class.const_set(:InvalidNullError, GraphQL::InvalidNullError.subclass_for(child_class))
7878
super
7979
end
8080

spec/dummy/test/application_system_test_case.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,14 @@
33

44
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
55
driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
6+
7+
teardown do
8+
# Adapted from https://medium.com/@coorasse/catch-javascript-errors-in-your-system-tests-89c2fe6773b1
9+
errors = page.driver.browser.manage.logs.get(:browser)
10+
if errors.present?
11+
errors.each do |error|
12+
assert_nil "#{error.level}: #{error.message}"
13+
end
14+
end
15+
end
616
end

spec/graphql/schema/mutation_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,17 @@
113113
response = Jazz::Schema.execute(query_str)
114114
assert_equal 2, response["errors"].length, "It should return two errors"
115115
end
116+
117+
it "raises a mutation-specific invalid null error" do
118+
query_str = "mutation { returnInvalidNull { int } }"
119+
response = Jazz::Schema.execute(query_str)
120+
assert_equal ["Cannot return null for non-nullable field ReturnInvalidNullPayload.int"], response["errors"].map { |e| e["message"] }
121+
if TESTING_INTERPRETER
122+
error = response.query.context.errors.first
123+
assert_instance_of Jazz::ReturnInvalidNull.payload_type::InvalidNullError, error
124+
assert_equal "Jazz::ReturnInvalidNull::ReturnInvalidNullPayload::InvalidNullError", error.class.inspect
125+
end
126+
end
116127
end
117128

118129
describe ".null" do

spec/support/jazz.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,14 @@ def resolve
776776
end
777777
end
778778

779+
class ReturnInvalidNull < GraphQL::Schema::Mutation
780+
field :int, Integer, null: false
781+
782+
def resolve
783+
{ int: nil }
784+
end
785+
end
786+
779787
class Mutation < BaseObject
780788
field :add_ensemble, Ensemble, null: false do
781789
argument :input, EnsembleInput, required: true
@@ -796,6 +804,7 @@ class Mutation < BaseObject
796804
field :has_extras, mutation: HasExtras
797805
field :has_extras_stripped, mutation: HasExtrasStripped
798806
field :has_field_extras, mutation: HasFieldExtras, extras: [:lookahead]
807+
field :return_invalid_null, mutation: ReturnInvalidNull
799808

800809
def add_ensemble(input:)
801810
ens = Models::Ensemble.new(input.name)

0 commit comments

Comments
 (0)