Current section
Files
Jump to
Current section
Files
lib/twilio/error.ex
defmodule Twilio.Error do
@moduledoc """
Twilio API error.
Represents both API errors (4xx/5xx responses with Twilio error bodies)
and connection-level errors (network failures, timeouts).
"""
@type error_type ::
:api_error
| :authentication_error
| :rate_limit_error
| :not_found_error
| :invalid_request_error
| :connection_error
@type t :: %__MODULE__{
type: error_type(),
message: String.t() | nil,
code: integer() | nil,
http_status: integer() | nil,
http_body: String.t() | nil,
more_info: String.t() | nil,
details: map() | nil,
request_id: String.t() | nil,
retry_after: integer() | nil
}
defstruct [
:type,
:message,
:code,
:http_status,
:http_body,
:more_info,
:details,
:request_id,
:retry_after
]
@doc "Build error from HTTP response status, body, and headers."
@spec from_response(integer(), String.t(), list()) :: t()
def from_response(status, body, headers) do
request_id = get_header(headers, "x-twilio-request-id")
retry_after_raw = get_header(headers, "retry-after")
error_data =
case JSON.decode(body) do
{:ok, data} -> data
_ -> %{}
end
%__MODULE__{
type: error_type_from_status(status),
message: error_data["message"],
code: error_data["code"],
http_status: status,
http_body: body,
more_info: error_data["more_info"],
details: error_data["details"],
request_id: request_id,
retry_after: parse_retry_after(retry_after_raw)
}
end
@doc "Build a connection error."
@spec connection_error(String.t()) :: t()
def connection_error(message) do
%__MODULE__{type: :connection_error, message: message}
end
@doc "Is this error retryable?"
@spec retryable?(t()) :: boolean()
def retryable?(%__MODULE__{type: :rate_limit_error}), do: true
def retryable?(%__MODULE__{http_status: status}) when is_integer(status) and status >= 500,
do: true
def retryable?(%__MODULE__{type: :connection_error}), do: true
def retryable?(_), do: false
defp error_type_from_status(401), do: :authentication_error
defp error_type_from_status(403), do: :authentication_error
defp error_type_from_status(404), do: :not_found_error
defp error_type_from_status(429), do: :rate_limit_error
defp error_type_from_status(status) when status >= 400 and status < 500,
do: :invalid_request_error
defp error_type_from_status(_), do: :api_error
defp get_header(headers, name) do
Enum.find_value(headers, fn {k, v} ->
if String.downcase(k) == name, do: v
end)
end
defp parse_retry_after(nil), do: nil
defp parse_retry_after(value) do
case Integer.parse(value) do
{seconds, _} -> seconds
:error -> nil
end
end
end