lib/graft/cli/errors.ex

defmodule Graft.CLI.Errors do
  @moduledoc false

  # Shared error-formatting helpers for `Mix.Tasks.Graft.*` tasks.
  # In `:text` mode, returns a concise human message routed to stderr.
  # In `:json` mode, returns a structured JSON document on stdout.

  alias Graft.Error

  @type stream :: :stdout | :stderr

  @spec format(Error.t(), :text | :json, String.t()) :: {:error, String.t(), stream()}
  def format(%Error{} = err, :text, task_name) do
    {:error, "#{task_name}: #{err.message}", :stderr}
  end

  def format(%Error{} = err, :json, _task_name) do
    payload = %{
      error: %{
        kind: to_string(err.kind),
        message: err.message,
        details: jsonable(err.details)
      }
    }

    {:error, Jason.encode!(payload), :stdout}
  end

  @doc "Recursively coerce values to JSON-safe shapes (atoms → strings, etc.)."
  def jsonable(map) when is_map(map) do
    Map.new(map, fn {k, v} -> {to_string(k), jsonable(v)} end)
  end

  def jsonable(list) when is_list(list), do: Enum.map(list, &jsonable/1)

  def jsonable(atom) when is_atom(atom) and not is_boolean(atom) and not is_nil(atom),
    do: to_string(atom)

  def jsonable(v) when is_binary(v) or is_number(v) or is_boolean(v) or is_nil(v), do: v
  def jsonable(v), do: inspect(v)
end