summaryrefslogtreecommitdiffstats
path: root/lib/src/models.dart
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-27 20:25:04 -0300
committerMurilo Ijanc <[email protected]>2026-07-27 20:53:07 -0300
commit82d92ed4291663bae94014b4363064dbb26c6a51 (patch)
tree69d91a59c955f1e6ec6fc0007324b5d73b3f0d01 /lib/src/models.dart
parent8e719fc715eb7ae9aa1c842e633f15b5c6cd3688 (diff)
downloaddart-0.2.0.tar.gz
Name the municipality behind a CEP that does not existHEADv0.2.0main
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.dart129
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