summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DESCRIPTION9
-rw-r--r--NAMESPACE1
-rw-r--r--R/api.R16
-rw-r--r--R/conditions.R30
-rw-r--r--R/http.R14
-rw-r--r--R/models.R102
-rw-r--r--README.md57
-rw-r--r--man/cep_lookup.Rd5
-rw-r--r--man/cepis_conditions.Rd27
-rw-r--r--man/cepis_faixa.Rd53
-rw-r--r--tests/helper/utils.R11
-rw-r--r--tests/test-lookup.R99
12 files changed, 403 insertions, 21 deletions
diff --git a/DESCRIPTION b/DESCRIPTION
index c6eb473..801e741 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,6 +1,6 @@
Package: cepis
Title: Client for the Public CEP API at cep.is
-Version: 0.1.0
+Version: 0.2.0
Authors@R:
person("Murilo", "Ijanc", email = "[email protected]",
role = c("aut", "cre"))
@@ -8,9 +8,10 @@ Description: Looks up Brazilian postal codes (CEP) against the public API
at <https://cep.is>, which serves official Correios data (DNE) refreshed
every two weeks. Resolves a single CEP, batches of up to 500 per request
with the order preserved, the coordinate the database has for about 57%
- of them, and the API's health. Retries transient failures with
- exponentially backed-off, jittered waits and never retries a definitive
- answer.
+ of them, and the API's health. A CEP that does not exist reports the
+ municipality whose range it fell into, when it fell into one. Retries
+ transient failures with exponentially backed-off, jittered waits and
+ never retries a definitive answer.
License: file LICENSE
URL: https://cep.is
Encoding: UTF-8
diff --git a/NAMESPACE b/NAMESPACE
index 406e52a..5c54515 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -2,6 +2,7 @@
S3method(print,cepis_address)
S3method(print,cepis_client)
+S3method(print,cepis_faixa)
S3method(print,cepis_geo)
S3method(print,cepis_health)
export(cep_health)
diff --git a/R/api.R b/R/api.R
index 0d13c67..895c52a 100644
--- a/R/api.R
+++ b/R/api.R
@@ -13,7 +13,10 @@
#' @section Conditions:
#' \describe{
#' \item{`cepis_invalid_cep`}{the CEP is not 8 digits. No request is made.}
-#' \item{`cepis_not_found`}{well-formed, but not in the database.}
+#' \item{`cepis_not_found`}{well-formed, but not in the database. Carries
+#' `faixa`, a [cepis_faixa] naming the municipality whose range the CEP
+#' fell into, when it fell into one. Still a not-found: the CEP does not
+#' exist, and a `faixa` is not an address.}
#' \item{`cepis_rate_limited`}{429 on every attempt.}
#' \item{`cepis_server_error`}{5xx on every attempt.}
#' \item{`cepis_network_error`}{the request never completed.}
@@ -38,10 +41,13 @@ cep_lookup <- function(cep, client = default_client()) {
response <- cepis_fetch(client, paste0("/", number))
if (response$status == 404L) {
- stop(not_found_error(
- paste0("CEP ", number, " does not exist in the DNE database"),
- cep = number
- ))
+ faixa <- new_faixa(json_field(response$body, "faixa"))
+ message <- paste0("CEP ", number, " does not exist in the DNE database")
+ if (!is.null(faixa)) {
+ message <- paste0(message, "; the range it falls in belongs to ",
+ faixa$localidade, "/", faixa$uf)
+ }
+ stop(not_found_error(message, cep = number, faixa = faixa))
}
if (response$status == 400L) {
stop(invalid_cep_error(
diff --git a/R/conditions.R b/R/conditions.R
index 1eebe5b..7cdb45b 100644
--- a/R/conditions.R
+++ b/R/conditions.R
@@ -38,6 +38,32 @@
#' proxy, an HTML error page. Useful as a diagnosis: the problem is not in
#' your code.
#'
+#' @section What cepis_not_found carries:
+#' `cep`, the 8 digits as they went to the API, and `faixa`: a [cepis_faixa]
+#' naming the municipality that owns the band of numbers the CEP fell into,
+#' or `NULL` when it fell in a band nobody owns. The API omits it, it never
+#' sends null, so `NULL` here is the same statement.
+#'
+#' It changes nothing about the answer — the CEP still does not exist, and a
+#' `faixa` is not an address — but it is usually the difference between
+#' telling someone "invalid CEP" and telling them "no such CEP in Areal/RJ":
+#'
+#' ```r
+#' tryCatch(
+#' cep_lookup("25845000"),
+#' cepis_not_found = function(e) {
+#' if (is.null(e$faixa)) {
+#' "no such CEP"
+#' } else {
+#' paste0("no such CEP in ", e$faixa$localidade, "/", e$faixa$uf)
+#' }
+#' }
+#' )
+#' ```
+#'
+#' The CEP that failed above is the DNE's own first number for Areal, and it
+#' is the shape of guess this field is for.
+#'
#' @name cepis_conditions
NULL
@@ -52,8 +78,8 @@ invalid_cep_error <- function(message, value = NULL) {
cepis_condition("cepis_invalid_cep", message, value = value)
}
-not_found_error <- function(message, cep = NULL) {
- cepis_condition("cepis_not_found", message, cep = cep)
+not_found_error <- function(message, cep = NULL, faixa = NULL) {
+ cepis_condition("cepis_not_found", message, cep = cep, faixa = faixa)
}
rate_limited_error <- function(message, retry_after = NULL) {
diff --git a/R/http.R b/R/http.R
index 6ef4511..08e8ddc 100644
--- a/R/http.R
+++ b/R/http.R
@@ -173,12 +173,22 @@ json_object <- function(body) {
data
}
-api_message <- function(body, fallback) {
+# The counterpart of json_object() for a body that is already an error: it
+# never signals, because a broken error body must not replace the error the
+# caller is being told about. Missing, malformed, empty: all of them NULL.
+json_field <- function(body, name) {
data <- tryCatch(
jsonlite::parse_json(decode_body(body), simplifyVector = FALSE),
error = function(e) NULL
)
- message <- if (is.list(data)) data[["mensagem"]] else NULL
+ if (!is.list(data) || is.null(names(data))) {
+ return(NULL)
+ }
+ data[[name]]
+}
+
+api_message <- function(body, fallback) {
+ message <- json_field(body, "mensagem")
if (is.character(message) && length(message) == 1L && nzchar(message)) {
message
} else {
diff --git a/R/models.R b/R/models.R
index 076db48..5d90898 100644
--- a/R/models.R
+++ b/R/models.R
@@ -62,6 +62,53 @@ NULL
#' @name cepis_geo
NULL
+#' The municipality a CEP that does not exist would have belonged to
+#'
+#' @description
+#' The `faixa` field of a [cepis_not_found][cepis_conditions] condition, a
+#' plain list of class `cepis_faixa`. It is **context for an error, not an
+#' address**: the CEP asked for does not exist, and nothing here says
+#' otherwise. What it does say is which municipality owns the band of numbers
+#' the CEP fell into.
+#'
+#' It exists because a CEP that fails is rarely random — it is usually
+#' someone who knows the city and guessed the number, and the classic guess is
+#' the `-000` that 957 municipalities never received.
+#'
+#' @section Fields:
+#' \describe{
+#' \item{`localidade`, `uf`, `estado`, `regiao`}{municipality, state code,
+#' state name, region — the same names, and the same meaning, as in a
+#' [cepis_address].}
+#' \item{`ibge`}{the municipality's IBGE code, so the answer can be joined
+#' to other data without matching on the name.}
+#' \item{`cep_ini`, `cep_fim`}{the smallest and the largest CEP that
+#' **actually exists** in that municipality. Not the range the Correios
+#' publish: the DNE's own start for Areal/RJ is `25845000`, which is
+#' precisely the CEP that just failed.}
+#' \item{`ceps`}{how many CEPs the municipality really has.}
+#' }
+#'
+#' @section The range is sparse:
+#' Areal's 118 CEPs are scattered over the ~4900 numbers between `25845001`
+#' and `25849899`. `cep_ini` and `cep_fim` are two endpoints that exist, not a
+#' promise about anything in between — never present the pair as a list of
+#' valid CEPs, and never iterate from one to the other. `ceps` is there so the
+#' emptiness of the interval is visible: 118 out of 4899.
+#'
+#' @section When there is no faixa:
+#' `NULL`, and not a list of empty strings, when the CEP falls in a band no
+#' municipality owns. The API **omits** the field rather than sending null,
+#' and this package keeps that distinction intact: `is.null(x$faixa)` means
+#' the number is close to nothing, not that the fields came back blank.
+#'
+#' Only [cep_lookup()] reports it. In [cep_lookup_many()] a CEP that does not
+#' exist is still `NULL` at its position: the endpoint behind a batch is CSV
+#' and says nothing about ranges.
+#'
+#' @name cepis_faixa
+NULL
+
#' The answer of `GET /health`
#'
#' @description
@@ -96,6 +143,15 @@ text_field <- function(data, name) {
as.character(value)
}
+count_field <- function(data, name) {
+ value <- data[[name]]
+ if (!is.numeric(value) && !is.character(value)) {
+ return(0L)
+ }
+ value <- suppressWarnings(as.numeric(value))
+ if (length(value) != 1L || is.na(value)) 0L else as.integer(value)
+}
+
new_geo <- function(block) {
lat <- suppressWarnings(as.numeric(block[["lat"]]))
lon <- suppressWarnings(as.numeric(block[["lon"]]))
@@ -121,6 +177,35 @@ new_geo <- function(block) {
)
}
+# Never signals. A faixa is decoration on a 404, and a malformed one must not
+# turn a clean "this CEP does not exist" into a parse failure: anything that
+# is not an object naming a municipality is simply no faixa at all.
+new_faixa <- function(block) {
+ if (!is.list(block) || is.null(names(block))) {
+ return(NULL)
+ }
+
+ localidade <- text_field(block, "localidade")
+ uf <- text_field(block, "uf")
+ if (!nzchar(localidade) || !nzchar(uf)) {
+ return(NULL)
+ }
+
+ structure(
+ list(
+ localidade = localidade,
+ uf = uf,
+ estado = text_field(block, "estado"),
+ regiao = text_field(block, "regiao"),
+ ibge = text_field(block, "ibge"),
+ cep_ini = text_field(block, "cep_ini"),
+ cep_fim = text_field(block, "cep_fim"),
+ ceps = count_field(block, "ceps")
+ ),
+ class = "cepis_faixa"
+ )
+}
+
new_address <- function(data) {
geo <- NULL
block <- data[["geo"]]
@@ -135,10 +220,6 @@ new_address <- function(data) {
}
new_health <- function(data) {
- count <- function(name) {
- value <- suppressWarnings(as.numeric(data[[name]]))
- if (length(value) != 1L || is.na(value)) 0L else as.integer(value)
- }
status <- text_field(data, "status")
structure(
@@ -146,8 +227,8 @@ new_health <- function(data) {
status = status,
ok = identical(status, "ok"),
dne = text_field(data, "dne"),
- ceps = count("ceps"),
- geo = count("geo"),
+ ceps = count_field(data, "ceps"),
+ geo = count_field(data, "geo"),
base_gerada_em = text_field(data, "base_gerada_em")
),
class = "cepis_health"
@@ -204,6 +285,15 @@ print.cepis_geo <- function(x, ...) {
}
#' @export
+print.cepis_faixa <- function(x, ...) {
+ cat("<cepis_faixa> ", x$localidade, "/", x$uf, " (IBGE ", x$ibge, ")\n",
+ sep = "")
+ cat(" ", x$ceps, " CEP(s), the first ", formatted_cep(x$cep_ini),
+ " and the last ", formatted_cep(x$cep_fim), "\n", sep = "")
+ invisible(x)
+}
+
+#' @export
print.cepis_health <- function(x, ...) {
cat("<cepis_health> ", x$status, "\n", sep = "")
cat(" dne: ", x$dne, " (generated ", x$base_gerada_em, ")\n", sep = "")
diff --git a/README.md b/README.md
index 64baf53..521aa42 100644
--- a/README.md
+++ b/README.md
@@ -167,7 +167,7 @@ Failures are classed conditions. Everything inherits `cepis_error` (and
| class | when | does retrying help? |
| --- | --- | --- |
| `cepis_invalid_cep` | not 8 digits | no |
-| `cepis_not_found` | well-formed, not in the database | no |
+| `cepis_not_found` | well-formed, not in the database; carries `faixa` | no |
| `cepis_rate_limited` | 429; carries `retry_after` | yes, later |
| `cepis_server_error` | 5xx; carries `status` | yes, later |
| `cepis_network_error` | DNS, connection, timeout, TLS | yes, later |
@@ -185,6 +185,61 @@ tryCatch(
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.
+### Where a CEP that does not exist would have been
+
+A CEP that fails is rarely random: it is usually someone who knows the city
+and guessed the number, and the classic guess is the `-000` that 957
+municipalities never received. When the number falls inside the range of a
+municipality, `cepis_not_found` carries a `faixa` naming it:
+
+```r
+tryCatch(
+ cep_lookup("25845000"),
+ cepis_not_found = function(e) {
+ if (is.null(e$faixa)) {
+ "no such CEP"
+ } else {
+ paste0("no such CEP in ", e$faixa$localidade, "/", e$faixa$uf)
+ }
+ }
+)
+#> "no such CEP in Areal/RJ"
+```
+
+The lookup **still failed**, and a `faixa` is not an address — it is context
+for the error, and answers "did you mean somewhere in Areal?" instead of
+"invalid CEP". The condition is the same `cepis_not_found` as before; code
+that ignores `faixa` keeps working.
+
+```r
+f <- e$faixa
+
+f$localidade # "Areal"
+f$uf # "RJ"
+f$estado # "Rio de Janeiro"
+f$regiao # "Sudeste"
+f$ibge # "3300225"
+f$cep_ini # "25845001"
+f$cep_fim # "25849899"
+f$ceps # 118
+```
+
+Two things about `cep_ini` and `cep_fim`:
+
+- They are the smallest and the largest CEP that **actually exist** there,
+ not the range the Correios publish. The DNE's own start for Areal is
+ `25845000` — exactly the CEP that just failed above.
+- **The range is sparse.** Areal's 118 CEPs are scattered over the ~4900
+ numbers between the two endpoints. `ceps` is there so that is visible:
+ never present the pair as a list of valid CEPs, and never iterate from one
+ to the other.
+
+`faixa` is `NULL` when the CEP falls in a band no municipality owns. The API
+omits the field rather than sending null, so `is.null(e$faixa)` says the
+number is close to nothing, not that the fields came back blank. Batches do
+not report it either: `cep_lookup_many()` still gives `NULL` at that
+position, because the endpoint behind it is CSV.
+
## Development
Everything runs through `make`:
diff --git a/man/cep_lookup.Rd b/man/cep_lookup.Rd
index 3c22a1e..f6f2d0b 100644
--- a/man/cep_lookup.Rd
+++ b/man/cep_lookup.Rd
@@ -23,7 +23,10 @@ has for it, including the coordinate when there is one.
\describe{
\item{\code{cepis_invalid_cep}}{the CEP is not 8 digits. No request is made.}
-\item{\code{cepis_not_found}}{well-formed, but not in the database.}
+\item{\code{cepis_not_found}}{well-formed, but not in the database. Carries
+\code{faixa}, a \link{cepis_faixa} naming the municipality whose range the CEP
+fell into, when it fell into one. Still a not-found: the CEP does not
+exist, and a \code{faixa} is not an address.}
\item{\code{cepis_rate_limited}}{429 on every attempt.}
\item{\code{cepis_server_error}}{5xx on every attempt.}
\item{\code{cepis_network_error}}{the request never completed.}
diff --git a/man/cepis_conditions.Rd b/man/cepis_conditions.Rd
index 4def4ff..9b25cbe 100644
--- a/man/cepis_conditions.Rd
+++ b/man/cepis_conditions.Rd
@@ -40,3 +40,30 @@ 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.
}
+\section{What cepis_not_found carries}{
+
+\code{cep}, the 8 digits as they went to the API, and \code{faixa}: a \link{cepis_faixa}
+naming the municipality that owns the band of numbers the CEP fell into,
+or \code{NULL} when it fell in a band nobody owns. The API omits it, it never
+sends null, so \code{NULL} here is the same statement.
+
+It changes nothing about the answer — the CEP still does not exist, and a
+\code{faixa} is not an address — but it is usually the difference between
+telling someone "invalid CEP" and telling them "no such CEP in Areal/RJ":
+
+\if{html}{\out{<div class="sourceCode r">}}\preformatted{tryCatch(
+ cep_lookup("25845000"),
+ cepis_not_found = function(e) \{
+ if (is.null(e$faixa)) \{
+ "no such CEP"
+ \} else \{
+ paste0("no such CEP in ", e$faixa$localidade, "/", e$faixa$uf)
+ \}
+ \}
+)
+}\if{html}{\out{</div>}}
+
+The CEP that failed above is the DNE's own first number for Areal, and it
+is the shape of guess this field is for.
+}
+
diff --git a/man/cepis_faixa.Rd b/man/cepis_faixa.Rd
new file mode 100644
index 0000000..5d3f7ff
--- /dev/null
+++ b/man/cepis_faixa.Rd
@@ -0,0 +1,53 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/models.R
+\name{cepis_faixa}
+\alias{cepis_faixa}
+\title{The municipality a CEP that does not exist would have belonged to}
+\description{
+The \code{faixa} field of a \link[=cepis_conditions]{cepis_not_found} condition, a
+plain list of class \code{cepis_faixa}. It is \strong{context for an error, not an
+address}: the CEP asked for does not exist, and nothing here says
+otherwise. What it does say is which municipality owns the band of numbers
+the CEP fell into.
+
+It exists because a CEP that fails is rarely random — it is usually
+someone who knows the city and guessed the number, and the classic guess is
+the \code{-000} that 957 municipalities never received.
+}
+\section{Fields}{
+
+\describe{
+\item{\code{localidade}, \code{uf}, \code{estado}, \code{regiao}}{municipality, state code,
+state name, region — the same names, and the same meaning, as in a
+\link{cepis_address}.}
+\item{\code{ibge}}{the municipality's IBGE code, so the answer can be joined
+to other data without matching on the name.}
+\item{\code{cep_ini}, \code{cep_fim}}{the smallest and the largest CEP that
+\strong{actually exists} in that municipality. Not the range the Correios
+publish: the DNE's own start for Areal/RJ is \code{25845000}, which is
+precisely the CEP that just failed.}
+\item{\code{ceps}}{how many CEPs the municipality really has.}
+}
+}
+
+\section{The range is sparse}{
+
+Areal's 118 CEPs are scattered over the ~4900 numbers between \code{25845001}
+and \code{25849899}. \code{cep_ini} and \code{cep_fim} are two endpoints that exist, not a
+promise about anything in between — never present the pair as a list of
+valid CEPs, and never iterate from one to the other. \code{ceps} is there so the
+emptiness of the interval is visible: 118 out of 4899.
+}
+
+\section{When there is no faixa}{
+
+\code{NULL}, and not a list of empty strings, when the CEP falls in a band no
+municipality owns. The API \strong{omits} the field rather than sending null,
+and this package keeps that distinction intact: \code{is.null(x$faixa)} means
+the number is close to nothing, not that the fields came back blank.
+
+Only \code{\link[=cep_lookup]{cep_lookup()}} reports it. In \code{\link[=cep_lookup_many]{cep_lookup_many()}} a CEP that does not
+exist is still \code{NULL} at its position: the endpoint behind a batch is CSV
+and says nothing about ranges.
+}
+
diff --git a/tests/helper/utils.R b/tests/helper/utils.R
index 3c24a2f..94a15ff 100644
--- a/tests/helper/utils.R
+++ b/tests/helper/utils.R
@@ -16,6 +16,17 @@ SE_ADDRESS <- list(
siafi = "7107"
)
+AREAL_FAIXA <- list(
+ localidade = "Areal",
+ uf = "RJ",
+ estado = "Rio de Janeiro",
+ regiao = "Sudeste",
+ ibge = "3300225",
+ cep_ini = "25845001",
+ cep_fim = "25849899",
+ ceps = 118L
+)
+
CSV_HEADER <- paste0(
"cep,logradouro,complemento,unidade,bairro,localidade,uf,estado,regiao,",
"ibge,gia,ddd,siafi"
diff --git a/tests/test-lookup.R b/tests/test-lookup.R
index 0223f4d..3ff4646 100644
--- a/tests/test-lookup.R
+++ b/tests/test-lookup.R
@@ -82,6 +82,105 @@ with_server(
}
)
+not_found_body <- function(faixa = NULL) {
+ body <- list(
+ erro = "cep_nao_encontrado",
+ mensagem = "CEP n\u00e3o existe na base do DNE"
+ )
+ if (!is.null(faixa)) {
+ body$faixa <- faixa
+ }
+ json(body)
+}
+
+# 25845000 is the DNE's own first number for Areal, and one of the 957 that
+# were never issued: the guess the faixa exists to answer.
+with_server(
+ list(route("/25845000", response(404L, not_found_body(AREAL_FAIXA)))),
+ function(client, server) {
+ condition <- check_condition(
+ "a CEP inside a municipality's range is still not found",
+ cep_lookup("25845000", client),
+ "cepis_not_found"
+ )
+ faixa <- condition$faixa
+ check("the condition carries the faixa", inherits(faixa, "cepis_faixa"))
+ check("the faixa names the municipality", identical(faixa$localidade, "Areal"))
+ check("the faixa carries the state", identical(faixa$uf, "RJ"))
+ check("the faixa carries the IBGE code", identical(faixa$ibge, "3300225"))
+ check("the faixa counts the CEPs that exist", identical(faixa$ceps, 118L))
+ check(
+ "the bounds are the CEPs that exist, unmasked",
+ identical(c(faixa$cep_ini, faixa$cep_fim), c("25845001", "25849899"))
+ )
+ check(
+ "the message names the municipality",
+ grepl("Areal/RJ", conditionMessage(condition), fixed = TRUE)
+ )
+ check("the faixa prints", length(capture.output(print(faixa))) == 2L)
+ check("a 404 with a faixa is not retried either", calls(server, "/25845000") == 1L)
+ }
+)
+
+with_server(
+ list(route("/99999999", response(404L, not_found_body()))),
+ function(client, server) {
+ condition <- check_condition(
+ "a CEP in a band nobody owns is not found too",
+ cep_lookup("99999999", client),
+ "cepis_not_found"
+ )
+ check("an omitted faixa is NULL", is.null(condition$faixa))
+ check("the CEP is still carried", identical(condition$cep, "99999999"))
+ check(
+ "the message claims no range",
+ !grepl("belongs to", conditionMessage(condition), fixed = TRUE)
+ )
+ }
+)
+
+# A faixa is decoration on an error. None of these may cost the caller the
+# error itself, which is the only thing it asked for.
+unusable <- list(
+ "a null faixa" = "{\"erro\":\"x\",\"faixa\":null}",
+ "an empty faixa" = "{\"erro\":\"x\",\"faixa\":{}}",
+ "a faixa that is an array" = "{\"erro\":\"x\",\"faixa\":[]}",
+ "a faixa that is a string" = "{\"erro\":\"x\",\"faixa\":\"Areal\"}",
+ "a faixa with no municipality" = "{\"erro\":\"x\",\"faixa\":{\"uf\":\"RJ\"}}",
+ "a faixa with an empty municipality" =
+ "{\"erro\":\"x\",\"faixa\":{\"localidade\":\"\",\"uf\":\"RJ\"}}",
+ "a truncated body" = "{\"erro\":\"cep_nao",
+ "a body that is not JSON at all" = "<html>captive portal</html>",
+ "an empty body" = ""
+)
+
+for (label in names(unusable)) {
+ with_server(
+ list(route("/99999999", response(404L, unusable[[label]]))),
+ function(client, server) {
+ condition <- check_condition(
+ paste0(label, " is still a clean cepis_not_found"),
+ cep_lookup("99999999", client),
+ "cepis_not_found"
+ )
+ check(paste0(label, " yields no faixa"), is.null(condition$faixa))
+ }
+ )
+}
+
+odd_count <- c(AREAL_FAIXA[names(AREAL_FAIXA) != "ceps"], list(ceps = "many"))
+with_server(
+ list(route("/25845000", response(404L, not_found_body(odd_count)))),
+ function(client, server) {
+ condition <- caught(cep_lookup("25845000", client))
+ check(
+ "an unreadable count does not lose the municipality",
+ identical(condition$faixa$localidade, "Areal")
+ )
+ check("an unreadable count reads as zero", identical(condition$faixa$ceps, 0L))
+ }
+)
+
with_server(
list(route("/01001000", response(200L, json(SE_ADDRESS)))),
function(client, server) {