defmodule Cepis.Address do @moduledoc """ A CEP and everything the database knows about it. **Field names are the API's own, in Portuguese** — `logradouro`, `bairro`, `localidade`. Everything else in this library is English; the fields are the one deliberate exception. The reason is fidelity: what you read in the JSON at is exactly what you access on the struct. Fields the database does not have for that CEP come back as empty strings, never `nil` — that is what the API returns, and changing it here would create a difference between reading the JSON and reading this library. `geo` is the exception: it is `nil` when there is no coordinate. There the difference is real, because the API **omits** the whole field in that case (about 43% of CEPs) rather than sending null. `raw` holds the decoded response exactly as it arrived, uninterpreted, so a field the API grows before this library is updated is not lost. """ alias Cepis.Geo defstruct cep: "", logradouro: "", complemento: "", unidade: "", bairro: "", localidade: "", uf: "", estado: "", regiao: "", ibge: "", gia: "", ddd: "", siafi: "", geo: nil, raw: %{} @type t :: %__MODULE__{ cep: String.t(), logradouro: String.t(), complemento: String.t(), unidade: String.t(), bairro: String.t(), localidade: String.t(), uf: String.t(), estado: String.t(), regiao: String.t(), ibge: String.t(), gia: String.t(), ddd: String.t(), siafi: String.t(), geo: Geo.t() | nil, raw: map() } @doc """ The CEP with its mask: `"01001-000"`. The native contract always returns it unmasked; formatting is presentation, and it lives here so it does not become a repeated interpolation in every project that uses this. A CEP that is not 8 characters is returned unchanged. """ @spec formatted_cep(t()) :: String.t() def formatted_cep(%__MODULE__{cep: <>}) do prefix <> "-" <> suffix end def formatted_cep(%__MODULE__{cep: cep}), do: cep @doc false @spec from_map(map()) :: t() def from_map(data) when is_map(data) do %__MODULE__{ cep: text(data, "cep"), logradouro: text(data, "logradouro"), complemento: text(data, "complemento"), unidade: text(data, "unidade"), bairro: text(data, "bairro"), localidade: text(data, "localidade"), uf: text(data, "uf"), estado: text(data, "estado"), regiao: text(data, "regiao"), ibge: text(data, "ibge"), gia: text(data, "gia"), ddd: text(data, "ddd"), siafi: text(data, "siafi"), geo: Geo.from_map(Map.get(data, "geo")), raw: data } end defp text(data, key) do case Map.get(data, key) do value when is_binary(value) -> value value when is_number(value) -> to_string(value) _ -> "" end end end