Validando e consultando CPF em Go: pacote enxuto, context e testes table-driven

Transparência: sou fundador da CPFHub, uma das APIs citadas no final. O pacote de validação é independente. Go é ótimo para serviços de backend que recebem cadastro em alto volume. Vamos escrever um pacote cpf pequeno, sem dependências, e um cliente HTTP que respeita context, timeout e limites de taxa. 1. O pacote package cpf import "strings" // Normalize remove tudo que não for dígito ASCII. func Normalize(s string) string { var b strings.Builder for _, r := range s { if r >= '0' && r <= '9' { b.WriteRune(r) } } return b.String() } // Valid confere tamanho, sequência repetida e dígitos verificadores. func Valid(s string) bool { c := Normalize(s) if len(c) != 11 || strings.Count(c, c[:1]) == 11 { return false } for t := 9; t < 11; t++ { sum := 0 for i := 0; i < t; i++ { sum += int(c[i]-'0') * (t + 1 - i) } if int(c[t]-'0') != sum*10%11%10 { return false } } return true } // Format aplica a máscara 000.000.000-00. func Format(s string) string { c := Normalize(s) if len(c) != 11 { return s } return c[:3] + "." + c[3:6] + "." + c[6:9] + "-" + c[9:] } 2. Testes table-driven func TestValid(t *testing.T) { cases := []struct { in string want bool }{ {"529.982.247-25", true}, {"52998224725", true}, {"11111111111", false}, {"52998224724", false}, {"", false}, } for _, c := range cases { if got := Valid(c.in); got != c.want { t.Errorf("Valid(%q) = %v, want %v", c.in, got, c.want) } } } Vale adicionar um fuzz test (go test -fuzz) garantindo que Valid nunca entra em pânico com entrada arbitrária. 3. Cliente HTTP Depois que o DV passa, uma API externa confirma se o CPF existe. Um cliente idiomático: type Data struct { CPF string `json:"cpf"` Name string `json:"name"` Gender string `json:"gender"` BirthDate string `json:"birthDate"` } var ErrNotFound = errors.New("cpf não encontrado") type RateLimitError struct{ RetryAfter string } func (e RateLimitError) Error() string { return "limite por minuto; retry-after " + e.RetryAfter } type Client struct { HTTP *http.Client APIKey string } func (c *Client) Lookup(ctx context.Context, raw string) (*Data, error) { n := cpf.Normalize(raw) if !cpf.Valid(n) { return nil, errors.New("cpf inválido") } req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.cpfhub.io/cpf/"+n, nil) req.Header.Set("x-api-key", c.APIKey) res, err := c.HTTP.Do(req) if err != nil { return nil, err } defer res.Body.Close() switch res.StatusCode { case http.StatusOK: var body struct { Success bool `json:"success"` Data Data `json:"data"` } if err := json.NewDecoder(res.Body).Decode(&body); err != nil { return nil, err } return &body.Data, nil case http.StatusNotFound: return nil, ErrNotFound case http.StatusTooManyRequests: return nil, RateLimitError{res.Header.Get("Retry-After")} default: return nil, fmt.Errorf("status %d", res.StatusCode) } } Uso: c := &Client{HTTP: &http.Client{Timeout: 10 * time.Second}, APIKey: os.Getenv("CPFHUB_API_KEY")} ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() d, err := c.Lookup(ctx, "529.982.247-25") switch { case errors.Is(err, ErrNotFound): // trate como resultado normal case err != nil: var rl RateLimitError if errors.As(err, &rl) { /* reagende */ } } 4. Controlando a taxa do seu lado Toda API tem limite por minuto. Em vez de descobrir isso tomando 429, limite antes com golang.org/x/time/rate: lim := rate.NewLimiter(rate.Every(2*time.Second), 1) // 30/min for _, doc := range docs { if err := lim.Wait(ctx); err != nil { break } go process(doc) } Para listas grandes, prefira um endpoint de lote se o provedor tiver: uma requisição em vez de milhares. 5. Dicas Nunca logue o CPF completo; use Format com mascaramento (***.982.247-**) nos logs. Use errors.Is/As em vez de comparar strings de erro. Não reutilize http.DefaultClient: ele não tem timeout. Sobre a CPFHub O cliente acima já está apontado para a CPFHub: GET /cpf/{cpf} com x-api-key, ~150 ms típicos (não SLA), 404 sem custo, e um POST /cpf/bulk para listas de até 10.000 CPFs. O plano gratuito tem 50 créditos por mês. Mais na documentação.
Take Your Experience to the Next Level
NewDownload our mobile app for a faster and better experience.
Comments
0U
Join the discussion
Sign in to leave a comment