defmodule Manganese.CoreKit.Structs.APIError do @moduledoc """ Represents an error that occurred in the data layer of the application. All actions should either succeed or fail with an API error. """ alias Manganese.CoreKit.Structs alias Manganese.CoreKit.Enumerations @type t :: %Structs.APIError{ type: atom, message: String.t | nil, status: Enumerations.HttpStatusCode.t } @enforce_keys [ :type, :status ] defstruct [ :type, :message, status: :internal_server_error ] # Utilities @doc """ """ @spec from_changeset(Ecto.Changeset.t) :: t def from_changeset(changeset) do if changeset.valid? do nil else Enum.map( changeset.errors, fn { field, { "has invalid format", _data }} -> %Structs.APIError{ type: String.to_atom(Atom.to_string(field) <> "_invalid"), status: :unprocessable_entity } { field, { "is invalid", _data }} -> %Structs.APIError{ type: String.to_atom(Atom.to_string(field) <> "_invalid"), status: :unprocessable_entity } { field, { "should be at least %{count} character(s)", _data }} -> %Structs.APIError{ type: String.to_atom(Atom.to_string(field) <> "_too_short"), status: :unprocessable_entity } { field, { "should be at most %{count} character(s)", _data }} -> %Structs.APIError{ type: String.to_atom(Atom.to_string(field) <> "_too_long"), status: :unprocessable_entity } { field, { "has already been taken", _data }} -> %Structs.APIError{ type: String.to_atom(Atom.to_string(field) <> "_conflict"), status: :conflict } { _field, { _, api_error: api_error }} -> api_error { _field, error } -> %Structs.APIError{ type: :error, status: :internal_server_error } end ) |> Enum.reduce( nil, fn changeset_api_error, prioritized_api_error -> Structs.APIError.prioritize changeset_api_error, prioritized_api_error end ) end end @doc """ Given two API errors, return the one with the greater HTTP response status. If the statuses are the same, `api_error1` will take precedence. """ @spec prioritize(t | nil, t | nil) :: t | nil def prioritize(nil, nil), do: nil def prioritize(nil, api_error2), do: api_error2 def prioritize(api_error1, nil), do: api_error1 def prioritize(api_error1, api_error2) do status1 = Enumerations.HttpStatusCode.to_integer api_error1.status status2 = Enumerations.HttpStatusCode.to_integer api_error2.status cond do status1 > status2 -> api_error1 status1 < status2 -> api_error2 true -> api_error1 end end end