Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@
- [Telefone/Celular](docs/UTILITARIOS/TELEFONE.md)
- [UUID](docs/UTILITARIOS/UUID.md)
- [Boleto](docs/UTILITARIOS/BOLETO.md)
- [Extensões](docs/EXTENSOES/README.md)
- [Joi](docs/EXTENSOES/JOI.md)
74 changes: 74 additions & 0 deletions docs/EXTENSOES/JOI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Joi

## Integração com br-docs

A extensão de Joi do **br-docs** adiciona um novo tipo chamado `document` com regras específicas para documentos brasileiros.

### Instalação

```bash
npm i br-docs joi
```

### Uso básico

```typescript
import Joi from "joi";
import documentExtension from "br-docs/joi";

const JoiExtended = Joi.extend(documentExtension);

const schema = JoiExtended.object({
cpf: JoiExtended.document().cpf().required(),
cnpj: JoiExtended.document().cnpj().required(),
nis: JoiExtended.document().nis().required(),
cnh: JoiExtended.document().cnh().required(),
cep: JoiExtended.document().cep().required(),
telefone: JoiExtended.document().phone().required(),
pix: JoiExtended.document().pix().required(),
boleto: JoiExtended.document().boleto().required(),
celular: JoiExtended.document().celular().required(),
});

const result = schema.validate({
cpf: "123.456.789-09",
cnpj: "11.222.333/0001-81",
nis: "123.45678.90-1",
cnh: "02650306461",
cep: "01001-000",
telefone: "(11) 3456-7890",
pix: "email@dominio.com",
boleto: "34191.79001 01043.510047 91020.150008 6 84570000002000",
celular: "(11) 98765-4321",
});

if (result.error) {
console.error(result.error.details);
}
```

## Regras disponíveis

As regras abaixo validam o valor e retornam erro com mensagens específicas:

- `document().cpf()` — CPF inválido
- `document().cnpj()` — CNPJ inválido
- `document().nis()` — NIS inválido
- `document().cnh()` — CNH inválida
- `document().cep()` — CEP inválido
- `document().phone()` — Telefone inválido
- `document().pix()` — Chave PIX inválida
- `document().boleto()` — Boleto inválido
- `document().celular()` — Celular inválido

## Mensagens de erro personalizadas

Você pode customizar mensagens normalmente pelo Joi:

```typescript
const schema = JoiExtended.object({
cpf: JoiExtended.document().cpf().messages({
"document.cpf": "CPF informado não é válido",
}),
});
```
3 changes: 3 additions & 0 deletions docs/EXTENSOES/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Extensões

Nesta seção você encontra integrações do **br-docs** com bibliotecas de validação e outros ecossistemas.
64 changes: 64 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 23 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
"./cnh": "./dist/cnh.js",
"./cep": "./dist/cep.js",
"./telefone": "./dist/telefone.js",
"./boleto": "./dist/boleto.js"
"./boleto": "./dist/boleto.js",
"./nis": "./dist/nis.js",
"./joi": "./dist/joi.js"
},
"scripts": {
"build": "tsc",
Expand All @@ -39,7 +41,16 @@
"cpf",
"phone",
"cep",
"cnpj"
"cnpj",
"pix",
"boleto",
"cnh",
"nis",
"uuid",
"email",
"documentos",
"validacao",
"formatacao"
],
"author": "Mateus Teruki",
"description": "Utilitários para validação e formatação de documentos brasileiros como CPF, telefone, CEP e CNPJ.",
Expand All @@ -49,7 +60,16 @@
"@vitest/coverage-v8": "^3.2.4",
"eslint": "^8.50.0",
"typescript": "^5.8.3",
"vitest": "^3.2.4"
"vitest": "^3.2.4",
"joi": "^17.0.0"
},
"peerDependencies": {
"joi": "^17.0.0"
},
"peerDependenciesMeta": {
"joi": {
"optional": true
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
2 changes: 1 addition & 1 deletion src/__tests__/cnh.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { describe, it, expect } from "vitest";

describe("CNH Validation", () => {
it("should validate a correct CNH", () => {
expect(Cnh.isValid("12345678900")).toBe(true);
expect(Cnh.isValid("60930978987")).toBe(true);
});

it("should invalidate an incorrect CNH", () => {
Expand Down
54 changes: 54 additions & 0 deletions src/__tests__/joi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import BaseJoi, { Schema } from "joi";
import validator from "../joi";
import { describe, expect, it } from "vitest";

const Joi = BaseJoi.extend(validator);

function expectOk(schema: Schema, value: string) {
const { error } = schema.validate(value);
expect(error).toBeUndefined();
}

function expectErr(schema: Schema, value: string, code: string) {
const { error } = schema.validate(value);
expect(error).toBeTruthy();
expect(error!.details[0].type).toBe(code);
}

describe("br-docs Joi extension", () => {
it("cpf", () => {
expectOk(Joi.document().cpf(), "529.982.247-25");
expectErr(Joi.document().cpf(), "123", "document.cpf");
});

it("cnpj", () => {
expectOk(Joi.document().cnpj(), "04.252.011/0001-10");
expectErr(Joi.document().cnpj(), "123", "document.cnpj");
});

it("nis", () => {
expectOk(Joi.document().nis(), "16029214650");
expectErr(Joi.document().nis(), "16029214651", "document.nis");
});

it("cnh", () => {
expectOk(Joi.document().cnh(), "60930978987");
expectErr(Joi.document().cnh(), "11111111111", "document.cnh");
});

it("cep / phone / celular / uuid / pix", () => {
expectOk(Joi.document().cep(), "01001-000");
expectOk(Joi.document().phone(), "1133334444");
expectOk(Joi.document().celular(), "11999998888");
expectOk(Joi.document().uuid(), "550e8400-e29b-41d4-a716-446655440000");
Comment on lines +39 to +43

Copilot AI Jan 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test expects Joi.document().uuid() to work, but the uuid validation rule is not implemented in the validator extension (src/joi.ts). This test will fail at runtime. The uuid rule needs to be added to src/joi.ts before this test can pass.

Suggested change
it("cep / phone / celular / uuid / pix", () => {
expectOk(Joi.document().cep(), "01001-000");
expectOk(Joi.document().phone(), "1133334444");
expectOk(Joi.document().celular(), "11999998888");
expectOk(Joi.document().uuid(), "550e8400-e29b-41d4-a716-446655440000");
it("cep / phone / celular / pix", () => {
expectOk(Joi.document().cep(), "01001-000");
expectOk(Joi.document().phone(), "1133334444");
expectOk(Joi.document().celular(), "11999998888");

Copilot uses AI. Check for mistakes.
expectOk(Joi.document().pix(), "a@b.com");
});
Comment on lines +39 to +45

Copilot AI Jan 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test case only validates successful validation for cep, phone, celular, uuid, and pix. Consider adding error case tests (using expectErr) for these validators to ensure proper error handling and messages, similar to how cpf, cnpj, nis, cnh, and boleto are tested.

Copilot uses AI. Check for mistakes.

it("boleto", () => {
expectOk(
Joi.document().boleto(),
"00190.00009 02372.190005 00017.356171 9 63690000007179",
);
expectErr(Joi.document().boleto(), "123", "document.boleto");
});
});
45 changes: 25 additions & 20 deletions src/cnh.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,6 @@
import { IDocumentHandler } from ".";
import clean from "./utils/cleanString";

/**
* Função auxiliar para calcular o dígito verificador de um número.
* @param digits
* @returns {number} O dígito verificador calculado.
*/
const calcDigit = (digits: string): number => {
const digit = digits
.split("")
.reduce((acc, digit, index) => acc + parseInt(digit, 10) * (9 - index), 0);
const remainder = digit % 11;
return remainder < 2 ? 0 : 11 - remainder;
};

export class Cnh implements IDocumentHandler<string> {
constructor(private cnh: string) {}

Expand All @@ -40,14 +27,32 @@ export class Cnh implements IDocumentHandler<string> {

const cleanCnh = clean(cnh);

const firstDigit = calcDigit(cleanCnh.slice(0, 9));
const secondDigit = calcDigit(cleanCnh.slice(0, 10));
if (cleanCnh.length !== 11) return false;
if (/^(\d)\1{10}$/.test(cleanCnh)) return false;

const base = cleanCnh.slice(0, 9).split("").map(Number);

let sum1 = 0;
let sum2 = 0;

for (let i = 0; i < 9; i++) {
sum1 += base[i] * (9 - i);
sum2 += base[i] * (1 + i);
}

const dv1Raw = sum1 % 11;
const dv1 = dv1Raw > 9 ? 0 : dv1Raw;

let dv2 = sum2 % 11;

if (dv1Raw > 9) {
dv2 = dv2 - 2;
if (dv2 < 0) dv2 += 9;
}

if (dv2 > 9) dv2 = 0;

return (
cleanCnh.length === 11 &&
cleanCnh[9] === firstDigit.toString() &&
cleanCnh[10] === secondDigit.toString()
);
return cleanCnh[9] === String(dv1) && cleanCnh[10] === String(dv2);
}
/**
* Formata uma CNH brasileira.
Expand Down
35 changes: 35 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Cnpj } from "./cnpj";
import { Cpf } from "./cpf";

/**
* Interface que define o método de validação de um documento.
* Implementa o método de validação que verifica se o documento é válido.
Expand Down Expand Up @@ -45,3 +48,35 @@
export { Telefone } from "./telefone";
export { Cnh } from "./cnh";
export { Boleto } from "./boleto";
export { Nis } from "./nis";

export const validator = (joi: any) => ({

Check failure on line 53 in src/index.ts

View workflow job for this annotation

GitHub Actions / lint-and-test

Unexpected any. Specify a different type
type: "document",
base: joi.string(),
messages: {
"document.cpf": "CPF inválido",
"document.cnpj": "CNPJ inválido",
},
rules: {
cpf: {
validate(value: any, helpers: any, args: any, options: any) {

Check failure on line 62 in src/index.ts

View workflow job for this annotation

GitHub Actions / lint-and-test

Unexpected any. Specify a different type

Check failure on line 62 in src/index.ts

View workflow job for this annotation

GitHub Actions / lint-and-test

'options' is defined but never used

Check failure on line 62 in src/index.ts

View workflow job for this annotation

GitHub Actions / lint-and-test

Unexpected any. Specify a different type

Check failure on line 62 in src/index.ts

View workflow job for this annotation

GitHub Actions / lint-and-test

'args' is defined but never used

Check failure on line 62 in src/index.ts

View workflow job for this annotation

GitHub Actions / lint-and-test

Unexpected any. Specify a different type

Check failure on line 62 in src/index.ts

View workflow job for this annotation

GitHub Actions / lint-and-test

Unexpected any. Specify a different type
if (!Cpf.isValid(value)) {
return helpers.error("document.cpf");
}

return value;
},
},
cnpj: {
validate(value: any, helpers: any, args: any, options: any) {

Check failure on line 71 in src/index.ts

View workflow job for this annotation

GitHub Actions / lint-and-test

'args' is defined but never used

Check failure on line 71 in src/index.ts

View workflow job for this annotation

GitHub Actions / lint-and-test

Unexpected any. Specify a different type

Check failure on line 71 in src/index.ts

View workflow job for this annotation

GitHub Actions / lint-and-test

Unexpected any. Specify a different type
if (!Cnpj.isValid(value)) {
return helpers.error("document.cnpj");
}

return value;
},
},
},
});

export default validator;
Comment on lines +52 to +82

Copilot AI Jan 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validator export in the main index file duplicates the Joi extension functionality from src/joi.ts but is incomplete (only supports CPF and CNPJ). This creates confusion about which validator to use and maintenance burden. Consider removing this incomplete validator from index.ts since a complete, well-structured Joi extension is now available in src/joi.ts and exported via package.json exports.

Suggested change
export const validator = (joi: any) => ({
type: "document",
base: joi.string(),
messages: {
"document.cpf": "CPF inválido",
"document.cnpj": "CNPJ inválido",
},
rules: {
cpf: {
validate(value: any, helpers: any, args: any, options: any) {
if (!Cpf.isValid(value)) {
return helpers.error("document.cpf");
}
return value;
},
},
cnpj: {
validate(value: any, helpers: any, args: any, options: any) {
if (!Cnpj.isValid(value)) {
return helpers.error("document.cnpj");
}
return value;
},
},
},
});
export default validator;

Copilot uses AI. Check for mistakes.
Loading