Skip to content
This repository was archived by the owner on Oct 12, 2026. It is now read-only.

feat: add PIS, voter ID, plate, combined document and PIX key - #6

Merged
jeferson0306 merged 1 commit into
masterfrom
feat/document-catalogue
Sep 4, 2026
Merged

jeferson0306 merged 1 commit into
masterfrom
feat/document-catalogue

Conversation

@jeferson0306

Copy link
Copy Markdown
Collaborator

Five new validators. They were chosen on one criterion: each has a single canonical algorithm that could be checked against a reference implementation, rather than recalled and hoped for. The ones that failed that test are listed at the bottom.

Parameter Accepts
pis PIS / PASEP / NIT / NIS — one number under four names
titulo Voter registration number
placa Vehicle plate, old and Mercosul patterns
documento Whichever of CPF or CNPJ the digit count describes
pix PIX key in all five forms: CPF, CNPJ, email, E.164 phone, random UUID

Structure

The check-digit arithmetic moves into utils/checkdigit.go. CPF, CNPJ and PIS share one rule — a weighted sum modulo 11, with the two remainders that would need two characters folded onto zero — so it now lives once rather than being copied into each new document along with its chance of getting the edge case wrong.

The voter ID is the interesting one

It does not share that rule, and my first implementation of it in this branch got three separate things wrong:

  1. Its check digits are the remainder itself, not eleven minus it.
  2. São Paulo (01) and Minas Gerais (02) outgrew the eight-digit sequence, so their numbers carry thirteen digits. The state code therefore has to be located from the end, never at a fixed offset — which the first version did.
  3. For those same two states, a remainder of zero yields 1, not 0.

All three were caught by checking against brazilian-utils before writing the tests, not after. It is the reason this PR ships five validators instead of ten.

Design notes

documento infers CPF or CNPJ from the digit count. Forms offer a single document field; making the caller decide which endpoint to call defeats the point of that field.

The PIX key infers its form for the same reason — a key arrives pasted into one box with nothing saying what kind it is. A phone key must carry +55: a bare national number is ambiguous, and the arrangement does not accept it.

Every new parameter joins validationCount. Left out, each would have been a way to smuggle a second validation past the one-per-request guard.

Verification

Three layers, because "I am fairly sure of the algorithm" is not a test:

  1. Fixed vectors published by brazilian-utils — 12056874107 and 12056412847 valid, 12056412547 and 12081636639 invalid.
  2. A differential run of 10,000 generated numbers against reference implementations transcribed flat into the test file — no shared helpers, no reuse of the code under test, so it compares two independent expressions of the same specification. The seed is fixed, so a failure is reproducible.
  3. 500 constructed-valid PIS numbers, because random input is almost never valid and a validator that returned false unconditionally would sail through a differential test otherwise.

Plus the usual table tests for plate, document and PIX, and handler tests covering the routes and the parameter collisions. go test ./... -race green; utils coverage 72% → 81%.

Deliberately not in this PR

CNH, Renavam, inscrição estadual and boleto.

CNH and Renavam have multiple published algorithm variants and I could not settle which is authoritative from the sources available. Inscrição estadual is twenty-seven different algorithms and deserves its own change. The boleto's linha digitável is well specified by FEBRABAN but mixes mod-10 and mod-11 across two different layouts, and it is large enough to review on its own.

Shipping a validator that is probably right is worse than not shipping it: a wrong answer from a validation service is more damaging than no answer, because the caller stops checking.

Five validators, chosen because each has a single canonical algorithm that
could be checked against a reference rather than recalled.

The check-digit arithmetic moves into utils/checkdigit.go. CPF, CNPJ and PIS
share the same rule — a weighted sum modulo 11, with the two remainders that
would need two characters folded onto zero — so it lives once instead of being
copied into each new document along with its chance of getting the edge case
wrong.

The voter ID does not share it, and that is the interesting one. Its check
digits are the remainder itself rather than eleven minus it; São Paulo and
Minas Gerais outgrew the eight-digit sequence so their numbers carry thirteen
digits, which means the state code has to be located from the end rather than
at a fixed offset; and for those two states a remainder of zero yields 1. The
first implementation of it here got all three wrong, which is why the tests
below exist in the shape they do.

The combined "documento" parameter accepts whichever of CPF or CNPJ the digit
count describes: forms offer one field, and making the caller decide which
endpoint to call defeats the point of that field. The PIX key infers its form
for the same reason — a key arrives pasted into one box with nothing saying
what kind it is.

Every new parameter joins the one-per-request guard. Left out, each would have
been a way to smuggle a second validation past it.

Verification: fixed vectors published by brazilian-utils, plus a differential
run of 10,000 generated numbers against reference implementations transcribed
flat into the test file, plus 500 constructed-valid PIS numbers — a validator
that returned false unconditionally would otherwise pass a differential test
against random input.
@jeferson0306
jeferson0306 merged commit d1e4f0f into master Sep 4, 2026
1 check passed
@jeferson0306
jeferson0306 deleted the feat/document-catalogue branch September 4, 2026 08:34
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant