Packages

Spawn and manage Google Compute Engine instances over the REST API, with pluggable auth, telemetry, and an ergonomic instance builder.

Current section

Files

Jump to
gcp_compute lib gcp_compute error.ex
Raw

lib/gcp_compute/error.ex

defmodule GcpCompute.Error do
@moduledoc """
Normalized error returned by every `GcpCompute` function.
Errors are returned as `{:error, %GcpCompute.Error{}}`. The struct is also an
exception, so it can be raised by the bang variants (`insert_instance!/3`, …)
or with `raise/1`.
## Fields
* `:reason` — a coarse, matchable atom (`:api_error`, `:transport`,
`:timeout`, `:token_fetch_failed`, `:operation_failed`, …).
* `:status` — HTTP status code, when the error came from an API response.
* `:message` — human readable message (from GCP when available).
* `:errors` — the raw `error.errors` list returned by the Compute API.
* `:body` — the underlying body/exception, for debugging.
* `:operation` — the failed `GcpCompute.Operation`, for operation errors.
"""
@type t :: %__MODULE__{
reason: atom(),
status: non_neg_integer() | nil,
message: String.t() | nil,
errors: list() | nil,
body: term(),
operation: GcpCompute.Operation.t() | nil
}
defexception [:reason, :status, :message, :errors, :body, :operation]
@impl true
def message(%__MODULE__{message: message}) when is_binary(message), do: message
def message(%__MODULE__{reason: reason, status: status}) when is_integer(status) do
"GCP Compute error (#{reason}, HTTP #{status})"
end
def message(%__MODULE__{reason: reason}), do: "GCP Compute error: #{inspect(reason)}"
@doc """
Build an error from a non-2xx Compute API response.
Handles the standard Google error envelope:
%{"error" => %{"code" => 403, "message" => "...", "errors" => [...]}}
"""
@spec from_response(non_neg_integer(), term()) :: t()
def from_response(status, %{"error" => %{} = error} = body) do
%__MODULE__{
reason: :api_error,
status: status,
message: error["message"],
errors: error["errors"],
body: body
}
end
def from_response(status, body) do
%__MODULE__{reason: :api_error, status: status, message: "HTTP #{status}", body: body}
end
@doc "Build a transport-level error from a `Req`/`Mint` exception."
@spec from_exception(Exception.t()) :: t()
def from_exception(exception) do
%__MODULE__{
reason: :transport,
message: Exception.message(exception),
body: exception
}
end
end
defimpl Inspect, for: GcpCompute.Error do
import Inspect.Algebra
# `:body` may hold a Req/Mint exception whose request headers carry the bearer
# token. Redact it so errors are safe to log / inspect / send to Sentry.
def inspect(error, opts) do
fields = [
reason: error.reason,
status: error.status,
message: error.message,
errors: error.errors,
body: error.body && :REDACTED,
operation: error.operation
]
concat(["#GcpCompute.Error<", to_doc(fields, opts), ">"])
end
end