defmodule Microwaveprop.Qrz.Record do @moduledoc """ Typed struct representing the QRZ.com callsign fields this app actually consumes. The full upstream XML has ~50 fields; the rest are preserved in the raw `data` column on `Microwaveprop.Qrz.Callsign` and can be read directly when needed. """ @derive Jason.Encoder @type t :: %__MODULE__{ call: String.t() | nil, fname: String.t() | nil, name: String.t() | nil, grid: String.t() | nil, addr1: String.t() | nil, addr2: String.t() | nil, state: String.t() | nil, zip: String.t() | nil, country: String.t() | nil, lat: float() | nil, lon: float() | nil } defstruct [:call, :fname, :name, :grid, :addr1, :addr2, :state, :zip, :country, :lat, :lon] @string_fields %{ "call" => :call, "fname" => :fname, "name" => :name, "grid" => :grid, "addr1" => :addr1, "addr2" => :addr2, "state" => :state, "zip" => :zip, "country" => :country } @float_fields %{ "lat" => :lat, "lon" => :lon } @spec from_map(map()) :: t() def from_map(data) when is_map(data) do %__MODULE__{} |> parse_fields(data, @string_fields, &parse_string/1) |> parse_fields(data, @float_fields, &parse_float/1) end defp parse_fields(record, data, field_map, parser) do Enum.reduce(field_map, record, fn {xml_key, struct_key}, acc -> case Map.get(data, xml_key) do nil -> acc value -> Map.put(acc, struct_key, parser.(value)) end end) end defp parse_string(value) when is_binary(value), do: value defp parse_string(_), do: nil defp parse_float(value) when is_binary(value) do case Float.parse(value) do {float, _} -> float :error -> nil end end defp parse_float(_), do: nil end