summaryrefslogtreecommitdiffstats
path: root/cepis.lua
blob: 1027db3bc48b1f77f4e0bcdfd5565a8d2cf39764 (plain)
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
--- Lua client for the public CEP API at [cep.is](https://cep.is).
--
-- Quick use — for a script, the module-level functions are enough:
--
--     local cepis = require("cepis")
--
--     local address, err = cepis.lookup("01001-000")
--     print(address.localidade, address.uf)  --> São Paulo  SP
--
-- In an application — build a client once and reuse it:
--
--     local client = cepis.new({ timeout = 5, total_timeout = 15 })
--     local address, err = client:lookup("01001000")
--
-- Pointing at a local instance (the API repo brings one up with `just up`):
--
--     local client = cepis.new({ base_url = "http://localhost:8080" })
--
-- Expected failures are returned, never thrown, and every error carries a
-- `kind` so the caller branches on one string instead of matching messages:
--
--     local address, err = cepis.lookup(cep_from_form)
--     if not address then
--       if err.kind == cepis.NOT_FOUND then
--         -- the CEP does not exist: ask the user for another
--       elseif err:is_retriable() then
--         -- network, server, rate limit: reschedule
--       end
--     end
--
-- `error()` is reserved for programmer mistakes — a bad option table, a wrong
-- argument type. Those are bugs to fix, not conditions to handle.
--
-- Note on field names: the API surface is English, but the **data fields keep
-- the API's own Portuguese names** (`logradouro`, `bairro`, `localidade`).
-- What you read in the JSON is what you index on the table.
--
-- The module-level functions share one default client, built on first use.
-- It applies process-wide, including to any other library that also requires
-- this module. Anyone needing different settings should build their own.
--
-- @module cepis

local client = require("cepis.client")
local errors = require("cepis.errors")
local models = require("cepis.models")

local M = {}

--- Library version.
M._VERSION = client.VERSION

--- Where the API lives unless `base_url` says otherwise: `https://cep.is`.
M.DEFAULT_URL = client.DEFAULT_URL

--- How many CEPs the batch endpoint accepts in one request: `500`.
M.MAX_BATCH = client.MAX_BATCH

--- The `Client` class. `cepis.new` is the shorthand for `Client.new`.
M.Client = client.Client

--- The `Address` metatable, for `getmetatable(x) == cepis.Address`.
M.Address = models.Address

--- The `Geo` metatable.
M.Geo = models.Geo

--- The `Faixa` metatable, for the `faixa` a `not_found` error may carry.
M.Faixa = models.Faixa

--- The `Health` metatable.
M.Health = models.Health

--- Error kind: the CEP is not 8 digits. No request was made.
M.INVALID_CEP = errors.INVALID_CEP

--- Error kind: well-formed, but absent from the DNE database.
--
-- `err.faixa` may name the municipality the CEP would have belonged to.
M.NOT_FOUND = errors.NOT_FOUND

--- Error kind: 429 on every attempt. `err.retry_after` may hold seconds.
M.RATE_LIMITED = errors.RATE_LIMITED

--- Error kind: 5xx on every attempt. `err.status` holds the last one seen.
M.SERVER_ERROR = errors.SERVER_ERROR

--- Error kind: the request never completed — DNS, connection, timeout, TLS.
M.NETWORK_ERROR = errors.NETWORK_ERROR

--- Error kind: a success status with a body that cannot be parsed.
M.INVALID_RESPONSE = errors.INVALID_RESPONSE

--- Every error kind, keyed by itself.
M.kinds = errors.kinds

--- Whether a value is one of this library's errors.
-- @param value anything
-- @treturn boolean
M.is_error = errors.is

--- Build a client. See `cepis.client.Client.new` for every option.
-- @tparam[opt] table opts
-- @treturn Client
M.new = client.Client.new

--- Return the CEP's 8 digits, accepting the mask.
-- @tparam string value
-- @treturn[1] string
-- @treturn[2] nil
-- @treturn[2] table the error
M.normalize_cep = client.normalize_cep

local default

--- The shared client behind the module-level functions.
--
-- Built on first use with every default. Do not mutate it: it applies
-- process-wide.
-- @treturn Client
function M.default()
  if not default then
    default = client.Client.new()
  end
  return default
end

--- Look up a CEP using the default configuration.
--
-- Shorthand for `cepis.default():lookup(cep)`.
-- @tparam string cep masked or unmasked
-- @treturn[1] Address
-- @treturn[2] nil
-- @treturn[2] table the error
function M.lookup(cep)
  return M.default():lookup(cep)
end

--- Look up many CEPs in one batch, using the default configuration.
--
-- Shorthand for `cepis.default():lookup_many(ceps)` — read that one, in
-- particular why a missing CEP is `false` and not `nil`, and the warning that
-- batches carry no coordinate.
-- @tparam table ceps list of strings
-- @treturn[1] table list of `Address|false`
-- @treturn[2] nil
-- @treturn[2] table the error
function M.lookup_many(ceps)
  return M.default():lookup_many(ceps)
end

--- Query the API's health using the default configuration.
--
-- Shorthand for `cepis.default():health()`.
-- @treturn[1] Health
-- @treturn[2] nil
-- @treturn[2] table the error
function M.health()
  return M.default():health()
end

return M