Skip to content

Commit 7547c12

Browse files
Merge pull request #21 from brazzcore/feature/22-generate-a-valid-cpf
[feature/22] :: generate a valid cpf
2 parents ee39394 + 8a494c2 commit 7547c12

File tree

5 files changed

+127
-2
lines changed

5 files changed

+127
-2
lines changed

examples/mock_example.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ func GenerateMockExample() {
3333
fmt.Println(constants.HeaderMain)
3434
fmt.Println(constants.SubHeader)
3535

36-
fmt.Printf("Person: %s %s, %s, %d years old\n",
37-
person_mock.FirstNameMale, person_mock.LastName, person_mock.Gender, person_mock.Age)
36+
fmt.Printf("Person: %s %s, %s, %d years old, CPF: %s\n",
37+
person_mock.FirstNameMale, person_mock.LastName, person_mock.Gender, person_mock.Age, person_mock.CPF)
3838
fmt.Printf("Address: %s, %d - %s, %s (%s) - %s\n",
3939
address_mock.Street, address_mock.Number, address_mock.City, address_mock.State, address_mock.UF, address_mock.ZIP)
4040
fmt.Printf("Phone: (%s) %s\n", phone_mock.AreaCode, phone_mock.Number)

pkg/mocai/entities/cpf/errors.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package cpf
2+
3+
import "errors"
4+
5+
var (
6+
ErrInvalidCPF = errors.New("invalid CPF")
7+
)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package cpf
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"math/rand"
7+
"strings"
8+
"time"
9+
)
10+
11+
// GenerateCPF generates a valid CPF number.
12+
// If formatted is true, the CPF will be returned in the format xxx.xxx.xxx-xx.
13+
// If formatted is false, the CPF will be returned as a plain string of 11 digits.
14+
func GenerateCPF(formatted bool) (string, error) {
15+
// Create a local random generator with a unique seed
16+
src := rand.NewSource(time.Now().UnixNano())
17+
r := rand.New(src)
18+
19+
// Generate the first 9 digits
20+
digits := make([]int, 9)
21+
for i := range digits {
22+
digits[i] = r.Intn(10)
23+
}
24+
25+
// Calculate the first check digit
26+
digits = append(digits, calculateCheckDigit(digits, 10))
27+
28+
// Calculate the second check digit
29+
digits = append(digits, calculateCheckDigit(digits, 11))
30+
31+
// Convert the digits to a string
32+
cpf := strings.Trim(strings.Join(strings.Fields(fmt.Sprint(digits)), ""), "[]")
33+
34+
// Format the CPF if requested
35+
if formatted {
36+
if len(cpf) != 11 {
37+
return "", errors.New(ErrInvalidCPF.Error())
38+
}
39+
return cpf[:3] + "." + cpf[3:6] + "." + cpf[6:9] + "-" + cpf[9:], nil
40+
}
41+
42+
return cpf, nil
43+
}
44+
45+
// calculateCheckDigit calculates the check digit for a CPF.
46+
func calculateCheckDigit(digits []int, weight int) int {
47+
sum := 0
48+
for _, digit := range digits {
49+
sum += digit * weight
50+
weight--
51+
}
52+
53+
remainder := sum % 11
54+
if remainder < 2 {
55+
return 0
56+
}
57+
return 11 - remainder
58+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package cpf
2+
3+
import (
4+
"strconv"
5+
"strings"
6+
)
7+
8+
// ValidateCPF checks if a CPF is valid.
9+
// It accepts both formatted (xxx.xxx.xxx-xx) and unformatted (xxxxxxxxxxx) CPFs.
10+
func ValidateCPF(cpf string) bool {
11+
// Remove formatting (dots and dashes)
12+
cpf = strings.ReplaceAll(cpf, ".", "")
13+
cpf = strings.ReplaceAll(cpf, "-", "")
14+
15+
// Check if the CPF has 11 digits
16+
if len(cpf) != 11 {
17+
return false
18+
}
19+
20+
// Convert the CPF string to a slice of integers
21+
digits := make([]int, 11)
22+
for i, char := range cpf {
23+
digit, err := strconv.Atoi(string(char))
24+
if err != nil {
25+
return false
26+
}
27+
digits[i] = digit
28+
}
29+
30+
// Check if all digits are the same (invalid CPF)
31+
allSame := true
32+
for i := 1; i < len(digits); i++ {
33+
if digits[i] != digits[0] {
34+
allSame = false
35+
break
36+
}
37+
}
38+
if allSame {
39+
return false
40+
}
41+
42+
// Calculate the first check digit
43+
firstCheckDigit := calculateCheckDigit(digits[:9], 10)
44+
if firstCheckDigit != digits[9] {
45+
return false
46+
}
47+
48+
// Calculate the second check digit
49+
secondCheckDigit := calculateCheckDigit(digits[:10], 11)
50+
return secondCheckDigit == digits[10]
51+
}

pkg/mocai/entities/person/generator.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"math/rand"
77
"strings"
88

9+
"github.com/brazzcore/mocai/pkg/mocai/entities/cpf"
910
"github.com/brazzcore/mocai/pkg/mocai/entities/gender"
1011
"github.com/brazzcore/mocai/pkg/mocai/translations"
1112
)
@@ -17,6 +18,7 @@ type Person struct {
1718
LastName string
1819
Gender gender.Gender
1920
Age int
21+
CPF string
2022
}
2123

2224
// GeneratePerson generates a mock person with random data.
@@ -49,6 +51,12 @@ func GeneratePerson() (*Person, error) {
4951
return nil, err
5052
}
5153

54+
// Generate a random CPF without a mask
55+
cpf, err := cpf.GenerateCPF(false)
56+
if err != nil {
57+
return nil, err
58+
}
59+
5260
// Validate required fields
5361
if firstNameMale == "" || firstNameFemale == "" || lastName == "" {
5462
return nil, fmt.Errorf("%s: missing required data (firstNameMale: %s, firstNameFemale: %s, lastName: %s)",
@@ -61,6 +69,7 @@ func GeneratePerson() (*Person, error) {
6169
LastName: lastName,
6270
Gender: gender,
6371
Age: rand.Intn(80) + 18,
72+
CPF: cpf,
6473
}
6574

6675
return createdPerson, nil

0 commit comments

Comments
 (0)