1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
#' Conditions signalled by cepis
#'
#' @description
#' Every failure raised by this package is a classed condition inheriting
#' from `cepis_error`, so one handler catches all of them:
#'
#' ```r
#' tryCatch(
#' cep_lookup(cep_from_form),
#' cepis_not_found = function(e) NULL,
#' cepis_error = function(e) reschedule()
#' )
#' ```
#'
#' Below `cepis_error` the split is by **the decision you have to make**, not
#' by the HTTP status that caused it:
#'
#' | class | when | does retrying help? |
#' | --- | --- | --- |
#' | `cepis_invalid_cep` | not 8 digits | no |
#' | `cepis_not_found` | well-formed, not in the database | no |
#' | `cepis_rate_limited` | 429 | yes, later |
#' | `cepis_server_error` | 5xx | yes, later |
#' | `cepis_network_error` | DNS, connection, timeout, TLS | yes, later |
#' | `cepis_invalid_response` | unintelligible response | probably not |
#'
#' That split is why `cepis_not_found` is not "the HTTP 404 condition": it is
#' also signalled with no request made at all, when the CEP is malformed
#' before it ever leaves the machine.
#'
#' Each condition carries the fields a caller needs to act on it:
#' `cepis_rate_limited` has `retry_after` (seconds the server asked for, or
#' `NULL`), `cepis_server_error` has `status`, `cepis_network_error` has
#' `url`, and `cepis_invalid_cep` has `value`, the input as it arrived.
#'
#' `cepis_invalid_response` almost always means something between you and the
#' 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:
#' `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
cepis_condition <- function(class, message, ...) {
structure(
list(message = message, call = NULL, ...),
class = c(class, "cepis_error", "error", "condition")
)
}
invalid_cep_error <- function(message, value = NULL) {
cepis_condition("cepis_invalid_cep", message, value = value)
}
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) {
cepis_condition("cepis_rate_limited", message, retry_after = retry_after)
}
server_error <- function(message, status) {
cepis_condition("cepis_server_error", message, status = status)
}
network_error <- function(message, url) {
cepis_condition("cepis_network_error", message, url = url)
}
invalid_response_error <- function(message) {
cepis_condition("cepis_invalid_response", message)
}
|