Skip to content

Commit 9a48f9d

Browse files
fix: fixing keyerror in GET /:id with includes (#400)
* fix: fixing keyerror in GET /:id with includes * chore: fixing credo check * chore: moving get invalid filter tests to get_related_tests
1 parent d0b390c commit 9a48f9d

3 files changed

Lines changed: 94 additions & 3 deletions

File tree

lib/ash_json_api/controllers/helpers.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,7 @@ defmodule AshJsonApi.Controllers.Helpers do
689689

690690
case query do
691691
{:error, error} ->
692-
{:error, Request.add_error(request, error, :filter)}
692+
Request.add_error(request, error, :filter)
693693

694694
{:ok, query} ->
695695
query =
@@ -787,7 +787,7 @@ defmodule AshJsonApi.Controllers.Helpers do
787787

788788
case query do
789789
{:error, error} ->
790-
{:error, Request.add_error(request, error, :filter)}
790+
Request.add_error(request, error, :filter)
791791

792792
{:ok, query} ->
793793
query =

lib/ash_json_api/json_schema/open_api.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ if Code.ensure_loaded?(OpenApiSpex) do
3434
end
3535
"""
3636
alias Ash.Query.Aggregate
37-
alias AshJsonApi.Resource.Route
3837
alias Ash.Resource.{Actions, Relationships}
38+
alias AshJsonApi.Resource.Route
3939

4040
alias OpenApiSpex.{
4141
Info,

test/acceptance/get_related_test.exs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,97 @@ defmodule Test.Acceptance.GetRelatedTest do
154154
:ok
155155
end
156156

157+
describe "GET /:id with invalid filter and includes" do
158+
test "returns JSON:API error document instead of crashing with KeyError" do
159+
# Create a post with comments
160+
post =
161+
Post
162+
|> Ash.Changeset.for_create(:create, %{name: "John Doe", content: "Test post"})
163+
|> Ash.create!()
164+
165+
_comment =
166+
Comment
167+
|> Ash.Changeset.for_create(:create, %{
168+
name: "Test comment",
169+
content: "comment",
170+
post_id: post.id
171+
})
172+
|> Ash.create!()
173+
174+
# This request triggers a filter error in fetch_record_from_path when the ID
175+
# format is invalid. The bug would cause a KeyError because fetch_record_from_path
176+
# returned {:error, request} instead of just request, and chain/3 would try to
177+
# access request.errors on the tuple.
178+
#
179+
# With the fix, this returns a proper 404 error document
180+
response =
181+
Domain
182+
|> get("/posts/invalid-id-format/comments?include=post", status: 404)
183+
184+
# Verify we get a proper JSON:API error response, not a crash
185+
assert is_map(response.resp_body)
186+
assert Map.has_key?(response.resp_body, "errors")
187+
errors = response.resp_body["errors"]
188+
assert is_list(errors)
189+
assert length(errors) > 0
190+
191+
# Verify the error has proper JSON:API structure
192+
error = hd(errors)
193+
assert is_map(error)
194+
assert Map.has_key?(error, "code")
195+
assert Map.has_key?(error, "title")
196+
assert Map.has_key?(error, "detail")
197+
assert Map.has_key?(error, "status")
198+
199+
# The key point: we got a proper error response (404), not a 500 crash
200+
assert error["status"] == "404"
201+
assert error["code"] == "not_found"
202+
end
203+
204+
test "returns JSON:API error for non-existent ID with includes" do
205+
# Use a valid UUID format but non-existent ID
206+
non_existent_id = Ash.UUID.generate()
207+
208+
response =
209+
Domain
210+
|> get("/posts/#{non_existent_id}/comments?include=post", status: 404)
211+
212+
# Verify we get a proper 404 JSON:API error response
213+
assert is_map(response.resp_body)
214+
assert Map.has_key?(response.resp_body, "errors")
215+
errors = response.resp_body["errors"]
216+
assert is_list(errors)
217+
assert length(errors) > 0
218+
219+
error = hd(errors)
220+
assert error["status"] == "404"
221+
assert error["code"] == "not_found"
222+
end
223+
224+
test "handles non-existent ID with complex includes gracefully" do
225+
# Use a non-existent ID to trigger the error path in fetch_record_from_path
226+
# This exercises the code path where the lookup fails and we need to add an error
227+
non_existent_id = Ash.UUID.generate()
228+
229+
# Before the fix, this would crash with KeyError when chain/3 tried to access
230+
# request.errors on {:error, request}. After the fix, it returns proper 404.
231+
response =
232+
Domain
233+
|> get("/posts/#{non_existent_id}/comments?include=post", status: 404)
234+
235+
# Should return error document, not crash
236+
assert is_map(response.resp_body)
237+
assert Map.has_key?(response.resp_body, "errors")
238+
errors = response.resp_body["errors"]
239+
assert is_list(errors)
240+
assert length(errors) > 0
241+
242+
error = hd(errors)
243+
assert error["status"] == "404"
244+
assert error["code"] == "not_found"
245+
end
246+
end
247+
157248
describe "index endpoint" do
158249
setup do
159250
post =

0 commit comments

Comments
 (0)