MobDev.Release.Errors (mob_dev v0.5.0)

Copy Markdown View Source

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_SRC not a git repo, OPENSSL_PREFIX not yet built, $ANDROID_NDK_ROOT missing. 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.posix reason. Caller action: check the path; if eacces/enospc etc., 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

category()

@type category() ::
  :precondition_failed
  | :cmd_failed
  | :parse_failed
  | :fs_failed
  | :infra_unreachable
  | :auth_required

detail()

@type detail() :: term()

t()

@type t() :: {:error, {category(), detail()}}

Functions

auth_required(hint)

@spec auth_required(String.t()) :: t()

Build an auth_required error. hint should suggest the renewal command.

cmd_failed(argv, exit_code, output)

@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(arg)

@spec format(t()) :: String.t()

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"

fs_failed(path, reason)

@spec fs_failed(Path.t(), atom()) :: t()

Build an fs_failed error. reason is the :file posix atom.

infra_unreachable(detail)

@spec infra_unreachable(term()) :: t()

Build an infra_unreachable error. Detail is opaque (HTTP status, transport error, etc.).

parse_failed(input, expected)

@spec parse_failed(term(), String.t()) :: t()

Build a parse_failed error. expected describes what we tried to parse.

precondition(msg)

@spec precondition(String.t()) :: t()

Build a precondition_failed error. msg should be human-readable.