-
Notifications
You must be signed in to change notification settings - Fork 2
/
cns.go
94 lines (77 loc) · 1.51 KB
/
cns.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package br
import (
"errors"
"unicode"
"github.com/phenpessoa/gutils/unsafex"
)
var (
// ErrInvalidCNS is an error returned when an invalid CNS is encountered.
ErrInvalidCNS = errors.New("br: invalid cns passed")
)
// NewCNS creates a new CNS instance from a string representation.
//
// It verifies the CNS's validity using checksum digits.
func NewCNS(s string) (CNS, error) {
cns := CNS(s)
if !cns.IsValid() {
return "", ErrInvalidCNS
}
return cns, nil
}
// CNS represents a Brazilian CNS.
type CNS string
// IsValid checks whether the provided CNS is valid based on its checksum
// digits.
func (cns CNS) IsValid() bool {
if len(cns) != 15 && len(cns) != 18 {
return false
}
if !isFirstCNSDigitValid(cns[0]) {
return false
}
var sum, pad int
for i := 0; i < 15; i++ {
cur := cns[i+pad]
if unicode.IsSpace(rune(cur)) {
if i != 3 && i != 7 && i != 11 {
return false
}
pad++
cur = cns[i+pad]
}
if cur < '0' || cur > '9' {
return false
}
sum += int(cur-'0') * (15 - i)
}
return sum%11 == 0
}
// String returns the CNS formatted as XXX XXXX XXXX XXXX.
func (cns CNS) String() string {
if !cns.IsValid() {
return ""
}
if len(cns) == 18 {
return string(cns)
}
out := make([]byte, 18)
var pad int
for i := range out {
switch i {
case 3, 8, 13:
out[i] = ' '
pad++
continue
}
out[i] = cns[i-pad]
}
return unsafex.String(out)
}
func isFirstCNSDigitValid(d byte) bool {
switch d {
case '1', '2', '7', '8', '9':
return true
default:
return false
}
}