Skip to content

Commit

Permalink
Update for improved static validation and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
rmosolgo committed Sep 7, 2022
1 parent 62cf0f3 commit bf6cb8f
Show file tree
Hide file tree
Showing 12 changed files with 33 additions and 60 deletions.
1 change: 1 addition & 0 deletions lib/graphql/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,7 @@ def default_directives
"include" => GraphQL::Schema::Directive::Include,
"skip" => GraphQL::Schema::Directive::Skip,
"deprecated" => GraphQL::Schema::Directive::Deprecated,
"oneOf" => GraphQL::Schema::Directive::OneOf,
}.freeze
end

Expand Down
1 change: 1 addition & 0 deletions lib/graphql/schema/directive/one_of.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class Directive < GraphQL::Schema::Member
class OneOf < GraphQL::Schema::Directive
description "Requires that exactly one field must be supplied and that field must not be `null`."
locations(GraphQL::Schema::Directive::INPUT_OBJECT)
default_directive true
end
end
end
Expand Down
8 changes: 1 addition & 7 deletions lib/graphql/schema/input_object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,7 @@ def self.one_of
end

def self.one_of?
if defined?(@one_of)
@one_of
elsif superclass.respond_to?(:one_of?)
superclass.one_of?
else
false
end
directives.any? { |d| d.is_a?(GraphQL::Schema::Directive::OneOf) }
end

def unwrap_value(value)
Expand Down
4 changes: 4 additions & 0 deletions lib/graphql/schema/late_bound_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ def inspect
"#<LateBoundType @name=#{name}>"
end

def non_null?
false
end

alias :to_s :inspect
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def on_input_object(node, parent)
return super unless parent.is_a?(GraphQL::Language::Nodes::Argument)

parent_type = get_parent_type(context, parent)
return super unless parent_type && parent_type.kind.input_object? && one_of?(parent_type)
return super unless parent_type && parent_type.kind.input_object? && parent_type.one_of?

validate_one_of_input_object(node, context, parent_type)
super
Expand Down Expand Up @@ -36,7 +36,7 @@ def validate_one_of_input_object(ast_node, context, parent_type)
if value.is_a?(GraphQL::Language::Nodes::NullValue)
add_error(
OneOfInputObjectsAreValidError.new(
"Field '#{input_object_type}.#{field}' must be non-null.",
"Argument '#{input_object_type}.#{field}' must be non-null.",
path: [*context.path, field],
nodes: ast_node.arguments.first,
input_object_type: input_object_type
Expand All @@ -61,12 +61,6 @@ def validate_one_of_input_object(ast_node, context, parent_type)
end
end
end

def one_of?(type)
type.directives.any? do |directive|
directive.is_a?(GraphQL::Schema::Directive::OneOf)
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -53,40 +53,6 @@ def validate_input_object(ast_node, context, parent)
nodes: ast_node,
))
end

if parent_type.one_of?
case present_fields.size
when 1
if ast_node.arguments.first.value.is_a?(Language::Nodes::NullValue)
add_error(RequiredInputObjectAttributesArePresentError.new(
"InputObject '#{parent_type.to_type_signature}' requires exactly one argument, but '#{present_fields.first}' was null.",
argument_name: nil,
argument_type: nil,
input_object_type: parent_type.to_type_signature,
path: path,
nodes: ast_node,
))
end
when 0
add_error(RequiredInputObjectAttributesArePresentError.new(
"InputObject '#{parent_type.to_type_signature}' requires exactly one argument, but none were provided.",
argument_name: nil,
argument_type: nil,
input_object_type: parent_type.to_type_signature,
path: path,
nodes: ast_node,
))
else # Too many
add_error(RequiredInputObjectAttributesArePresentError.new(
"InputObject '#{parent_type.to_type_signature}' requires exactly one argument, but #{present_fields.size} were provided.",
argument_name: nil,
argument_type: nil,
input_object_type: parent_type.to_type_signature,
path: path,
nodes: ast_node,
))
end
end
end
end
end
Expand Down
9 changes: 9 additions & 0 deletions spec/graphql/introspection/directive_type_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@
"onFragment" => false,
"onOperation" => false,
},
{
"name" => "oneOf",
"args" => [],
"locations"=>["INPUT_OBJECT"],
"isRepeatable" => false,
"onField" => false,
"onFragment" => false,
"onOperation" => false,
},
{
"name"=>"doStuff",
"args"=>[],
Expand Down
2 changes: 1 addition & 1 deletion spec/graphql/introspection/schema_type_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def self.visible?(context)
|}

