package Cepis::Error; use strict; use warnings; our $VERSION = '0.1.0'; use overload '""' => sub { $_[0]->message }, 'bool' => sub { 1 }, fallback => 1; sub new { my ( $class, %args ) = @_; my $self = {%args}; $self->{message} = $class unless defined $self->{message}; return bless $self, $class; } sub throw { my $class = shift; die $class->new(@_); } sub message { return $_[0]->{message} } package Cepis::Error::InvalidCEP; use strict; use warnings; use parent -norequire, 'Cepis::Error'; our $VERSION = '0.1.0'; package Cepis::Error::CEPNotFound; use strict; use warnings; use parent -norequire, 'Cepis::Error'; our $VERSION = '0.1.0'; package Cepis::Error::Network; use strict; use warnings; use parent -norequire, 'Cepis::Error'; our $VERSION = '0.1.0'; package Cepis::Error::ServerError; use strict; use warnings; use parent -norequire, 'Cepis::Error'; our $VERSION = '0.1.0'; sub status { return $_[0]->{status} } package Cepis::Error::RateLimited; use strict; use warnings; use parent -norequire, 'Cepis::Error'; our $VERSION = '0.1.0'; sub retry_after { return $_[0]->{retry_after} } package Cepis::Error::InvalidResponse; use strict; use warnings; use parent -norequire, 'Cepis::Error'; our $VERSION = '0.1.0'; 1; __END__ =encoding UTF-8 =head1 NAME Cepis::Error - exception hierarchy for L =head1 SYNOPSIS use Cepis; my $address = eval { Cepis::lookup($cep_from_form) }; if ( !$address ) { my $err = $@; if ( ref $err && $err->isa('Cepis::Error::CEPNotFound') ) { # the CEP does not exist: ask the user for another } elsif ( ref $err && $err->isa('Cepis::Error') ) { # network, server, rate limit: retry later } else { die $err; } } =head1 DESCRIPTION Every failure raised by this library is an object thrown with C, and every one of them inherits from C. One C<< ref $@ && $@->isa('Cepis::Error') >> therefore catches the lot. Below the base class the split is by B, not by the HTTP status that caused it: =over 4 =item * L and L are problems with the I. Retrying is pointless — the same CEP will fail again. =item * L, L and L are problems with I. Retrying may help, and the client already retried on its own before throwing any of them. =back That split is why C is not "the HTTP 404 exception": C is thrown without any request being made at all, when the CEP is malformed before it ever leaves the machine. Every error stringifies to its message, so C and C<"$@"> do the obvious thing. =head1 CLASSES =head2 Cepis::Error Base of every error. The only accessor it guarantees is C. =over 4 =item C<< Cepis::Error->new( message => $text, %fields ) >> Builds the object. Extra fields are stored as-is and become available to subclasses. =item C<< Cepis::Error->throw( message => $text, %fields ) >> Builds the object and Cs with it. =item C<< $err->message >> The human-readable message. Also what the object stringifies to. =back =head2 Cepis::Error::InvalidCEP The CEP is not 8 digits. Usually thrown B a request ever being made — the client checks the format before going to the network. It is also thrown when the API answers 400 to a CEP that passed local validation, in which case it carries the API's own message. =head2 Cepis::Error::CEPNotFound The CEP is well-formed but does not exist in the DNE database. =head2 Cepis::Error::Network The request never completed: DNS, refused connection, timeout, TLS. Thrown only after every configured attempt has failed — with one deliberate exception. When C is C and Perl has no TLS support installed, this is thrown immediately, before any attempt, with a message naming the two modules to install. Retrying cannot fix a missing module. See L. =head2 Cepis::Error::ServerError The API answered 5xx on every attempt. =over 4 =item C<< $err->status >> The HTTP status seen on the last attempt. =back =head2 Cepis::Error::RateLimited The API, or the edge in front of it, answered 429 on every attempt. =over 4 =item C<< $err->retry_after >> What the server asked for, in seconds, or C if it did not say. The client already honoured this across the attempts it made; it is exposed for callers who want to reschedule the work. =back =head2 Cepis::Error::InvalidResponse A success status came back with a body that cannot be parsed. In practice this means something between you and the API is answering instead of it — a captive Wi-Fi portal, a corporate proxy, an HTML error page. Useful as a diagnosis: the problem is not in your code. =head1 SEE ALSO L, L =head1 LICENCE ISC. Copyright (c) 2026 Murilo Ijanc Emurilo@ijanc.orgE. =cut