diff options
Diffstat (limited to 'cepis.go')
| -rw-r--r-- | cepis.go | 103 |
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 +} |