summaryrefslogtreecommitdiffstats
path: root/lib/Cepis/Address.pm
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-26 14:40:05 -0300
committerMurilo Ijanc <[email protected]>2026-07-26 14:40:05 -0300
commit0bc578d6d334c7f1ccaf329cd61bb8bce76b7f59 (patch)
tree86f73108f0432fd8330edcaab8b8ae7018322dee /lib/Cepis/Address.pm
downloadperl-0.1.0.tar.gz
Initial importv0.1.0
Diffstat (limited to 'lib/Cepis/Address.pm')
-rw-r--r--lib/Cepis/Address.pm213
1 files changed, 213 insertions, 0 deletions
diff --git a/lib/Cepis/Address.pm b/lib/Cepis/Address.pm
new file mode 100644
index 0000000..a938d6f
--- /dev/null
+++ b/lib/Cepis/Address.pm
@@ -0,0 +1,213 @@
+package Cepis::Address;
+
+use strict;
+use warnings;
+
+use Cepis::Geo;
+
+our $VERSION = '0.1.0';
+
+our @FIELDS = qw(
+ cep logradouro complemento unidade bairro localidade
+ uf estado regiao ibge gia ddd siafi
+);
+
+{
+ no strict 'refs';
+ for my $name (@FIELDS) {
+ *{$name} = sub { return $_[0]->{$name} };
+ }
+}
+
+sub from_json {
+ my ( $class, $data ) = @_;
+
+ my %raw = %{ $data || {} };
+ my %self;
+ for my $name (@FIELDS) {
+ $self{$name} = _string( $raw{$name} );
+ }
+ $self{geo} = Cepis::Geo->from_json( $raw{geo} );
+ $self{raw} = \%raw;
+
+ return bless \%self, $class;
+}
+
+sub geo { return $_[0]->{geo} }
+sub raw { return $_[0]->{raw} }
+
+sub field {
+ my ( $self, $name ) = @_;
+ return $self->{raw}{$name};
+}
+
+sub formatted_cep {
+ my ($self) = @_;
+ my $cep = $self->{cep};
+ return $cep unless length($cep) == 8;
+ return substr( $cep, 0, 5 ) . '-' . substr( $cep, 5, 3 );
+}
+
+sub to_hash {
+ my ($self) = @_;
+ my %out = map { $_ => $self->{$_} } @FIELDS;
+ $out{geo} = $self->{geo} ? $self->{geo}->to_hash : undef;
+ return \%out;
+}
+
+sub _string {
+ my ($value) = @_;
+ return '' unless defined $value;
+ return '' if ref $value;
+ return "$value";
+}
+
+1;
+
+__END__
+
+=encoding UTF-8
+
+=head1 NAME
+
+Cepis::Address - a CEP and everything the database knows about it
+
+=head1 SYNOPSIS
+
+ my $a = Cepis::lookup('01310-100');
+
+ $a->logradouro; # "Avenida Paulista"
+ $a->localidade; # "São Paulo"
+ $a->uf; # "SP"
+ $a->ibge; # "3550308"
+ $a->cep; # "01310100" (always unmasked)
+ $a->formatted_cep; # "01310-100"
+
+=head1 DESCRIPTION
+
+B<Field names are the API's own, in Portuguese> — C<logradouro>, C<bairro>,
+C<localidade>. Everything else in this library is English; the fields are the
+one deliberate exception.
+
+The reason is fidelity: what you read in the JSON at
+L<https://cep.is/01001000> is exactly what you read on the object. Translating
+to C<street>/C<city> would mean maintaining a field-by-field mapping and forcing
+anyone reading the API docs alongside this library to translate back in their
+head. The values are Portuguese either way — they come from the Correios
+database.
+
+Fields the database does not have for that CEP come back as empty strings, never
+C<undef>. That is what the API returns, and changing it here would create a
+difference between reading the JSON and reading this library.
+
+L</geo> is the exception: it is C<undef> when there is no coordinate. There the
+difference is real, because the API B<omits> the whole field in that case (about
+43% of CEPs) rather than sending null.
+
+=head1 CONSTRUCTOR
+
+=head2 from_json
+
+ my $address = Cepis::Address->from_json( $hashref );
+
+Builds from the decoded JSON object the API returned.
+
+Unknown fields are ignored during construction but preserved in L</raw>. A new
+field in the API therefore cannot break this library.
+
+A malformed C<geo> block yields C<< geo => undef >> rather than propagating the
+error.
+
+=head1 ACCESSORS
+
+=head2 cep
+
+The 8 digits, unmasked.
+
+=head2 logradouro
+
+Street name.
+
+=head2 complemento
+
+Complement, such as C<"lado ímpar">.
+
+=head2 unidade
+
+Unit, when the CEP belongs to a specific building or post office box range.
+
+=head2 bairro
+
+Neighbourhood.
+
+=head2 localidade
+
+Municipality name.
+
+=head2 uf
+
+Two-letter state code.
+
+=head2 estado
+
+Full state name.
+
+=head2 regiao
+
+Region of the country, such as C<"Sudeste">.
+
+=head2 ibge
+
+IBGE municipality code.
+
+=head2 gia
+
+GIA code (São Paulo state tax registry).
+
+=head2 ddd
+
+Area code for telephone dialling.
+
+=head2 siafi
+
+SIAFI municipality code.
+
+=head2 geo
+
+A L<Cepis::Geo> object, or C<undef> when the database has no coordinate for
+this CEP. Addresses coming out of C<lookup_many> always have C<undef> here — see
+L<Cepis::Client/lookup_many>.
+
+=head2 raw
+
+The decoded response exactly as it arrived, as a hashref, so a field the API
+grows before this library is updated is not lost.
+
+=head2 field
+
+ $address->field('campo_do_futuro');
+
+Reads any field of the raw response by name, including one this release does not
+know about. Returns C<undef> when the field is absent.
+
+=head2 formatted_cep
+
+The CEP with its mask: C<01001-000>.
+
+The API always returns it unmasked; formatting is presentation, and it lives
+here so it does not become a repeated C<substr> in every project that uses this.
+
+=head2 to_hash
+
+Every named field plus C<geo> as a plain hashref, with C<geo> itself flattened
+to a hashref or C<undef>.
+
+=head1 SEE ALSO
+
+L<Cepis>, L<Cepis::Geo>
+
+=head1 LICENCE
+
+ISC. Copyright (c) 2026 Murilo Ijanc E<lt>[email protected]<gt>.
+
+=cut