Skip to content

Commit

Permalink
fix: make results nullable again if root level errors are enabled (#114)
Browse files Browse the repository at this point in the history
Building upon #110, this restores the old behaviour of the result being nullable
when root level errors are present.

While the result is guaranteed to not be nullable in standard conditions (since
either result or errors are always present), when errors are moved to the root
level it could become null, so declaring it non-nullable propagates the null up
to the data field.

This actually causes compatibility problems with some client libraries (e.g.
Relay) that expect the inner result to be null, _not_ data, if there's an error.

This also adds dedicated RootLevelErrors versions of the Api and the Schema
since the configuration is accessed at compile time now, so put_env was not
enough to test them correctly.
  • Loading branch information
rbino authored Feb 9, 2024
1 parent c82512d commit 70eae5f
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 17 deletions.
19 changes: 10 additions & 9 deletions lib/resource/resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -610,9 +610,7 @@ defmodule AshGraphql.Resource do
module: schema,
name: to_string(mutation.name),
description: Ash.Resource.Info.action(resource, mutation.action).description,
type: %Absinthe.Blueprint.TypeReference.NonNull{
of_type: String.to_atom("#{mutation.name}_result")
},
type: mutation_result_type(mutation.name, api),
__reference__: ref(__ENV__)
}
end
Expand Down Expand Up @@ -656,9 +654,7 @@ defmodule AshGraphql.Resource do
module: schema,
name: to_string(mutation.name),
description: Ash.Resource.Info.action(resource, mutation.action).description,
type: %Absinthe.Blueprint.TypeReference.NonNull{
of_type: String.to_atom("#{mutation.name}_result")
},
type: mutation_result_type(mutation.name, api),
__reference__: ref(__ENV__)
}

Expand Down Expand Up @@ -710,13 +706,18 @@ defmodule AshGraphql.Resource do
module: schema,
name: to_string(mutation.name),
description: Ash.Resource.Info.action(resource, mutation.action).description,
type: %Absinthe.Blueprint.TypeReference.NonNull{
of_type: String.to_atom("#{mutation.name}_result")
},
type: mutation_result_type(mutation.name, api),
__reference__: ref(__ENV__)
}
end

defp mutation_result_type(mutation_name, api) do
type = String.to_atom("#{mutation_name}_result")
root_level_errors? = AshGraphql.Api.Info.root_level_errors?(api)

maybe_wrap_non_null(type, not root_level_errors?)
end

defp mutation_args(%{identity: false} = mutation, resource, schema) do
mutation_read_args(mutation, resource, schema)
end
Expand Down
70 changes: 62 additions & 8 deletions test/errors_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ defmodule AshGraphql.ErrorsTest do
end

test "errors can be configured to be shown in the root" do
Application.put_env(:ash_graphql, AshGraphql.Test.Api, graphql: [root_level_errors?: true])

resp =
"""
mutation CreatePost($input: CreatePostInput) {
Expand All @@ -27,7 +25,7 @@ defmodule AshGraphql.ErrorsTest do
}
}
"""
|> Absinthe.run(AshGraphql.Test.Schema,
|> Absinthe.run(AshGraphql.Test.RootLevelErrorsSchema,
variables: %{
"input" => %{
"text" => "foobar",
Expand All @@ -38,7 +36,7 @@ defmodule AshGraphql.ErrorsTest do

assert {:ok, result} = resp

assert %{data: nil, errors: [%{message: message}]} = result
assert %{data: %{"createPost" => nil}, errors: [%{message: message}]} = result

assert message =~ "confirmation did not match value"
end
Expand Down Expand Up @@ -111,8 +109,8 @@ defmodule AshGraphql.ErrorsTest do
end

test "showing raised errors alongside root errors shows raised errors in the root" do
Application.put_env(:ash_graphql, AshGraphql.Test.Api,
graphql: [show_raised_errors?: true, root_level_errors?: true]
Application.put_env(:ash_graphql, AshGraphql.Test.RootLevelErrorsApi,
graphql: [show_raised_errors?: true]
)

resp =
Expand All @@ -128,7 +126,7 @@ defmodule AshGraphql.ErrorsTest do
}
}
"""
|> Absinthe.run(AshGraphql.Test.Schema,
|> Absinthe.run(AshGraphql.Test.RootLevelErrorsSchema,
variables: %{
"input" => %{
"text" => "foobar"
Expand All @@ -139,7 +137,7 @@ defmodule AshGraphql.ErrorsTest do
assert {:ok, result} = resp

assert %{
data: nil,
data: %{"createPostWithError" => nil},
errors: [
%{message: message}
]
Expand Down Expand Up @@ -415,4 +413,60 @@ defmodule AshGraphql.ErrorsTest do
assert fields["type"]["ofType"]["kind"] == "NON_NULL"
assert fields["type"]["ofType"]["ofType"]["name"] == "String"
end

test "mutation result is non nullable without root level errors" do
{:ok, %{data: data}} =
"""
query {
__schema {
mutationType {
name
fields {
name
type {
kind
ofType {
name
}
}
}
}
}
}
"""
|> Absinthe.run(AshGraphql.Test.Schema)

create_post_mutation =
data["__schema"]["mutationType"]["fields"]
|> Enum.find(fn field -> field["name"] == "createPost" end)

assert create_post_mutation["type"]["kind"] == "NON_NULL"
assert create_post_mutation["type"]["ofType"]["name"] == "CreatePostResult"
end

test "mutation result is nullable with root level errors" do
{:ok, %{data: data}} =
"""
query {
__schema {
mutationType {
name
fields {
name
type {
name
}
}
}
}
}
"""
|> Absinthe.run(AshGraphql.Test.RootLevelErrorsSchema)

create_post_mutation =
data["__schema"]["mutationType"]["fields"]
|> Enum.find(fn field -> field["name"] == "createPost" end)

assert create_post_mutation["type"]["name"] == "CreatePostResult"
end
end
17 changes: 17 additions & 0 deletions test/support/root_level_errors_api.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule AshGraphql.Test.RootLevelErrorsApi do
@moduledoc false

use Ash.Api,
extensions: [
AshGraphql.Api
],
otp_app: :ash_graphql

graphql do
root_level_errors? true
end

resources do
registry(AshGraphql.Test.Registry)
end
end
30 changes: 30 additions & 0 deletions test/support/root_level_errors_schema.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
defmodule AshGraphql.Test.RootLevelErrorsSchema do
@moduledoc false

use Absinthe.Schema

@apis [AshGraphql.Test.RootLevelErrorsApi]

use AshGraphql, apis: @apis

query do
end

mutation do
end

object :foo do
field(:foo, :string)
field(:bar, :string)
end

input_object :foo_input do
field(:foo, non_null(:string))
field(:bar, non_null(:string))
end

enum :status do
value(:open, description: "The post is open")
value(:closed, description: "The post is closed")
end
end

0 comments on commit 70eae5f

Please sign in to comment.