Skip to content

Commit 20d9b79

Browse files
joeybloggsjoeybloggs
authored andcommitted
add isbn, isbn10 and isbn validators + tests + documentation
1 parent 05e0fe1 commit 20d9b79

File tree

4 files changed

+173
-0
lines changed

4 files changed

+173
-0
lines changed

baked_in.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,65 @@ var BakedInValidators = map[string]Func{
5050
"excludes": excludes,
5151
"excludesall": excludesAll,
5252
"excludesrune": excludesRune,
53+
"isbn": isISBN,
54+
"isbn10": isISBN10,
55+
"isbn13": isISBN13,
56+
}
57+
58+
func isISBN(top interface{}, current interface{}, field interface{}, param string) bool {
59+
return isISBN10(top, current, field, param) || isISBN13(top, current, field, param)
60+
}
61+
62+
func isISBN13(top interface{}, current interface{}, field interface{}, param string) bool {
63+
64+
s := strings.Replace(strings.Replace(field.(string), "-", "", 4), " ", "", 4)
65+
66+
if !matchesRegex(iSBN13Regex, s) {
67+
return false
68+
}
69+
70+
var checksum int32
71+
var i int32
72+
73+
factor := []int32{1, 3}
74+
75+
for i = 0; i < 12; i++ {
76+
checksum += factor[i%2] * int32(s[i]-'0')
77+
}
78+
79+
if (int32(s[12]-'0'))-((10-(checksum%10))%10) == 0 {
80+
return true
81+
}
82+
83+
return false
84+
}
85+
86+
func isISBN10(top interface{}, current interface{}, field interface{}, param string) bool {
87+
88+
s := strings.Replace(strings.Replace(field.(string), "-", "", 3), " ", "", 3)
89+
90+
if !matchesRegex(iSBN10Regex, s) {
91+
return false
92+
}
93+
94+
var checksum int32
95+
var i int32
96+
97+
for i = 0; i < 9; i++ {
98+
checksum += (i + 1) * int32(s[i]-'0')
99+
}
100+
101+
if s[9] == 'X' {
102+
checksum += 10 * 10
103+
} else {
104+
checksum += 10 * int32(s[9]-'0')
105+
}
106+
107+
if checksum%11 == 0 {
108+
return true
109+
}
110+
111+
return false
53112
}
54113

55114
func excludesRune(top interface{}, current interface{}, field interface{}, param string) bool {

doc.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,18 @@ Here is a list of the current built in validators:
362362
This validates that a string value does not contain the supplied rune value.
363363
(Usage: excludesrune=@)
364364
365+
isbn
366+
This validates that a string value contains a valid isbn10 or isbn13 value.
367+
(Usage: isbn)
368+
369+
isbn10
370+
This validates that a string value contains a valid isbn10 value.
371+
(Usage: isbn10)
372+
373+
isbn13
374+
This validates that a string value contains a valid isbn13 value.
375+
(Usage: isbn13)
376+
365377
Validator notes:
366378
367379
regex

regexes.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const (
1515
hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
1616
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:\\(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22)))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
1717
base64RegexString = "(?:^(?:[A-Za-z0-9+\\/]{4}\\n?)*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=)$)"
18+
iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$"
19+
iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$"
1820
)
1921

2022
var (
@@ -30,6 +32,8 @@ var (
3032
hslaRegex = regexp.MustCompile(hslaRegexString)
3133
emailRegex = regexp.MustCompile(emailRegexString)
3234
base64Regex = regexp.MustCompile(base64RegexString)
35+
iSBN10Regex = regexp.MustCompile(iSBN10RegexString)
36+
iSBN13Regex = regexp.MustCompile(iSBN13RegexString)
3337
)
3438

3539
func matchesRegex(regex *regexp.Regexp, field interface{}) bool {

validator_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,104 @@ func AssertMapFieldError(t *testing.T, s map[string]*FieldError, field string, e
226226
EqualSkip(t, 2, val.Tag, expectedTag)
227227
}
228228

229+
func TestISBNValidation(t *testing.T) {
230+
tests := []struct {
231+
param string
232+
expected bool
233+
}{
234+
{"", false},
235+
{"foo", false},
236+
{"3836221195", true},
237+
{"1-61729-085-8", true},
238+
{"3 423 21412 0", true},
239+
{"3 401 01319 X", true},
240+
{"9784873113685", true},
241+
{"978-4-87311-368-5", true},
242+
{"978 3401013190", true},
243+
{"978-3-8362-2119-1", true},
244+
}
245+
246+
for i, test := range tests {
247+
248+
err := validate.Field(test.param, "isbn")
249+
250+
if test.expected == true {
251+
if !IsEqual(t, err, nil) {
252+
t.Fatalf("Index: %d ISBN failed Error: %s", i, err)
253+
}
254+
} else {
255+
if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "isbn") {
256+
t.Fatalf("Index: %d ISBN failed Error: %s", i, err)
257+
}
258+
}
259+
}
260+
}
261+
262+
func TestISBN13Validation(t *testing.T) {
263+
tests := []struct {
264+
param string
265+
expected bool
266+
}{
267+
{"", false},
268+
{"foo", false},
269+
{"3-8362-2119-5", false},
270+
{"01234567890ab", false},
271+
{"978 3 8362 2119 0", false},
272+
{"9784873113685", true},
273+
{"978-4-87311-368-5", true},
274+
{"978 3401013190", true},
275+
{"978-3-8362-2119-1", true},
276+
}
277+
278+
for i, test := range tests {
279+
280+
err := validate.Field(test.param, "isbn13")
281+
282+
if test.expected == true {
283+
if !IsEqual(t, err, nil) {
284+
t.Fatalf("Index: %d ISBN13 failed Error: %s", i, err)
285+
}
286+
} else {
287+
if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "isbn13") {
288+
t.Fatalf("Index: %d ISBN13 failed Error: %s", i, err)
289+
}
290+
}
291+
}
292+
}
293+
294+
func TestISBN10Validation(t *testing.T) {
295+
tests := []struct {
296+
param string
297+
expected bool
298+
}{
299+
{"", false},
300+
{"foo", false},
301+
{"3423214121", false},
302+
{"978-3836221191", false},
303+
{"3-423-21412-1", false},
304+
{"3 423 21412 1", false},
305+
{"3836221195", true},
306+
{"1-61729-085-8", true},
307+
{"3 423 21412 0", true},
308+
{"3 401 01319 X", true},
309+
}
310+
311+
for i, test := range tests {
312+
313+
err := validate.Field(test.param, "isbn10")
314+
315+
if test.expected == true {
316+
if !IsEqual(t, err, nil) {
317+
t.Fatalf("Index: %d ISBN10 failed Error: %s", i, err)
318+
}
319+
} else {
320+
if IsEqual(t, err, nil) || !IsEqual(t, err.Tag, "isbn10") {
321+
t.Fatalf("Index: %d ISBN10 failed Error: %s", i, err)
322+
}
323+
}
324+
}
325+
}
326+
229327
func TestExcludesRuneValidation(t *testing.T) {
230328

231329
tests := []struct {

0 commit comments

Comments
 (0)