Typed error tags used across the MobDev.Release.* modules.
These exist for one specific reason: when the release pipeline fails in
production, the caller needs to instantly distinguish "it's our bug"
from "it's an external infra issue" from "the user's environment isn't
set up right." Shell scripts blob all three together as exit 1; this
module gives every release function a tagged-tuple shape so the
top-level Mix task can format an actionable message.
Categories
Each error is {:error, {category, detail}}. The category is one of:
:precondition_failed— a checked precondition didn't hold before we did real work. Example:OTP_SRCnot a git repo,OPENSSL_PREFIXnot yet built,$ANDROID_NDK_ROOTmissing. Caller action: fix the environment, retry.:cmd_failed— an external command exited nonzero. Detail includes the command, exit code, and captured output. Caller action: read the output. If it looks like our code's fault, file a bug; if it looks like the tool's fault, escalate.:parse_failed— output we expected to parse didn't match. This is almost always our bug — a tool's output format drifted or our regex was wrong. Caller action: file a bug.:fs_failed— a filesystem operation failed. Detail carries the:file.posixreason. Caller action: check the path; ifeacces/enospcetc., it's environmental.:infra_unreachable— a network/GH/etc. operation failed. Carries the HTTP status or transport error. Caller action: check status.github.com / status.hex.pm. Not our bug.:auth_required— credentials missing or expired. Carries a hint about which credential needs renewal. Caller action: run the auth refresh command we suggest.
Convenience helpers
Each category has a constructor and a guard. Use the constructors in return values; pattern-match on the tag in the formatter.
def foo do
with {:ok, hash} <- read_hash(),
{:ok, _} <- validate(hash) do
{:ok, hash}
end
end
# In the Mix task:
case Release.full() do
{:ok, _} -> :ok
{:error, {:precondition_failed, msg}} -> Mix.raise(msg)
{:error, {:auth_required, hint}} -> Mix.raise("auth: " <> hint)
{:error, other} -> Mix.raise("release failed: " <> inspect(other))
end
Summary
Functions
Build an auth_required error. hint should suggest the renewal command.
Build a cmd_failed error. Captures the command's argv, exit code, and the head of its captured output (truncated to avoid pinning huge build logs to memory).
Format a tagged error for end-user display. Returns a string suitable
for passing to Mix.raise/1 or IO.puts/1. Uses the category to
produce an actionable message.
Build an fs_failed error. reason is the :file posix atom.
Build an infra_unreachable error. Detail is opaque (HTTP status, transport error, etc.).
Build a parse_failed error. expected describes what we tried to parse.
Build a precondition_failed error. msg should be human-readable.
Types
Functions
Build an auth_required error. hint should suggest the renewal command.
@spec cmd_failed([String.t()], non_neg_integer(), String.t()) :: t()
Build a cmd_failed error. Captures the command's argv, exit code, and the head of its captured output (truncated to avoid pinning huge build logs to memory).
Format a tagged error for end-user display. Returns a string suitable
for passing to Mix.raise/1 or IO.puts/1. Uses the category to
produce an actionable message.
iex> MobDev.Release.Errors.format({:error, {:precondition_failed, "OTP_SRC missing"}})
"precondition failed — OTP_SRC missing"
Build an fs_failed error. reason is the :file posix atom.
Build an infra_unreachable error. Detail is opaque (HTTP status, transport error, etc.).
Build a parse_failed error. expected describes what we tried to parse.
Build a precondition_failed error. msg should be human-readable.