defmodule Stellar.Horizon.Mapping do
@moduledoc """
Takes a result map or list of maps from Horizon response and returns a struct
(e.g. `%Horizon.Transaction{}`) or list of structs.
"""
@type attr_value :: any()
@type attr_type ::
:integer
| :float
| :date_time
| {:map, Keyword.t()}
| {:struct, module()}
| {:list, atom(), Keyword.t() | module()}
@spec build(module :: struct(), attrs :: map()) :: struct()
def build(module, attrs) do
attrs =
module
|> Map.from_struct()
|> Map.keys()
|> (&Map.take(attrs, &1)).()
struct!(module, attrs)
end
@spec parse(module :: struct(), mapping :: Keyword.t()) :: struct()
def parse(module, []), do: module
def parse(module, [{attr, type} | attrs]) do
value = Map.get(module, attr)
parsed_value = do_parse(type, value)
module
|> Map.put(attr, parsed_value)
|> parse(attrs)
end
@spec do_parse(type :: attr_type(), value :: attr_value()) :: attr_value()
defp do_parse(:integer, value) when is_bitstring(value), do: String.to_integer(value)
defp do_parse(:float, value) when is_bitstring(value), do: String.to_float(value)
defp do_parse(:date_time, value) when is_bitstring(value) do
case DateTime.from_iso8601(value) do
{:ok, date_time, _offset} -> date_time
_error -> value
end
end
defp do_parse({:map, mapping}, value) when is_map(value) do
Enum.reduce(mapping, value, fn {key, type}, acc ->
acc
|> Map.get(key)
|> (&do_parse(type, &1)).()
|> (&Map.put(acc, key, &1)).()
end)
end
defp do_parse({:struct, module}, value) when not is_nil(value), do: module.new(value)
defp do_parse({:list, :map, mapping}, values) when is_list(values) do
Enum.map(values, &do_parse({:map, mapping}, &1))
end
defp do_parse({:list, :struct, module}, values) when is_list(values) do
Enum.map(values, &module.new(&1))
end
defp do_parse(_type, value), do: value
end