diff --git a/DOC.md b/DOC.md
index c56b2b6..129dd97 100644
--- a/DOC.md
+++ b/DOC.md
@@ -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
+
+
+```go
+var (
+ // ErrGte indicates that the value is not greater than or equal to the given value.
+ ErrGte = NewCheckError("NOT_GTE")
+)
+```
+
+
+
+```go
+var (
+ // ErrLte indicates that the value is not less than or equal to the given value.
+ ErrLte = NewCheckError("NOT_LTE")
+)
+```
+
```go
@@ -707,6 +727,15 @@ func main() {
+
+## func [IsGte]()
+
+```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.
+
## func [IsHex]()
@@ -944,6 +973,15 @@ func main() {
+
+## func [IsLte]()
+
+```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.
+
## func [IsMAC]()
@@ -1173,7 +1211,7 @@ func RegisterLocale(locale string, messages map[string]string)
RegisterLocale registers the localized error messages for the given locale.
-## func [RegisterMaker]()
+## func [RegisterMaker]()
```go
func RegisterMaker(name string, maker MakeCheckFunc)
diff --git a/README.md b/README.md
index 395b858..1389044 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/gte.go b/gte.go
new file mode 100644
index 0000000..1ae5fa5
--- /dev/null
+++ b/gte.go
@@ -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,
+ },
+ )
+}
diff --git a/gte_test.go b/gte_test.go
new file mode 100644
index 0000000..48df21f
--- /dev/null
+++ b/gte_test.go
@@ -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)
+}
diff --git a/locales/DOC.md b/locales/DOC.md
index 88718f0..f34002c 100644
--- a/locales/DOC.md
+++ b/locales/DOC.md
@@ -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 }}.",
diff --git a/locales/en_us.go b/locales/en_us.go
index c35c55b..01cd1eb 100644
--- a/locales/en_us.go
+++ b/locales/en_us.go
@@ -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 }}.",
diff --git a/lte.go b/lte.go
new file mode 100644
index 0000000..884bcd8
--- /dev/null
+++ b/lte.go
@@ -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,
+ },
+ )
+}
diff --git a/lte_test.go b/lte_test.go
new file mode 100644
index 0000000..d039f53
--- /dev/null
+++ b/lte_test.go
@@ -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)
+}
diff --git a/maker.go b/maker.go
index 935d04b..ed1b529 100644
--- a/maker.go
+++ b/maker.go
@@ -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,