diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-27 20:25:04 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-27 20:53:07 -0300 |
| commit | 82d92ed4291663bae94014b4363064dbb26c6a51 (patch) | |
| tree | 69d91a59c955f1e6ec6fc0007324b5d73b3f0d01 /lib/src/models.dart | |
| parent | 8e719fc715eb7ae9aa1c842e633f15b5c6cd3688 (diff) | |
| download | dart-3f63360ff982ef2a932eca814fdd74d4cb4b1de9.tar.gz | |
A CEP that fails a lookup 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 attaches a faixa
block to the 404 naming the municipality that band belongs to, so the
caller can answer "no, but you were looking at Areal/RJ" instead of just
"no".
It stays an error. Faixa hangs off CepNotFoundException, never off Address,
and cepIni/cepFim are the extremes that actually exist in the municipality
rather than the range the Correios publish — echoing back the published
start would return the very CEP that just failed. The interval is sparse,
which is what ceps is for; it is not a list of alternatives.
Parsing is deliberately forgiving: a missing, empty or malformed block is
null. Losing a clean not-found to a parse error over the hint attached to
it would be a bad trade.
Diffstat (limited to 'lib/src/models.dart')
| -rw-r--r-- | lib/src/models.dart | 129 |
1 files changed, 126 insertions, 3 deletions
diff --git a/lib/src/models.dart b/lib/src/models.dart index 57a008f..378ebb3 100644 --- a/lib/src/models.dart +++ b/lib/src/models.dart @@ -12,9 +12,10 @@ /// come from the Correios database. /// /// Only the *casing* changes, where Dart demands it: the API's `raio_m` is -/// [Geo.raioM] and its `base_gerada_em` is [Health.baseGeradaEm]. Every other -/// field is a single lowercase word and is spelled exactly as the API spells -/// it. +/// [Geo.raioM], its `cep_ini` and `cep_fim` are [Faixa.cepIni] and +/// [Faixa.cepFim], and its `base_gerada_em` is [Health.baseGeradaEm]. Every +/// other field is a single lowercase word and is spelled exactly as the API +/// spells it. library; /// The CEP's coordinate, when the database has one. @@ -263,6 +264,128 @@ class Address { 'localidade: $localidade, uf: $uf, geo: $geo)'; } +/// The municipality a CEP that does not exist *would* belong to. +/// +/// This is what the API sends in the `faixa` block of a **404**, and it arrives +/// on `CepNotFoundException.faixa`. It is context for an error, never an +/// address: the CEP asked for still does not exist, and nothing here says +/// otherwise. +/// +/// It exists because a failing CEP is rarely random — it is usually someone who +/// knows the city and guessed the number, and the classic guess is the `-000`, +/// which 957 municipalities never received. Naming the municipality turns "no" +/// into "no, but you were looking at Areal/RJ". +/// +/// [cepIni] and [cepFim] are the smallest and the largest CEP that **actually +/// exist** in that municipality — not the bounds the Correios publish. Areal's +/// published range starts at `25845000`, which is exactly the CEP that just +/// failed; echoing it back would say nothing. +/// +/// **The range is sparse.** Areal's [ceps] is 118, spread over the ~9000 +/// numbers between [cepIni] and [cepFim]. The pair is not a list of valid CEPs +/// and iterating it would be wrong; [ceps] is there so the emptiness is +/// visible. +class Faixa { + /// Creates a municipality band from its parts. + const Faixa({ + required this.localidade, + required this.uf, + required this.estado, + required this.regiao, + required this.ibge, + required this.cepIni, + required this.cepFim, + required this.ceps, + }); + + /// Municipality that owns the band the CEP fell in. + final String localidade; + + /// Two-letter state code, such as `'RJ'`. + final String uf; + + /// State name in full. + final String estado; + + /// Region of the country, such as `'Sudeste'`. + final String regiao; + + /// IBGE municipality code — the key to join against public census data. + final String ibge; + + /// Smallest CEP that exists in the municipality, 8 digits. + /// + /// The API spells this field `cep_ini`; Dart's naming rules make it `cepIni` + /// here. It is an observed minimum, not the Correios' published start. + final String cepIni; + + /// Largest CEP that exists in the municipality, 8 digits. + /// + /// The API spells this field `cep_fim`; Dart's naming rules make it `cepFim` + /// here. It is an observed maximum, not the Correios' published end. + final String cepFim; + + /// How many CEPs exist between [cepIni] and [cepFim], inclusive. + /// + /// Almost always far fewer than the arithmetic distance between the two: the + /// band is sparse, and this is the number that says how sparse. + final int ceps; + + /// Builds a band from the API's `faixa` block, or returns `null`. + /// + /// A missing or malformed block yields `null` rather than throwing. The 404 + /// is the answer; losing it to a parse error over the hint attached to it + /// would be a bad trade. + /// + /// `localidade` is the gate: without a municipality name there is nothing to + /// tell the caller, so an empty one is the same as no block at all. + static Faixa? fromJson(Map<String, dynamic> data) { + final localidade = _text(data['localidade']); + if (localidade.isEmpty) { + return null; + } + return Faixa( + localidade: localidade, + uf: _text(data['uf']), + estado: _text(data['estado']), + regiao: _text(data['regiao']), + ibge: _text(data['ibge']), + cepIni: _text(data['cep_ini']), + cepFim: _text(data['cep_fim']), + ceps: _toInt(data['ceps']) ?? 0, + ); + } + + @override + bool operator ==(Object other) => + other is Faixa && + other.localidade == localidade && + other.uf == uf && + other.estado == estado && + other.regiao == regiao && + other.ibge == ibge && + other.cepIni == cepIni && + other.cepFim == cepFim && + other.ceps == ceps; + + @override + int get hashCode => Object.hash( + localidade, + uf, + estado, + regiao, + ibge, + cepIni, + cepFim, + ceps, + ); + + @override + String toString() => + 'Faixa(localidade: $localidade, uf: $uf, cepIni: $cepIni, ' + 'cepFim: $cepFim, ceps: $ceps)'; +} + /// The response of `GET /health`. /// /// Good for two things: checking that the API answers, and knowing **which |