lib/appsignal/utils/type.ex

defmodule Appsignal.Utils.Type do
  defstruct [:type]

  def from(term) do
    %__MODULE__{type: of(term)}
  end

  defp of(term) when is_boolean(term), do: "boolean"

  defp of(nil), do: "nil"

  defp of(term) when is_integer(term), do: "integer"

  defp of(term) when is_float(term), do: "float"

  defp of(term) when is_pid(term), do: "pid"

  defp of(term) when is_atom(term), do: "atom"

  defp of(term) when is_bitstring(term) and not is_binary(term), do: "bitstring"

  defp of(term) when is_binary(term), do: "binary"

  defp of(term) when is_function(term), do: "function"

  defp of(term) when is_port(term), do: "port"

  defp of(term) when is_reference(term), do: "reference"

  defp of(term) when is_tuple(term) do
    "{#{Enum.map_join(Tuple.to_list(term), ", ", &of/1)}}"
  end

  defp of(term) when is_list(term) do
    "[#{Enum.map_join(term, ", ", &of/1)}]"
  end

  defp of(term) when is_map(term) do
    {struct, map} = Map.pop(term, :__struct__)

    "%#{Appsignal.Utils.module_name(struct)}{#{Enum.map_join(map, ", ", fn {key, value} -> "#{of(key)} => #{of(value)}" end)}}"
  end

  defp of(_term), do: "unknown"
end

defimpl Inspect, for: Appsignal.Utils.Type do
  def inspect(%Appsignal.Utils.Type{type: type}, _opts), do: type
end

defimpl String.Chars, for: Appsignal.Utils.Type do
  def to_string(%Appsignal.Utils.Type{type: type}), do: type
end