Packages

ISO/HL7 FHIR Application Server

Current section

Files

Jump to
hl7 lib validation cast_error.ex
Raw

lib/validation/cast_error.ex

defmodule Xema.CastError do
@moduledoc """
Raised when a cast fails.
"""
alias Xema.CastError
alias Xema.ValidationError
defexception [:message, :path, :required, :to, :value, :key, :error]
@type t :: %CastError{
error: struct | nil,
key: String.t() | atom | nil,
message: String.t() | nil,
path: [atom | integer | String.t()] | nil,
required: [atom] | nil,
to: atom | list | nil,
value: term | nil
}
@indent " "
@impl true
def message(%{message: nil} = exception), do: format_error(exception)
def message(%{message: message}), do: message
@impl true
def blame(exception, stacktrace) do
message = message(exception)
{%{exception | message: message}, stacktrace}
end
@doc """
Formats the error map to an error message.
"""
@spec format_error(CastError.t()) :: String.t()
def format_error(error) do
error
|> traverse_error()
|> List.flatten()
|> Enum.join("\n")
end
defp traverse_error(%{path: path, to: to, error: error, value: value}) when not is_nil(error) do
["cannot cast #{inspect(value)} to #{inspect(to)}, " <> error.message <> at_path(path)]
end
defp traverse_error(%{path: path, to: :atom, value: value}) when is_binary(value) do
["cannot cast #{inspect(value)} to :atom, the atom is unknown" <> at_path(path)]
end
defp traverse_error(%{path: path, to: to, key: {:ambiguous, key}}) do
[
"cannot cast #{to_string(key)} to #{inspect(to)} key" <>
at_path(path) <> ", the key is ambiguous"
]
end
defp traverse_error(%{path: path, to: to, key: key}) when is_binary(key) do
[
"cannot cast #{inspect(key)} to #{inspect(to)} key" <>
at_path(path) <> ", the atom is unknown"
]
end
defp traverse_error(%{path: path, to: to, key: key, value: value}) when not is_nil(key) do
[
"cannot cast #{inspect(value)} to #{inspect(to)}, " <>
"key #{inspect(key)} not found in #{inspect(to)}" <> at_path(path)
]
end
defp traverse_error(%{path: path, to: to, value: value}) when is_list(to) do
if Enum.all?(to, &is_atom/1) do
["cannot cast #{inspect(value)} to any of #{inspect(to)}" <> at_path(path)]
else
errors = to |> Enum.map(fn error -> traverse_error(error) end) |> indent()
["cannot cast #{inspect(value)}" <> at_path(path) <> " to any of:" | errors]
end
end
defp traverse_error(%{path: path, to: to, value: value, required: required})
when not is_nil(required) do
[
"cannot cast #{inspect(value)} to #{inspect(to)}" <>
"#{at_path(path)} missing required keys #{inspect(required)}"
]
end
defp traverse_error(%{path: path, to: to, value: value}) do
["cannot cast #{inspect(value)} to #{inspect(to)}" <> at_path(path)]
end
defp traverse_error(%{reason: %ValidationError{} = reason, value: value} = error) do
to = if is_nil(error.module), do: error.to, else: error.module
["cannot cast #{inspect(value)} to #{inspect(to)} - #{Exception.message(reason)}"]
end
defp at_path([]), do: ""
defp at_path(path), do: " at #{inspect(path)}"
defp indent(list) when is_list(list), do: Enum.map(list, &indent/1)
defp indent(str), do: @indent <> str
end