Add LUHN validation checker and tests to v2. (#138)
# Describe Request Add LUHN validation checker and tests to v2. # Change Type New code.
This commit is contained in:
parent
aca9811584
commit
c6c47adc2b
3 changed files with 138 additions and 0 deletions
61
v2/luhn.go
Normal file
61
v2/luhn.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) 2023-2024 Onur Cinar.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameLUHN is the name of the LUHN check.
|
||||
nameLUHN = "luhn"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotLUHN indicates that the given value is not a valid LUHN number.
|
||||
ErrNotLUHN = NewCheckError("LUHN")
|
||||
)
|
||||
|
||||
// IsLUHN checks if the value is a valid LUHN number.
|
||||
func IsLUHN(value string) (string, error) {
|
||||
var sum int
|
||||
var alt bool
|
||||
|
||||
for i := len(value) - 1; i >= 0; i-- {
|
||||
r := rune(value[i])
|
||||
if !unicode.IsDigit(r) {
|
||||
return value, ErrNotLUHN
|
||||
}
|
||||
|
||||
n := int(r - '0')
|
||||
if alt {
|
||||
n *= 2
|
||||
if n > 9 {
|
||||
n -= 9
|
||||
}
|
||||
}
|
||||
sum += n
|
||||
alt = !alt
|
||||
}
|
||||
|
||||
if sum%10 != 0 {
|
||||
return value, ErrNotLUHN
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// checkLUHN checks if the value is a valid LUHN number.
|
||||
func checkLUHN(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsLUHN(value.Interface().(string))
|
||||
return value, err
|
||||
}
|
||||
|
||||
// makeLUHN makes a checker function for the LUHN checker.
|
||||
func makeLUHN(_ string) CheckFunc[reflect.Value] {
|
||||
return checkLUHN
|
||||
}
|
||||
76
v2/luhn_test.go
Normal file
76
v2/luhn_test.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) 2023-2024 Onur Cinar.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsLUHN() {
|
||||
_, err := v2.IsLUHN("79927398713")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLUHNInvalid(t *testing.T) {
|
||||
_, err := v2.IsLUHN("123456789")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLUHNValid(t *testing.T) {
|
||||
_, err := v2.IsLUHN("79927398713")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLUHNNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Card struct {
|
||||
Number int `checkers:"luhn"`
|
||||
}
|
||||
|
||||
card := &Card{}
|
||||
|
||||
v2.CheckStruct(card)
|
||||
}
|
||||
|
||||
func TestCheckLUHNInvalid(t *testing.T) {
|
||||
type Card struct {
|
||||
Number string `checkers:"luhn"`
|
||||
}
|
||||
|
||||
card := &Card{
|
||||
Number: "123456789",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(card)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLUHNValid(t *testing.T) {
|
||||
type Card struct {
|
||||
Number string `checkers:"luhn"`
|
||||
}
|
||||
|
||||
card := &Card{
|
||||
Number: "79927398713",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(card)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ var makers = map[string]MakeCheckFunc{
|
|||
nameIPv4: makeIPv4,
|
||||
nameIPv6: makeIPv6,
|
||||
nameISBN: makeISBN,
|
||||
nameLUHN: makeLUHN,
|
||||
nameMaxLen: makeMaxLen,
|
||||
nameMinLen: makeMinLen,
|
||||
nameRequired: makeRequired,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue