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
|
// Package cepis is a Go client for the public CEP API at https://cep.is.
// Official Correios data (DNE), refreshed every two weeks.
//
// For a script, the package-level functions are enough — they share one
// default [Client] with the default configuration:
//
// addr, err := cepis.Lookup(ctx, "01001-000")
// if err != nil {
// return err
// }
// fmt.Println(addr.Logradouro, addr.Localidade, addr.UF)
//
// In an application, build a client once and reuse it:
//
// client, err := cepis.New(
// cepis.WithTimeout(5*time.Second),
// cepis.WithTotalTimeout(15*time.Second),
// )
//
// Pointing at a local instance (the API repo brings one up with `just up`):
//
// client, err := cepis.New(cepis.WithBaseURL("http://localhost:8080"))
//
// Errors are split by the decision the caller has to make — whether retrying
// helps or not — and all of them match [Err]:
//
// switch {
// case errors.Is(err, cepis.ErrCEPNotFound):
// // the CEP does not exist: ask the user for another
// case errors.Is(err, cepis.Err):
// // network, server, rate limit: retry later
// }
//
// 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 access on the struct.
package cepis
import (
"context"
"strings"
)
var defaultClient = mustDefault()
func mustDefault() *Client {
client, err := New()
if err != nil {
panic("cepis: default client is not constructible: " + err.Error())
}
return client
}
// Default returns the client behind the package-level functions. It uses the
// default configuration and is safe to share across goroutines. It cannot be
// reconfigured on purpose: it applies process-wide, third-party libraries
// included. Anyone needing different settings should build their own with
// [New].
func Default() *Client { return defaultClient }
// Lookup resolves a CEP using the default client. See [Client.Lookup].
func Lookup(ctx context.Context, cep string) (*Address, error) {
return defaultClient.Lookup(ctx, cep)
}
// LookupMany resolves many CEPs in one batch using the default client. See
// [Client.LookupMany], especially the note that batches carry no coordinate.
func LookupMany(ctx context.Context, ceps []string) ([]*Address, error) {
return defaultClient.LookupMany(ctx, ceps)
}
// CheckHealth queries the API's health using the default client. See
// [Client.Health]. It is not called Health because that name belongs to the
// type it returns.
func CheckHealth(ctx context.Context) (*Health, error) {
return defaultClient.Health(ctx)
}
// NormalizeCEP returns the CEP's 8 digits, accepting the mask: "01001-000",
// "01001000" and " 01001-000 " all become "01001000".
//
// It fails with [ErrInvalidCEP] unless exactly 8 digits remain. Every lookup
// runs it before making a request: a 7-digit CEP is the caller's bug, and
// spending a round trip for the server to say so wastes both sides' time.
func NormalizeCEP(value string) (string, error) {
cleaned := strings.Map(func(r rune) rune {
switch r {
case '-', '.', ' ', '\t', '\n', '\r':
return -1
}
return r
}, value)
if len(cleaned) != 8 {
return "", newError(ErrInvalidCEP, "CEP must be 8 digits, got %q", value)
}
for _, r := range cleaned {
if r < '0' || r > '9' {
return "", newError(ErrInvalidCEP, "CEP must be 8 digits, got %q", value)
}
}
return cleaned, nil
}
|