summaryrefslogtreecommitdiffstats
path: root/lib/cepis.ex
diff options
context:
space:
mode:
Diffstat (limited to 'lib/cepis.ex')
-rw-r--r--lib/cepis.ex50
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)})