Skip to content

Commit 5bd1b70

Browse files
committed
fix: handle relationship inputs w/o id in them
fix: improve error handling around invalid relationship inputs
1 parent 5f91a77 commit 5bd1b70

File tree

3 files changed

+106
-18
lines changed

3 files changed

+106
-18
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule AshJsonApi.Error.InvalidRelationshipInput do
2+
@moduledoc """
3+
Returned when the request body provided is invalid
4+
"""
5+
6+
use Splode.Error, class: :invalid, fields: [:relationship, :input]
7+
8+
def message(exception) do
9+
"Invalid relationship input for #{exception.relationship}: #{Jason.encode!(exception.input)}"
10+
end
11+
12+
defimpl AshJsonApi.ToJsonApiError do
13+
def to_json_api_error(error) do
14+
%AshJsonApi.Error{
15+
id: Ash.UUID.generate(),
16+
status_code: 400,
17+
source_pointer: "/data/relationships/#{error.relationship}",
18+
code: "invalid_body",
19+
title: "InvalidBody",
20+
detail: "invalid relationship input",
21+
meta: %{}
22+
}
23+
end
24+
end
25+
end

lib/ash_json_api/request.ex

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ defmodule AshJsonApi.Request do
88
InvalidHeader,
99
InvalidIncludes,
1010
InvalidQuery,
11+
InvalidRelationshipInput,
1112
InvalidType,
1213
UnacceptableMediaType,
1314
UnsupportedMediaType
@@ -851,14 +852,15 @@ defmodule AshJsonApi.Request do
851852
when is_map(relationships) do
852853
Enum.reduce(relationships, request, fn {name, value}, request ->
853854
with %{"data" => data} when is_map(data) or is_list(data) <- value,
854-
arg when not is_nil(arg) <-
855-
Enum.find(
856-
action.arguments,
857-
&(to_string(&1.name) == name &&
858-
has_relationship_argument?(relationship_arguments, &1.name))
859-
),
855+
{:arg, arg} when not is_nil(arg) <-
856+
{:arg,
857+
Enum.find(
858+
action.arguments,
859+
&(to_string(&1.name) == name &&
860+
has_relationship_argument?(relationship_arguments, &1.name))
861+
)},
860862
{:ok, change_value} <-
861-
relationship_change_value(data) do
863+
relationship_change_value(name, data) do
862864
case find_relationship_argument(relationship_arguments, arg.name) do
863865
{:id, _arg} ->
864866
%{
@@ -873,8 +875,33 @@ defmodule AshJsonApi.Request do
873875
}
874876
end
875877
else
876-
_ ->
877-
add_error(request, "invalid relationship input: #{name}", request.route.type)
878+
{:arg, nil} ->
879+
add_error(
880+
request,
881+
Ash.Error.Invalid.NoSuchInput.exception(input: name, resource: request.resource),
882+
request.route.type
883+
)
884+
885+
{:error, error} ->
886+
add_error(
887+
request,
888+
error,
889+
request.route.type
890+
)
891+
892+
%{"data" => data} ->
893+
add_error(
894+
request,
895+
InvalidRelationshipInput.exception(relationship: name, input: data),
896+
request.route.type
897+
)
898+
899+
other ->
900+
add_error(
901+
request,
902+
InvalidRelationshipInput.exception(relationship: name, input: other),
903+
request.route.type
904+
)
878905
end
879906
end)
880907
end
@@ -923,10 +950,10 @@ defmodule AshJsonApi.Request do
923950
%{request | resource_identifiers: nil}
924951
end
925952

926-
defp relationship_change_value(value)
953+
defp relationship_change_value(name, value)
927954
when is_list(value) do
928955
value
929-
|> Stream.map(&relationship_change_value(&1))
956+
|> Stream.map(&relationship_change_value(name, &1))
930957
|> Enum.reduce_while({:ok, []}, fn
931958
{:ok, change}, {:ok, changes} ->
932959
{:cont, {:ok, [change | changes]}}
@@ -938,24 +965,24 @@ defmodule AshJsonApi.Request do
938965
{:ok, changes} ->
939966
{:ok, Enum.reverse(changes)}
940967

941-
{:error, error} ->
942-
{:error, error}
968+
{:error, input} ->
969+
{:error, InvalidRelationshipInput.exception(relationship: name, input: input)}
943970
end
944971
end
945972

946-
defp relationship_change_value(%{"id" => id} = value) do
973+
defp relationship_change_value(_name, %{"id" => id} = value) do
947974
case Map.fetch(value, "meta") do
948975
{:ok, meta} -> {:ok, Map.put(meta, "id", id)}
949976
_ -> {:ok, %{"id" => id}}
950977
end
951978
end
952979

953-
defp relationship_change_value(value) when value in [nil, %{}] do
954-
{:ok, nil}
980+
defp relationship_change_value(_name, value) when is_nil(value) or is_map(value) do
981+
{:ok, value}
955982
end
956983

957-
defp relationship_change_value(value) do
958-
{:error, value}
984+
defp relationship_change_value(name, value) do
985+
{:error, InvalidRelationshipInput.exception(relationship: name, input: value)}
959986
end
960987

961988
defp url(conn) do

test/acceptance/post_test.exs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,42 @@ defmodule Test.Acceptance.PostTest do
659659
assert %{"data" => %{"attributes" => %{"hidden" => "hidden"}}} = response.resp_body
660660
end
661661

662+
test "creating with invalid relationships displays the correct error" do
663+
id = Ecto.UUID.generate()
664+
665+
response =
666+
Domain
667+
|> post("/posts", %{
668+
data: %{
669+
type: "post",
670+
attributes: %{
671+
id: id,
672+
name: "Post 2",
673+
hidden: "hidden"
674+
},
675+
relationships: %{
676+
author: %{
677+
data: "foo"
678+
}
679+
}
680+
}
681+
})
682+
683+
# response is a Plug.
684+
assert %{
685+
"errors" => [
686+
%{
687+
"code" => "invalid_body",
688+
"detail" => "invalid relationship input",
689+
"source" => %{"pointer" => "/data/relationships/author"},
690+
"status" => "400",
691+
"title" => "InvalidBody"
692+
}
693+
],
694+
"jsonapi" => %{"version" => "1.0"}
695+
} = response.resp_body
696+
end
697+
662698
test "create with all attributes in accept list with email along with relationship", %{
663699
author: author
664700
} do

0 commit comments

Comments
 (0)