"Streaming" Errors from One Resource to Another

I have two resources that are conceptually related but that I have kept distinct to separately model interface (Front) concerns and the data layer (Back) concerns.

defmodule Back do
  # Simplified example
  use Ash.Resource, data_layer: AshPostgres.DataLayer

  attributes do
    uuid_primary_key :id
    attribute :the_attribute, :string, allow_nil?: false
  end

  identities do
    identity :unique_identity, [:the_attribute]
  end
end
defmodule Front do
  # Simplified example
  use Ash.Resource

  attributes do
    attribute :the_same_conceptual_attribute, :string
  end
end

Is there a way to “stream” errors related to that field from the Back resource to the Front resource?

Particularly in this case, how can I pass the allow_nil? (I know I can duplicate it) and the uniqueness constraints to Front?

I personally would not separate my resources like that, but that is neither here nor there and I’m sure you have your reasons :slight_smile:

As for modifying the errors, it depends. If the field names are the same then you can generally just return the errors that you get from your Back resource to your Front resource.

If not, you will likely need to do some transformation. This is just a shot in the dark, but is an example of remapping field names. We can potentially add helpers to do this sort of thing, but honestly the concept of mapping “any error that X operation can produce” into “some error that makes sense for Y operation” is one of the hardest problems in programming in my experience :slight_smile:

case Back.do_thing() do
  {:error, error_class} ->
     {:error, %{error_class | errors: Enum.map(errors, &transform_error/1)}}
  {:ok, result} ->
     {:ok, result}
end

defp transform_error(error) do
  error
  |> transform_field()
  |> transform_fields()
end

defp transform_field(%{field: field} = error) do
  %{error | field: remap_field(field)}
end

defp transform_fields(%{fields: fields} = error) do
   %{error | fields: Enum.map(fields, &remap_field/1)}
end

defp remap_field(:foo), do: :some_conceptual_foo
defp remap_field(:bar), do: :some_conceptual_bar
defp remap_field(field), do: field

Thanks!

This solves my case