Class: RuboCop::Cop::GraphQL::NullabilityMismatch

Inherits:
Base
  • Object
show all
Includes:
GraphQL::Sorbet
Defined in:
lib/rubocop/cop/graphql/nullability_mismatch.rb

Overview

A non-null field should not have a resolver whose Sorbet signature returns a nilable type. The two disagree: the schema promises a value, the signature admits nil, and graphql-ruby raises an invalid-null error for every object that resolves to nil.

Sorbet cannot catch this, because the field declaration is not part of the signature. Only the crashing direction is reported: a nullable field with a non-nilable resolver is merely imprecise, not broken.

Codebases without Sorbet signatures never trigger this cop.

Examples:

# bad

class UserType < BaseObject
  field :name, String, null: false

  sig { override.returns(T.nilable(String)) }
  def name
    object.name
  end
end

# good - the schema admits what the resolver may return

class UserType < BaseObject
  field :name, String, null: true

  sig { override.returns(T.nilable(String)) }
  def name
    object.name
  end
end

# good - the resolver guarantees what the schema promises

class UserType < BaseObject
  field :name, String, null: false

  sig { override.returns(String) }
  def name
    object.name || "anonymous"
  end
end

Constant Summary collapse

MSG =
"Field `%<field>s` is `null: false` but its resolver signature returns a " \
"nilable type, so a nil resolves to an invalid null error."

Instance Method Summary collapse

Methods included from GraphQL::Sorbet

#has_sorbet_signature?, #sorbet_signature, #sorbet_signature_for

Instance Method Details

#on_class(node) ⇒ Object Also known as: on_module



56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/rubocop/cop/graphql/nullability_mismatch.rb', line 56

def on_class(node)
  non_null_fields = collect_non_null_fields(node)
  return if non_null_fields.empty?

  node.each_descendant(:def) do |def_node|
    next unless owned_by?(def_node, node)

    field_node = non_null_fields[def_node.method_name]
    next unless field_node && nilable_signature?(def_node)

    field_name = RuboCop::GraphQL::Field.new(field_node).name
    add_offense(field_node, message: format(MSG, field: field_name))
  end
end