1.8 KiB · text History 9c0fc16
defmodule LambentExWeb.Channels.Payload do
  @moduledoc """
  The pubsub payloads the liveviews consume are built for elixir, not for the
  wire: ip addresses are tuples, machine and link options are keyword lists,
  and map keys are a mix of atoms and strings. None of that survives a json
  encoder, so everything a channel pushes goes through here first.
  """

  @doc """
  Recursively makes a term json-safe. Keyword lists become objects, other
  tuples become arrays, atoms and structs become strings or plain maps.
  """
  def wire(%DateTime{} = dt), do: DateTime.to_iso8601(dt)
  def wire(%NaiveDateTime{} = dt), do: NaiveDateTime.to_iso8601(dt)
  def wire(%_struct{} = s), do: s |> Map.from_struct() |> wire()

  def wire(%{} = map) do
    Map.new(map, fn {k, v} -> {key(k), wire(v)} end)
  end

  def wire(list) when is_list(list) do
    case Keyword.keyword?(list) and list != [] do
      true -> list |> Map.new() |> wire()
      false -> Enum.map(list, &wire/1)
    end
  end

  def wire(tuple) when is_tuple(tuple), do: tuple |> Tuple.to_list() |> wire()

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

  def wire(pid) when is_pid(pid), do: inspect(pid)
  def wire(other), do: other

  @doc """
  Devices carry their address as an `:inet` tuple, which reads better on the
  wire as the dotted string the ui shows.
  """
  def device(device) when is_map(device) do
    case Map.get(device, "ip") do
      ip when is_tuple(ip) -> device |> Map.put("ip", ip |> Tuple.to_list() |> Enum.join("."))
      _otherwise -> device
    end
    |> wire()
  end

  def devices(devices) when is_map(devices) do
    Map.new(devices, fn {mac, device} -> {mac, device(device)} end)
  end

  defp key(k) when is_binary(k), do: k
  defp key(k), do: to_string(k)
end