From 9d062496e494ea7081f993366a1ca92fab6948bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hendrik=20Mour=C3=A3o?= Date: Wed, 14 May 2025 18:13:20 -0300 Subject: [PATCH 1/2] Implement alphanumeric CNPJ validations --- cpfcnpj.go | 51 ++++++++++++++++++++++++++++++++++----------------- util.go | 12 ++++++++++++ 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/cpfcnpj.go b/cpfcnpj.go index a49b5c0..2b921fb 100644 --- a/cpfcnpj.go +++ b/cpfcnpj.go @@ -8,12 +8,25 @@ import ( // "Cadastro" will be used internally to define both CPF and CNPJ. var ( - cpfRegexp = regexp.MustCompile(`^\d{3}\.?\d{3}\.?\d{3}-?\d{2}$`) - cnpjRegexp = regexp.MustCompile( - `^\d{2}\.?\d{3}\.?\d{3}\/?(:?\d{3}[1-9]|\d{2}[1-9]\d|\d[1-9]\d{2}|[1-9]\d{3})-?\d{2}$`, - ) + CPFRegexp = regexp.MustCompile(`^\d{3}\.?\d{3}\.?\d{3}-?\d{2}$`) + CNPJRegexp = regexp.MustCompile(`^[0-9A-Z]{2}\.?[0-9A-Z]{3}\.?[0-9A-Z]{3}/?[0-9A-Z]{4}-?[0-9]{2}$`) +) + +type DocumentType string + +const ( + CPF DocumentType = "cpf" + CNPJ DocumentType = "cnpj" ) +func (d *DocumentType) IsCPF() bool { + return *d == CPF +} + +func (d *DocumentType) IsCNPJ() bool { + return *d == CNPJ +} + // IsCPF verifies if the given string is a valid CPF document. func IsCPF(doc string) bool { const ( @@ -21,7 +34,7 @@ func IsCPF(doc string) bool { pos = 10 ) - return isCadastro(doc, cpfRegexp, size, pos) + return isCPFOrCNPJ(doc, CPF, size, pos) } // IsCNPJ verifies if the given string is a valid CNPJ document. @@ -31,22 +44,26 @@ func IsCNPJ(doc string) bool { pos = 5 ) - return isCadastro(doc, cnpjRegexp, size, pos) + return isCPFOrCNPJ(doc, CNPJ, size, pos) } -// isCadastro generates the digits for a given CPF or CNPJ and compares it with -// the original digits. -func isCadastro( - doc string, - pattern *regexp.Regexp, - size int, - position int, -) bool { - if !pattern.MatchString(doc) { - return false +// isCPFOrCNPJ generates the digits for a given CPF or CNPJ and compares it with the original digits. +func isCPFOrCNPJ(doc string, docType DocumentType, size int, position int) bool { + if docType.IsCPF() { + if !CPFRegexp.MatchString(doc) { + return false + } + + cleanNonDigits(&doc) } - cleanNonDigits(&doc) + if docType.IsCNPJ() { + if !CNPJRegexp.MatchString(doc) { + return false + } + + cleanNonDigitsAndNonLetters(&doc) + } // Invalidates documents with all digits equal. if allEq(doc) { diff --git a/util.go b/util.go index de04e40..a587581 100644 --- a/util.go +++ b/util.go @@ -57,3 +57,15 @@ func isFrom(uf UF, ufs []UF) bool { } return false } + +// cleanNonDigitsAndNonLetters removes every rune that is not a digit and letters. +func cleanNonDigitsAndNonLetters(doc *string) { + buf := bytes.NewBufferString("") + for _, r := range *doc { + if unicode.IsDigit(r) || unicode.IsLetter(r) { + buf.WriteRune(r) + } + } + + *doc = buf.String() +} From 051b315d6eafdd97514c0ff3bb7a7ceb604e9549 Mon Sep 17 00:00:00 2001 From: Rafael Escobar Date: Sat, 21 Jun 2025 09:06:54 -0300 Subject: [PATCH 2/2] Refactor alphanumeric CNPJ code Merge from `main` and change some things: - Remove `DocumentType` - Move (now called) `cleanCadastro` to `cpfcnpj.go` - Re-ordered and added a new test case --- cpfcnpj.go | 62 ++++++++++++++++++++++--------------------------- cpfcnpj_test.go | 15 ++++++++++++ util.go | 12 ---------- 3 files changed, 43 insertions(+), 46 deletions(-) diff --git a/cpfcnpj.go b/cpfcnpj.go index 2b921fb..d4032af 100644 --- a/cpfcnpj.go +++ b/cpfcnpj.go @@ -1,32 +1,19 @@ package brdoc import ( + "bytes" "regexp" "strconv" + "unicode" ) // "Cadastro" will be used internally to define both CPF and CNPJ. var ( - CPFRegexp = regexp.MustCompile(`^\d{3}\.?\d{3}\.?\d{3}-?\d{2}$`) - CNPJRegexp = regexp.MustCompile(`^[0-9A-Z]{2}\.?[0-9A-Z]{3}\.?[0-9A-Z]{3}/?[0-9A-Z]{4}-?[0-9]{2}$`) + cpfRegexp = regexp.MustCompile(`^\d{3}\.?\d{3}\.?\d{3}-?\d{2}$`) + cnpjRegexp = regexp.MustCompile(`^[0-9A-Z]{2}\.?[0-9A-Z]{3}\.?[0-9A-Z]{3}/?[0-9A-Z]{4}-?[0-9]{2}$`) ) -type DocumentType string - -const ( - CPF DocumentType = "cpf" - CNPJ DocumentType = "cnpj" -) - -func (d *DocumentType) IsCPF() bool { - return *d == CPF -} - -func (d *DocumentType) IsCNPJ() bool { - return *d == CNPJ -} - // IsCPF verifies if the given string is a valid CPF document. func IsCPF(doc string) bool { const ( @@ -34,7 +21,7 @@ func IsCPF(doc string) bool { pos = 10 ) - return isCPFOrCNPJ(doc, CPF, size, pos) + return isCadastro(doc, cpfRegexp, size, pos) } // IsCNPJ verifies if the given string is a valid CNPJ document. @@ -44,26 +31,22 @@ func IsCNPJ(doc string) bool { pos = 5 ) - return isCPFOrCNPJ(doc, CNPJ, size, pos) + return isCadastro(doc, cnpjRegexp, size, pos) } -// isCPFOrCNPJ generates the digits for a given CPF or CNPJ and compares it with the original digits. -func isCPFOrCNPJ(doc string, docType DocumentType, size int, position int) bool { - if docType.IsCPF() { - if !CPFRegexp.MatchString(doc) { - return false - } - - cleanNonDigits(&doc) +// isCadastro generates the digits for a given CPF or CNPJ and compares it with +// the original digits. +func isCadastro( + doc string, + pattern *regexp.Regexp, + size int, + position int, +) bool { + if !pattern.MatchString(doc) { + return false } - if docType.IsCNPJ() { - if !CNPJRegexp.MatchString(doc) { - return false - } - - cleanNonDigitsAndNonLetters(&doc) - } + cleanCadastro(&doc) // Invalidates documents with all digits equal. if allEq(doc) { @@ -98,3 +81,14 @@ func calcCadastroDigit(doc string, position int) string { return strconv.Itoa(11 - sum) } + +func cleanCadastro(doc *string) { + buf := bytes.NewBufferString("") + for _, r := range *doc { + if unicode.IsDigit(r) || unicode.IsLetter(r) { + buf.WriteRune(r) + } + } + + *doc = buf.String() +} diff --git a/cpfcnpj_test.go b/cpfcnpj_test.go index 5169f89..8a523c5 100644 --- a/cpfcnpj_test.go +++ b/cpfcnpj_test.go @@ -32,6 +32,8 @@ func TestIsCPF(t *testing.T) { {"InvalidFormat", "099-075-865.60", false}, {"Valid", "248.438.034-80", true}, + {"Valid", "099.075.865-60", true}, + {"Valid", "24843803480", true}, {"Valid", "09907586560", true}, } { t.Run(testName(i, tc.name), func(t *testing.T) { @@ -63,12 +65,25 @@ func TestIsCNPJ(t *testing.T) { {"InvalidDigits", "26.637.142/0001-85", false}, {"InvalidDigits", "74.221.325/0001-03", false}, + {"InvalidDigits", "WE.0PP.M5F/0001-92", false}, {"InvalidFormat", "26-637-142.0001/58", false}, {"InvalidFormat", "74-221-325.0001/30", false}, {"Valid", "26.637.142/0001-58", true}, + {"Valid", "74.221.325/0001-30", true}, + {"Valid", "19.JA2.KO8/Z001-51", true}, + {"Valid", "SC.RCN.1NI/0001-30", true}, + {"Valid", "4Y.2OP.G99/0001-41", true}, + {"Valid", "WE.0PP.M4F/0001-91", true}, + {"Valid", "12.ABC.345/01DE-35", true}, + {"Valid", "26637142000158", true}, {"Valid", "74221325000130", true}, + {"Valid", "19JA2KO8Z00151", true}, + {"Valid", "SCRCN1NI000130", true}, + {"Valid", "4Y2OPG99000141", true}, + {"Valid", "WE0PPM4F000191", true}, + {"Valid", "12ABC34501DE35", true}, } { t.Run(testName(i, tc.name), func(t *testing.T) { assertEq(t, tc.valid, IsCNPJ(tc.doc)) diff --git a/util.go b/util.go index a587581..de04e40 100644 --- a/util.go +++ b/util.go @@ -57,15 +57,3 @@ func isFrom(uf UF, ufs []UF) bool { } return false } - -// cleanNonDigitsAndNonLetters removes every rune that is not a digit and letters. -func cleanNonDigitsAndNonLetters(doc *string) { - buf := bytes.NewBufferString("") - for _, r := range *doc { - if unicode.IsDigit(r) || unicode.IsLetter(r) { - buf.WriteRune(r) - } - } - - *doc = buf.String() -}