summaryrefslogtreecommitdiffstats
path: root/lib/Cepis/Client.pm
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-27 20:34:36 -0300
committerMurilo Ijanc <[email protected]>2026-07-27 20:34:36 -0300
commitf2f3e5b5b22f5db49670391fb747a52fd492b9db (patch)
tree3d02d13f3111bdd7381470d3fd3db1f58e00bc8c /lib/Cepis/Client.pm
parent0bc578d6d334c7f1ccaf329cd61bb8bce76b7f59 (diff)
downloadperl-0.2.0.tar.gz
Carry the municipality on a CEP that does not existHEADv0.2.0main
A CEP that fails is rarely a random number. It is usually someone who knows the city and guessed the digits, and the classic guess is the -000 that 957 municipalities never received. The API now answers those 404s with a faixa block naming the municipality that owns the band, so the library can say "25845000 does not exist in Areal/RJ" instead of "not found". It stays an error: lookup still throws Cepis::Error::CEPNotFound, and the faixa hangs off the exception rather than turning a miss into an address. Parsing it is deliberately forgiving — a missing, empty or malformed block yields undef, because losing the clean not-found to a parse failure would be a worse outcome than losing the context. cep_ini and cep_fim are the smallest and largest CEP that actually exist, not the bounds the Correios publish, and the interval between them is sparse. That is why ceps is exposed alongside them and why the docs say twice that the pair is not a list of valid CEPs.
Diffstat (limited to 'lib/Cepis/Client.pm')
-rw-r--r--lib/Cepis/Client.pm44
1 files changed, 40 insertions, 4 deletions
diff --git a/lib/Cepis/Client.pm b/lib/Cepis/Client.pm
index 35eb51b..e75868b 100644
--- a/lib/Cepis/Client.pm
+++ b/lib/Cepis/Client.pm
@@ -13,9 +13,10 @@ use Time::HiRes ();
use Cepis::Address;
use Cepis::CSV;
use Cepis::Error;
+use Cepis::Faixa;
use Cepis::Health;
-our $VERSION = '0.1.0';
+our $VERSION = '0.2.0';
use constant DEFAULT_URL => 'https://cep.is';
use constant MAX_BATCH => 500;
@@ -120,8 +121,10 @@ sub lookup {
my ( $status, $body ) = $self->_request("/$number");
if ( $status == 404 ) {
- Cepis::Error::CEPNotFound->throw(
- message => "CEP $number does not exist in the DNE database" );
+ my $faixa = _faixa($body);
+ my $message = "CEP $number does not exist in the DNE database";
+ $message .= ' (it would fall in ' . $faixa->label . ')' if $faixa;
+ Cepis::Error::CEPNotFound->throw( message => $message, faixa => $faixa );
}
if ( $status == 400 ) {
Cepis::Error::InvalidCEP->throw(
@@ -394,6 +397,14 @@ sub _error_message {
return "$data->{mensagem}";
}
+sub _faixa {
+ my ($body) = @_;
+
+ my $data = eval { $JSON->decode( defined $body ? $body : '' ) };
+ return undef unless ref $data eq 'HASH';
+ return Cepis::Faixa->from_json( $data->{faixa} );
+}
+
sub _decode {
my ($body) = @_;
return '' unless defined $body;
@@ -575,6 +586,10 @@ A 400 from the API is surfaced as L<Cepis::Error::InvalidCEP> carrying the API's
own message: the format already passed local validation, so the server knows
something this client does not.
+A 404 becomes L<Cepis::Error::CEPNotFound>, and that error may carry a
+L<Cepis::Faixa> in C<< $err->faixa >> naming the municipality the CEP would have
+belonged to — see L</"A not-found that knows where it was">.
+
Throws L<Cepis::Error::InvalidCEP> when the CEP is not 8 digits (no request is
made), L<Cepis::Error::CEPNotFound> when it is well-formed but absent from the
database, L<Cepis::Error::RateLimited> on 429 after every attempt,
@@ -583,6 +598,27 @@ L<Cepis::Error::Network> when the request never completed, and
L<Cepis::Error::InvalidResponse> when a success status carries an unreadable
body.
+=head3 A not-found that knows where it was
+
+ my $address = eval { $client->lookup('25845000') };
+ if ( !$address && ref $@ && $@->isa('Cepis::Error::CEPNotFound') ) {
+ if ( my $faixa = $@->faixa ) {
+ printf "%s does not exist in %s (%d CEPs, %s..%s)\n",
+ '25845000', $faixa->label, $faixa->ceps,
+ $faixa->cep_ini, $faixa->cep_fim;
+ }
+ }
+
+The API's 404 body may carry a C<faixa> block naming the municipality that owns
+the band the CEP fell into. When it does, the error carries a L<Cepis::Faixa>
+and its message names the municipality; when the CEP fell in a band nobody owns,
+the API omits the block and C<< $err->faixa >> is C<undef>.
+
+B<It is still a 404.> Nothing here turns a not-found into an address; the
+lookup threw, and C<faixa> only says where the CEP would have been. Nor is
+C<cep_ini>..C<cep_fim> a list of valid CEPs — the range is sparse, which is what
+C<ceps> is for. L<Cepis::Faixa> spells both out.
+
=head2 lookup_many
my $result = $client->lookup_many( \@ceps );
@@ -643,7 +679,7 @@ C<500>, the most CEPs the API accepts in a single batch request.
=head1 SEE ALSO
-L<Cepis>, L<Cepis::Address>, L<Cepis::Health>, L<Cepis::Error>
+L<Cepis>, L<Cepis::Address>, L<Cepis::Health>, L<Cepis::Error>, L<Cepis::Faixa>
=head1 LICENCE