fix: honor the countable option in pagination

the default unfortunately had to be changed to `true`, given that it was being ignored before

chore: clean up error declarations
This commit is contained in:
Zach Daniel 2024-08-30 15:34:40 -04:00
parent 0007d0e60a
commit 9b0fb15ecb
24 changed files with 34 additions and 29 deletions

View file

@ -2,7 +2,7 @@ defmodule Ash.Actions.Read do
@moduledoc false @moduledoc false
alias Ash.Actions.Helpers alias Ash.Actions.Helpers
alias Ash.Error.Invalid.{LimitRequired, PaginationRequired} alias Ash.Error.Invalid.{LimitRequired, NonCountableAction, PaginationRequired}
alias Ash.Filter alias Ash.Filter
require Logger require Logger
@ -2367,6 +2367,10 @@ defmodule Ash.Actions.Read do
{:ok, starting_query} {:ok, starting_query}
end end
page_opts[:count] == true && !action.pagination.countable ->
{:error,
NonCountableAction.exception(resource: starting_query.resource, action: action.name)}
page_opts[:limit] -> page_opts[:limit] ->
page_opts = page_opts =
Keyword.put( Keyword.put(

View file

@ -1,6 +1,5 @@
defmodule Ash.Error.Action.InvalidArgument do defmodule Ash.Error.Action.InvalidArgument do
@moduledoc "Used when an invalid value is provided for an action argument" @moduledoc "Used when an invalid value is provided for an action argument"
use Ash.Error.Exception
use Splode.Error, fields: [:field, :message, :value], class: :invalid use Splode.Error, fields: [:field, :message, :value], class: :invalid

View file

@ -1,6 +1,5 @@
defmodule Ash.Error.Changes.ActionRequiresActor do defmodule Ash.Error.Changes.ActionRequiresActor do
@moduledoc "Used when an actor is referenced in a filter template, but no actor exists" @moduledoc "Used when an actor is referenced in a filter template, but no actor exists"
use Ash.Error.Exception
use Splode.Error, fields: [], class: :invalid use Splode.Error, fields: [], class: :invalid

View file

@ -1,6 +1,5 @@
defmodule Ash.Error.Changes.InvalidArgument do defmodule Ash.Error.Changes.InvalidArgument do
@moduledoc "Used when an invalid value is provided for an action argument" @moduledoc "Used when an invalid value is provided for an action argument"
use Ash.Error.Exception
use Splode.Error, fields: [:field, :message, :value], class: :invalid use Splode.Error, fields: [:field, :message, :value], class: :invalid

View file

@ -1,6 +1,5 @@
defmodule Ash.Error.Changes.InvalidAttribute do defmodule Ash.Error.Changes.InvalidAttribute do
@moduledoc "Used when an invalid value is provided for an attribute change" @moduledoc "Used when an invalid value is provided for an attribute change"
use Ash.Error.Exception
use Splode.Error, fields: [:field, :message, :private_vars, :value], class: :invalid use Splode.Error, fields: [:field, :message, :private_vars, :value], class: :invalid

View file

@ -1,6 +1,5 @@
defmodule Ash.Error.Changes.InvalidChanges do defmodule Ash.Error.Changes.InvalidChanges do
@moduledoc "Used when a change is provided that covers multiple attributes/relationships" @moduledoc "Used when a change is provided that covers multiple attributes/relationships"
use Ash.Error.Exception
use Splode.Error, fields: [:fields, :message, :validation, :value], class: :invalid use Splode.Error, fields: [:fields, :message, :validation, :value], class: :invalid

View file

@ -1,6 +1,5 @@
defmodule Ash.Error.Changes.InvalidRelationship do defmodule Ash.Error.Changes.InvalidRelationship do
@moduledoc "Used when an invalid value is provided for a relationship change" @moduledoc "Used when an invalid value is provided for a relationship change"
use Ash.Error.Exception
use Splode.Error, fields: [:relationship, :message], class: :invalid use Splode.Error, fields: [:relationship, :message], class: :invalid

View file

@ -1,6 +1,5 @@
defmodule Ash.Error.Changes.NoSuchAttribute do defmodule Ash.Error.Changes.NoSuchAttribute do
@moduledoc "Used when a change is provided for an attribute that does not exist" @moduledoc "Used when a change is provided for an attribute that does not exist"
use Ash.Error.Exception
use Splode.Error, fields: [:resource, :attribute], class: :invalid use Splode.Error, fields: [:resource, :attribute], class: :invalid

View file

@ -1,6 +1,5 @@
defmodule Ash.Error.Changes.NoSuchRelationship do defmodule Ash.Error.Changes.NoSuchRelationship do
@moduledoc "Used when a change is provided for an relationship that does not exist" @moduledoc "Used when a change is provided for an relationship that does not exist"
use Ash.Error.Exception
use Splode.Error, fields: [:resource, :relationship], class: :invalid use Splode.Error, fields: [:resource, :relationship], class: :invalid

View file

@ -1,6 +1,5 @@
defmodule Ash.Error.Changes.Required do defmodule Ash.Error.Changes.Required do
@moduledoc "Used when an attribute or relationship is required" @moduledoc "Used when an attribute or relationship is required"
use Ash.Error.Exception
use Splode.Error, fields: [:field, :type, :resource], class: :invalid use Splode.Error, fields: [:field, :type, :resource], class: :invalid

View file

@ -1,6 +1,5 @@
defmodule Ash.Error.Changes.StaleRecord do defmodule Ash.Error.Changes.StaleRecord do
@moduledoc "Used when a stale record is attempted to be updated or deleted" @moduledoc "Used when a stale record is attempted to be updated or deleted"
use Ash.Error.Exception
use Splode.Error, fields: [:resource, :filter], class: :invalid use Splode.Error, fields: [:resource, :filter], class: :invalid

View file

@ -1,6 +1,5 @@
defmodule Ash.Error.Forbidden.CannotFilterCreates do defmodule Ash.Error.Forbidden.CannotFilterCreates do
@moduledoc "Used when a create action would be filtered" @moduledoc "Used when a create action would be filtered"
use Ash.Error.Exception
use Splode.Error, fields: [], class: :forbidden use Splode.Error, fields: [], class: :forbidden

View file

@ -1,6 +1,5 @@
defmodule Ash.Error.Forbidden.DomainRequiresActor do defmodule Ash.Error.Forbidden.DomainRequiresActor do
@moduledoc "Used when a domain that has `require_actor? true` is provided no actor" @moduledoc "Used when a domain that has `require_actor? true` is provided no actor"
use Ash.Error.Exception
use Splode.Error, fields: [:domain], class: :forbidden use Splode.Error, fields: [:domain], class: :forbidden

View file

@ -1,6 +1,5 @@
defmodule Ash.Error.Forbidden.DomainRequiresAuthorization do defmodule Ash.Error.Forbidden.DomainRequiresAuthorization do
@moduledoc "Used when a domain that has `authorize :always` is provided authorize?: false" @moduledoc "Used when a domain that has `authorize :always` is provided authorize?: false"
use Ash.Error.Exception
use Splode.Error, fields: [:domain], class: :forbidden use Splode.Error, fields: [:domain], class: :forbidden

View file

@ -2,7 +2,6 @@ defmodule Ash.Error.Forbidden.ForbiddenField do
@moduledoc "Raised in cases where access to a specific field was prevented" @moduledoc "Raised in cases where access to a specific field was prevented"
require Logger require Logger
use Ash.Error.Exception
use Splode.Error, fields: [:resource, :field], class: :forbidden use Splode.Error, fields: [:resource, :field], class: :forbidden

View file

@ -1,7 +1,5 @@
defmodule Ash.Error.Forbidden.InitialDataRequired do defmodule Ash.Error.Forbidden.InitialDataRequired do
@moduledoc "Used when " @moduledoc "Used when initial data was not supplied when it was required"
use Ash.Error.Exception
use Splode.Error, fields: [:source], class: :forbidden use Splode.Error, fields: [:source], class: :forbidden
def message(%{source: source}) do def message(%{source: source}) do

View file

@ -1,7 +1,5 @@
defmodule Ash.Error.Forbidden.MustPassStrictCheck do defmodule Ash.Error.Forbidden.MustPassStrictCheck do
@moduledoc "Used when unreachable code/conditions are reached in the framework" @moduledoc "Used when unreachable code/conditions are reached in the framework"
use Ash.Error.Exception
use Splode.Error, fields: [], class: :forbidden use Splode.Error, fields: [], class: :forbidden
def message(_) do def message(_) do

View file

@ -1,7 +1,5 @@
defmodule Ash.Error.Forbidden.Placeholder do defmodule Ash.Error.Forbidden.Placeholder do
@moduledoc "A placeholder exception that the user should never see" @moduledoc "A placeholder exception that the user should never see"
use Ash.Error.Exception
use Splode.Error, fields: [:authorizer], class: :forbidden use Splode.Error, fields: [:authorizer], class: :forbidden
def from_json(%{"authorizer" => authorizer}) do def from_json(%{"authorizer" => authorizer}) do

View file

@ -1,7 +1,5 @@
defmodule Ash.Error.Framework.AssumptionFailed do defmodule Ash.Error.Framework.AssumptionFailed do
@moduledoc "Used when unreachable code/conditions are reached in the framework" @moduledoc "Used when unreachable code/conditions are reached in the framework"
use Ash.Error.Exception
use Splode.Error, fields: [:message], class: :framework use Splode.Error, fields: [:message], class: :framework
def message(%{message: message}) do def message(%{message: message}) do

View file

@ -1,7 +1,5 @@
defmodule Ash.Error.Framework.CanNotBeAtomic do defmodule Ash.Error.Framework.CanNotBeAtomic do
@moduledoc "Used when a change that is only atomic cannot be done atomically" @moduledoc "Used when a change that is only atomic cannot be done atomically"
use Ash.Error.Exception
use Splode.Error, fields: [:resource, :change, :reason], class: :framework use Splode.Error, fields: [:resource, :change, :reason], class: :framework
def message(error) do def message(error) do

View file

@ -1,7 +1,5 @@
defmodule Ash.Error.Framework.FlagAssertionFailed do defmodule Ash.Error.Framework.FlagAssertionFailed do
@moduledoc "Used when unreachable code/conditions are reached in the framework" @moduledoc "Used when unreachable code/conditions are reached in the framework"
use Ash.Error.Exception
use Splode.Error, fields: [:flag, :heading], class: :framework use Splode.Error, fields: [:flag, :heading], class: :framework
def message(error) do def message(error) do

View file

@ -0,0 +1,8 @@
defmodule Ash.Error.Invalid.NonCountableAction do
@moduledoc "Used when page[:count] option is passed but the action's pagination is not countable."
use Splode.Error, fields: [:resource, :action], class: :invalid
def message(%{resource: resource, action: action}) do
"Action #{inspect(resource)}.#{action} cannot be counted while paginating but a count was requested."
end
end

View file

@ -109,11 +109,12 @@ defmodule Ash.Resource.Actions.Read do
type: :pos_integer, type: :pos_integer,
doc: "The default page size to apply, if one is not supplied" doc: "The default page size to apply, if one is not supplied"
], ],
# Change this default in 4.0
countable: [ countable: [
type: {:in, [true, false, :by_default]}, type: {:in, [true, false, :by_default]},
doc: doc:
"Whether not a returned page will have a full count of all records. Use `:by_default` to do it automatically.", "Whether not a returned page will have a full count of all records. Use `:by_default` to do it automatically.",
default: false default: true
], ],
max_page_size: [ max_page_size: [
type: :pos_integer, type: :pos_integer,

View file

@ -21,6 +21,14 @@ defmodule Ash.Test.CountTest do
default_limit 5 default_limit 5
end end
end end
read :non_countable do
pagination do
offset? true
default_limit 5
countable false
end
end
end end
ets do ets do
@ -77,4 +85,14 @@ defmodule Ash.Test.CountTest do
assert count == 10 assert count == 10
end end
test "trying to count a non-countable action is invalid" do
assert_raise Ash.Error.Invalid,
~r/cannot be counted while paginating/,
fn ->
Countable
|> Ash.Query.for_read(:non_countable)
|> Ash.read!(actor: %{id: "foo"}, page: [count: true])
end
end
end end