From 0bc578d6d334c7f1ccaf329cd61bb8bce76b7f59 Mon Sep 17 00:00:00 2001 From: Murilo Ijanc Date: Sun, 26 Jul 2026 14:40:05 -0300 Subject: Initial import --- lib/Cepis/Error.pm | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 lib/Cepis/Error.pm (limited to 'lib/Cepis/Error.pm') diff --git a/lib/Cepis/Error.pm b/lib/Cepis/Error.pm new file mode 100644 index 0000000..080b0a1 --- /dev/null +++ b/lib/Cepis/Error.pm @@ -0,0 +1,226 @@ +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 -- cgit v1.3