it "only returns visible directives" do
expected_dirs = ['deprecated', 'include', 'skip', 'visibleDirective']
expected_dirs = ['deprecated', 'include', 'skip', 'oneOf', 'visibleDirective']
directives = result['data']['__schema']['directives'].map { |dir| dir.fetch('name') }
assert_equal(expected_dirs.sort, directives.sort)
end
Expand Down
4 changes: 2 additions & 2 deletions spec/graphql/schema/build_from_definition_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def assert_schema_and_compare_output(definition)

parsed_schema = GraphQL::Schema.from_definition(schema)
hello_type = parsed_schema.get_type("Hello")
assert_equal ["deprecated", "foo", "greeting", "greeting2", "hashed", "include", "language", "skip"], parsed_schema.directives.keys.sort
assert_equal ["deprecated", "foo", "greeting", "greeting2", "hashed", "include", "language", "oneOf", "skip"], parsed_schema.directives.keys.sort
parsed_schema.directives.values.each do |dir_class|
assert dir_class < GraphQL::Schema::Directive
end
Expand Down Expand Up @@ -268,7 +268,7 @@ def assert_schema_and_compare_output(definition)
SCHEMA

built_schema = GraphQL::Schema.from_definition(schema)
assert_equal ['deprecated', 'include', 'skip'], built_schema.directives.keys.sort
assert_equal ['deprecated', 'include', 'oneOf', 'skip'], built_schema.directives.keys.sort
end

it 'supports overriding built-in directives' do
Expand Down
13 changes: 6 additions & 7 deletions spec/graphql/schema/input_object_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1167,8 +1167,7 @@ def f(a:)

it "prints in the SDL" do
sdl = OneOfSchema.to_definition
assert_includes sdl, "\ninput OneOfInput @oneOf {\n"
assert_includes sdl, "directive @oneOf on INPUT_OBJECT\n"
assert_includes sdl, "input OneOfInput @oneOf {\n"
end

it "shows in the introspection query" do
Expand Down Expand Up @@ -1218,20 +1217,20 @@ def f(a:)

it "rejects queries with multiple values" do
res = OneOfSchema.execute("{ f(a: { arg1: 5 , arg2: 4 }) }")
assert_equal ["InputObject 'OneOfInput' requires exactly one argument, but 2 were provided."], res["errors"].map { |e| e["message"] }
assert_equal ["OneOf Input Object 'OneOfInput' must specify exactly one key."], res["errors"].map { |e| e["message"] }

res = OneOfSchema.execute("{ f(a: {}) }")
assert_equal ["InputObject 'OneOfInput' requires exactly one argument, but none were provided."], res["errors"].map { |e| e["message"] }
assert_equal ["OneOf Input Object 'OneOfInput' must specify exactly one key."], res["errors"].map { |e| e["message"] }

res = OneOfSchema.execute("{ f(a: { arg1: 5 , arg2: null }) }")
assert_equal ["InputObject 'OneOfInput' requires exactly one argument, but 2 were provided."], res["errors"].map { |e| e["message"] }
assert_equal ["OneOf Input Object 'OneOfInput' must specify exactly one key."], res["errors"].map { |e| e["message"] }

res = OneOfSchema.execute("query($arg2: Int!) { f(a: { arg1: 5 , arg2: $arg2 }) }", variables: { arg2: nil })
assert_equal ["InputObject 'OneOfInput' requires exactly one argument, but 2 were provided."], res["errors"].map { |e| e["message"] }
assert_equal ["OneOf Input Object 'OneOfInput' must specify exactly one key."], res["errors"].map { |e| e["message"] }


res = OneOfSchema.execute("{ f(a: { arg2: null }) }")
assert_equal ["InputObject 'OneOfInput' requires exactly one argument, but 'arg2' was null."], res["errors"].map { |e| e["message"] }
assert_equal ["Argument 'OneOfInput.arg2' must be non-null."], res["errors"].map { |e| e["message"] }


q_str = "query($args: OneOfInput!) { f(a: $args) }"
Expand Down
5 changes: 5 additions & 0 deletions spec/graphql/schema/printer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ class Subscription < GraphQL::Schema::Object
if: Boolean!
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"""
Requires that exactly one field must be supplied and that field must not be `null`.
"""
directive @oneOf on INPUT_OBJECT
"""
Directs the executor to skip this field or fragment when the `if` argument is true.
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
it "finds errors" do
expected = [
{
"message" => "Field 'OneOfArgInput.stringField' must be non-null.",
"message" => "Argument 'OneOfArgInput.stringField' must be non-null.",
"locations" => [{ "line" => 3, "column" => 35 }],
"path" => ["query", "oneOfArgField", "oneOfArg", "stringField"],
"extensions" => {
Expand Down

0 comments on commit bf6cb8f

Please sign in to comment.