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/Address.pm | 213 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 lib/Cepis/Address.pm (limited to 'lib/Cepis/Address.pm') 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 — C, C, +C. 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 is exactly what you read on the object. Translating +to C/C 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. That is what the API returns, and changing it here would create a +difference between reading the JSON and reading this library. + +L is the exception: it is C when there is no coordinate. There the +difference is real, because the API B 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. A new +field in the API therefore cannot break this library. + +A malformed C 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 object, or C when the database has no coordinate for +this CEP. Addresses coming out of C always have C here — see +L. + +=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 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 in every project that uses this. + +=head2 to_hash + +Every named field plus C as a plain hashref, with C itself flattened +to a hashref or C. + +=head1 SEE ALSO + +L, L + +=head1 LICENCE + +ISC. Copyright (c) 2026 Murilo Ijanc Emurilo@ijanc.orgE. + +=cut -- cgit v1.3