diff --git a/lib/ash/actions/read/read.ex b/lib/ash/actions/read/read.ex index 7d238526..149da97e 100644 --- a/lib/ash/actions/read/read.ex +++ b/lib/ash/actions/read/read.ex @@ -2,7 +2,7 @@ defmodule Ash.Actions.Read do @moduledoc false alias Ash.Actions.Helpers - alias Ash.Error.Invalid.{LimitRequired, PaginationRequired} + alias Ash.Error.Invalid.{LimitRequired, NonCountableAction, PaginationRequired} alias Ash.Filter require Logger @@ -2367,6 +2367,10 @@ defmodule Ash.Actions.Read do {:ok, starting_query} end + page_opts[:count] == true && !action.pagination.countable -> + {:error, + NonCountableAction.exception(resource: starting_query.resource, action: action.name)} + page_opts[:limit] -> page_opts = Keyword.put( diff --git a/lib/ash/error/action/invalid_argument.ex b/lib/ash/error/action/invalid_argument.ex index d4e35b45..f4502211 100644 --- a/lib/ash/error/action/invalid_argument.ex +++ b/lib/ash/error/action/invalid_argument.ex @@ -1,6 +1,5 @@ defmodule Ash.Error.Action.InvalidArgument do @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 diff --git a/lib/ash/error/changes/action_requires_actor.ex b/lib/ash/error/changes/action_requires_actor.ex index 6ddf53a9..1d5e9431 100644 --- a/lib/ash/error/changes/action_requires_actor.ex +++ b/lib/ash/error/changes/action_requires_actor.ex @@ -1,6 +1,5 @@ defmodule Ash.Error.Changes.ActionRequiresActor do @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 diff --git a/lib/ash/error/changes/invalid_argument.ex b/lib/ash/error/changes/invalid_argument.ex index a515983d..6e795f79 100644 --- a/lib/ash/error/changes/invalid_argument.ex +++ b/lib/ash/error/changes/invalid_argument.ex @@ -1,6 +1,5 @@ defmodule Ash.Error.Changes.InvalidArgument do @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 diff --git a/lib/ash/error/changes/invalid_attribute.ex b/lib/ash/error/changes/invalid_attribute.ex index f36a7ddd..3f2446ef 100644 --- a/lib/ash/error/changes/invalid_attribute.ex +++ b/lib/ash/error/changes/invalid_attribute.ex @@ -1,6 +1,5 @@ defmodule Ash.Error.Changes.InvalidAttribute do @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 diff --git a/lib/ash/error/changes/invalid_changes.ex b/lib/ash/error/changes/invalid_changes.ex index c8dced32..32710dac 100644 --- a/lib/ash/error/changes/invalid_changes.ex +++ b/lib/ash/error/changes/invalid_changes.ex @@ -1,6 +1,5 @@ defmodule Ash.Error.Changes.InvalidChanges do @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 diff --git a/lib/ash/error/changes/invalid_relationship.ex b/lib/ash/error/changes/invalid_relationship.ex index 3946da71..3fabc8f9 100644 --- a/lib/ash/error/changes/invalid_relationship.ex +++ b/lib/ash/error/changes/invalid_relationship.ex @@ -1,6 +1,5 @@ defmodule Ash.Error.Changes.InvalidRelationship do @moduledoc "Used when an invalid value is provided for a relationship change" - use Ash.Error.Exception use Splode.Error, fields: [:relationship, :message], class: :invalid diff --git a/lib/ash/error/changes/no_such_attribute.ex b/lib/ash/error/changes/no_such_attribute.ex index 1c06ea5f..e272695d 100644 --- a/lib/ash/error/changes/no_such_attribute.ex +++ b/lib/ash/error/changes/no_such_attribute.ex @@ -1,6 +1,5 @@ defmodule Ash.Error.Changes.NoSuchAttribute do @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 diff --git a/lib/ash/error/changes/no_such_relationship.ex b/lib/ash/error/changes/no_such_relationship.ex index 5401f2ca..5da3b1c7 100644 --- a/lib/ash/error/changes/no_such_relationship.ex +++ b/lib/ash/error/changes/no_such_relationship.ex @@ -1,6 +1,5 @@ defmodule Ash.Error.Changes.NoSuchRelationship do @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 diff --git a/lib/ash/error/changes/required.ex b/lib/ash/error/changes/required.ex index c42e8d22..ef0b0d40 100644 --- a/lib/ash/error/changes/required.ex +++ b/lib/ash/error/changes/required.ex @@ -1,6 +1,5 @@ defmodule Ash.Error.Changes.Required do @moduledoc "Used when an attribute or relationship is required" - use Ash.Error.Exception use Splode.Error, fields: [:field, :type, :resource], class: :invalid diff --git a/lib/ash/error/changes/stale_record.ex b/lib/ash/error/changes/stale_record.ex index fe1112be..76bec4ee 100644 --- a/lib/ash/error/changes/stale_record.ex +++ b/lib/ash/error/changes/stale_record.ex @@ -1,6 +1,5 @@ defmodule Ash.Error.Changes.StaleRecord do @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 diff --git a/lib/ash/error/forbidden/cannot_filter_creates.ex b/lib/ash/error/forbidden/cannot_filter_creates.ex index 4c696df8..53dfcb6f 100644 --- a/lib/ash/error/forbidden/cannot_filter_creates.ex +++ b/lib/ash/error/forbidden/cannot_filter_creates.ex @@ -1,6 +1,5 @@ defmodule Ash.Error.Forbidden.CannotFilterCreates do @moduledoc "Used when a create action would be filtered" - use Ash.Error.Exception use Splode.Error, fields: [], class: :forbidden diff --git a/lib/ash/error/forbidden/domain_requires_actor.ex b/lib/ash/error/forbidden/domain_requires_actor.ex index c2afb9ef..f869a47c 100644 --- a/lib/ash/error/forbidden/domain_requires_actor.ex +++ b/lib/ash/error/forbidden/domain_requires_actor.ex @@ -1,6 +1,5 @@ defmodule Ash.Error.Forbidden.DomainRequiresActor do @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 diff --git a/lib/ash/error/forbidden/domain_requires_authorization.ex b/lib/ash/error/forbidden/domain_requires_authorization.ex index b8cc5164..c230eb74 100644 --- a/lib/ash/error/forbidden/domain_requires_authorization.ex +++ b/lib/ash/error/forbidden/domain_requires_authorization.ex @@ -1,6 +1,5 @@ defmodule Ash.Error.Forbidden.DomainRequiresAuthorization do @moduledoc "Used when a domain that has `authorize :always` is provided authorize?: false" - use Ash.Error.Exception use Splode.Error, fields: [:domain], class: :forbidden diff --git a/lib/ash/error/forbidden/forbidden_field.ex b/lib/ash/error/forbidden/forbidden_field.ex index 7791ab4e..d781f181 100644 --- a/lib/ash/error/forbidden/forbidden_field.ex +++ b/lib/ash/error/forbidden/forbidden_field.ex @@ -2,7 +2,6 @@ defmodule Ash.Error.Forbidden.ForbiddenField do @moduledoc "Raised in cases where access to a specific field was prevented" require Logger - use Ash.Error.Exception use Splode.Error, fields: [:resource, :field], class: :forbidden diff --git a/lib/ash/error/forbidden/initial_data_required.ex b/lib/ash/error/forbidden/initial_data_required.ex index bdb3ad4c..62011108 100644 --- a/lib/ash/error/forbidden/initial_data_required.ex +++ b/lib/ash/error/forbidden/initial_data_required.ex @@ -1,7 +1,5 @@ defmodule Ash.Error.Forbidden.InitialDataRequired do - @moduledoc "Used when " - use Ash.Error.Exception - + @moduledoc "Used when initial data was not supplied when it was required" use Splode.Error, fields: [:source], class: :forbidden def message(%{source: source}) do diff --git a/lib/ash/error/forbidden/must_pass_strict_check.ex b/lib/ash/error/forbidden/must_pass_strict_check.ex index d3218f54..15a9cf4c 100644 --- a/lib/ash/error/forbidden/must_pass_strict_check.ex +++ b/lib/ash/error/forbidden/must_pass_strict_check.ex @@ -1,7 +1,5 @@ defmodule Ash.Error.Forbidden.MustPassStrictCheck do @moduledoc "Used when unreachable code/conditions are reached in the framework" - use Ash.Error.Exception - use Splode.Error, fields: [], class: :forbidden def message(_) do diff --git a/lib/ash/error/forbidden/placeholder.ex b/lib/ash/error/forbidden/placeholder.ex index a25a4f14..7e6412fe 100644 --- a/lib/ash/error/forbidden/placeholder.ex +++ b/lib/ash/error/forbidden/placeholder.ex @@ -1,7 +1,5 @@ defmodule Ash.Error.Forbidden.Placeholder do @moduledoc "A placeholder exception that the user should never see" - use Ash.Error.Exception - use Splode.Error, fields: [:authorizer], class: :forbidden def from_json(%{"authorizer" => authorizer}) do diff --git a/lib/ash/error/framework/assumption_failed.ex b/lib/ash/error/framework/assumption_failed.ex index 6b68edc3..a34bd5c0 100644 --- a/lib/ash/error/framework/assumption_failed.ex +++ b/lib/ash/error/framework/assumption_failed.ex @@ -1,7 +1,5 @@ defmodule Ash.Error.Framework.AssumptionFailed do @moduledoc "Used when unreachable code/conditions are reached in the framework" - use Ash.Error.Exception - use Splode.Error, fields: [:message], class: :framework def message(%{message: message}) do diff --git a/lib/ash/error/framework/can_not_be_atomic.ex b/lib/ash/error/framework/can_not_be_atomic.ex index db1156d1..7d295c3d 100644 --- a/lib/ash/error/framework/can_not_be_atomic.ex +++ b/lib/ash/error/framework/can_not_be_atomic.ex @@ -1,7 +1,5 @@ defmodule Ash.Error.Framework.CanNotBeAtomic do @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 def message(error) do diff --git a/lib/ash/error/framework/flag_assertion_failed.ex b/lib/ash/error/framework/flag_assertion_failed.ex index b9e96339..e2a42b56 100644 --- a/lib/ash/error/framework/flag_assertion_failed.ex +++ b/lib/ash/error/framework/flag_assertion_failed.ex @@ -1,7 +1,5 @@ defmodule Ash.Error.Framework.FlagAssertionFailed do @moduledoc "Used when unreachable code/conditions are reached in the framework" - use Ash.Error.Exception - use Splode.Error, fields: [:flag, :heading], class: :framework def message(error) do diff --git a/lib/ash/error/invalid/non_countable_action.ex b/lib/ash/error/invalid/non_countable_action.ex new file mode 100644 index 00000000..8f4d568b --- /dev/null +++ b/lib/ash/error/invalid/non_countable_action.ex @@ -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 diff --git a/lib/ash/resource/actions/read.ex b/lib/ash/resource/actions/read.ex index 873e3fa9..32917c9f 100644 --- a/lib/ash/resource/actions/read.ex +++ b/lib/ash/resource/actions/read.ex @@ -109,11 +109,12 @@ defmodule Ash.Resource.Actions.Read do type: :pos_integer, doc: "The default page size to apply, if one is not supplied" ], + # Change this default in 4.0 countable: [ type: {:in, [true, false, :by_default]}, doc: "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: [ type: :pos_integer, diff --git a/test/count_test.exs b/test/count_test.exs index a37e34ea..6428e167 100644 --- a/test/count_test.exs +++ b/test/count_test.exs @@ -21,6 +21,14 @@ defmodule Ash.Test.CountTest do default_limit 5 end end + + read :non_countable do + pagination do + offset? true + default_limit 5 + countable false + end + end end ets do @@ -77,4 +85,14 @@ defmodule Ash.Test.CountTest do assert count == 10 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