# `MobDev.Release.Errors`
[🔗](https://github.com/genericjam/mob_dev/blob/master/lib/mob_dev/release/errors.ex#L1)

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

# `category`

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

# `detail`

```elixir
@type detail() :: term()
```

# `t`

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

# `auth_required`

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

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

# `cmd_failed`

```elixir
@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`

```elixir
@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`

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

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

# `infra_unreachable`

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

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

# `parse_failed`

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

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

# `precondition`

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

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
