How to return custom errors in a after_transaction call?

Let's say I have a change with the following implementation:
def change(changeset, _opts, _context),
do: Ash.Changeset.after_transaction(changeset, &do_change/2)

defp do_change(_changeset, _return) do
{:error, :something}
end
def change(changeset, _opts, _context),
do: Ash.Changeset.after_transaction(changeset, &do_change/2)

defp do_change(_changeset, _return) do
{:error, :something}
end
As you can see, my change just ignores everything and returns {:error, :something}. In other words, I want to create a change that in some conditions, I want to return an error with some custom data. The problem is that instead of Ash returning {:error, :something} directly, it returns:
{:error,
%Ash.Error.Unknown{
errors: [
%Ash.Error.Unknown.UnknownError{
error: "unknown error: :something",
field: nil,
changeset: nil,
query: nil,
error_context: [],
vars: [],
path: [],
stacktrace: #Stacktrace<>,
class: :unknown
}
],
stacktraces?: true,
changeset: #Ash.Changeset<
api: FeedbackCupcake.Payments,
action_type: :update,
action: :refresh,
attributes: %{},
relationships: %{},
arguments: %{create_payment_transaction?: true},
errors: [
%Ash.Error.Unknown.UnknownError{
error: "unknown error: :something",
field: nil,
changeset: nil,
query: nil,
error_context: [],
vars: [],
path: [],
stacktrace: #Stacktrace<>,
class: :unknown
}
],
data: #FeedbackCupcake.Payments.Customer<
...
>,
context: %{
...
>,
authorize?: false
},
valid?: true
>,
query: nil,
error_context: [nil],
vars: [],
path: [],
stacktrace: #Stacktrace<>,
class: :unknown
}}
{:error,
%Ash.Error.Unknown{
errors: [
%Ash.Error.Unknown.UnknownError{
error: "unknown error: :something",
field: nil,
changeset: nil,
query: nil,
error_context: [],
vars: [],
path: [],
stacktrace: #Stacktrace<>,
class: :unknown
}
],
stacktraces?: true,
changeset: #Ash.Changeset<
api: FeedbackCupcake.Payments,
action_type: :update,
action: :refresh,
attributes: %{},
relationships: %{},
arguments: %{create_payment_transaction?: true},
errors: [
%Ash.Error.Unknown.UnknownError{
error: "unknown error: :something",
field: nil,
changeset: nil,
query: nil,
error_context: [],
vars: [],
path: [],
stacktrace: #Stacktrace<>,
class: :unknown
}
],
data: #FeedbackCupcake.Payments.Customer<
...
>,
context: %{
...
>,
authorize?: false
},
valid?: true
>,
query: nil,
error_context: [nil],
vars: [],
path: [],
stacktrace: #Stacktrace<>,
class: :unknown
}}
So it actually converts :something into a string and returns it as a UnknownError. Is there some way to actually return :something directly or create a custom error that will have it as one of the fields?
6 Replies
ZachDaniel
ZachDaniel2y ago
Yes, if you create a custom Ash error with def_ash_error
ZachDaniel
ZachDaniel2y ago
All of the ash errors are built this way, so you could copy this one for example: https://github.com/ash-project/ash/blob/main/lib/ash/error/changes/invalid_attribute.ex
GitHub
ash/lib/ash/error/changes/invalid_attribute.ex at main · ash-projec...
A declarative and extensible framework for building Elixir applications. - ash/lib/ash/error/changes/invalid_attribute.ex at main · ash-project/ash
ZachDaniel
ZachDaniel2y ago
If what you return is an ash error, then it won't be wrapped in an unknown error
Blibs
BlibsOP2y ago
Thanks @Zach Daniel , so, doing your suggestion I came up with this error:
defmodule FeedbackCupcake.Payments.Errors.UnfinishedTransaction do
@moduledoc """
An error representing a payment transaction that failed.

Will contain an updated version of `Customer` inside.
"""
use Ash.Error.Exception

def_ash_error([:customer, :error, stacktraces?: false], class: :unknown)

def exception(opts) do
if opts[:error] do
super(Keyword.update(opts, :errors, [opts[:error]], &[opts[:error] | &1]))
else
super(opts)
end
end

defimpl Ash.ErrorKind do
def id(_), do: Ash.UUID.generate()

def code(_), do: "unfinished_transaction"

def message(%{error: error}), do: Ash.Error.error_messages([error], nil, false)
end
end
defmodule FeedbackCupcake.Payments.Errors.UnfinishedTransaction do
@moduledoc """
An error representing a payment transaction that failed.

Will contain an updated version of `Customer` inside.
"""
use Ash.Error.Exception

def_ash_error([:customer, :error, stacktraces?: false], class: :unknown)

def exception(opts) do
if opts[:error] do
super(Keyword.update(opts, :errors, [opts[:error]], &[opts[:error] | &1]))
else
super(opts)
end
end

defimpl Ash.ErrorKind do
def id(_), do: Ash.UUID.generate()

def code(_), do: "unfinished_transaction"

def message(%{error: error}), do: Ash.Error.error_messages([error], nil, false)
end
end
This seems to work great, but the error always arrive inside a Ash.Error.Unknown error in the errors field. Is there some way to make my custom error the top error returned?
ZachDaniel
ZachDaniel2y ago
Oh, no you cant All errors will always come back in an error class I.e “unknown” or “invalid” or “forbidden” Those will always be the top level errors You can read more about it in the errors guide in the docs
Blibs
BlibsOP2y ago
Got it, thanks!

Did you find this page helpful?