summaryrefslogtreecommitdiffstats
path: root/cepis.go
diff options
context:
space:
mode:
Diffstat (limited to 'cepis.go')
-rw-r--r--cepis.go103
1 files changed, 103 insertions, 0 deletions
diff --git a/cepis.go b/cepis.go
new file mode 100644
index 0000000..b596c12
--- /dev/null
+++ b/cepis.go
@@ -0,0 +1,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
+}