summaryrefslogtreecommitdiffstats
path: root/lib/cepis/client.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/cepis/client.rb')
-rw-r--r--lib/cepis/client.rb41
1 files changed, 39 insertions, 2 deletions
diff --git a/lib/cepis/client.rb b/lib/cepis/client.rb
index f451061..8480315 100644
--- a/lib/cepis/client.rb
+++ b/lib/cepis/client.rb
@@ -113,10 +113,15 @@ module Cepis
# message: the format already passed local validation, so the server knows
# something this client does not.
#
+ # A 404 may carry {CEPNotFound#faixa}: the municipality that owns the band
+ # of numbers the CEP fell into. It is still a 404 and still an exception —
+ # the context does not make a nonexistent CEP into an address. See {Faixa}.
+ #
# @param cep [String] masked or unmasked.
# @return [Address]
# @raise [InvalidCEP] the CEP is not 8 digits (no request is made).
- # @raise [CEPNotFound] well-formed, but not in the database.
+ # @raise [CEPNotFound] well-formed, but not in the database. Carries
+ # {CEPNotFound#faixa} when the API named a municipality for the band.
# @raise [RateLimited] 429 after every attempt.
# @raise [ServerError] 5xx after every attempt.
# @raise [NetworkError] the request never completed.
@@ -125,7 +130,7 @@ module Cepis
number = Cepis.normalize_cep(cep)
status, body = request("/#{number}")
- raise CEPNotFound, "CEP #{number} does not exist in the DNE database" if status == 404
+ raise not_found(number, body) if status == 404
raise InvalidCEP, error_message(body, "CEP #{number} rejected") if status == 400
Address.from_json(parse_json(body))
@@ -219,6 +224,38 @@ module Cepis
body.to_s.dup.force_encoding(Encoding::UTF_8).scrub
end
+ # The {CEPNotFound} for a 404, with the band read off the body when it is
+ # there.
+ #
+ # Every step of reading it is allowed to fail into `nil`: an absent
+ # `faixa` is the ordinary case (the API omits it, it never sends null),
+ # and a body that is not JSON at all is a proxy's doing, not the caller's.
+ # Neither may promote a definitive, well-understood not-found into an
+ # {InvalidResponse} — the reason the lookup failed would be replaced by
+ # the reason the consolation prize failed to parse.
+ def not_found(number, body)
+ faixa = begin
+ data = JSON.parse(decode(body))
+ data.is_a?(Hash) ? Faixa.from_json(data["faixa"]) : nil
+ rescue JSON::ParserError
+ nil
+ end
+
+ CEPNotFound.new(not_found_message(number, faixa), faixa: faixa)
+ end
+
+ # The message names the municipality when the API named one, because
+ # "does not exist" plus "the numbers around it are Areal/RJ" is what makes
+ # the log line diagnosable. The counts stay in the message too, so that
+ # nobody reads the two CEPs as the ends of a contiguous range.
+ def not_found_message(number, faixa)
+ base = "CEP #{number} does not exist in the DNE database"
+ return base if faixa.nil?
+
+ "#{base} (it falls in the band of #{faixa.localidade}/#{faixa.uf}, which has " \
+ "#{faixa.ceps} CEP(s) scattered between #{faixa.cep_ini} and #{faixa.cep_fim})"
+ end
+
def error_message(body, fallback)
data = JSON.parse(decode(body))
data.is_a?(Hash) && data["mensagem"] ? String(data["mensagem"]) : fallback