Current section
Files
Jump to
Current section
Files
lib/rea/client/error.ex
defmodule Rea.Client.Error do
@moduledoc """
Error handling for the REA Pricing API client.
This module defines error types and provides helper functions for
working with API errors. The Pricing API follows RFC-7807 for error responses.
"""
defstruct [:type, :message, :status, :details]
@type t :: %__MODULE__{
type: atom(),
message: String.t(),
status: integer() | nil,
details: map() | nil
}
@doc """
Creates a new error struct from various error formats.
## Examples
Rea.Client.Error.new({:unauthorized, %{"title" => "Unauthorised access"}})
#=> %Rea.Client.Error{type: :unauthorized, message: "Unauthorised access", status: 401}
Rea.Client.Error.new({:bad_request, %{"title" => "Unknown location", "detail" => "..."}})
#=> %Rea.Client.Error{type: :bad_request, message: "Unknown location", status: 400, details: %{...}}
"""
@spec new(atom() | tuple()) :: t()
def new({:unauthorized, body}) do
%__MODULE__{
type: :unauthorized,
message: extract_title(body) || "Unauthorized - authentication failed or token is invalid",
status: 401,
details: body
}
end
def new({:forbidden, body}) do
%__MODULE__{
type: :forbidden,
message:
extract_title(body) || "Forbidden - you don't have permission to access this resource",
status: 403,
details: body
}
end
def new({:not_found, body}) do
%__MODULE__{
type: :not_found,
message: extract_title(body) || "Resource not found",
status: 404,
details: body
}
end
def new({:bad_request, body}) do
%__MODULE__{
type: :bad_request,
message: extract_title(body) || "Bad request - invalid parameters",
status: 400,
details: body
}
end
def new({:client_error, status, body}) do
%__MODULE__{
type: :client_error,
message: "Client error: #{extract_title(body) || "Unknown error"}",
status: status,
details: body
}
end
def new({:server_error, status, body}) do
%__MODULE__{
type: :server_error,
message: "Server error: #{extract_title(body) || "Unknown error"}",
status: status,
details: body
}
end
def new({:auth_failed, status, body}) do
%__MODULE__{
type: :auth_failed,
message: "Authentication failed: #{extract_title(body) || "Unknown error"}",
status: status,
details: body
}
end
def new({:request_failed, reason}) do
%__MODULE__{
type: :request_failed,
message: "Request failed: #{inspect(reason)}",
details: %{reason: reason}
}
end
def new(error) when is_atom(error) do
%__MODULE__{
type: error,
message: error |> Kernel.to_string() |> String.replace("_", " ") |> String.capitalize()
}
end
@doc """
Converts an error to a human-readable string.
"""
@spec message(t()) :: String.t()
def message(%__MODULE__{message: msg}), do: msg
@doc """
Returns the detailed error message from the API response, if available.
"""
@spec detail(t()) :: String.t() | nil
def detail(%__MODULE__{details: %{"detail" => detail}}), do: detail
def detail(_), do: nil
@doc """
Returns the error type URI from the API response, if available.
"""
@spec error_type_uri(t()) :: String.t() | nil
def error_type_uri(%__MODULE__{details: %{"type" => type}}), do: type
def error_type_uri(_), do: nil
@doc """
Returns true if the error is an authentication error.
"""
@spec auth_error?(t()) :: boolean()
def auth_error?(%__MODULE__{type: type}) when type in [:unauthorized, :auth_failed], do: true
def auth_error?(_), do: false
defp extract_title(body) when is_map(body) do
body["title"] || body["message"] || body["error"]
end
defp extract_title(body) when is_binary(body), do: body
defp extract_title(_), do: nil
end