diff options
Diffstat (limited to 'lib/cepis.ex')
| -rw-r--r-- | lib/cepis.ex | 50 |
1 files changed, 46 insertions, 4 deletions
diff --git a/lib/cepis.ex b/lib/cepis.ex index 249e3f1..95d0579 100644 --- a/lib/cepis.ex +++ b/lib/cepis.ex @@ -27,7 +27,7 @@ defmodule Cepis do the JSON is what you access on the struct. See `Cepis.Address`. """ - alias Cepis.{Address, Client, CSV, Health, HTTP} + alias Cepis.{Address, Client, CSV, Faixa, Health, HTTP} alias Cepis.{InvalidCEPError, InvalidResponseError, NotFoundError} @max_batch 500 @@ -93,11 +93,30 @@ defmodule Cepis do message: the format already passed local validation, so the server knows something this client does not. + ## When the CEP does not exist + + A failing CEP is rarely random — it is usually someone who knows the city and + guessed the number, most often the `-000`, which 957 municipalities never + received. So `Cepis.NotFoundError` may carry a `Cepis.Faixa` naming the + municipality that number *would* have belonged to: + + {:error, %Cepis.NotFoundError{faixa: faixa}} = Cepis.lookup("25845000") + + if faixa do + # "Areal", "RJ" — the CEP is still not in the database + {faixa.localidade, faixa.uf} + end + + It is still a not-found, and `faixa` is context for the failure rather than + an address. It is `nil` whenever the number falls in a band no municipality + owns. + ## Errors * `Cepis.InvalidCEPError` — not 8 digits (no request is made), or the API rejected it. - * `Cepis.NotFoundError` — well-formed, but not in the database. + * `Cepis.NotFoundError` — well-formed, but not in the database. May carry + the municipality in `faixa`. * `Cepis.RateLimitError` — 429 after every attempt. * `Cepis.ServerError` — 5xx after every attempt. * `Cepis.NetworkError` — the request never completed. @@ -197,11 +216,14 @@ defmodule Cepis do @spec health!(Client.t() | keyword()) :: Health.t() def health!(client \\ []), do: unwrap(health(client)) - defp from_response(404, _body, number) do + defp from_response(404, body, number) do + faixa = faixa(body) + {:error, %NotFoundError{ cep: number, - message: "CEP #{number} does not exist in the DNE database" + faixa: faixa, + message: not_found_message(number, faixa) }} end @@ -213,6 +235,26 @@ defmodule Cepis do with {:ok, data} <- decode(body), do: {:ok, Address.from_map(data)} end + # A 404 body is a courtesy, not a contract: if it is absent, truncated or not + # JSON at all, the not-found still stands on its own. Nothing here may turn a + # clean definitive answer into a parse failure. + defp faixa(body) do + case decode(body) do + {:ok, data} -> Faixa.from_map(Map.get(data, "faixa")) + {:error, _reason} -> nil + end + end + + defp not_found_message(number, nil) do + "CEP #{number} does not exist in the DNE database" + end + + defp not_found_message(number, %Faixa{} = faixa) do + "CEP #{number} does not exist in the DNE database — the range belongs to " <> + "#{faixa.localidade}/#{faixa.uf}, which has #{faixa.ceps} CEPs between " <> + "#{faixa.cep_ini} and #{faixa.cep_fim}" + end + defp lookup_chunk(client, ceps) do query = URI.encode_query(%{"ceps" => Enum.map_join(ceps, ",", &to_string/1)}) |