Gte and Lte checks are added. (#161)

# Describe Request

Greater than or equal tp (Gte) and less than or equal to (Lte) checks
are added.

# Change Type

New checks.
This commit is contained in:
Onur Cinar 2024-12-30 22:10:35 -08:00 committed by GitHub
commit 1188cff9b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 460 additions and 2 deletions

40
DOC.md
View file

@ -29,6 +29,7 @@ Package v2 Checker is a Go library for validating user input through checker rul
- [func IsDiscoverCreditCard\(number string\) \(string, error\)](<#IsDiscoverCreditCard>)
- [func IsEmail\(value string\) \(string, error\)](<#IsEmail>)
- [func IsFQDN\(value string\) \(string, error\)](<#IsFQDN>)
- [func IsGte\[T cmp.Ordered\]\(value, n T\) \(T, error\)](<#IsGte>)
- [func IsHex\(value string\) \(string, error\)](<#IsHex>)
- [func IsIP\(value string\) \(string, error\)](<#IsIP>)
- [func IsIPv4\(value string\) \(string, error\)](<#IsIPv4>)
@ -36,6 +37,7 @@ Package v2 Checker is a Go library for validating user input through checker rul
- [func IsISBN\(value string\) \(string, error\)](<#IsISBN>)
- [func IsJcbCreditCard\(number string\) \(string, error\)](<#IsJcbCreditCard>)
- [func IsLUHN\(value string\) \(string, error\)](<#IsLUHN>)
- [func IsLte\[T cmp.Ordered\]\(value, n T\) \(T, error\)](<#IsLte>)
- [func IsMAC\(value string\) \(string, error\)](<#IsMAC>)
- [func IsMasterCardCreditCard\(number string\) \(string, error\)](<#IsMasterCardCreditCard>)
- [func IsRegexp\(expression, value string\) \(string, error\)](<#IsRegexp>)
@ -80,6 +82,24 @@ const (
## Variables
<a name="ErrGte"></a>
```go
var (
// ErrGte indicates that the value is not greater than or equal to the given value.
ErrGte = NewCheckError("NOT_GTE")
)
```
<a name="ErrLte"></a>
```go
var (
// ErrLte indicates that the value is not less than or equal to the given value.
ErrLte = NewCheckError("NOT_LTE")
)
```
<a name="ErrMaxLen"></a>
```go
@ -707,6 +727,15 @@ func main() {
</p>
</details>
<a name="IsGte"></a>
## func [IsGte](<https://github.com/cinar/checker/blob/main/gte.go#L25>)
```go
func IsGte[T cmp.Ordered](value, n T) (T, error)
```
IsGte checks if the value is greater than or equal to the given value.
<a name="IsHex"></a>
## func [IsHex](<https://github.com/cinar/checker/blob/main/hex.go#L23>)
@ -944,6 +973,15 @@ func main() {
</p>
</details>
<a name="IsLte"></a>
## func [IsLte](<https://github.com/cinar/checker/blob/main/lte.go#L25>)
```go
func IsLte[T cmp.Ordered](value, n T) (T, error)
```
IsLte checks if the value is less than or equal to the given value.
<a name="IsMAC"></a>
## func [IsMAC](<https://github.com/cinar/checker/blob/main/mac.go#L24>)
@ -1173,7 +1211,7 @@ func RegisterLocale(locale string, messages map[string]string)
RegisterLocale registers the localized error messages for the given locale.
<a name="RegisterMaker"></a>
## func [RegisterMaker](<https://github.com/cinar/checker/blob/main/maker.go#L51>)
## func [RegisterMaker](<https://github.com/cinar/checker/blob/main/maker.go#L53>)
```go
func RegisterMaker(name string, maker MakeCheckFunc)

View file

@ -104,11 +104,13 @@ type Person struct {
- [`digits`](DOC.md#IsDigits): Ensures the string contains only digits.
- [`email`](DOC.md#IsEmail): Ensures the string is a valid email address.
- [`fqdn`](DOC.md#IsFQDN): Ensures the string is a valid fully qualified domain name.
- [`hex`](DOC.md#IsHex): Ensures the string contains only hex digits.
- [`gte`](DOC.md#IsGte): Ensures the value is greater than or equal to the specified number.
- [`hex`](DOC.md#IsHex): Ensures the string contains only hexadecimal digits.
- [`ip`](DOC.md#IsIP): Ensures the string is a valid IP address.
- [`ipv4`](DOC.md#IsIPv4): Ensures the string is a valid IPv4 address.
- [`ipv6`](DOC.md#IsIPv6): Ensures the string is a valid IPv6 address.
- [`isbn`](DOC.md#IsISBN): Ensures the string is a valid ISBN.
- [`lte`](DOC.md#ISLte): Ensures the value is less than or equal to the specified number.
- [`luhn`](DOC.md#IsLUHN): Ensures the string is a valid LUHN number.
- [`mac`](DOC.md#IsMAC): Ensures the string is a valid MAC address.
- [`max-len`](DOC.md#func-maxlen): Ensures the length of the given value (string, slice, or map) is at most n.

67
gte.go Normal file
View file

@ -0,0 +1,67 @@
// 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 (
"cmp"
"reflect"
"strconv"
)
const (
// nameGte is the name of the greater than or equal to check.
nameGte = "gte"
)
var (
// ErrGte indicates that the value is not greater than or equal to the given value.
ErrGte = NewCheckError("NOT_GTE")
)
// IsGte checks if the value is greater than or equal to the given value.
func IsGte[T cmp.Ordered](value, n T) (T, error) {
if cmp.Compare(value, n) < 0 {
return value, newGteError(n)
}
return value, nil
}
// makeGte creates a greater than or equal to check function from a string parameter.
// Panics if the parameter cannot be parsed as a number.
func makeGte(params string) CheckFunc[reflect.Value] {
n, err := strconv.ParseFloat(params, 64)
if err != nil {
panic("unable to parse params as float")
}
return func(value reflect.Value) (reflect.Value, error) {
v := reflect.Indirect(value)
switch {
case v.CanInt():
_, err := IsGte(float64(v.Int()), n)
return v, err
case v.CanFloat():
_, err := IsGte(v.Float(), n)
return v, err
default:
panic("value is not numeric")
}
}
}
// newGteError creates a new greater than or equal to error with the given value.
func newGteError[T cmp.Ordered](n T) error {
return NewCheckErrorWithData(
ErrGte.Code,
map[string]interface{}{
"n": n,
},
)
}

139
gte_test.go Normal file
View file

@ -0,0 +1,139 @@
// 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 (
"errors"
"testing"
v2 "github.com/cinar/checker/v2"
)
func TestGteIntSuccess(t *testing.T) {
value := 4
result, err := v2.IsGte(value, 4)
if result != value {
t.Fatalf("result (%d) is not the original value (%d)", result, value)
}
if err != nil {
t.Fatal(err)
}
}
func TestGteIntError(t *testing.T) {
value := 4
result, err := v2.IsGte(value, 5)
if result != value {
t.Fatalf("result (%d) is not the original value (%d)", result, value)
}
if err == nil {
t.Fatal("expected error")
}
message := "Value cannot be less than 5."
if err.Error() != message {
t.Fatalf("expected %s actual %s", message, err.Error())
}
}
func TestReflectGteIntError(t *testing.T) {
type Person struct {
Age int `checkers:"gte:18"`
}
person := &Person{
Age: 16,
}
errs, ok := v2.CheckStruct(person)
if ok {
t.Fatalf("expected errors")
}
if !errors.Is(errs["Age"], v2.ErrGte) {
t.Fatalf("expected ErrGte")
}
}
func TestReflectGteIntInvalidGte(t *testing.T) {
defer FailIfNoPanic(t, "expected panic")
type Person struct {
Age int `checkers:"gte:abcd"`
}
person := &Person{
Age: 16,
}
v2.CheckStruct(person)
}
func TestReflectGteIntInvalidType(t *testing.T) {
defer FailIfNoPanic(t, "expected panic")
type Person struct {
Age string `checkers:"gte:18"`
}
person := &Person{
Age: "18",
}
v2.CheckStruct(person)
}
func TestReflectGteFloatError(t *testing.T) {
type Person struct {
Weight float64 `checkers:"gte:165.0"`
}
person := &Person{
Weight: 150,
}
errs, ok := v2.CheckStruct(person)
if ok {
t.Fatalf("expected errors")
}
if !errors.Is(errs["Weight"], v2.ErrGte) {
t.Fatalf("expected ErrGte")
}
}
func TestReflectGteFloatInvalidGte(t *testing.T) {
defer FailIfNoPanic(t, "expected panic")
type Person struct {
Weight float64 `checkers:"gte:abcd"`
}
person := &Person{
Weight: 170,
}
v2.CheckStruct(person)
}
func TestReflectGteFloatInvalidType(t *testing.T) {
defer FailIfNoPanic(t, "expected panic")
type Person struct {
Weight string `checkers:"gte:165.0"`
}
person := &Person{
Weight: "170",
}
v2.CheckStruct(person)
}

View file

@ -40,11 +40,13 @@ var EnUSMessages = map[string]string{
"NOT_DIGITS": "Can only contain digits.",
"NOT_EMAIL": "Not a valid email address.",
"NOT_FQDN": "Not a fully qualified domain name (FQDN).",
"NOT_GTE": "Value cannot be less than {{ .n }}.",
"NOT_HEX": "Can only contain hexadecimal characters.",
"NOT_IP": "Not a valid IP address.",
"NOT_IPV4": "Not a valid IPv4 address.",
"NOT_IPV6": "Not a valid IPv6 address.",
"NOT_ISBN": "Not a valid ISBN number.",
"NOT_LTE": "Value cannot be less than {{ .n }}.",
"NOT_LUHN": "Not a valid LUHN number.",
"NOT_MAC": "Not a valid MAC address.",
"NOT_MAX_LEN": "Value cannot be greater than {{ .max }}.",

View file

@ -14,11 +14,13 @@ var EnUSMessages = map[string]string{
"NOT_DIGITS": "Can only contain digits.",
"NOT_EMAIL": "Not a valid email address.",
"NOT_FQDN": "Not a fully qualified domain name (FQDN).",
"NOT_GTE": "Value cannot be less than {{ .n }}.",
"NOT_HEX": "Can only contain hexadecimal characters.",
"NOT_IP": "Not a valid IP address.",
"NOT_IPV4": "Not a valid IPv4 address.",
"NOT_IPV6": "Not a valid IPv6 address.",
"NOT_ISBN": "Not a valid ISBN number.",
"NOT_LTE": "Value cannot be less than {{ .n }}.",
"NOT_LUHN": "Not a valid LUHN number.",
"NOT_MAC": "Not a valid MAC address.",
"NOT_MAX_LEN": "Value cannot be greater than {{ .max }}.",

67
lte.go Normal file
View file

@ -0,0 +1,67 @@
// 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 (
"cmp"
"reflect"
"strconv"
)
const (
// nameLte is the name of the less than or equal to check.
nameLte = "lte"
)
var (
// ErrLte indicates that the value is not less than or equal to the given value.
ErrLte = NewCheckError("NOT_LTE")
)
// IsLte checks if the value is less than or equal to the given value.
func IsLte[T cmp.Ordered](value, n T) (T, error) {
if cmp.Compare(value, n) > 0 {
return value, newLteError(n)
}
return value, nil
}
// makeLte creates a less than or equal to check function from a string parameter.
// Panics if the parameter cannot be parsed as a number.
func makeLte(params string) CheckFunc[reflect.Value] {
n, err := strconv.ParseFloat(params, 64)
if err != nil {
panic("unable to parse params as float")
}
return func(value reflect.Value) (reflect.Value, error) {
v := reflect.Indirect(value)
switch {
case v.CanInt():
_, err := IsLte(float64(v.Int()), n)
return v, err
case v.CanFloat():
_, err := IsLte(v.Float(), n)
return v, err
default:
panic("value is not numeric")
}
}
}
// newLteError creates a new less than or equal to error with the given value.
func newLteError[T cmp.Ordered](n T) error {
return NewCheckErrorWithData(
ErrLte.Code,
map[string]interface{}{
"n": n,
},
)
}

139
lte_test.go Normal file
View file

@ -0,0 +1,139 @@
// 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 (
"errors"
"testing"
v2 "github.com/cinar/checker/v2"
)
func TestLteIntSuccess(t *testing.T) {
value := 4
result, err := v2.IsLte(value, 4)
if result != value {
t.Fatalf("result (%d) is not the original value (%d)", result, value)
}
if err != nil {
t.Fatal(err)
}
}
func TestLteIntError(t *testing.T) {
value := 6
result, err := v2.IsLte(value, 5)
if result != value {
t.Fatalf("result (%d) is not the original value (%d)", result, value)
}
if err == nil {
t.Fatal("expected error")
}
message := "Value cannot be less than 5."
if err.Error() != message {
t.Fatalf("expected %s actual %s", message, err.Error())
}
}
func TestReflectLteIntError(t *testing.T) {
type Person struct {
Age int `checkers:"lte:18"`
}
person := &Person{
Age: 21,
}
errs, ok := v2.CheckStruct(person)
if ok {
t.Fatalf("expected errors")
}
if !errors.Is(errs["Age"], v2.ErrLte) {
t.Fatalf("expected ErrLte")
}
}
func TestReflectLteIntInvalidLte(t *testing.T) {
defer FailIfNoPanic(t, "expected panic")
type Person struct {
Age int `checkers:"lte:abcd"`
}
person := &Person{
Age: 16,
}
v2.CheckStruct(person)
}
func TestReflectLteIntInvalidType(t *testing.T) {
defer FailIfNoPanic(t, "expected panic")
type Person struct {
Age string `checkers:"lte:18"`
}
person := &Person{
Age: "18",
}
v2.CheckStruct(person)
}
func TestReflectLteFloatError(t *testing.T) {
type Person struct {
Weight float64 `checkers:"lte:165.0"`
}
person := &Person{
Weight: 170,
}
errs, ok := v2.CheckStruct(person)
if ok {
t.Fatalf("expected errors")
}
if !errors.Is(errs["Weight"], v2.ErrLte) {
t.Fatalf("expected ErrLte")
}
}
func TestReflectLteFloatInvalidLte(t *testing.T) {
defer FailIfNoPanic(t, "expected panic")
type Person struct {
Weight float64 `checkers:"lte:abcd"`
}
person := &Person{
Weight: 170,
}
v2.CheckStruct(person)
}
func TestReflectLteFloatInvalidType(t *testing.T) {
defer FailIfNoPanic(t, "expected panic")
type Person struct {
Weight string `checkers:"lte:165.0"`
}
person := &Person{
Weight: "170",
}
v2.CheckStruct(person)
}

View file

@ -23,6 +23,7 @@ var makers = map[string]MakeCheckFunc{
nameDigits: makeDigits,
nameEmail: makeEmail,
nameFQDN: makeFQDN,
nameGte: makeGte,
nameHex: makeHex,
nameHTMLEscape: makeHTMLEscape,
nameHTMLUnescape: makeHTMLUnescape,
@ -31,6 +32,7 @@ var makers = map[string]MakeCheckFunc{
nameIPv6: makeIPv6,
nameISBN: makeISBN,
nameLower: makeLower,
nameLte: makeLte,
nameLUHN: makeLUHN,
nameMAC: makeMAC,
nameMaxLen: makeMaxLen,