diff --git a/README.md b/README.md index 8b730b6..c417965 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,7 @@ Baked-in Validations | tiger192 | TIGER192 hash | | semver | Semantic Versioning 2.0.0 | | ulid | Universally Unique Lexicographically Sortable Identifier ULID | +| cpf | Registration of Individuals | ### Comparisons: | Tag | Description | diff --git a/baked_in.go b/baked_in.go index f2f0939..545526b 100644 --- a/baked_in.go +++ b/baked_in.go @@ -214,6 +214,7 @@ var ( "semver": isSemverFormat, "dns_rfc1035_label": isDnsRFC1035LabelFormat, "credit_card": isCreditCard, + "cpf": isCPFFormat, } ) @@ -2519,3 +2520,39 @@ func isCreditCard(fl FieldLevel) bool { } return (sum % 10) == 0 } + +// isCPFFormat is the validation function for validating if the current field's value is a valid CPF, defined by Federal Revenue of Brazil +func isCPFFormat(fl FieldLevel) bool { + cpfFirstDigitTable := []int{10, 9, 8, 7, 6, 5, 4, 3, 2} + cpfSecondDigitTable := []int{11, 10, 9, 8, 7, 6, 5, 4, 3, 2} + + cpf := fl.Field().String() + if len(cpf) != 11 { + return false + } + + firstPart := cpf[0:9] + sum := sumDigit(firstPart, cpfFirstDigitTable) + + r1 := sum % 11 + d1 := 0 + + if r1 >= 2 { + d1 = 11 - r1 + } + + secondPart := firstPart + strconv.Itoa(d1) + + dsum := sumDigit(secondPart, cpfSecondDigitTable) + + r2 := dsum % 11 + d2 := 0 + + if r2 >= 2 { + d2 = 11 - r2 + } + + finalPart := fmt.Sprintf("%s%d%d", firstPart, d1, d2) + return finalPart == cpf +} + diff --git a/doc.go b/doc.go index 7341c67..fdfd8e6 100644 --- a/doc.go +++ b/doc.go @@ -1047,6 +1047,12 @@ This validates that a string value contains a valid ULID value. Usage: ulid +Registration of Individuals (CPF) + +This validates that a string value contains a valid CPF. + + Usage: cpf + ASCII This validates that a string value contains only ASCII characters. diff --git a/translations/en/en.go b/translations/en/en.go index ee05f91..cd97559 100644 --- a/translations/en/en.go +++ b/translations/en/en.go @@ -1146,6 +1146,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er translation: "{0} must be a valid ULID", override: false, }, + { + tag: "cpf", + translation: "{0} must be a valid CPF", + override: false, + }, { tag: "ascii", translation: "{0} must contain only ascii characters", diff --git a/translations/en/en_test.go b/translations/en/en_test.go index 9cb6deb..8d4e4b8 100644 --- a/translations/en/en_test.go +++ b/translations/en/en_test.go @@ -106,6 +106,7 @@ func TestTranslations(t *testing.T) { UUID4 string `validate:"uuid4"` UUID5 string `validate:"uuid5"` ULID string `validate:"ulid"` + CPF string `validate:"cpf"` ASCII string `validate:"ascii"` PrintableASCII string `validate:"printascii"` MultiByte string `validate:"multibyte"` @@ -334,6 +335,10 @@ func TestTranslations(t *testing.T) { ns: "Test.ULID", expected: "ULID must be a valid ULID", }, + { + ns: "Test.CPF", + expected: "CPF must be a valid CPF", + }, { ns: "Test.ISBN", expected: "ISBN must be a valid ISBN number", diff --git a/util.go b/util.go index 36da855..5c9a334 100644 --- a/util.go +++ b/util.go @@ -286,3 +286,24 @@ func panicIf(err error) { panic(err.Error()) } } + +// Receive a sequence of integers and an array of index. Returns the sum of index values +// Used for CPF/CNPJ validation +func sumDigit(s string, table []int) int { + + if len(s) != len(table) { + return 0 + } + + sum := 0 + + for i, v := range table { + c := string(s[i]) + d, err := strconv.Atoi(c) + if err == nil { + sum += v * d + } + } + + return sum +} diff --git a/validator_test.go b/validator_test.go index 1ebdea5..69035ec 100644 --- a/validator_test.go +++ b/validator_test.go @@ -12286,3 +12286,52 @@ func TestMultiOrOperatorGroup(t *testing.T) { } } } + +func TestCPFField(t *testing.T) { + tests := []struct { + Value string `validate:"cpf"` + expected bool + }{ + {"33888222044", true}, //249//293 + {"16814496097", true}, //233 290 + {"37919050073", true}, + {"338.882.220-44", false}, + {"02588789854INVALID", false}, + {"33888222042", false}, + } + + validate := New() + + for i, test := range tests { + errs := validate.Struct(test) + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d cpf failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d cpf failed Error: %s", i, errs) + } + } + } +} + +func TestSumDigit(t *testing.T) { + tests := []struct { + sequence string `validate:"cpf"` + indexs []int + expected int + }{ + {"338882220", []int{10, 9, 8, 7, 6, 5, 4, 3, 2}, 249}, + {"3388822204", []int{11, 10, 9, 8, 7, 6, 5, 4, 3, 2}, 293}, + {"168144960", []int{10, 9, 8, 7, 6, 5, 4, 3, 2}, 233}, + {"1681449609", []int{11, 10, 9, 8, 7, 6, 5, 4, 3, 2}, 290}, + } + + for i, test := range tests { + result := sumDigit(test.sequence, test.indexs) + if !IsEqual(result, test.expected) { + t.Fatalf("Index: %d cpf failed. Expected: %d, received: %d", i, test.expected, result) + } + } +}