From 4ecdcea91e8cdcaa5b5a51c585c1d6cd5d93967b Mon Sep 17 00:00:00 2001 From: Peter Hartman Date: Thu, 26 Oct 2023 03:08:42 +0100 Subject: [PATCH] fix: restore support for custom paginators (#6) --- lib/data_layer/data_layer.ex | 5 +- test/custom_pagination_test.exs | 111 ++++++++++++++++++++++++++++++++ test/hackernews_test.exs | 13 ++-- test/open_api_cybrid_test.exs | 10 +-- test/open_api_petstore_test.exs | 12 +--- test/petstore_test.exs | 28 ++++---- 6 files changed, 139 insertions(+), 40 deletions(-) create mode 100644 test/custom_pagination_test.exs diff --git a/lib/data_layer/data_layer.ex b/lib/data_layer/data_layer.ex index 5dfe501..b019df0 100644 --- a/lib/data_layer/data_layer.ex +++ b/lib/data_layer/data_layer.ex @@ -330,7 +330,7 @@ defmodule AshJsonApiWrapper.DataLayer do end @impl true - def set_context(_resource, query, context) do + def set_context(resource, query, context) do params = context[:data_layer][:query_params] || %{} headers = Map.to_list(context[:data_layer][:headers] || %{}) @@ -343,6 +343,7 @@ defmodule AshJsonApiWrapper.DataLayer do headers: headers, api: query.api, action: action, + endpoint: AshJsonApiWrapper.DataLayer.Info.endpoint(resource, action.name), context: context }} end @@ -668,7 +669,7 @@ defmodule AshJsonApiWrapper.DataLayer do end defp do_sort({:ok, results}, %{sort: sort}) when sort not in [nil, []] do - Ash.Sort.runtime_sort(results, sort) + Ash.Sort.runtime_sort(results, sort, []) end defp do_sort(other, _), do: other diff --git a/test/custom_pagination_test.exs b/test/custom_pagination_test.exs new file mode 100644 index 0000000..8c44512 --- /dev/null +++ b/test/custom_pagination_test.exs @@ -0,0 +1,111 @@ +defmodule AshJsonApiWrapper.CustomPagination.Test do + use ExUnit.Case + require Ash.Query + @moduletag :custom_pagination + + # ── Custom paginator ── + + defmodule CustomPaginator do + use AshJsonApiWrapper.Paginator + + def cursor do + case :ets.whereis(:cursor) do + :undefined -> + :ets.new(:cursor, [:set, :protected, :named_table]) + |> :ets.insert({self(), 1}) + + 1 + + _ -> + [{_, value} | _rest] = :ets.lookup(:cursor, self()) + value + end + end + + def increment_cursor do + :ets.insert(:cursor, {self(), cursor() + 1}) + end + + def reset_cursor do + :ets.insert(:cursor, {self(), 1}) + end + + def continue(_response, [], _) do + reset_cursor() + :halt + end + + def continue(_response, _entities, _opts) do + increment_cursor() + {:ok, %{params: %{:p => cursor()}}} + end + end + + # ── Resource ── + + defmodule Users do + use Ash.Resource, + data_layer: AshJsonApiWrapper.DataLayer, + validate_api_inclusion?: false + + json_api_wrapper do + tesla(Tesla) + + endpoints do + base("https://65383945a543859d1bb1528e.mockapi.io/api/v1") + + endpoint :list_users do + path("/users") + limit_with {:param, "l"} + runtime_sort? true + paginator CustomPaginator + end + end + end + + actions do + read(:list_users) do + primary?(true) + + pagination do + offset?(true) + required?(true) + default_limit(50) + end + end + end + + attributes do + attribute :id, :integer do + primary_key?(true) + allow_nil?(false) + end + + attribute(:name, :string) + end + end + + defmodule Api do + use Ash.Api, validate_config_inclusion?: false + + resources do + allow_unregistered?(true) + end + end + + # ── Test it! ── + + test "it works" do + Application.put_env(:ash, :validate_api_resource_inclusion?, false) + Application.put_env(:ash, :validate_api_config_inclusion?, false) + + users = + Users + |> Ash.Query.for_read(:list_users) + |> Api.read!(page: [limit: 99]) + + user_count = users.results |> Enum.count() + + assert(user_count == 99) + end +end diff --git a/test/hackernews_test.exs b/test/hackernews_test.exs index 72e00a4..60bca73 100644 --- a/test/hackernews_test.exs +++ b/test/hackernews_test.exs @@ -6,14 +6,15 @@ defmodule AshJsonApiWrapper.Hackernews.Test do defmodule TopStory do @moduledoc false use Ash.Resource, - data_layer: AshJsonApiWrapper.DataLayer + data_layer: AshJsonApiWrapper.DataLayer, + validate_api_inclusion?: false json_api_wrapper do endpoints do base "https://hacker-news.firebaseio.com/v0/" endpoint :read do - limit_with "limitToFirst" + limit_with {:param, "limitToFirst"} path "topstories.json" end end @@ -59,7 +60,8 @@ defmodule AshJsonApiWrapper.Hackernews.Test do defmodule Story do @moduledoc false use Ash.Resource, - data_layer: AshJsonApiWrapper.DataLayer + data_layer: AshJsonApiWrapper.DataLayer, + validate_api_inclusion?: false calculations do calculate(:short_url, :string, ShortUrl) @@ -110,7 +112,8 @@ defmodule AshJsonApiWrapper.Hackernews.Test do defmodule User do @moduledoc false use Ash.Resource, - data_layer: AshJsonApiWrapper.DataLayer + data_layer: AshJsonApiWrapper.DataLayer, + validate_api_inclusion?: false attributes do attribute :id, :string do @@ -142,7 +145,7 @@ defmodule AshJsonApiWrapper.Hackernews.Test do defmodule Api do @moduledoc false - use Ash.Api + use Ash.Api, validate_config_inclusion?: false resources do allow_unregistered?(true) diff --git a/test/open_api_cybrid_test.exs b/test/open_api_cybrid_test.exs index 7bf1c4d..aec23ab 100644 --- a/test/open_api_cybrid_test.exs +++ b/test/open_api_cybrid_test.exs @@ -1,7 +1,7 @@ defmodule AshJsonApiWrapper.OpenApi.CybridTest do use ExUnit.Case - require Ash.Query + @moduletag :oapi_cybrid @json "test/support/cybrid.json" |> File.read!() |> Jason.decode!() @@ -38,14 +38,6 @@ defmodule AshJsonApiWrapper.OpenApi.CybridTest do ] ] - defmodule Api do - use Ash.Api - - resources do - allow_unregistered? true - end - end - test "it does stuff" do @json |> AshJsonApiWrapper.OpenApi.ResourceGenerator.generate(@config) diff --git a/test/open_api_petstore_test.exs b/test/open_api_petstore_test.exs index b6c3060..ecea4e9 100644 --- a/test/open_api_petstore_test.exs +++ b/test/open_api_petstore_test.exs @@ -1,7 +1,7 @@ defmodule AshJsonApiWrapper.OpenApi.PetstoreTest do use ExUnit.Case - require Ash.Query + @moduletag :oapi_petstore @json "test/support/pet_store.json" |> File.read!() |> Jason.decode!() @@ -17,7 +17,7 @@ defmodule AshJsonApiWrapper.OpenApi.PetstoreTest do tesla: TestingTesla, endpoint: "https://petstore3.swagger.io/api/v3", resources: [ - "Petstore.Order": [ + Petstore: [ path: "/store/order/{orderId}", object_type: "components.schemas.Order", primary_key: "id", @@ -31,14 +31,6 @@ defmodule AshJsonApiWrapper.OpenApi.PetstoreTest do ] ] - defmodule Api do - use Ash.Api - - resources do - allow_unregistered? true - end - end - test "it does stuff" do @json |> AshJsonApiWrapper.OpenApi.ResourceGenerator.generate(@config) diff --git a/test/petstore_test.exs b/test/petstore_test.exs index 17f42e0..aadb895 100644 --- a/test/petstore_test.exs +++ b/test/petstore_test.exs @@ -1,7 +1,6 @@ defmodule AshJsonApiWrapper.Petstore.Test do use ExUnit.Case require Ash.Query - @moduletag :petstore defmodule TestingTesla do @@ -9,8 +8,10 @@ defmodule AshJsonApiWrapper.Petstore.Test do # plug Tesla.Middleware.Logger end - defmodule Petstore.Order do - use Ash.Resource, data_layer: AshJsonApiWrapper.DataLayer + defmodule Petstore do + use Ash.Resource, + data_layer: AshJsonApiWrapper.DataLayer, + validate_api_inclusion?: false json_api_wrapper do tesla(TestingTesla) @@ -18,7 +19,7 @@ defmodule AshJsonApiWrapper.Petstore.Test do endpoints do base("https://petstore3.swagger.io/api/v3") - endpoint [:find_pets_by_status, :fpbs] do + endpoint [:find_pets_by_status, :by_status] do path("/pet/findByStatus") field :status do @@ -37,11 +38,11 @@ defmodule AshJsonApiWrapper.Petstore.Test do actions do read(:find_pets_by_status) do - primary? true + primary? false end - read(:fpbs) do - primary? false + read(:by_status) do + primary? true end read(:pet) do @@ -68,8 +69,7 @@ defmodule AshJsonApiWrapper.Petstore.Test do end defmodule Api do - @moduledoc false - use Ash.Api + use Ash.Api, validate_config_inclusion?: false resources do allow_unregistered?(true) @@ -77,19 +77,19 @@ defmodule AshJsonApiWrapper.Petstore.Test do end test "it works" do - Petstore.Order + Petstore |> Ash.Query.for_read(:find_pets_by_status) |> Ash.Query.filter(status == "pending") |> Api.read!() - Petstore.Order - |> Ash.Query.for_read(:fpbs) + Petstore + |> Ash.Query.for_read(:by_status) |> Ash.Query.filter(status == "available") |> Api.read!() - Petstore.Order + Petstore |> Ash.Query.for_read(:pet) - |> Ash.Query.filter(id == 1) + |> Ash.Query.filter(id == 10) |> Api.read!() end end