Describe the bug
FieldExtension#after_resolve uses || to choose between value and values, which treats falsy-but-valid return values (e.g. false) as absent. When a mutation field is declared null: false and resolves to false, the extension returns nil instead, triggering null propagation.
Versions
graphql version: 2.5.24 (regression from 2.5.23)
rails (or other framework): 8.0.5
GraphQL schema
class MyFieldExtension < GraphQL::Schema::FieldExtension
def resolve(object:, arguments:, context:, **_rest)
yield(object, arguments)
end
# after_resolve NOT overridden — inherits base class default
end
class BaseMutation < GraphQL::Schema::Mutation
field_class BaseField # BaseField pushes MyFieldExtension onto every field
end
class Mutations::DoSomething < BaseMutation
field :success, Boolean, null: false
def resolve
{ success: false } # error path
end
end
GraphQL query
mutation {
doSomething {
success
}
}
Expected response:
{ "data": { "doSomething": { "success": false } } }
Actual response (2.5.24):
{ "data": null, "errors": [{ "message": "Cannot return null for non-nullable field DoSomethingPayload.success" }] }
Steps to reproduce
- Create a mutation with a
Boolean, null: false field named success.
- Attach any
FieldExtension subclass that does not override after_resolve (inheriting the base class default).
- Execute the mutation through a code path that returns
{ success: false }.
- Observe the null propagation error. The
success: true path is unaffected.
Expected behavior
false is a valid non-null Boolean. A field extension that does not override after_resolve should pass the resolved value through unchanged, as it did in 2.5.23.
Actual behavior
The default after_resolve in 2.5.24 changed from returning value to returning value || values. When value is false, false || nil evaluates to nil, which violates the null: false constraint and nullifies the parent object.
The regression is in this diff between 2.5.23 and 2.5.24:
# 2.5.23
def after_resolve(object:, arguments:, context:, value:, memo:)
value
end
# 2.5.24
def after_resolve(object: nil, objects: nil, arguments:, context:, values: nil, value: nil, memo:)
value || values # ← breaks when value is false
end
Describe the bug
FieldExtension#after_resolveuses||to choose betweenvalueandvalues, which treats falsy-but-valid return values (e.g.false) as absent. When a mutation field is declarednull: falseand resolves tofalse, the extension returnsnilinstead, triggering null propagation.Versions
graphqlversion: 2.5.24 (regression from 2.5.23)rails(or other framework): 8.0.5GraphQL schema
GraphQL query
Expected response:
Actual response (2.5.24):
Steps to reproduce
Boolean, null: falsefield namedsuccess.FieldExtensionsubclass that does not overrideafter_resolve(inheriting the base class default).{ success: false }.success: truepath is unaffected.Expected behavior
falseis a valid non-nullBoolean. A field extension that does not overrideafter_resolveshould pass the resolved value through unchanged, as it did in 2.5.23.Actual behavior
The default
after_resolvein 2.5.24 changed from returningvalueto returningvalue || values. Whenvalueisfalse,false || nilevaluates tonil, which violates thenull: falseconstraint and nullifies the parent object.The regression is in this diff between 2.5.23 and 2.5.24: