From 7d7501ad59d4d180cdc85d16797cad32bb70f615 Mon Sep 17 00:00:00 2001 From: Skander Mzali Date: Tue, 29 Jul 2025 14:09:56 -0700 Subject: [PATCH 1/3] Add failing nested domain route test --- test/acceptance/patch_test.exs | 61 ++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/acceptance/patch_test.exs b/test/acceptance/patch_test.exs index 6077096f..724bf239 100644 --- a/test/acceptance/patch_test.exs +++ b/test/acceptance/patch_test.exs @@ -129,6 +129,10 @@ defmodule Test.Acceptance.PatchTest do require_atomic?(false) accept([:preferences]) end + + update :update_author_name do + accept([:name]) + end end attributes do @@ -223,6 +227,10 @@ defmodule Test.Acceptance.PatchTest do accept([:email, :name]) end + update :update_post_name do + accept([:name]) + end + action :fake_update, :struct do constraints(instance_of: __MODULE__) argument(:id, :uuid, allow_nil?: false) @@ -328,6 +336,18 @@ defmodule Test.Acceptance.PatchTest do resource(Post) resource(Bio) end + + json_api do + routes do + base_route "/domain_posts", Post do + patch :update_post_name, route: "/:id/update_post_name" + + base_route "/:post_id/authors", Author do + patch :update_author_name, route: "/:id/update_author_name" + end + end + end + end end defmodule Router do @@ -811,4 +831,45 @@ defmodule Test.Acceptance.PatchTest do assert bio_content == bio.bio end end + + describe "domain routes" do + setup do + id = Ecto.UUID.generate() + + author = + Author + |> Ash.Changeset.for_create(:create, %{id: Ecto.UUID.generate(), name: "John"}) + |> Ash.create!() + + posts = + Enum.map(1..2, fn _ -> + Post + |> Ash.Changeset.for_create(:create, %{name: "Valid Post", id: id}) + |> Ash.Changeset.force_change_attribute(:author_id, author.id) + |> Ash.Changeset.force_change_attribute(:hidden, "hidden") + |> Ash.create!() + end) + + %{posts: posts, author: author} + end + + test "patch updates post attribute", %{posts: [%{id: post_id} | _]} do + assert %{status: 200} = + Domain + |> patch("/domain_posts/#{post_id}/update_post_name", %{ + data: %{attributes: %{name: "New name"}} + }) + end + + test "patch updates nested route attribute", %{ + author: %{id: author_id}, + posts: [%{id: post_id} | _] + } do + assert %{status: 200} = + Domain + |> patch("/domain_posts/#{post_id}/authors/#{author_id}/update_author_name", %{ + data: %{attributes: %{name: "New name"}} + }) + end + end end From f319c04ffa17cb6562565924643ea1a8aea74c36 Mon Sep 17 00:00:00 2001 From: Skander Mzali Date: Tue, 29 Jul 2025 14:12:27 -0700 Subject: [PATCH 2/3] Make patch test success clearer --- test/acceptance/patch_test.exs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/acceptance/patch_test.exs b/test/acceptance/patch_test.exs index 724bf239..c0ee19a6 100644 --- a/test/acceptance/patch_test.exs +++ b/test/acceptance/patch_test.exs @@ -854,10 +854,13 @@ defmodule Test.Acceptance.PatchTest do end test "patch updates post attribute", %{posts: [%{id: post_id} | _]} do - assert %{status: 200} = + assert %{ + status: 200, + resp_body: %{"data" => %{"attributes" => %{"name" => "New post name"}}} + } = Domain |> patch("/domain_posts/#{post_id}/update_post_name", %{ - data: %{attributes: %{name: "New name"}} + data: %{attributes: %{name: "New post name"}} }) end @@ -865,10 +868,13 @@ defmodule Test.Acceptance.PatchTest do author: %{id: author_id}, posts: [%{id: post_id} | _] } do - assert %{status: 200} = + assert %{ + status: 200, + resp_body: %{"data" => %{"attributes" => %{"name" => "New author name"}}} + } = Domain |> patch("/domain_posts/#{post_id}/authors/#{author_id}/update_author_name", %{ - data: %{attributes: %{name: "New name"}} + data: %{attributes: %{name: "New author name"}} }) end end From 22b90e11dee1e053131545033fae2060dc95fb19 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 30 Jul 2025 12:13:19 -0400 Subject: [PATCH 3/3] improvement: make error clearer for unused route params --- lib/ash_json_api/controllers/helpers.ex | 3 ++- lib/ash_json_api/error/error.ex | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/ash_json_api/controllers/helpers.ex b/lib/ash_json_api/controllers/helpers.ex index 8e0f2812..b4197cc2 100644 --- a/lib/ash_json_api/controllers/helpers.ex +++ b/lib/ash_json_api/controllers/helpers.ex @@ -623,13 +623,14 @@ defmodule AshJsonApi.Controllers.Helpers do # Normal parameter handling case Enum.find(action.arguments, &(to_string(&1.name) == key)) do nil -> - case Ash.Resource.Info.public_attribute(resource, key) do + case Ash.Resource.Info.attribute(resource, key) do nil -> {:halt, {:error, Ash.Error.Invalid.NoSuchInput.exception( resource: resource, action: action.name, + input: key, inputs: [] )}} diff --git a/lib/ash_json_api/error/error.ex b/lib/ash_json_api/error/error.ex index 3557c9c2..4be73a36 100644 --- a/lib/ash_json_api/error/error.ex +++ b/lib/ash_json_api/error/error.ex @@ -359,13 +359,22 @@ end defimpl AshJsonApi.ToJsonApiError, for: Ash.Error.Invalid.NoSuchInput do def to_json_api_error(error) do + vars = + if error.input do + error.vars + |> Map.new() + |> Map.put(:input, error.input) + else + Map.new(error.vars) + end + %AshJsonApi.Error{ id: Ash.UUID.generate(), status_code: 422, code: "no_such_input", title: "NoSuchInput", detail: "no such input", - meta: Map.new(error.vars) + meta: vars } end end