Compare commits
1 commit
main
...
config-rul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff27adbde5 |
130 changed files with 3778 additions and 5076 deletions
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"name": "Go",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/go",
|
||||
"image": "mcr.microsoft.com/devcontainers/go:0-1.20-bullseye",
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
output: "{{.Dir}}/DOC.md"
|
||||
repository:
|
||||
url: https://github.com/cinar/checker
|
||||
|
|
@ -20,7 +20,7 @@ community include:
|
|||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our errors,
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
|
|
|||
243
README.md
243
README.md
|
|
@ -1,248 +1,143 @@
|
|||
[](https://godoc.org/github.com/cinar/checker)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://goreportcard.com/report/github.com/cinar/checker)
|
||||

|
||||

|
||||
[](https://codecov.io/gh/cinar/checker)
|
||||
|
||||
# Checker
|
||||
|
||||
Checker is a lightweight Go library designed to validate user input efficiently. It supports validation of both struct fields and individual input values.
|
||||
Checker is a Go library that helps you validate user input. It can be used to validate user input stored in a struct, or to validate individual pieces of input.
|
||||
|
||||
While there are numerous validation libraries available, Checker stands out due to its simplicity and lack of external dependencies. This makes it an ideal choice for developers who prefer to minimize dependencies and maintain control over their tools. Checker is straightforward to use and effectively meets your validation needs.
|
||||
There are many validation libraries available, but I prefer to build my own tools and avoid pulling in unnecessary dependencies. That's why I created Checker, a simple validation library with no dependencies. It's easy to use and gets the job done.
|
||||
|
||||
## Usage
|
||||
|
||||
To begin using the Checker library, install it with the following command:
|
||||
To get started, install the Checker library with the following command:
|
||||
|
||||
```bash
|
||||
go get github.com/cinar/checker/v2
|
||||
go get github.com/cinar/checker
|
||||
```
|
||||
|
||||
Then, import the library into your source file as shown below:
|
||||
Next, you will need to import the library into your source file. You can do this by following the example below:
|
||||
|
||||
```golang
|
||||
import (
|
||||
checker "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
```
|
||||
|
||||
### Validating User Input Stored in a Struct
|
||||
|
||||
Checker can validate user input stored in a struct by listing the checkers in the struct tags for each field. Here is an example:
|
||||
Checker can be used in two ways. The first way is to validate user input stored in a struct. To do this, you can list the checkers through the struct tag for each field. Here is an example:
|
||||
|
||||
```golang
|
||||
type Person struct {
|
||||
Name string `checkers:"trim required"`
|
||||
Name string `checkers:"required"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: " Onur Cinar ",
|
||||
}
|
||||
person := &Person{}
|
||||
|
||||
errors, valid := checker.CheckStruct(person)
|
||||
mistakes, valid := checker.Check(person)
|
||||
if !valid {
|
||||
// Handle validation errors
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
### Validating Individual User Input with Multiple Checkers
|
||||
|
||||
You can also validate individual user input by calling checker functions directly. Here is an example:
|
||||
|
||||
```golang
|
||||
name := " Onur Cinar "
|
||||
|
||||
name, err := checker.Check(name, checker.Trim, checker.Required)
|
||||
if err != nil {
|
||||
// Handle validation error
|
||||
}
|
||||
```
|
||||
|
||||
The checkers and normalizers can also be provided through a config string. Here is an example:
|
||||
|
||||
```golang
|
||||
name := " Onur Cinar "
|
||||
|
||||
name, err := checker.CheckWithConfig(name, "trim requied")
|
||||
if err != nil {
|
||||
// Handle validation error
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Validating Individual User Input
|
||||
|
||||
For simpler validation, you can call individual checker functions. Here is an example:
|
||||
If you do not want to validate user input stored in a struct, you can individually call the checker functions to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
name := "Onur Cinar"
|
||||
var name
|
||||
|
||||
err := checker.IsRequired(name)
|
||||
if err != nil {
|
||||
// Handle validation error
|
||||
result := checker.IsRequired(name)
|
||||
if result != ResultValid {
|
||||
// Send the result back to the user
|
||||
}
|
||||
```
|
||||
|
||||
## Normalizers and Checkers
|
||||
|
||||
Checkers validate user input, while normalizers transform it into a preferred format. For example, a normalizer can trim spaces from a string or convert it to title case.
|
||||
Checkers are used to check for problems in user input, while normalizers are used to transform user input into a preferred format. For example, a normalizer could be used to trim spaces from the beginning and end of a string, or to convert a string to title case.
|
||||
|
||||
Although combining checkers and normalizers into a single library might seem unconventional, using them together can be beneficial. They can be mixed in any order when defining validation steps. For instance, you can use the `trim` normalizer with the `required` checker to first trim the input and then ensure it is provided. Here is an example:
|
||||
I am not entirely happy with the decision to combine checkers and normalizers into a single library, but using them together can be useful. Normalizers and checkers can be mixed in any order when defining the validation steps for user data. For example, the trim normalizer can be used in conjunction with the required checker to first trim the user input and then check if the user provided the required information. Here is an example:
|
||||
|
||||
```golang
|
||||
type Person struct {
|
||||
Name string `checkers:"trim required"`
|
||||
Name string `checkers:"trim required"`
|
||||
}
|
||||
```
|
||||
|
||||
# Checkers Provided
|
||||
|
||||
- [`ascii`](DOC.md#IsASCII): Ensures the string contains only ASCII characters.
|
||||
- [`alphanumeric`](DOC.md#IsAlphanumeric): Ensures the string contains only letters and numbers.
|
||||
- [`credit-card`](DOC.md#IsAnyCreditCard): Ensures the string is a valid credit card number.
|
||||
- [`cidr`](DOC.md#IsCIDR): Ensures the string is a valid CIDR notation.
|
||||
- [`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.
|
||||
- [`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.
|
||||
- [`min-len`](DOC.md#func-minlen): Ensures the length of the given value (string, slice, or map) is at least n.
|
||||
- [`required`](DOC.md#func-required) Ensures the value is provided.
|
||||
- [`regexp`](DOC.md#func-makeregexpchecker) Ensured the string matches the pattern.
|
||||
- [`time`](DOC.md#func-istime) Ensured the string matches the provided time layout.
|
||||
- [`url`](DOC.md#IsURL): Ensures the string is a valid URL.
|
||||
This package currently provides the following checkers:
|
||||
|
||||
- [alphanumeric](doc/checkers/alphanumeric.md) checks if the given string consists of only alphanumeric characters.
|
||||
- [ascii](doc/checkers/ascii.md) checks if the given string consists of only ASCII characters.
|
||||
- [cidr](doc/checkers/cidr.md) checker checks if the value is a valid CIDR notation IP address and prefix length.
|
||||
- [credit-card](doc/checkers/credit_card.md) checks if the given value is a valid credit card number.
|
||||
- [digits](doc/checkers/digits.md) checks if the given string consists of only digit characters.
|
||||
- [email](doc/checkers/email.md) checks if the given string is an email address.
|
||||
- [fqdn](doc/checkers/fqdn.md) checks if the given string is a fully qualified domain name.
|
||||
- [ip](doc/checkers/ip.md) checks if the given value is an IP address.
|
||||
- [ipv4](doc/checkers/ipv4.md) checks if the given value is an IPv4 address.
|
||||
- [ipv6](doc/checkers/ipv6.md) checks if the given value is an IPv6 address.
|
||||
- [isbn](doc/checkers/isbn.md) checks if the given value is a valid ISBN number.
|
||||
- [luhn](doc/checkers/luhn.md) checks if the given number is valid based on the Luhn algorithm.
|
||||
- [mac](doc/checkers/mac.md) checks if the given value is a valid an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet IP over InfiniBand link-layer address.
|
||||
- [max](doc/checkers/max.md) checks if the given value is less than the given maximum.
|
||||
- [max-length](doc/checkers/maxlength.md) checks if the length of the given value is less than the given maximum length.
|
||||
- [min](doc/checkers/min.md) checks if the given value is greather than the given minimum.
|
||||
- [min-length](doc/checkers/minlength.md) checks if the length of the given value is greather than the given minimum length.
|
||||
- [regexp](doc/checkers/regexp.md) checks if the given string matches the regexp pattern.
|
||||
- [required](doc/checkers/required.md) checks if the required value is provided.
|
||||
- [same](doc/checkers/same.md) checks if the given value is equal to the value of the field with the given name.
|
||||
- [url](doc/checkers/url.md) checks if the given value is a valid URL.
|
||||
|
||||
# Normalizers Provided
|
||||
|
||||
- [`lower`](DOC.md#Lower): Converts the string to lowercase.
|
||||
- [`title`](DOC.md#Title): Converts the string to title case.
|
||||
- [`trim-left`](DOC.md#TrimLeft): Trims whitespace from the left side of the string.
|
||||
- [`trim-right`](DOC.md#TrimRight): Trims whitespace from the right side of the string.
|
||||
- [`trim`](DOC.md#TrimSpace): Trims whitespace from both sides of the string.
|
||||
- [`upper`](DOC.md#Upper): Converts the string to uppercase.
|
||||
- [`html-escape`](DOC.md#HTMLEscape): Escapes special characters in the string for HTML.
|
||||
- [`html-unescape`](DOC.md#HTMLUnescape): Unescapes special characters in the string for HTML.
|
||||
- [`url-escape`](DOC.md#URLEscape): Escapes special characters in the string for URLs.
|
||||
- [`url-unescape`](DOC.md#URLUnescape): Unescapes special characters in the string for URLs.
|
||||
This package currently provides the following normalizers. They can be mixed with the checkers when defining the validation steps for user data.
|
||||
|
||||
# Custom Checkers and Normalizers
|
||||
- [html-escape](doc/normalizers/html_escape.md) applies HTML escaping to special characters.
|
||||
- [html-unescape](doc/normalizers/html_unescape.md) applies HTML unescaping to special characters.
|
||||
- [lower](doc/normalizers/lower.md) maps all Unicode letters in the given value to their lower case.
|
||||
- [upper](doc/normalizers/upper.md) maps all Unicode letters in the given value to their upper case.
|
||||
- [title](doc/normalizers/title.md) maps the first letter of each word to their upper case.
|
||||
- [trim](doc/normalizers/trim.md) removes the whitespaces at the beginning and at the end of the given value.
|
||||
- [trim-left](doc/normalizers/trim_left.md) removes the whitespaces at the beginning of the given value.
|
||||
- [trim-right](doc/normalizers/trim_right.md) removes the whitespaces at the end of the given value.
|
||||
- [url-escape](doc/normalizers/url_escape.md) applies URL escaping to special characters.
|
||||
- [url-unescape](doc/normalizers/url_unescape.md) applies URL unescaping to special characters.
|
||||
|
||||
You can define custom checkers or normalizers and register them for use in your validation logic. Here is an example of how to create and register a custom checker:
|
||||
# Custom Checkers
|
||||
|
||||
To define a custom checker, you need to create a new function with the following parameters:
|
||||
|
||||
```golang
|
||||
checker.RegisterMaker("is-fruit", func(params string) v2.CheckFunc[reflect.Value] {
|
||||
return func(value reflect.Value) (reflect.Value, error) {
|
||||
stringValue := value.Interface().(string)
|
||||
|
||||
if stringValue == "apple" || stringValue == "banana" {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
return value, v2.NewCheckError("NOT_FRUIT")
|
||||
}
|
||||
})
|
||||
func CustomChecker(value, parent reflect.Value) Result {
|
||||
return ResultValid
|
||||
}
|
||||
```
|
||||
|
||||
In this example, the custom checker `is-fruit` checks if the input value is either "apple" or "banana". If the value is not one of these, it returns an error.
|
||||
|
||||
Once registered, you can use your custom checker in struct tags just like the built-in checkers:
|
||||
type MakeFunc
|
||||
You also need to create a make function that takes the checker configuration and returns a reference to the checker function.
|
||||
|
||||
```golang
|
||||
type Item struct {
|
||||
Name string `checkers:"is-fruit"`
|
||||
}
|
||||
|
||||
item := &Item{
|
||||
Name: "banana",
|
||||
}
|
||||
|
||||
errors, valid := v2.CheckStruct(item)
|
||||
if !valid {
|
||||
fmt.Println(errors)
|
||||
func CustomMaker(params string) CheckFunc {
|
||||
return CustomChecker
|
||||
}
|
||||
```
|
||||
|
||||
In this example, the `is-fruit` checker is used to validate that the `Name` field of the `Item` struct is either "apple" or "banana".
|
||||
|
||||
# Slice and Item Level Checkers
|
||||
|
||||
When adding checker struct tags to a slice, you can use the `@` prefix to indicate that the checker should be applied to the slice itself. Checkers without the `@` prefix will be applied to the individual items within the slice. Here is an example:
|
||||
Finally, you need to call the ```Register``` function to register your custom checker.
|
||||
|
||||
```golang
|
||||
type Person struct {
|
||||
Name string `checkers:"required"`
|
||||
Emails []string `checkers:"@max-len:2 max-len:64"`
|
||||
}
|
||||
checker.Register("custom-checker", CustomMaker)
|
||||
```
|
||||
|
||||
In this example:
|
||||
- `@max-len:2` ensures that the `Emails` slice itself has at most two items.
|
||||
- `max-len:64` ensures that each email string within the `Emails` slice has a maximum length of 64 characters.
|
||||
|
||||
# Localized Error Messages
|
||||
|
||||
When validation fails, Checker returns an error. By default, the [Error()](DOC.md#CheckError.Error) function provides a human-readable error message in `en-US` locale.
|
||||
Once you have registered your custom checker, you can use it by simply specifying its name.
|
||||
|
||||
```golang
|
||||
_, err := checker.IsEmail("abcd")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
// Output: Not a valid email address.
|
||||
}
|
||||
```
|
||||
|
||||
To get error messages in other languages, use the [ErrorWithLocale()](DOC.md#CheckError.ErrorWithLocale) function. By default, only `en-US` is registered. You can register additional languages by calling [RegisterLocale](DOC.md#RegisterLocale).
|
||||
|
||||
```golang
|
||||
// Register de-DE localized error messages.
|
||||
checker.RegisterLocale(locales.DeDE, locales.DeDEMessages)
|
||||
|
||||
_, err := checker.IsEmail("abcd")
|
||||
if err != nil {
|
||||
fmt.Println(err.ErrorWithLocale(locales.DeDE))
|
||||
// Output: Keine gültige E-Mail-Adresse.
|
||||
}
|
||||
```
|
||||
|
||||
You can also customize existing error messages or add new ones to `locales.EnUSMessages` and other locale maps.
|
||||
|
||||
```golang
|
||||
// Register the en-US localized error message for the custom NOT_FRUIT error code.
|
||||
locales.EnUSMessages["NOT_FRUIT"] = "Not a fruit name."
|
||||
|
||||
errors, valid := v2.CheckStruct(item)
|
||||
if !valid {
|
||||
fmt.Println(errors)
|
||||
// Output: map[Name:Not a fruit name.]
|
||||
}
|
||||
```
|
||||
|
||||
Error messages are generated using Golang template functions, allowing them to include variables.
|
||||
|
||||
```golang
|
||||
// Custrom checker error containing the item name.
|
||||
err := checker.NewCheckErrorWithData(
|
||||
"NOT_FRUIT",
|
||||
map[string]interface{}{
|
||||
"name": "abcd",
|
||||
},
|
||||
)
|
||||
|
||||
// Register the en-US localized error message for the custom NOT_FRUIT error code.
|
||||
locales.EnUSMessages["NOT_FRUIT"] = "Name {{ .name }} is not a fruit name."
|
||||
|
||||
errors, valid := v2.CheckStruct(item)
|
||||
if !valid {
|
||||
fmt.Println(errors)
|
||||
// Output: map[Name:Name abcd is not a fruit name.]
|
||||
type User struct {
|
||||
Username string `checkers:"custom-checker"`
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,43 +1,37 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameAlphanumeric is the name of the alphanumeric check.
|
||||
nameAlphanumeric = "alphanumeric"
|
||||
)
|
||||
// CheckerAlphanumeric is the name of the checker.
|
||||
const CheckerAlphanumeric = "alphanumeric"
|
||||
|
||||
var (
|
||||
// ErrNotAlphanumeric indicates that the given string contains non-alphanumeric characters.
|
||||
ErrNotAlphanumeric = NewCheckError("NOT_ALPHANUMERIC")
|
||||
)
|
||||
// ResultNotAlphanumeric indicates that the given string contains non-alphanumeric characters.
|
||||
const ResultNotAlphanumeric = "NOT_ALPHANUMERIC"
|
||||
|
||||
// IsAlphanumeric checks if the given string consists of only alphanumeric characters.
|
||||
func IsAlphanumeric(value string) (string, error) {
|
||||
func IsAlphanumeric(value string) Result {
|
||||
for _, c := range value {
|
||||
if !unicode.IsDigit(c) && !unicode.IsLetter(c) {
|
||||
return value, ErrNotAlphanumeric
|
||||
return ResultNotAlphanumeric
|
||||
}
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// checkAlphanumeric checks if the given string consists of only alphanumeric characters.
|
||||
func isAlphanumeric(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsAlphanumeric(value.Interface().(string))
|
||||
return value, err
|
||||
return ResultValid
|
||||
}
|
||||
|
||||
// makeAlphanumeric makes a checker function for the alphanumeric checker.
|
||||
func makeAlphanumeric(_ string) CheckFunc[reflect.Value] {
|
||||
return isAlphanumeric
|
||||
func makeAlphanumeric(_ string) CheckFunc {
|
||||
return checkAlphanumeric
|
||||
}
|
||||
|
||||
// checkAlphanumeric checks if the given string consists of only alphanumeric characters.
|
||||
func checkAlphanumeric(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsAlphanumeric(value.String())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,76 +1,69 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsAlphanumeric() {
|
||||
_, err := v2.IsAlphanumeric("ABcd1234")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
result := checker.IsAlphanumeric("ABcd1234")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAlphanumericInvalid(t *testing.T) {
|
||||
_, err := v2.IsAlphanumeric("-/")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
if checker.IsAlphanumeric("-/") == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAlphanumericValid(t *testing.T) {
|
||||
_, err := v2.IsAlphanumeric("ABcd1234")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if checker.IsAlphanumeric("ABcd1234") != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAlphanumericNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Person struct {
|
||||
Name int `checkers:"alphanumeric"`
|
||||
type User struct {
|
||||
Username int `checkers:"alphanumeric"`
|
||||
}
|
||||
|
||||
person := &Person{}
|
||||
user := &User{}
|
||||
|
||||
v2.CheckStruct(person)
|
||||
checker.Check(user)
|
||||
}
|
||||
|
||||
func TestCheckAlphanumericInvalid(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"alphanumeric"`
|
||||
type User struct {
|
||||
Username string `checkers:"alphanumeric"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "name-/",
|
||||
user := &User{
|
||||
Username: "user-/",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(person)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
_, valid := checker.Check(user)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAlphanumericValid(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"alphanumeric"`
|
||||
type User struct {
|
||||
Username string `checkers:"alphanumeric"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "ABcd1234",
|
||||
user := &User{
|
||||
Username: "ABcd1234",
|
||||
}
|
||||
|
||||
errs, ok := v2.CheckStruct(person)
|
||||
if !ok {
|
||||
t.Fatal(errs)
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
ascii.go
44
ascii.go
|
|
@ -1,43 +1,37 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameASCII is the name of the ASCII check.
|
||||
nameASCII = "ascii"
|
||||
)
|
||||
// CheckerASCII is the name of the checker.
|
||||
const CheckerASCII = "ascii"
|
||||
|
||||
var (
|
||||
// ErrNotASCII indicates that the given string contains non-ASCII characters.
|
||||
ErrNotASCII = NewCheckError("NOT_ASCII")
|
||||
)
|
||||
// ResultNotASCII indicates that the given string contains non-ASCII characters.
|
||||
const ResultNotASCII = "NOT_ASCII"
|
||||
|
||||
// IsASCII checks if the given string consists of only ASCII characters.
|
||||
func IsASCII(value string) (string, error) {
|
||||
func IsASCII(value string) Result {
|
||||
for _, c := range value {
|
||||
if c > unicode.MaxASCII {
|
||||
return value, ErrNotASCII
|
||||
return ResultNotASCII
|
||||
}
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// checkASCII checks if the given string consists of only ASCII characters.
|
||||
func isASCII(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsASCII(value.Interface().(string))
|
||||
return value, err
|
||||
return ResultValid
|
||||
}
|
||||
|
||||
// makeASCII makes a checker function for the ASCII checker.
|
||||
func makeASCII(_ string) CheckFunc[reflect.Value] {
|
||||
return isASCII
|
||||
func makeASCII(_ string) CheckFunc {
|
||||
return checkASCII
|
||||
}
|
||||
|
||||
// checkASCII checks if the given string consists of only ASCII characters.
|
||||
func checkASCII(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsASCII(value.String())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,48 +1,41 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsASCII() {
|
||||
_, err := v2.IsASCII("Checker")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
result := checker.IsASCII("Checker")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsASCIIInvalid(t *testing.T) {
|
||||
_, err := v2.IsASCII("𝄞 Music!")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
if checker.IsASCII("𝄞 Music!") == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsASCIIValid(t *testing.T) {
|
||||
_, err := v2.IsASCII("Checker")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if checker.IsASCII("Checker") != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckASCIINonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type User struct {
|
||||
Username int `checkers:"ascii"`
|
||||
Age int `checkers:"ascii"`
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
|
||||
v2.CheckStruct(user)
|
||||
checker.Check(user)
|
||||
}
|
||||
|
||||
func TestCheckASCIIInvalid(t *testing.T) {
|
||||
|
|
@ -54,9 +47,9 @@ func TestCheckASCIIInvalid(t *testing.T) {
|
|||
Username: "𝄞 Music!",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(user)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
_, valid := checker.Check(user)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -69,8 +62,8 @@ func TestCheckASCIIValid(t *testing.T) {
|
|||
Username: "checker",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(user)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
// 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 (
|
||||
"html/template"
|
||||
"strings"
|
||||
|
||||
"github.com/cinar/checker/v2/locales"
|
||||
)
|
||||
|
||||
// CheckError defines the check error.
|
||||
type CheckError struct {
|
||||
// Code is the error code.
|
||||
Code string
|
||||
|
||||
// data is the error data.
|
||||
Data map[string]interface{}
|
||||
}
|
||||
|
||||
const (
|
||||
// DefaultLocale is the default locale.
|
||||
DefaultLocale = locales.EnUS
|
||||
)
|
||||
|
||||
// errorMessages is the map of localized error messages.
|
||||
var errorMessages = map[string]map[string]string{
|
||||
locales.EnUS: locales.EnUSMessages,
|
||||
}
|
||||
|
||||
// NewCheckError creates a new check error with the given code.
|
||||
func NewCheckError(code string) *CheckError {
|
||||
return NewCheckErrorWithData(
|
||||
code,
|
||||
make(map[string]interface{}),
|
||||
)
|
||||
}
|
||||
|
||||
// NewCheckErrorWithData creates a new check error with the given code and data.
|
||||
func NewCheckErrorWithData(code string, data map[string]interface{}) *CheckError {
|
||||
return &CheckError{
|
||||
Code: code,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the error message for the check.
|
||||
func (c *CheckError) Error() string {
|
||||
return c.ErrorWithLocale(DefaultLocale)
|
||||
}
|
||||
|
||||
// Is reports whether the check error is the same as the target error.
|
||||
func (c *CheckError) Is(target error) bool {
|
||||
if other, ok := target.(*CheckError); ok {
|
||||
return c.Code == other.Code
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ErrorWithLocale returns the localized error message for the check with the given locale.
|
||||
func (c *CheckError) ErrorWithLocale(locale string) string {
|
||||
tmpl, err := template.New("error").Parse(getLocalizedErrorMessage(locale, c.Code))
|
||||
if err != nil {
|
||||
return c.Code
|
||||
}
|
||||
|
||||
var message strings.Builder
|
||||
if err := tmpl.Execute(&message, c.Data); err != nil {
|
||||
return c.Code
|
||||
}
|
||||
|
||||
return message.String()
|
||||
}
|
||||
|
||||
// RegisterLocale registers the localized error messages for the given locale.
|
||||
func RegisterLocale(locale string, messages map[string]string) {
|
||||
errorMessages[locale] = messages
|
||||
}
|
||||
|
||||
// getLocalizedErrorMessage returns the localized error message for the given locale and code.
|
||||
func getLocalizedErrorMessage(locale, code string) string {
|
||||
if messages, found := errorMessages[locale]; found {
|
||||
if message, exists := messages[code]; exists {
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
if messages, found := errorMessages[DefaultLocale]; found {
|
||||
if message, exists := messages[code]; exists {
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
// 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 (
|
||||
"io/fs"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker/v2/locales"
|
||||
)
|
||||
|
||||
func TestCheckErrorWithNotLocalizedCode(t *testing.T) {
|
||||
code := "TEST"
|
||||
|
||||
err := v2.NewCheckError(code)
|
||||
|
||||
if err.Error() != code {
|
||||
t.Fatalf("actual %s expected %s", err.Error(), code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckErrorWithLocalizedCode(t *testing.T) {
|
||||
code := "TEST"
|
||||
message := "Test message"
|
||||
|
||||
locales.EnUSMessages[code] = message
|
||||
|
||||
err := v2.NewCheckError(code)
|
||||
|
||||
if err.ErrorWithLocale("fr-FR") != message {
|
||||
t.Fatalf("actual %s expected %s", err.Error(), message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckErrorWithDefaultLocalizedCode(t *testing.T) {
|
||||
code := "TEST"
|
||||
message := "Test message"
|
||||
|
||||
locales.EnUSMessages[code] = message
|
||||
|
||||
err := v2.NewCheckError(code)
|
||||
|
||||
if err.Error() != message {
|
||||
t.Fatalf("actual %s expected %s", err.Error(), message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckErrorWithDataAndLocalizedCode(t *testing.T) {
|
||||
code := "TEST"
|
||||
message := "Test message {{.Name}}"
|
||||
|
||||
locales.EnUSMessages[code] = message
|
||||
|
||||
err := v2.NewCheckErrorWithData(code, map[string]interface{}{
|
||||
"Name": "Onur",
|
||||
})
|
||||
|
||||
expected := "Test message Onur"
|
||||
|
||||
if err.Error() != expected {
|
||||
t.Fatalf("actual %s expected %s", err.Error(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckErrorWithLocalizedCodeInvalidTemplate(t *testing.T) {
|
||||
code := "TEST"
|
||||
message := "Test message {{}"
|
||||
|
||||
locales.EnUSMessages[code] = message
|
||||
|
||||
err := v2.NewCheckError(code)
|
||||
|
||||
if err.Error() != code {
|
||||
t.Fatalf("actual %s expected %s", err.Error(), code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckErrorWithLocalizedCodeInvalidExecute(t *testing.T) {
|
||||
code := "TEST"
|
||||
message := "{{ len .Name}}"
|
||||
|
||||
locales.EnUSMessages[code] = message
|
||||
|
||||
err := v2.NewCheckError(code)
|
||||
|
||||
if err.Error() != code {
|
||||
t.Fatalf("actual %s expected %s", err.Error(), code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckErrorIsSuccess(t *testing.T) {
|
||||
code := "TEST"
|
||||
|
||||
err1 := v2.NewCheckError(code)
|
||||
err2 := v2.NewCheckError(code)
|
||||
|
||||
if !err1.Is(err2) {
|
||||
t.Fatalf("actual %t expected %t", err1.Is(err2), true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckErrorIsFailure(t *testing.T) {
|
||||
code1 := "TEST1"
|
||||
code2 := "TEST2"
|
||||
|
||||
err1 := v2.NewCheckError(code1)
|
||||
err2 := v2.NewCheckError(code2)
|
||||
|
||||
if err1.Is(err2) {
|
||||
t.Fatalf("actual %t expected %t", err1.Is(err2), false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckErrorIsFailureWithDifferentType(t *testing.T) {
|
||||
code := "TEST"
|
||||
|
||||
err1 := v2.NewCheckError(code)
|
||||
err2 := fs.ErrExist
|
||||
|
||||
if err1.Is(err2) {
|
||||
t.Fatalf("actual %t expected %t", err1.Is(err2), false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterLocale(t *testing.T) {
|
||||
locale := "de-DE"
|
||||
code := "TEST"
|
||||
message := "Testmeldung"
|
||||
|
||||
v2.RegisterLocale(locale, map[string]string{
|
||||
code: message,
|
||||
})
|
||||
|
||||
err := v2.NewCheckError(code)
|
||||
|
||||
if err.ErrorWithLocale("de-DE") != message {
|
||||
t.Fatalf("actual %s expected %s", err.Error(), message)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
// 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
|
||||
|
||||
// CheckFunc is a function that takes a value of type T and performs
|
||||
// a check on it. It returns the resulting value and any error that
|
||||
// occurred during the check.
|
||||
type CheckFunc[T any] func(value T) (T, error)
|
||||
247
checker.go
247
checker.go
|
|
@ -1,10 +1,11 @@
|
|||
// Copyright (c) 2023-2024 Onur Cinar.
|
||||
// Package checker is a Go library for validating user input through struct tags.
|
||||
//
|
||||
// https://github.com/cinar/checker
|
||||
//
|
||||
// Copyright 2023 Onur Cinar. All rights reserved.
|
||||
// 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 Checker is a Go library for validating user input through checker rules provided in struct tags.
|
||||
package v2
|
||||
package checker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -12,60 +13,83 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// checkerTag is the name of the field tag used for checker.
|
||||
checkerTag = "checkers"
|
||||
// Result is a unique textual identifier for the mistake.
|
||||
type Result string
|
||||
|
||||
// sliceConfigPrefix is the prefix used to distinguish slice-level checks from item-level checks.
|
||||
sliceConfigPrefix = "@"
|
||||
)
|
||||
// CheckFunc defines the checker function.
|
||||
type CheckFunc func(value, parent reflect.Value) Result
|
||||
|
||||
// checkStructJob defines a check strcut job.
|
||||
type checkStructJob struct {
|
||||
// MakeFunc defines the maker function.
|
||||
type MakeFunc func(params string) CheckFunc
|
||||
|
||||
// Mistakes provides mapping to checker result for the invalid fields.
|
||||
type Mistakes map[string]Result
|
||||
|
||||
type checkerJob struct {
|
||||
Parent reflect.Value
|
||||
Name string
|
||||
Value reflect.Value
|
||||
Config string
|
||||
}
|
||||
|
||||
// Check applies the given check functions to a value sequentially.
|
||||
// It returns the final value and the first encountered error, if any.
|
||||
func Check[T any](value T, checks ...CheckFunc[T]) (T, error) {
|
||||
var err error
|
||||
// ResultValid result indicates that the user input is valid.
|
||||
const ResultValid Result = "VALID"
|
||||
|
||||
for _, check := range checks {
|
||||
value, err = check(value)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// makers provides mapping to maker function for the checkers.
|
||||
var makers = map[string]MakeFunc{
|
||||
CheckerAlphanumeric: makeAlphanumeric,
|
||||
CheckerASCII: makeASCII,
|
||||
CheckerCreditCard: makeCreditCard,
|
||||
CheckerCidr: makeCidr,
|
||||
CheckerDigits: makeDigits,
|
||||
CheckerEmail: makeEmail,
|
||||
CheckerFqdn: makeFqdn,
|
||||
CheckerIP: makeIP,
|
||||
CheckerIPV4: makeIPV4,
|
||||
CheckerIPV6: makeIPV6,
|
||||
CheckerISBN: makeISBN,
|
||||
CheckerLuhn: makeLuhn,
|
||||
CheckerMac: makeMac,
|
||||
CheckerMax: makeMax,
|
||||
CheckerMaxLength: makeMaxLength,
|
||||
CheckerMin: makeMin,
|
||||
CheckerMinLength: makeMinLength,
|
||||
CheckerRegexp: makeRegexp,
|
||||
CheckerRequired: makeRequired,
|
||||
CheckerSame: makeSame,
|
||||
CheckerURL: makeURL,
|
||||
NormalizerHTMLEscape: makeHTMLEscape,
|
||||
NormalizerHTMLUnescape: makeHTMLUnescape,
|
||||
NormalizerLower: makeLower,
|
||||
NormalizerUpper: makeUpper,
|
||||
NormalizerTitle: makeTitle,
|
||||
NormalizerTrim: makeTrim,
|
||||
NormalizerTrimLeft: makeTrimLeft,
|
||||
NormalizerTrimRight: makeTrimRight,
|
||||
NormalizerURLEscape: makeURLEscape,
|
||||
NormalizerURLUnescape: makeURLUnescape,
|
||||
}
|
||||
|
||||
// Register registers the given checker name and the maker function.
|
||||
func Register(name string, maker MakeFunc) {
|
||||
makers[name] = maker
|
||||
}
|
||||
|
||||
// Check checks the given struct based on the checkers listed in each field's strcut tag named checkers.
|
||||
func Check(s interface{}) (Mistakes, bool) {
|
||||
root := reflect.Indirect(reflect.ValueOf(s))
|
||||
if root.Kind() != reflect.Struct {
|
||||
panic("expecting struct")
|
||||
}
|
||||
|
||||
return value, err
|
||||
}
|
||||
mistakes := Mistakes{}
|
||||
|
||||
// CheckWithConfig applies the check functions specified by the config string to the given value.
|
||||
// It returns the modified value and the first encountered error, if any.
|
||||
func CheckWithConfig[T any](value T, config string) (T, error) {
|
||||
newValue, err := ReflectCheckWithConfig(reflect.Indirect(reflect.ValueOf(value)), config)
|
||||
return newValue.Interface().(T), err
|
||||
}
|
||||
|
||||
// ReflectCheckWithConfig applies the check functions specified by the config string
|
||||
// to the given reflect.Value. It returns the modified reflect.Value and the first
|
||||
// encountered error, if any.
|
||||
func ReflectCheckWithConfig(value reflect.Value, config string) (reflect.Value, error) {
|
||||
return Check(value, makeChecks(config)...)
|
||||
}
|
||||
|
||||
// CheckStruct checks the given struct based on the validation rules specified in the
|
||||
// "checker" tag of each struct field. It returns a map of field names to their
|
||||
// corresponding errors, and a boolean indicating if all checks passed.
|
||||
func CheckStruct(st any) (map[string]error, bool) {
|
||||
errs := make(map[string]error)
|
||||
|
||||
jobs := []*checkStructJob{
|
||||
jobs := []checkerJob{
|
||||
{
|
||||
Name: "",
|
||||
Value: reflect.Indirect(reflect.ValueOf(st)),
|
||||
Parent: reflect.ValueOf(nil),
|
||||
Name: "",
|
||||
Value: root,
|
||||
Config: "",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -73,82 +97,73 @@ func CheckStruct(st any) (map[string]error, bool) {
|
|||
job := jobs[0]
|
||||
jobs = jobs[1:]
|
||||
|
||||
switch job.Value.Kind() {
|
||||
case reflect.Struct:
|
||||
if job.Value.Kind() == reflect.Struct {
|
||||
for i := 0; i < job.Value.NumField(); i++ {
|
||||
field := job.Value.Type().Field(i)
|
||||
addJob := field.Type.Kind() == reflect.Struct
|
||||
config := ""
|
||||
|
||||
name := fieldName(job.Name, field)
|
||||
value := reflect.Indirect(job.Value.FieldByIndex(field.Index))
|
||||
if !addJob {
|
||||
config = field.Tag.Get("checkers")
|
||||
addJob = config != ""
|
||||
}
|
||||
|
||||
jobs = append(jobs, &checkStructJob{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Config: field.Tag.Get(checkerTag),
|
||||
})
|
||||
if addJob {
|
||||
name := field.Name
|
||||
if job.Name != "" {
|
||||
name = job.Name + "." + name
|
||||
}
|
||||
|
||||
jobs = append(jobs, checkerJob{
|
||||
Parent: job.Value,
|
||||
Name: name,
|
||||
Value: reflect.Indirect(job.Value.FieldByIndex(field.Index)),
|
||||
Config: config,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
sliceConfig, itemConfig := splitSliceConfig(job.Config)
|
||||
job.Config = sliceConfig
|
||||
|
||||
for i := 0; i < job.Value.Len(); i++ {
|
||||
name := fmt.Sprintf("%s[%d]", job.Name, i)
|
||||
value := reflect.Indirect(job.Value.Index(i))
|
||||
|
||||
jobs = append(jobs, &checkStructJob{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Config: itemConfig,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if job.Config != "" {
|
||||
newValue, err := ReflectCheckWithConfig(job.Value, job.Config)
|
||||
if err != nil {
|
||||
errs[job.Name] = err
|
||||
}
|
||||
|
||||
job.Value.Set(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
return errs, len(errs) == 0
|
||||
}
|
||||
|
||||
// fieldName returns the field name. If a "json" tag is present, it uses the
|
||||
// tag value instead. It also prepends the parent struct's name (if any) to
|
||||
// create a fully qualified field name.
|
||||
func fieldName(prefix string, field reflect.StructField) string {
|
||||
// Default to field name
|
||||
name := field.Name
|
||||
|
||||
// Use json tag if present
|
||||
if jsonTag, ok := field.Tag.Lookup("json"); ok {
|
||||
name = jsonTag
|
||||
}
|
||||
|
||||
// Prepend parent name
|
||||
if prefix != "" {
|
||||
name = prefix + "." + name
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// splitSliceConfig splits config string into slice and item-level configurations.
|
||||
func splitSliceConfig(config string) (string, string) {
|
||||
sliceFileds := make([]string, 0)
|
||||
itemFields := make([]string, 0)
|
||||
|
||||
for _, configField := range strings.Fields(config) {
|
||||
if strings.HasPrefix(configField, sliceConfigPrefix) {
|
||||
sliceFileds = append(sliceFileds, strings.TrimPrefix(configField, sliceConfigPrefix))
|
||||
} else {
|
||||
itemFields = append(itemFields, configField)
|
||||
for _, checker := range initCheckers(job.Config) {
|
||||
if result := checker(job.Value, job.Parent); result != ResultValid {
|
||||
mistakes[job.Name] = result
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(sliceFileds, " "), strings.Join(itemFields, " ")
|
||||
return mistakes, len(mistakes) == 0
|
||||
}
|
||||
|
||||
// initCheckers initializes the checkers provided in the config.
|
||||
func initCheckers(config string) []CheckFunc {
|
||||
fields := strings.Fields(config)
|
||||
checkers := make([]CheckFunc, len(fields))
|
||||
|
||||
for i, field := range fields {
|
||||
name, params, _ := strings.Cut(field, ":")
|
||||
|
||||
maker, ok := makers[name]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("checker %s is unknown", name))
|
||||
}
|
||||
|
||||
checkers[i] = maker(params)
|
||||
}
|
||||
|
||||
return checkers
|
||||
}
|
||||
|
||||
// numberOf gives value's numerical value given that it is either an int or a float.
|
||||
func numberOf(value reflect.Value) float64 {
|
||||
switch value.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return float64(value.Int())
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return value.Float()
|
||||
|
||||
default:
|
||||
panic("expecting int or float")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
117
checker2.go
Normal file
117
checker2.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
FieldIndex []int
|
||||
Checkers []CheckFunc
|
||||
}
|
||||
|
||||
// rulesRegistry stores a collection of rules associated with a specific type.
|
||||
var rulesRegistry = map[reflect.Type][]*Rule{}
|
||||
|
||||
// checkersCache stores checkers associated with a specific config.
|
||||
var checkersCache = map[string]CheckFunc{}
|
||||
|
||||
// RegisterRulesFromConfig registers rules based on the provided struct type and config map.
|
||||
func RegisterRulesFromConfig(structType reflect.Type, nameToConfig map[string]string) error {
|
||||
rules := make([]*Rule, len(nameToConfig))
|
||||
|
||||
for name, config := range nameToConfig {
|
||||
field, ok := structType.FieldByName(name)
|
||||
if !ok {
|
||||
return fmt.Errorf("field %s not found", name)
|
||||
}
|
||||
|
||||
checkers, err := initCheckersFromConfig(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("field %s has errors: %w", name, err)
|
||||
}
|
||||
|
||||
rules = append(rules, &Rule{
|
||||
FieldIndex: field.Index,
|
||||
Checkers: checkers,
|
||||
})
|
||||
}
|
||||
|
||||
rulesRegistry[structType] = rules
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterRulesFromTag registers rules based on the provided struct type and field tags.
|
||||
func RegisterRulesFromTag(structType reflect.Type) error {
|
||||
structTypes := []reflect.Type{structType}
|
||||
|
||||
for len(structTypes) > 0 {
|
||||
structType := structTypes[0]
|
||||
structTypes = structTypes[1:]
|
||||
|
||||
// Skip already registed structs
|
||||
_, found := rulesRegistry[structType]
|
||||
if found {
|
||||
continue
|
||||
}
|
||||
|
||||
var rules []*Rule
|
||||
|
||||
for i := range structType.NumField() {
|
||||
field := structType.Field(i)
|
||||
|
||||
// Queue the nested structs
|
||||
if field.Type.Kind() == reflect.Struct {
|
||||
structTypes = append(structTypes, field.Type)
|
||||
continue
|
||||
}
|
||||
|
||||
config := field.Tag.Get("checkers")
|
||||
if config == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
checkers, err := initCheckersFromConfig(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("field %s has errors: %w", field.Name, err)
|
||||
}
|
||||
|
||||
rules = append(rules, &Rule{
|
||||
FieldIndex: field.Index,
|
||||
Checkers: checkers,
|
||||
})
|
||||
}
|
||||
|
||||
rulesRegistry[structType] = rules
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initCheckersFromConfig parses the given config string and initializes checker instances.
|
||||
func initCheckersFromConfig(config string) ([]CheckFunc, error) {
|
||||
items := strings.Fields(config)
|
||||
checkers := make([]CheckFunc, len(items))
|
||||
|
||||
for i, item := range items {
|
||||
checker, ok := checkersCache[item]
|
||||
if ok {
|
||||
checkers[i] = checker
|
||||
continue
|
||||
}
|
||||
|
||||
name, params, _ := strings.Cut(item, ":")
|
||||
|
||||
maker, ok := makers[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("checker %s not found", name)
|
||||
}
|
||||
|
||||
checkers[i] = maker(params)
|
||||
checkersCache[item] = checkers[i]
|
||||
}
|
||||
|
||||
return checkers, nil
|
||||
}
|
||||
41
checker2_test.go
Normal file
41
checker2_test.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package checker_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func TestRegisterRulesFromConfig(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Email string
|
||||
}
|
||||
|
||||
err := checker.RegisterRulesFromConfig(reflect.TypeFor[Person](), map[string]string{
|
||||
"Name": "trim required",
|
||||
"Email": "required email",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterRulesFromTag(t *testing.T) {
|
||||
type Address struct {
|
||||
Street string `checkers:"required"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string `checkers:"trim required"`
|
||||
Email string `checkers:"required email"`
|
||||
Home Address
|
||||
Work Address
|
||||
}
|
||||
|
||||
err := checker.RegisterRulesFromTag(reflect.TypeFor[Person]())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
285
checker_test.go
285
checker_test.go
|
|
@ -1,198 +1,175 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleCheck() {
|
||||
name := " Onur Cinar "
|
||||
func TestInitCheckersUnknown(t *testing.T) {
|
||||
defer FailIfNoPanic(t)
|
||||
|
||||
name, err := v2.Check(name, v2.TrimSpace, v2.Required)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(name)
|
||||
// Output: Onur Cinar
|
||||
initCheckers("unknown")
|
||||
}
|
||||
|
||||
func ExampleCheckStruct() {
|
||||
func TestInitCheckersKnwon(t *testing.T) {
|
||||
checkers := initCheckers("required")
|
||||
|
||||
if len(checkers) != 1 {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if reflect.ValueOf(checkers[0]).Pointer() != reflect.ValueOf(checkRequired).Pointer() {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
var checker CheckFunc = func(_, _ reflect.Value) Result {
|
||||
return ResultValid
|
||||
}
|
||||
|
||||
var maker MakeFunc = func(_ string) CheckFunc {
|
||||
return checker
|
||||
}
|
||||
|
||||
name := "test"
|
||||
|
||||
Register(name, maker)
|
||||
|
||||
checkers := initCheckers(name)
|
||||
|
||||
if len(checkers) != 1 {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if reflect.ValueOf(checkers[0]).Pointer() != reflect.ValueOf(checker).Pointer() {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckInvalid(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"trim required"`
|
||||
Name string `checkers:"required"`
|
||||
}
|
||||
|
||||
person := &Person{}
|
||||
|
||||
mistakes, valid := Check(person)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if len(mistakes) != 1 {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if mistakes["Name"] != ResultRequired {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckValid(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"required"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: " Onur Cinar ",
|
||||
Name: "Onur",
|
||||
}
|
||||
|
||||
errs, ok := v2.CheckStruct(person)
|
||||
if !ok {
|
||||
fmt.Println(errs)
|
||||
return
|
||||
mistakes, valid := Check(person)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
fmt.Println(person.Name)
|
||||
// Output: Onur Cinar
|
||||
}
|
||||
|
||||
func TestCheckTrimSpaceRequiredSuccess(t *testing.T) {
|
||||
input := " test "
|
||||
expected := "test"
|
||||
|
||||
actual, err := v2.Check(input, v2.TrimSpace, v2.Required)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
if len(mistakes) != 0 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckTrimSpaceRequiredMissing(t *testing.T) {
|
||||
input := " "
|
||||
expected := ""
|
||||
func TestCheckNoStruct(t *testing.T) {
|
||||
defer FailIfNoPanic(t)
|
||||
|
||||
actual, err := v2.Check(input, v2.TrimSpace, v2.Required)
|
||||
if !errors.Is(err, v2.ErrRequired) {
|
||||
t.Fatalf("got unexpected error %v", err)
|
||||
}
|
||||
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
s := "unknown"
|
||||
Check(s)
|
||||
}
|
||||
|
||||
func TestCheckWithConfigSuccess(t *testing.T) {
|
||||
input := " test "
|
||||
expected := "test"
|
||||
|
||||
actual, err := v2.CheckWithConfig(input, "trim required")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckWithConfigRequiredMissing(t *testing.T) {
|
||||
input := " "
|
||||
expected := ""
|
||||
|
||||
actual, err := v2.CheckWithConfig(input, "trim required")
|
||||
if !errors.Is(err, v2.ErrRequired) {
|
||||
t.Fatalf("got unexpected error %v", err)
|
||||
}
|
||||
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckStructSuccess(t *testing.T) {
|
||||
func TestCheckNestedStruct(t *testing.T) {
|
||||
type Address struct {
|
||||
Street string `checkers:"required"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string `checkers:"required"`
|
||||
Address *Address
|
||||
Name string `checkers:"required"`
|
||||
Home Address
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "Onur Cinar",
|
||||
Address: &Address{
|
||||
Street: "1234 Main",
|
||||
},
|
||||
person := &Person{}
|
||||
|
||||
mistakes, valid := Check(person)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
errors, ok := v2.CheckStruct(person)
|
||||
if !ok {
|
||||
t.Fatalf("got unexpected errors %v", errors)
|
||||
if len(mistakes) != 2 {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if mistakes["Name"] != ResultRequired {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if mistakes["Home.Street"] != ResultRequired {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckStructRequiredMissing(t *testing.T) {
|
||||
func TestNumberOfInvalid(t *testing.T) {
|
||||
defer FailIfNoPanic(t)
|
||||
|
||||
s := "invalid"
|
||||
|
||||
numberOf(reflect.ValueOf(s))
|
||||
}
|
||||
|
||||
func TestNumberOfInt(t *testing.T) {
|
||||
n := 10
|
||||
|
||||
if numberOf(reflect.ValueOf(n)) != float64(n) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumberOfFloat(t *testing.T) {
|
||||
n := float64(10.10)
|
||||
|
||||
if numberOf(reflect.ValueOf(n)) != n {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckerNamesAreLowerCase(t *testing.T) {
|
||||
for checker := range makers {
|
||||
if strings.ToLower(checker) != checker {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCheck(b *testing.B) {
|
||||
type Address struct {
|
||||
Street string `checkers:"required"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string `checkers:"required"`
|
||||
Address *Address
|
||||
Name string `checkers:"required"`
|
||||
Home Address
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "",
|
||||
Address: &Address{
|
||||
Street: "",
|
||||
},
|
||||
}
|
||||
person := &Person{}
|
||||
|
||||
errs, ok := v2.CheckStruct(person)
|
||||
if ok {
|
||||
t.Fatal("expected errors")
|
||||
}
|
||||
|
||||
if !errors.Is(errs["Name"], v2.ErrRequired) {
|
||||
t.Fatalf("expected name required %v", errs)
|
||||
}
|
||||
|
||||
if !errors.Is(errs["Address.Street"], v2.ErrRequired) {
|
||||
t.Fatalf("expected streed required %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckStructCustomName(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `json:"name" checkers:"required"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "",
|
||||
}
|
||||
|
||||
errs, ok := v2.CheckStruct(person)
|
||||
if ok {
|
||||
t.Fatal("expected errors")
|
||||
}
|
||||
|
||||
if !errors.Is(errs["name"], v2.ErrRequired) {
|
||||
t.Fatalf("expected name required %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckStructSlice(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"required"`
|
||||
Emails []string `checkers:"@max-len:1 max-len:4"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "Onur Cinar",
|
||||
Emails: []string{
|
||||
"onur.cinar",
|
||||
},
|
||||
}
|
||||
|
||||
errs, ok := v2.CheckStruct(person)
|
||||
if ok {
|
||||
t.Fatal("expected errors")
|
||||
}
|
||||
|
||||
if !errors.Is(errs["Emails[0]"], v2.ErrMaxLen) {
|
||||
t.Fatalf("expected email max len")
|
||||
for n := 0; n < b.N; n++ {
|
||||
Check(person)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
cidr.go
44
cidr.go
|
|
@ -1,42 +1,36 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameCIDR is the name of the CIDR check.
|
||||
nameCIDR = "cidr"
|
||||
)
|
||||
// CheckerCidr is the name of the checker.
|
||||
const CheckerCidr = "cidr"
|
||||
|
||||
var (
|
||||
// ErrNotCIDR indicates that the given value is not a valid CIDR.
|
||||
ErrNotCIDR = NewCheckError("NOT_CIDR")
|
||||
)
|
||||
// ResultNotCidr indicates that the given value is not a valid CIDR.
|
||||
const ResultNotCidr = "NOT_CIDR"
|
||||
|
||||
// IsCIDR checks if the value is a valid CIDR notation IP address and prefix length.
|
||||
func IsCIDR(value string) (string, error) {
|
||||
// IsCidr checker checks if the value is a valid CIDR notation IP address and prefix length.
|
||||
func IsCidr(value string) Result {
|
||||
_, _, err := net.ParseCIDR(value)
|
||||
if err != nil {
|
||||
return value, ErrNotCIDR
|
||||
return ResultNotCidr
|
||||
}
|
||||
|
||||
return value, nil
|
||||
return ResultValid
|
||||
}
|
||||
|
||||
// checkCIDR checks if the value is a valid CIDR notation IP address and prefix length.
|
||||
func checkCIDR(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsCIDR(value.Interface().(string))
|
||||
return value, err
|
||||
// makeCidr makes a checker function for the ip checker.
|
||||
func makeCidr(_ string) CheckFunc {
|
||||
return checkCidr
|
||||
}
|
||||
|
||||
// makeCIDR makes a checker function for the CIDR checker.
|
||||
func makeCIDR(_ string) CheckFunc[reflect.Value] {
|
||||
return checkCIDR
|
||||
// checkCidr checker checks if the value is a valid CIDR notation IP address and prefix length.
|
||||
func checkCidr(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsCidr(value.String())
|
||||
}
|
||||
|
|
|
|||
55
cidr_test.go
55
cidr_test.go
|
|
@ -1,40 +1,33 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsCIDR() {
|
||||
_, err := v2.IsCIDR("2001:db8::/32")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
func ExampleIsCidr() {
|
||||
result := checker.IsCidr("2001:db8::/32")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCIDRInvalid(t *testing.T) {
|
||||
_, err := v2.IsCIDR("900.800.200.100//24")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
func TestIsCidrInvalid(t *testing.T) {
|
||||
if checker.IsCidr("900.800.200.100//24") == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCIDRValid(t *testing.T) {
|
||||
_, err := v2.IsCIDR("2001:db8::/32")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
func TestIsCidrValid(t *testing.T) {
|
||||
if checker.IsCidr("2001:db8::/32") != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCIDRNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
func TestCheckCidrNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Network struct {
|
||||
Subnet int `checkers:"cidr"`
|
||||
|
|
@ -42,10 +35,10 @@ func TestCheckCIDRNonString(t *testing.T) {
|
|||
|
||||
network := &Network{}
|
||||
|
||||
v2.CheckStruct(network)
|
||||
checker.Check(network)
|
||||
}
|
||||
|
||||
func TestCheckCIDRInvalid(t *testing.T) {
|
||||
func TestCheckCidrInvalid(t *testing.T) {
|
||||
type Network struct {
|
||||
Subnet string `checkers:"cidr"`
|
||||
}
|
||||
|
|
@ -54,13 +47,13 @@ func TestCheckCIDRInvalid(t *testing.T) {
|
|||
Subnet: "900.800.200.100//24",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
_, valid := checker.Check(network)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCIDRValid(t *testing.T) {
|
||||
func TestCheckCidrValid(t *testing.T) {
|
||||
type Network struct {
|
||||
Subnet string `checkers:"cidr"`
|
||||
}
|
||||
|
|
@ -69,8 +62,8 @@ func TestCheckCIDRValid(t *testing.T) {
|
|||
Subnet: "192.0.2.0/24",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
_, valid := checker.Check(network)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
134
credit_card.go
134
credit_card.go
|
|
@ -1,9 +1,4 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
|
@ -11,108 +6,104 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameCreditCard is the name of the credit card check.
|
||||
nameCreditCard = "credit-card"
|
||||
)
|
||||
// CheckerCreditCard is the name of the checker.
|
||||
const CheckerCreditCard = "credit-card"
|
||||
|
||||
var (
|
||||
// ErrNotCreditCard indicates that the given value is not a valid credit card number.
|
||||
ErrNotCreditCard = NewCheckError("NOT_CREDIT_CARD")
|
||||
// ResultNotCreditCard indicates that the given value is not a valid credit card number.
|
||||
const ResultNotCreditCard = "NOT_CREDIT_CARD"
|
||||
|
||||
// amexExpression is the regexp for the AMEX cards. They start with 34 or 37, and has 15 digits.
|
||||
amexExpression = "(?:^(?:3[47])[0-9]{13}$)"
|
||||
amexPattern = regexp.MustCompile(amexExpression)
|
||||
// amexExpression is the regexp for the AMEX cards. They start with 34 or 37, and has 15 digits.
|
||||
var amexExpression = "(?:^(?:3[47])[0-9]{13}$)"
|
||||
var amexPattern = regexp.MustCompile(amexExpression)
|
||||
|
||||
// dinersExpression is the regexp for the Diners cards. They start with 305, 36, 38, and has 14 digits.
|
||||
dinersExpression = "(?:^3(?:(?:05[0-9]{11})|(?:[68][0-9]{12}))$)"
|
||||
dinersPattern = regexp.MustCompile(dinersExpression)
|
||||
// dinersExpression is the regexp for the Diners cards. They start with 305, 36, 38, and has 14 digits.
|
||||
var dinersExpression = "(?:^3(?:(?:05[0-9]{11})|(?:[68][0-9]{12}))$)"
|
||||
var dinersPattern = regexp.MustCompile(dinersExpression)
|
||||
|
||||
// discoverExpression is the regexp for the Discover cards. They start with 6011 and has 16 digits.
|
||||
discoverExpression = "(?:^6011[0-9]{12}$)"
|
||||
discoverPattern = regexp.MustCompile(discoverExpression)
|
||||
// discoverExpression is the regexp for the Discover cards. They start with 6011 and has 16 digits.
|
||||
var discoverExpression = "(?:^6011[0-9]{12}$)"
|
||||
var discoverPattern = regexp.MustCompile(discoverExpression)
|
||||
|
||||
// jcbExpression is the regexp for the JCB 15 cards. They start with 2131, 1800, and has 15 digits, or start with 35 and has 16 digits.
|
||||
jcbExpression = "(?:^(?:(?:2131)|(?:1800)|(?:35[0-9]{3}))[0-9]{11}$)"
|
||||
jcbPattern = regexp.MustCompile(jcbExpression)
|
||||
// jcbExpression is the regexp for the JCB 15 cards. They start with 2131, 1800, and has 15 digits, or start with 35 and has 16 digits.
|
||||
var jcbExpression = "(?:^(?:(?:2131)|(?:1800)|(?:35[0-9]{3}))[0-9]{11}$)"
|
||||
var jcbPattern = regexp.MustCompile(jcbExpression)
|
||||
|
||||
// masterCardExpression is the regexp for the MasterCard cards. They start with 51, 52, 53, 54, or 55, and has 15 digits.
|
||||
masterCardExpression = "(?:^5[12345][0-9]{14}$)"
|
||||
masterCardPattern = regexp.MustCompile(masterCardExpression)
|
||||
// masterCardExpression is the regexp for the MasterCard cards. They start with 51, 52, 53, 54, or 55, and has 15 digits.
|
||||
var masterCardExpression = "(?:^5[12345][0-9]{14}$)"
|
||||
var masterCardPattern = regexp.MustCompile(masterCardExpression)
|
||||
|
||||
// unionPayExpression is the regexp for the UnionPay cards. They start either with 62 or 67, and has 16 digits, or they start with 81 and has 16 to 19 digits.
|
||||
unionPayExpression = "(?:(?:6[27][0-9]{14})|(?:81[0-9]{14,17})^$)"
|
||||
unionPayPattern = regexp.MustCompile(unionPayExpression)
|
||||
// unionPayExpression is the regexp for the UnionPay cards. They start either with 62 or 67, and has 16 digits, or they start with 81 and has 16 to 19 digits.
|
||||
var unionPayExpression = "(?:(?:6[27][0-9]{14})|(?:81[0-9]{14,17})^$)"
|
||||
var unionPayPattern = regexp.MustCompile(unionPayExpression)
|
||||
|
||||
// visaExpression is the regexp for the Visa cards. They start with 4 and has 13 or 16 digits.
|
||||
visaExpression = "(?:^4[0-9]{12}(?:[0-9]{3})?$)"
|
||||
visaPattern = regexp.MustCompile(visaExpression)
|
||||
// visaExpression is the regexp for the Visa cards. They start with 4 and has 13 or 16 digits.
|
||||
var visaExpression = "(?:^4[0-9]{12}(?:[0-9]{3})?$)"
|
||||
var visaPattern = regexp.MustCompile(visaExpression)
|
||||
|
||||
// anyCreditCardPattern is the regexp for any credit card.
|
||||
anyCreditCardPattern = regexp.MustCompile(strings.Join([]string{
|
||||
amexExpression,
|
||||
dinersExpression,
|
||||
discoverExpression,
|
||||
jcbExpression,
|
||||
masterCardExpression,
|
||||
unionPayExpression,
|
||||
visaExpression,
|
||||
}, "|"))
|
||||
// anyCreditCardPattern is the regexp for any credit card.
|
||||
var anyCreditCardPattern = regexp.MustCompile(strings.Join([]string{
|
||||
amexExpression,
|
||||
dinersExpression,
|
||||
discoverExpression,
|
||||
jcbExpression,
|
||||
masterCardExpression,
|
||||
unionPayExpression,
|
||||
visaExpression,
|
||||
}, "|"))
|
||||
|
||||
// creditCardPatterns is the mapping for credit card names to patterns.
|
||||
creditCardPatterns = map[string]*regexp.Regexp{
|
||||
"amex": amexPattern,
|
||||
"diners": dinersPattern,
|
||||
"discover": discoverPattern,
|
||||
"jcb": jcbPattern,
|
||||
"mastercard": masterCardPattern,
|
||||
"unionpay": unionPayPattern,
|
||||
"visa": visaPattern,
|
||||
}
|
||||
)
|
||||
// creditCardPatterns is the mapping for credit card names to patterns.
|
||||
var creditCardPatterns = map[string]*regexp.Regexp{
|
||||
"amex": amexPattern,
|
||||
"diners": dinersPattern,
|
||||
"discover": discoverPattern,
|
||||
"jcb": jcbPattern,
|
||||
"mastercard": masterCardPattern,
|
||||
"unionpay": unionPayPattern,
|
||||
"visa": visaPattern,
|
||||
}
|
||||
|
||||
// IsAnyCreditCard checks if the given value is a valid credit card number.
|
||||
func IsAnyCreditCard(number string) (string, error) {
|
||||
func IsAnyCreditCard(number string) Result {
|
||||
return isCreditCard(number, anyCreditCardPattern)
|
||||
}
|
||||
|
||||
// IsAmexCreditCard checks if the given valie is a valid AMEX credit card.
|
||||
func IsAmexCreditCard(number string) (string, error) {
|
||||
func IsAmexCreditCard(number string) Result {
|
||||
return isCreditCard(number, amexPattern)
|
||||
}
|
||||
|
||||
// IsDinersCreditCard checks if the given valie is a valid Diners credit card.
|
||||
func IsDinersCreditCard(number string) (string, error) {
|
||||
func IsDinersCreditCard(number string) Result {
|
||||
return isCreditCard(number, dinersPattern)
|
||||
}
|
||||
|
||||
// IsDiscoverCreditCard checks if the given valie is a valid Discover credit card.
|
||||
func IsDiscoverCreditCard(number string) (string, error) {
|
||||
func IsDiscoverCreditCard(number string) Result {
|
||||
return isCreditCard(number, discoverPattern)
|
||||
}
|
||||
|
||||
// IsJcbCreditCard checks if the given valie is a valid JCB 15 credit card.
|
||||
func IsJcbCreditCard(number string) (string, error) {
|
||||
func IsJcbCreditCard(number string) Result {
|
||||
return isCreditCard(number, jcbPattern)
|
||||
}
|
||||
|
||||
// IsMasterCardCreditCard checks if the given valie is a valid MasterCard credit card.
|
||||
func IsMasterCardCreditCard(number string) (string, error) {
|
||||
func IsMasterCardCreditCard(number string) Result {
|
||||
return isCreditCard(number, masterCardPattern)
|
||||
}
|
||||
|
||||
// IsUnionPayCreditCard checks if the given valie is a valid UnionPay credit card.
|
||||
func IsUnionPayCreditCard(number string) (string, error) {
|
||||
func IsUnionPayCreditCard(number string) Result {
|
||||
return isCreditCard(number, unionPayPattern)
|
||||
}
|
||||
|
||||
// IsVisaCreditCard checks if the given valie is a valid Visa credit card.
|
||||
func IsVisaCreditCard(number string) (string, error) {
|
||||
func IsVisaCreditCard(number string) Result {
|
||||
return isCreditCard(number, visaPattern)
|
||||
}
|
||||
|
||||
// makeCreditCard makes a checker function for the credit card checker.
|
||||
func makeCreditCard(config string) CheckFunc[reflect.Value] {
|
||||
func makeCreditCard(config string) CheckFunc {
|
||||
patterns := []*regexp.Regexp{}
|
||||
|
||||
if config != "" {
|
||||
|
|
@ -128,7 +119,7 @@ func makeCreditCard(config string) CheckFunc[reflect.Value] {
|
|||
patterns = append(patterns, anyCreditCardPattern)
|
||||
}
|
||||
|
||||
return func(value reflect.Value) (reflect.Value, error) {
|
||||
return func(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
|
@ -136,21 +127,20 @@ func makeCreditCard(config string) CheckFunc[reflect.Value] {
|
|||
number := value.String()
|
||||
|
||||
for _, pattern := range patterns {
|
||||
_, err := isCreditCard(number, pattern)
|
||||
if err == nil {
|
||||
return value, nil
|
||||
if isCreditCard(number, pattern) == ResultValid {
|
||||
return ResultValid
|
||||
}
|
||||
}
|
||||
|
||||
return value, ErrNotCreditCard
|
||||
return ResultNotCreditCard
|
||||
}
|
||||
}
|
||||
|
||||
// isCreditCard checks if the given number based on the given credit card pattern and the Luhn algorithm check digit.
|
||||
func isCreditCard(number string, pattern *regexp.Regexp) (string, error) {
|
||||
func isCreditCard(number string, pattern *regexp.Regexp) Result {
|
||||
if !pattern.MatchString(number) {
|
||||
return number, ErrNotCreditCard
|
||||
return ResultNotCreditCard
|
||||
}
|
||||
|
||||
return IsLUHN(number)
|
||||
return IsLuhn(number)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,10 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
// Test numbers from https://stripe.com/docs/testing
|
||||
|
|
@ -35,213 +30,212 @@ func changeToInvalidLuhn(number string) string {
|
|||
}
|
||||
|
||||
func ExampleIsAnyCreditCard() {
|
||||
_, err := v2.IsAnyCreditCard("6011111111111117")
|
||||
result := checker.IsAnyCreditCard("6011111111111117")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAnyCreditCardValid(t *testing.T) {
|
||||
_, err := v2.IsAnyCreditCard(amexCard)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
if checker.IsAnyCreditCard(amexCard) != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAnyCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsAnyCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
if checker.IsAnyCreditCard(invalidCard) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAnyCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsAnyCreditCard(changeToInvalidLuhn(amexCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
if checker.IsAnyCreditCard(changeToInvalidLuhn(amexCard)) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsAmexCreditCard() {
|
||||
_, err := v2.IsAmexCreditCard("378282246310005")
|
||||
result := checker.IsAmexCreditCard("378282246310005")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAmexCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsAmexCreditCard(amexCard); err != nil {
|
||||
t.Error(err)
|
||||
if checker.IsAmexCreditCard(amexCard) != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAmexCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsAmexCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
if checker.IsAmexCreditCard(invalidCard) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAmexCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsAmexCreditCard(changeToInvalidLuhn(amexCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
if checker.IsAmexCreditCard(changeToInvalidLuhn(amexCard)) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsDinersCreditCard() {
|
||||
_, err := v2.IsDinersCreditCard("36227206271667")
|
||||
result := checker.IsDinersCreditCard("36227206271667")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
func TestIsDinersCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsDinersCreditCard(dinersCard); err != nil {
|
||||
t.Error(err)
|
||||
if checker.IsDinersCreditCard(dinersCard) != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDinersCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsDinersCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
if checker.IsDinersCreditCard(invalidCard) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDinersCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsDinersCreditCard(changeToInvalidLuhn(dinersCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
if checker.IsDinersCreditCard(changeToInvalidLuhn(dinersCard)) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsDiscoverCreditCard() {
|
||||
_, err := v2.IsDiscoverCreditCard("6011111111111117")
|
||||
result := checker.IsDiscoverCreditCard("6011111111111117")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
func TestIsDiscoverCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsDiscoverCreditCard(discoverCard); err != nil {
|
||||
t.Error(err)
|
||||
if checker.IsDiscoverCreditCard(discoverCard) != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDiscoverCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsDiscoverCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
if checker.IsDiscoverCreditCard(invalidCard) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDiscoverCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsDiscoverCreditCard(changeToInvalidLuhn(discoverCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
if checker.IsDiscoverCreditCard(changeToInvalidLuhn(discoverCard)) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsJcbCreditCard() {
|
||||
_, err := v2.IsJcbCreditCard("3530111333300000")
|
||||
result := checker.IsJcbCreditCard("3530111333300000")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJcbCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsJcbCreditCard(jcbCard); err != nil {
|
||||
t.Error(err)
|
||||
if checker.IsJcbCreditCard(jcbCard) != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJcbCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsJcbCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
if checker.IsJcbCreditCard(invalidCard) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJcbCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsJcbCreditCard(changeToInvalidLuhn(jcbCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
if checker.IsJcbCreditCard(changeToInvalidLuhn(jcbCard)) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsMasterCardCreditCard() {
|
||||
_, err := v2.IsMasterCardCreditCard("5555555555554444")
|
||||
result := checker.IsMasterCardCreditCard("5555555555554444")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMasterCardCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsMasterCardCreditCard(masterCard); err != nil {
|
||||
t.Error(err)
|
||||
if checker.IsMasterCardCreditCard(masterCard) != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMasterCardCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsMasterCardCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
if checker.IsMasterCardCreditCard(invalidCard) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMasterCardCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsMasterCardCreditCard(changeToInvalidLuhn(masterCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
if checker.IsMasterCardCreditCard(changeToInvalidLuhn(masterCard)) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsUnionPayCreditCard() {
|
||||
_, err := v2.IsUnionPayCreditCard("6200000000000005")
|
||||
result := checker.IsUnionPayCreditCard("6200000000000005")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUnionPayCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsUnionPayCreditCard(unionPayCard); err != nil {
|
||||
t.Error(err)
|
||||
if checker.IsUnionPayCreditCard(unionPayCard) != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUnionPayCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsUnionPayCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
if checker.IsUnionPayCreditCard(invalidCard) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUnionPayCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsUnionPayCreditCard(changeToInvalidLuhn(unionPayCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
if checker.IsUnionPayCreditCard(changeToInvalidLuhn(unionPayCard)) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsVisaCreditCard() {
|
||||
_, err := v2.IsVisaCreditCard("4111111111111111")
|
||||
result := checker.IsVisaCreditCard("4111111111111111")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
func TestIsVisaCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsVisaCreditCard(visaCard); err != nil {
|
||||
t.Error(err)
|
||||
if checker.IsVisaCreditCard(visaCard) != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsVisaCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsVisaCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
if checker.IsVisaCreditCard(invalidCard) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsVisaCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsVisaCreditCard(changeToInvalidLuhn(visaCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
if checker.IsVisaCreditCard(changeToInvalidLuhn(visaCard)) == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCreditCardNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic for non-string credit card")
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Order struct {
|
||||
CreditCard int `checkers:"credit-card"`
|
||||
|
|
@ -249,7 +243,7 @@ func TestCheckCreditCardNonString(t *testing.T) {
|
|||
|
||||
order := &Order{}
|
||||
|
||||
v2.CheckStruct(order)
|
||||
checker.Check(order)
|
||||
}
|
||||
|
||||
func TestCheckCreditCardValid(t *testing.T) {
|
||||
|
|
@ -261,7 +255,7 @@ func TestCheckCreditCardValid(t *testing.T) {
|
|||
CreditCard: amexCard,
|
||||
}
|
||||
|
||||
_, valid := v2.CheckStruct(order)
|
||||
_, valid := checker.Check(order)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
|
|
@ -276,14 +270,14 @@ func TestCheckCreditCardInvalid(t *testing.T) {
|
|||
CreditCard: invalidCard,
|
||||
}
|
||||
|
||||
_, valid := v2.CheckStruct(order)
|
||||
_, valid := checker.Check(order)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCreditCardMultipleUnknown(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic for unknown credit card")
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card:amex,unknown"`
|
||||
|
|
@ -293,7 +287,7 @@ func TestCheckCreditCardMultipleUnknown(t *testing.T) {
|
|||
CreditCard: amexCard,
|
||||
}
|
||||
|
||||
v2.CheckStruct(order)
|
||||
checker.Check(order)
|
||||
}
|
||||
|
||||
func TestCheckCreditCardMultipleInvalid(t *testing.T) {
|
||||
|
|
@ -305,7 +299,7 @@ func TestCheckCreditCardMultipleInvalid(t *testing.T) {
|
|||
CreditCard: discoverCard,
|
||||
}
|
||||
|
||||
_, valid := v2.CheckStruct(order)
|
||||
_, valid := checker.Check(order)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
|
|
|
|||
47
digits.go
47
digits.go
|
|
@ -1,42 +1,37 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameDigits is the name of the digits check.
|
||||
nameDigits = "digits"
|
||||
)
|
||||
// CheckerDigits is the name of the checker.
|
||||
const CheckerDigits = "digits"
|
||||
|
||||
var (
|
||||
// ErrNotDigits indicates that the given value is not a valid digits string.
|
||||
ErrNotDigits = NewCheckError("NOT_DIGITS")
|
||||
)
|
||||
// ResultNotDigits indicates that the given string contains non-digit characters.
|
||||
const ResultNotDigits = "NOT_DIGITS"
|
||||
|
||||
// IsDigits checks if the value contains only digit characters.
|
||||
func IsDigits(value string) (string, error) {
|
||||
for _, r := range value {
|
||||
if !unicode.IsDigit(r) {
|
||||
return value, ErrNotDigits
|
||||
// IsDigits checks if the given string consists of only digit characters.
|
||||
func IsDigits(value string) Result {
|
||||
for _, c := range value {
|
||||
if !unicode.IsDigit(c) {
|
||||
return ResultNotDigits
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// checkDigits checks if the value contains only digit characters.
|
||||
func checkDigits(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsDigits(value.Interface().(string))
|
||||
return value, err
|
||||
return ResultValid
|
||||
}
|
||||
|
||||
// makeDigits makes a checker function for the digits checker.
|
||||
func makeDigits(_ string) CheckFunc[reflect.Value] {
|
||||
func makeDigits(_ string) CheckFunc {
|
||||
return checkDigits
|
||||
}
|
||||
|
||||
// checkDigits checks if the given string consists of only digit characters.
|
||||
func checkDigits(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsDigits(value.String())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,76 +1,69 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsDigits() {
|
||||
_, err := v2.IsDigits("123456")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
result := checker.IsDigits("1234")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDigitsInvalid(t *testing.T) {
|
||||
_, err := v2.IsDigits("123a456")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
if checker.IsDigits("checker") == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDigitsValid(t *testing.T) {
|
||||
_, err := v2.IsDigits("123456")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if checker.IsDigits("1234") != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckDigitsNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Code struct {
|
||||
Value int `checkers:"digits"`
|
||||
type User struct {
|
||||
ID int `checkers:"digits"`
|
||||
}
|
||||
|
||||
code := &Code{}
|
||||
user := &User{}
|
||||
|
||||
v2.CheckStruct(code)
|
||||
checker.Check(user)
|
||||
}
|
||||
|
||||
func TestCheckDigitsInvalid(t *testing.T) {
|
||||
type Code struct {
|
||||
Value string `checkers:"digits"`
|
||||
type User struct {
|
||||
ID string `checkers:"digits"`
|
||||
}
|
||||
|
||||
code := &Code{
|
||||
Value: "123a456",
|
||||
user := &User{
|
||||
ID: "checker",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(code)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
_, valid := checker.Check(user)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckDigitsValid(t *testing.T) {
|
||||
type Code struct {
|
||||
Value string `checkers:"digits"`
|
||||
type User struct {
|
||||
ID string `checkers:"digits"`
|
||||
}
|
||||
|
||||
code := &Code{
|
||||
Value: "123456",
|
||||
user := &User{
|
||||
ID: "1234",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(code)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
doc/checkers/alphanumeric.md
Normal file
28
doc/checkers/alphanumeric.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Alphanumeric Checker
|
||||
|
||||
The `alphanumeric` checker checks if the given string consists of only alphanumeric characters. If the string contains non-alphanumeric characters, the checker will return the `NOT_ALPHANUMERIC` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Username string `checkers:"alphanumeric"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "ABcd1234",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `alphanumeric` checker function [`IsAlphanumeric`](https://pkg.go.dev/github.com/cinar/checker#IsAlphanumeric) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsAlphanumeric("ABcd1234")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
28
doc/checkers/ascii.md
Normal file
28
doc/checkers/ascii.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# ASCII Checker
|
||||
|
||||
The `ascii` checker checks if the given string consists of only ASCII characters. If the string contains non-ASCII characters, the checker will return the `NOT_ASCII` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Username string `checkers:"ascii"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "checker",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `ascii` checker function [`IsASCII`](https://pkg.go.dev/github.com/cinar/checker#IsASCII) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsASCII("Checker")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
28
doc/checkers/cidr.md
Normal file
28
doc/checkers/cidr.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# CIDR Checker
|
||||
|
||||
The `cidr` checker checks if the value is a valid CIDR notation IP address and prefix length, like `192.0.2.0/24` or `2001:db8::/32`, as defined in [RFC 4632](https://rfc-editor.org/rfc/rfc4632.html) and [RFC 4291](https://rfc-editor.org/rfc/rfc4291.html). If the value is not a valid CIDR notation, the checker will return the `NOT_CIDR` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type Network struct {
|
||||
Subnet string `checkers:"cidr"`
|
||||
}
|
||||
|
||||
network := &Network{
|
||||
Subnet: "192.0.2.0/24",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(network)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `cidr` checker function [`IsCidr`](https://pkg.go.dev/github.com/cinar/checker#IsASCII) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsCidr("2001:db8::/32")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
62
doc/checkers/credit_card.md
Normal file
62
doc/checkers/credit_card.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# Credit Card Checker
|
||||
|
||||
The `credit-card` checker checks if the given value is a valid credit card number. If the given value is not a valid credit card number, the checker will return the `NOT_CREDIT_CARD` result.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```golang
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: invalidCard,
|
||||
}
|
||||
|
||||
_, valid := checker.Check(order)
|
||||
if valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
The checker currently knows about AMEX, Diners, Discover, JCB, MasterCard, UnionPay, and VISA credit card numbers.
|
||||
|
||||
If you would like to check for a subset of those credit cards, you can specify them through the checker config parameter. Here is an example:
|
||||
|
||||
```golang
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card:amex,visa"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: "6011111111111117",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(order)
|
||||
if valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
If you would like to verify a credit card that is not listed here, please use the [luhn](luhn.md) checker to use the Luhn Algorithm to verify the check digit.
|
||||
|
||||
In your custom checkers, you can call the `credit-card` checker functions below to validate the user input.
|
||||
|
||||
- [`IsAnyCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsAnyCreditCard): checks if the given value is a valid credit card number.
|
||||
- [`IsAmexCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsAmexCreditCard): checks if the given value is a valid AMEX credit card number.
|
||||
- [`IsDinersCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsDinersCreditCard): checks if the given value is a valid Diners credit card number.
|
||||
- [`IsDiscoverCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsDiscoverCreditCard): checks if the given value is a valid Discover credit card number.
|
||||
- [`IsJcbCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsJcbCreditCard): checks if the given value is a valid JCB credit card number.
|
||||
- [`IsMasterCardCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsMasterCardCreditCard): checks if the given value is a valid MasterCard credit card number.
|
||||
- [`IsUnionPayCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsUnionPayCreditCard): checks if the given value is a valid UnionPay credit card number.
|
||||
- [`IsVisaCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsVisaCreditCard): checks if the given value is a valid VISA credit card number.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsAnyCreditCard("6011111111111117")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
28
doc/checkers/digits.md
Normal file
28
doc/checkers/digits.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Digits Checker
|
||||
|
||||
The `digits` checker checks if the given string consists of only digit characters. If the string contains non-digit characters, the checker will return the `NOT_DIGITS` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Id string `checkers:"digits"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Id: "1234",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `digits` checker function [`IsDigits`](https://pkg.go.dev/github.com/cinar/checker#IsDigits) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsDigits("1234")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
28
doc/checkers/email.md
Normal file
28
doc/checkers/email.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Email Checker
|
||||
|
||||
The `email` checker checks if the given string is an email address. If the given string is not a valid email address, the checker will return the `NOT_EMAIL` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Email string `checkers:"email"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Email: "user@zdo.com",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `email` checker function [`IsEmail`](https://pkg.go.dev/github.com/cinar/checker#IsEmail) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsEmail("user@zdo.com")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
28
doc/checkers/fqdn.md
Normal file
28
doc/checkers/fqdn.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# FQDN Checker
|
||||
|
||||
The Full Qualified Domain Name (FQDN) is the complete domain name for a computer or host on the internet. The `fqdn` checker checks if the given string consists of a FQDN. If the string is not a valid FQDN, the checker will return the `NOT_FQDN` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type Request struct {
|
||||
Domain string `checkers:"fqdn"`
|
||||
}
|
||||
|
||||
request := &Request{
|
||||
Domain: "zdo.com",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `fqdn` checker function [`IsFqdn`](https://pkg.go.dev/github.com/cinar/checker#IsFqdn) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsFqdn("zdo.com")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
28
doc/checkers/ip.md
Normal file
28
doc/checkers/ip.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# IP Checker
|
||||
|
||||
The `ip` checker checks if the value is an IP address. If the value is not an IP address, the checker will return the `NOT_IP` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type Request struct {
|
||||
RemoteIP string `checkers:"ip"`
|
||||
}
|
||||
|
||||
request := &Request{
|
||||
RemoteIP: "192.168.1.1",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `ip` checker function [`IsIP`](https://pkg.go.dev/github.com/cinar/checker#IsIP) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsIP("2001:db8::68")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
28
doc/checkers/ipv4.md
Normal file
28
doc/checkers/ipv4.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# IPv4 Checker
|
||||
|
||||
The `ipv4` checker checks if the value is an IPv4 address. If the value is not an IPv4 address, the checker will return the `NOT_IP_V4` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type Request struct {
|
||||
RemoteIP string `checkers:"ipv4"`
|
||||
}
|
||||
|
||||
request := &Request{
|
||||
RemoteIP: "192.168.1.1",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `ipv4` checker function [`IsIPV4`](https://pkg.go.dev/github.com/cinar/checker#IsIPV4) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsIPV4("192.168.1.1")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
28
doc/checkers/ipv6.md
Normal file
28
doc/checkers/ipv6.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# IPv6 Checker
|
||||
|
||||
The `ipv6` checker checks if the value is an IPv6 address. If the value is not an IPv6 address, the checker will return the `NOT_IP_V6` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type Request struct {
|
||||
RemoteIP string `checkers:"ipv6"`
|
||||
}
|
||||
|
||||
request := &Request{
|
||||
RemoteIP: "2001:db8::68",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `ipv6` checker function [`IsIPV6`](https://pkg.go.dev/github.com/cinar/checker#IsIPV6) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsIPV6("2001:db8::68")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
52
doc/checkers/isbn.md
Normal file
52
doc/checkers/isbn.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# ISBN Checker
|
||||
|
||||
An [ISBN (International Standard Book Number)](https://en.wikipedia.org/wiki/International_Standard_Book_Number) is a 10 or 13 digit number that is used to identify a book.
|
||||
|
||||
The `isbn` checker checks if the given value is a valid ISBN. If the given value is not a valid ISBN, the checker will return the `NOT_ISBN` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type Book struct {
|
||||
ISBN string `checkers:"isbn"`
|
||||
}
|
||||
|
||||
book := &Book{
|
||||
ISBN: "1430248270",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(book)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
The `isbn` checker can also be configured to check for a 10 digit or a 13 digit number. Here is an example:
|
||||
|
||||
```golang
|
||||
type Book struct {
|
||||
ISBN string `checkers:"isbn:13"`
|
||||
}
|
||||
|
||||
book := &Book{
|
||||
ISBN: "9781430248279",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(book)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `isbn` checker functions below to validate the user input.
|
||||
|
||||
- [`IsISBN`](https://pkg.go.dev/github.com/cinar/checker#IsISBN) checks if the given value is a valid ISBN number.
|
||||
- [`IsISBN10`](https://pkg.go.dev/github.com/cinar/checker#IsISBN10) checks if the given value is a valid ISBN 10 number.
|
||||
- [`IsISBN13`](https://pkg.go.dev/github.com/cinar/checker#IsISBN13) checks if the given value is a valid ISBN 13 number.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsISBN("1430248270")
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
28
doc/checkers/luhn.md
Normal file
28
doc/checkers/luhn.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Luhn Checker
|
||||
|
||||
The `luhn` checker checks if the given number is valid based on the Luhn Algorithm. If the given number is not valid, it will return the `NOT_LUHN` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"luhn"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: "4012888888881881",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(order)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `luhn` checker function [`IsLuhn`](https://pkg.go.dev/github.com/cinar/checker#IsLuhn) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsLuhn("4012888888881881")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
28
doc/checkers/mac.md
Normal file
28
doc/checkers/mac.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# MAC Checker
|
||||
|
||||
The `mac` checker checks if the value is a valid an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet IP over InfiniBand link-layer address. If the value is not a valid MAC address, the checker will return the `NOT_MAC` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type Network struct {
|
||||
HardwareAddress string `checkers:"mac"`
|
||||
}
|
||||
|
||||
network := &Network{
|
||||
HardwareAddress: "00:00:5e:00:53:01",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(network)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `mac` checker function [`IsMac`](https://pkg.go.dev/github.com/cinar/checker#IsMac) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsMac("00:00:5e:00:53:01")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
30
doc/checkers/max.md
Normal file
30
doc/checkers/max.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Max Checker
|
||||
|
||||
The `max` checker checks if the given ```int``` or ```float``` value is less than the given maximum. If the value is above the maximum, the checker will return the `NOT_MAX` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type Order struct {
|
||||
Quantity int `checkers:"max:10"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
Quantity: 5,
|
||||
}
|
||||
|
||||
mistakes, valid := checker.Check(order)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `max` checker function [`IsMax`](https://pkg.go.dev/github.com/cinar/checker#IsMax) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
quantity := 5
|
||||
|
||||
result := checker.IsMax(quantity, 10)
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
32
doc/checkers/maxlength.md
Normal file
32
doc/checkers/maxlength.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Max Length Checker
|
||||
|
||||
The `max-length` checker checks if the length of the given value is less than the given maximum length. If the length of the value is above the maximum length, the checker will return the `NOT_MAX_LENGTH` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Password string `checkers:"max-length:4"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Password: "1234",
|
||||
}
|
||||
|
||||
mistakes, valid := checker.Check(user)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
The checker can be applied to all types that are supported by the [reflect.Value.Len()](https://pkg.go.dev/reflect#Value.Len) function.
|
||||
|
||||
If you do not want to validate user input stored in a struct, you can individually call the `max-length` checker function [`IsMaxLength`](https://pkg.go.dev/github.com/cinar/checker#IsMaxLength) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
s := "1234"
|
||||
|
||||
result := checker.IsMaxLength(s, 4)
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
30
doc/checkers/min.md
Normal file
30
doc/checkers/min.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Min Checker
|
||||
|
||||
The `min` checker checks if the given ```int``` or ```float``` value is greather than the given minimum. If the value is below the minimum, the checker will return the `NOT_MIN` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Age int `checkers:"min:21"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Age: 45,
|
||||
}
|
||||
|
||||
mistakes, valid := checker.Check(user)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `min` checker function [`IsMin`](https://pkg.go.dev/github.com/cinar/checker#IsMin) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
age := 45
|
||||
|
||||
result := checker.IsMin(age, 21)
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
32
doc/checkers/minlength.md
Normal file
32
doc/checkers/minlength.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Min Length Checker
|
||||
|
||||
The `min-length` checker checks if the length of the given value is greather than the given minimum length. If the length of the value is below the minimum length, the checker will return the `NOT_MIN_LENGTH` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Password string `checkers:"min-length:4"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Password: "1234",
|
||||
}
|
||||
|
||||
mistakes, valid := checker.Check(user)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
The checker can be applied to all types that are supported by the [reflect.Value.Len()](https://pkg.go.dev/reflect#Value.Len) function.
|
||||
|
||||
If you do not want to validate user input stored in a struct, you can individually call the `min-length` checker function [`IsMinLength`](https://pkg.go.dev/github.com/cinar/checker#IsMinLength) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
s := "1234"
|
||||
|
||||
result := checker.IsMinLength(s, 4)
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
48
doc/checkers/regexp.md
Normal file
48
doc/checkers/regexp.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# Regexp Checker
|
||||
|
||||
The `regexp` checker checks if the given string matches the given regexp. If the given string does not match, the checker will return the `NOT_MATCH` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Username string `checkers:"regexp:^[A-Za-z]+$"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "abcd",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
The `regexp` checker can be used to build other checkers for other regexp patterns. In order to do that, you can use the [`MakeRegexpChecker`](https://pkg.go.dev/github.com/cinar/checker#MakeRegexpChecker) function. The function takes an expression and a result to return when the the given string is not a match. Here is an example:
|
||||
|
||||
```golang
|
||||
checkHex := checker.MakeRegexpChecker("^[A-Fa-f0-9]+$", "NOT_HEX")
|
||||
|
||||
result := checkHex(reflect.ValueOf("f0f0f0"), reflect.ValueOf(nil))
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
To register the new regexp checker to validate user input in struct, [`Register`](https://pkg.go.dev/github.com/cinar/checker#Register) function can be used. Here is an example:
|
||||
|
||||
```golang
|
||||
checker.Register("hex", checker.MakeRegexpMaker("^[A-Fa-f0-9]+$", "NOT_HEX"))
|
||||
|
||||
type Theme struct {
|
||||
Color string `checkers:hex`
|
||||
}
|
||||
|
||||
theme := &Theme{
|
||||
Color: "f0f0f0",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(theme)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
27
doc/checkers/required.md
Normal file
27
doc/checkers/required.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Required Checker
|
||||
|
||||
The `required` checker checks for the presence of required input. If the input is not present, the checker will return the `REQUIRED` result. Here is an example:
|
||||
|
||||
```golang
|
||||
type Person struct {
|
||||
Name string `checkers:"required"`
|
||||
}
|
||||
|
||||
person := &Person{}
|
||||
|
||||
mistakes, valid := checker.Check(person)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
If you do not want to validate user input stored in a struct, you can individually call the `required` checker function [`IsRequired`](https://pkg.go.dev/github.com/cinar/checker#IsRequired) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
var name string
|
||||
|
||||
result := checker.IsRequired(name)
|
||||
if result != checker.ResultValid {
|
||||
// Send the result back to the user
|
||||
}
|
||||
```
|
||||
20
doc/checkers/same.md
Normal file
20
doc/checkers/same.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Same Checker
|
||||
|
||||
The `same` checker checks if the given value is equal to the value of the other field specified by its name. If they are not equal, the checker will return the `NOT_SAME` result. In the example below, the `same` checker ensures that the value in the `Confirm` field matches the value in the `Password` field.
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Password string
|
||||
Confirm string `checkers:"same:Password"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Password: "1234",
|
||||
Confirm: "1234",
|
||||
}
|
||||
|
||||
mistakes, valid := checker.Check(user)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
29
doc/checkers/url.md
Normal file
29
doc/checkers/url.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# URL Checker
|
||||
|
||||
The `url` checker checks if the given value is a valid URL. If the given value is not a valid URL, the checker will return the `NOT_URL` result. The checker uses [ParseRequestURI](https://pkg.go.dev/net/url#ParseRequestURI) function to parse the URL, and then checks if the schema or the host are both set.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```golang
|
||||
type Bookmark struct {
|
||||
URL string `checkers:"url"`
|
||||
}
|
||||
|
||||
bookmark := &Bookmark{
|
||||
URL: "https://zdo.com",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(bookmark)
|
||||
if !valid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
|
||||
In your custom checkers, you can call the `url` checker function [`IsURL`](https://pkg.go.dev/github.com/cinar/checker#IsURL) to validate the user input. Here is an example:
|
||||
|
||||
```golang
|
||||
result := checker.IsURL("https://zdo.com")
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
```
|
||||
19
doc/normalizers/html_escape.md
Normal file
19
doc/normalizers/html_escape.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# HTML Escape Normalizer
|
||||
|
||||
The `html-escape` normalizer uses [html.EscapeString](https://pkg.go.dev/html#EscapeString) to escape special characters like "<" to become "<". It escapes only five such characters: <, >, &, ' and ".
|
||||
|
||||
```golang
|
||||
type Comment struct {
|
||||
Body string `checkers:"html-escape"`
|
||||
}
|
||||
|
||||
comment := &Comment{
|
||||
Body: "<tag> \"Checker\" & 'Library' </tag>",
|
||||
}
|
||||
|
||||
checker.Check(comment)
|
||||
|
||||
// Outputs:
|
||||
// <tag> "Checker" & 'Library' </tag>
|
||||
fmt.Println(comment.Body)
|
||||
```
|
||||
22
doc/normalizers/html_unescape.md
Normal file
22
doc/normalizers/html_unescape.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# HTML Unescape Normalizer
|
||||
|
||||
The `html-unescape` normalizer uses [html.UnescapeString](https://pkg.go.dev/html#UnescapeString) to unescape entities like "<" to become "<". It unescapes a larger range of entities than EscapeString escapes. For example, "á" unescapes to "á", as does "á" and "á".
|
||||
|
||||
```golang
|
||||
type Comment struct {
|
||||
Body string `checkers:"html-unescape"`
|
||||
}
|
||||
|
||||
comment := &Comment{
|
||||
Body: "<tag> "Checker" & 'Library' </tag>",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(comment)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// Outputs:
|
||||
// <tag> \"Checker\" & 'Library' </tag>
|
||||
fmt.Println(comment.Body)
|
||||
```
|
||||
17
doc/normalizers/lower.md
Normal file
17
doc/normalizers/lower.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Lower Case Normalizer
|
||||
|
||||
The `lower` normalizer maps all Unicode letters in the given value to their lower case. It can be mixed with checkers and other normalizers when defining the validation steps for user data.
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Username string `checkers:"lower"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "chECker",
|
||||
}
|
||||
|
||||
checker.Check(user)
|
||||
|
||||
fmt.Println(user.Username) // checker
|
||||
```
|
||||
17
doc/normalizers/title.md
Normal file
17
doc/normalizers/title.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Title Case Normalizer
|
||||
|
||||
The `title` normalizer maps the first letter of each word to their upper case. It can be mixed with checkers and other normalizers when defining the validation steps for user data.
|
||||
|
||||
```golang
|
||||
type Book struct {
|
||||
Chapter string `checkers:"title"`
|
||||
}
|
||||
|
||||
book := &Book{
|
||||
Chapter: "THE checker",
|
||||
}
|
||||
|
||||
checker.Check(book)
|
||||
|
||||
fmt.Println(book.Chapter) // The Checker
|
||||
```
|
||||
17
doc/normalizers/trim.md
Normal file
17
doc/normalizers/trim.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Trim Normalizer
|
||||
|
||||
The `trim` normalizer removes the whitespaces at the beginning and at the end of the given value. It can be mixed with checkers and other normalizers when defining the validation steps for user data.
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Username string `checkers:"trim"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: " normalizer ",
|
||||
}
|
||||
|
||||
checker.Check(user)
|
||||
|
||||
fmt.Println(user.Username) // CHECKER
|
||||
```
|
||||
17
doc/normalizers/trim_left.md
Normal file
17
doc/normalizers/trim_left.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Trim Left Normalizer
|
||||
|
||||
The `trim-left` normalizer removes the whitespaces at the beginning of the given value. It can be mixed with checkers and other normalizers when defining the validation steps for user data.
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Username string `checkers:"trim-left"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: " normalizer",
|
||||
}
|
||||
|
||||
checker.Check(user)
|
||||
|
||||
fmt.Println(user.Username) // normalizer
|
||||
```
|
||||
17
doc/normalizers/trim_right.md
Normal file
17
doc/normalizers/trim_right.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Trim Right Normalizer
|
||||
|
||||
The `trim-right` normalizer removes the whitespaces at the end of the given value. It can be mixed with checkers and other normalizers when defining the validation steps for user data.
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Username string `checkers:"trim-right"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "normalizer ",
|
||||
}
|
||||
|
||||
checker.Check(user)
|
||||
|
||||
fmt.Println(user.Username) // CHECKER
|
||||
```
|
||||
15
doc/normalizers/upper.md
Normal file
15
doc/normalizers/upper.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Upper Case Normalizer
|
||||
|
||||
The `upper` normalizer maps all Unicode letters in the given value to their upper case. It can be mixed with checkers and other normalizers when defining the validation steps for user data.
|
||||
|
||||
```golang
|
||||
type User struct {
|
||||
Username string `checkers:"upper"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "chECker",
|
||||
}
|
||||
|
||||
fmt.Println(user.Username) // CHECKER
|
||||
```
|
||||
22
doc/normalizers/url_escape.md
Normal file
22
doc/normalizers/url_escape.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# URL Escape Normalizer
|
||||
|
||||
The `url-escape` normalizer uses [net.url.QueryEscape](https://pkg.go.dev/net/url#QueryEscape) to escape the string so it can be safely placed inside a URL query.
|
||||
|
||||
```golang
|
||||
type Request struct {
|
||||
Query string `checkers:"url-escape"`
|
||||
}
|
||||
|
||||
request := &Request{
|
||||
Query: "param1/param2 = 1 + 2 & 3 + 4",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// Outputs:
|
||||
// param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4
|
||||
fmt.Println(request.Query)
|
||||
```
|
||||
26
doc/normalizers/url_unescape.md
Normal file
26
doc/normalizers/url_unescape.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# URL Unescape Normalizer
|
||||
|
||||
The `url-unescape` normalizer uses [net.url.QueryUnescape](https://pkg.go.dev/net/url#QueryUnescape) to converte each 3-byte encoded substring of the form "%AB" into the hex-decoded byte 0xAB.
|
||||
|
||||
```golang
|
||||
type Request struct {
|
||||
Query string `checkers:"url-unescape"`
|
||||
}
|
||||
|
||||
request := &Request{
|
||||
Query: "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if request.Query != "param1/param2 = 1 + 2 & 3 + 4" {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// Outputs:
|
||||
// param1/param2 = 1 + 2 & 3 + 4
|
||||
fmt.Println(comment.Body)
|
||||
```
|
||||
15
doc/optimization.md
Normal file
15
doc/optimization.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Optimization Guide
|
||||
|
||||
The ```BenchmarkCheck``` function helps profiling the library. Generate a ```profile.out``` my issuing the command below.
|
||||
|
||||
```
|
||||
go test -bench=. -benchmem -cpuprofile profile.out
|
||||
```
|
||||
|
||||
View the analysis through the ```pprof``` by issuing the command below.
|
||||
|
||||
```
|
||||
go tool pprof -http localhost:9000 profile.out
|
||||
```
|
||||
|
||||
Use the web interface for further optimization work.
|
||||
135
email.go
135
email.go
|
|
@ -1,41 +1,122 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"net/mail"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameEmail is the name of the email check.
|
||||
nameEmail = "email"
|
||||
)
|
||||
// CheckerEmail is the name of the checker.
|
||||
const CheckerEmail = "email"
|
||||
|
||||
var (
|
||||
// ErrNotEmail indicates that the given value is not a valid email address.
|
||||
ErrNotEmail = NewCheckError("NOT_EMAIL")
|
||||
)
|
||||
// ResultNotEmail indicates that the given string is not a valid email.
|
||||
const ResultNotEmail = "NOT_EMAIL"
|
||||
|
||||
// IsEmail checks if the value is a valid email address.
|
||||
func IsEmail(value string) (string, error) {
|
||||
_, err := mail.ParseAddress(value)
|
||||
if err != nil {
|
||||
return value, ErrNotEmail
|
||||
// ipV6Prefix is the IPv6 prefix for the domain.
|
||||
const ipV6Prefix = "[IPv6:"
|
||||
|
||||
// notQuotedChars is the valid not quoted characters.
|
||||
var notQuotedChars = regexp.MustCompile("[a-zA-Z0-9!#$%&'*\\+\\-/=?^_`{|}~]")
|
||||
|
||||
// IsEmail checks if the given string is an email address.
|
||||
func IsEmail(email string) Result {
|
||||
atIndex := strings.LastIndex(email, "@")
|
||||
if atIndex == -1 || atIndex == len(email)-1 {
|
||||
return ResultNotEmail
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// checkEmail checks if the value is a valid email address.
|
||||
func checkEmail(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsEmail(value.Interface().(string))
|
||||
return value, err
|
||||
domain := email[atIndex+1:]
|
||||
if isValidEmailDomain(domain) != ResultValid {
|
||||
return ResultNotEmail
|
||||
}
|
||||
|
||||
return isValidEmailUser(email[:atIndex])
|
||||
}
|
||||
|
||||
// makeEmail makes a checker function for the email checker.
|
||||
func makeEmail(_ string) CheckFunc[reflect.Value] {
|
||||
func makeEmail(_ string) CheckFunc {
|
||||
return checkEmail
|
||||
}
|
||||
|
||||
// checkEmail checks if the given string is an email address.
|
||||
func checkEmail(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsEmail(value.String())
|
||||
}
|
||||
|
||||
// isValidEmailDomain checks if the email domain is a IPv4 or IPv6 address, or a FQDN.
|
||||
func isValidEmailDomain(domain string) Result {
|
||||
if len(domain) > 255 {
|
||||
return ResultNotEmail
|
||||
}
|
||||
|
||||
if domain[0] == '[' {
|
||||
if strings.HasPrefix(domain, ipV6Prefix) {
|
||||
// postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]
|
||||
return IsIPV6(domain[len(ipV6Prefix) : len(domain)-1])
|
||||
}
|
||||
|
||||
// postmaster@[123.123.123.123]
|
||||
return IsIPV4(domain[1 : len(domain)-1])
|
||||
}
|
||||
|
||||
return IsFqdn(domain)
|
||||
}
|
||||
|
||||
// isValidEmailUser checks if the email user is valid.
|
||||
func isValidEmailUser(user string) Result {
|
||||
// Cannot be empty user
|
||||
if user == "" || len(user) > 64 {
|
||||
return ResultNotEmail
|
||||
}
|
||||
|
||||
// Cannot start or end with dot
|
||||
if user[0] == '.' || user[len(user)-1] == '.' {
|
||||
return ResultNotEmail
|
||||
}
|
||||
|
||||
return isValidEmailUserCharacters(user)
|
||||
}
|
||||
|
||||
// isValidEmailUserCharacters if the email user characters are valid.
|
||||
func isValidEmailUserCharacters(user string) Result {
|
||||
quoted := false
|
||||
start := true
|
||||
prev := ' '
|
||||
|
||||
for _, c := range user {
|
||||
// Cannot have a double dot unless quoted
|
||||
if !quoted && c == '.' && prev == '.' {
|
||||
return ResultNotEmail
|
||||
}
|
||||
|
||||
if start {
|
||||
start = false
|
||||
|
||||
if c == '"' {
|
||||
quoted = true
|
||||
prev = c
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !quoted {
|
||||
if c == '.' {
|
||||
start = true
|
||||
} else if !notQuotedChars.MatchString(string(c)) {
|
||||
return ResultNotEmail
|
||||
}
|
||||
} else {
|
||||
if c == '"' && prev != '\\' {
|
||||
quoted = false
|
||||
}
|
||||
}
|
||||
|
||||
prev = c
|
||||
}
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
|
|
|
|||
109
email_test.go
109
email_test.go
|
|
@ -1,40 +1,21 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsEmail() {
|
||||
_, err := v2.IsEmail("test@example.com")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
result := checker.IsEmail("user@zdo.com")
|
||||
|
||||
func TestIsEmailInvalid(t *testing.T) {
|
||||
_, err := v2.IsEmail("invalid-email")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmailValid(t *testing.T) {
|
||||
_, err := v2.IsEmail("test@example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckEmailNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type User struct {
|
||||
Email int `checkers:"email"`
|
||||
|
|
@ -42,22 +23,7 @@ func TestCheckEmailNonString(t *testing.T) {
|
|||
|
||||
user := &User{}
|
||||
|
||||
v2.CheckStruct(user)
|
||||
}
|
||||
|
||||
func TestCheckEmailInvalid(t *testing.T) {
|
||||
type User struct {
|
||||
Email string `checkers:"email"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Email: "invalid-email",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(user)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
checker.Check(user)
|
||||
}
|
||||
|
||||
func TestCheckEmailValid(t *testing.T) {
|
||||
|
|
@ -66,11 +32,64 @@ func TestCheckEmailValid(t *testing.T) {
|
|||
}
|
||||
|
||||
user := &User{
|
||||
Email: "test@example.com",
|
||||
Email: "user@zdo.com",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(user)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmailValid(t *testing.T) {
|
||||
validEmails := []string{
|
||||
"simple@example.com",
|
||||
"very.common@example.com",
|
||||
"disposable.style.email.with+symbol@example.com",
|
||||
"other.email-with-hyphen@and.subdomains.example.com",
|
||||
"fully-qualified-domain@example.com",
|
||||
"user.name+tag+sorting@example.com",
|
||||
"x@example.com",
|
||||
"example-indeed@strange-example.com",
|
||||
"test/test@test.com",
|
||||
"example@s.example",
|
||||
"\" \"@example.org",
|
||||
"\"john..doe\"@example.org",
|
||||
"mailhost!username@example.org",
|
||||
"\"very.(),:;<>[]\\\".VERY.\\\"very@\\\\ \\\"very\\\".unusual\"@strange.example.com",
|
||||
"user%example.com@example.org",
|
||||
"user-@example.org",
|
||||
"postmaster@[123.123.123.123]",
|
||||
"postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
|
||||
}
|
||||
|
||||
for _, email := range validEmails {
|
||||
if checker.IsEmail(email) != checker.ResultValid {
|
||||
t.Fatal(email)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmailInvalid(t *testing.T) {
|
||||
validEmails := []string{
|
||||
"Abc.example.com",
|
||||
"A@b@c@example.com",
|
||||
"a\"b(c)d,e:f;g<h>i[j\\k]l@example.com",
|
||||
"just\"not\"right@example.com",
|
||||
"this is\"not\\allowed@example.com",
|
||||
"this\\ still\\\"not\\\\allowed@example.com",
|
||||
"1234567890123456789012345678901234567890123456789012345678901234+x@example.com",
|
||||
"i_like_underscore@but_its_not_allowed_in_this_part.example.com",
|
||||
"QA[icon]CHOCOLATE[icon]@test.com",
|
||||
".cannot.start.with.dot@example.com",
|
||||
"cannot.end.with.dot.@example.com",
|
||||
"cannot.have..double.dots@example.com",
|
||||
"user@domaincannotbemorethan255charactersdomaincannotbemorethan255charactersdomaincannotbemorethan255charactersdomaincannotbemorethan255charactersdomaincannotbemorethan255charactersdomaincannotbemorethan255charactersdomaincannotbemorethan255charactersdomaincannotbemorethan255characters.com",
|
||||
}
|
||||
|
||||
for _, email := range validEmails {
|
||||
if checker.IsEmail(email) == checker.ResultValid {
|
||||
t.Fatal(email)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
92
fqdn.go
92
fqdn.go
|
|
@ -1,43 +1,71 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameFQDN is the name of the FQDN check.
|
||||
nameFQDN = "fqdn"
|
||||
)
|
||||
// CheckerFqdn is the name of the checker.
|
||||
const CheckerFqdn = "fqdn"
|
||||
|
||||
var (
|
||||
// ErrNotFQDN indicates that the given value is not a valid FQDN.
|
||||
ErrNotFQDN = NewCheckError("FQDN")
|
||||
// ResultNotFqdn indicates that the given string is not a valid FQDN.
|
||||
const ResultNotFqdn = "NOT_FQDN"
|
||||
|
||||
// fqdnRegex is the regular expression for validating FQDN.
|
||||
fqdnRegex = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
|
||||
)
|
||||
// Valid characters excluding full-width characters.
|
||||
var fqdnValidChars = regexp.MustCompile("^[a-z0-9\u00a1-\uff00\uff06-\uffff\\-]+$")
|
||||
|
||||
// IsFQDN checks if the value is a valid fully qualified domain name (FQDN).
|
||||
func IsFQDN(value string) (string, error) {
|
||||
if !fqdnRegex.MatchString(value) {
|
||||
return value, ErrNotFQDN
|
||||
}
|
||||
return value, nil
|
||||
// IsFqdn checks if the given string is a fully qualified domain name.
|
||||
func IsFqdn(domain string) Result {
|
||||
parts := strings.Split(domain, ".")
|
||||
|
||||
// Require TLD
|
||||
if len(parts) < 2 {
|
||||
return ResultNotFqdn
|
||||
}
|
||||
|
||||
tld := parts[len(parts)-1]
|
||||
|
||||
// Should be all numeric TLD
|
||||
if IsDigits(tld) == ResultValid {
|
||||
return ResultNotFqdn
|
||||
}
|
||||
|
||||
// Short TLD
|
||||
if len(tld) < 2 {
|
||||
return ResultNotFqdn
|
||||
}
|
||||
|
||||
for _, part := range parts {
|
||||
// Cannot be more than 63 characters
|
||||
if len(part) > 63 {
|
||||
return ResultNotFqdn
|
||||
}
|
||||
|
||||
// Check for valid characters
|
||||
if !fqdnValidChars.MatchString(part) {
|
||||
return ResultNotFqdn
|
||||
}
|
||||
|
||||
// Should not start or end with a hyphen (-) character.
|
||||
if part[0] == '-' || part[len(part)-1] == '-' {
|
||||
return ResultNotFqdn
|
||||
}
|
||||
}
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
|
||||
// checkFQDN checks if the value is a valid fully qualified domain name (FQDN).
|
||||
func checkFQDN(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsFQDN(value.Interface().(string))
|
||||
return value, err
|
||||
// makeFqdn makes a checker function for the fqdn checker.
|
||||
func makeFqdn(_ string) CheckFunc {
|
||||
return checkFqdn
|
||||
}
|
||||
|
||||
// makeFQDN makes a checker function for the FQDN checker.
|
||||
func makeFQDN(_ string) CheckFunc[reflect.Value] {
|
||||
return checkFQDN
|
||||
}
|
||||
// checkFqdn checks if the given string is a fully qualified domain name.
|
||||
func checkFqdn(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsFqdn(value.String())
|
||||
}
|
||||
|
|
|
|||
132
fqdn_test.go
132
fqdn_test.go
|
|
@ -1,76 +1,96 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsFQDN() {
|
||||
_, err := v2.IsFQDN("example.com")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
func ExampleIsFqdn() {
|
||||
result := checker.IsFqdn("zdo.com")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFQDNInvalid(t *testing.T) {
|
||||
_, err := v2.IsFQDN("invalid_fqdn")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
func TestCheckFdqnWithoutTld(t *testing.T) {
|
||||
if checker.IsFqdn("abcd") != checker.ResultNotFqdn {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFQDNValid(t *testing.T) {
|
||||
_, err := v2.IsFQDN("example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
func TestCheckFdqnShortTld(t *testing.T) {
|
||||
if checker.IsFqdn("abcd.c") != checker.ResultNotFqdn {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFQDNNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Domain struct {
|
||||
Name int `checkers:"fqdn"`
|
||||
}
|
||||
|
||||
domain := &Domain{}
|
||||
|
||||
v2.CheckStruct(domain)
|
||||
}
|
||||
|
||||
func TestCheckFQDNInvalid(t *testing.T) {
|
||||
type Domain struct {
|
||||
Name string `checkers:"fqdn"`
|
||||
}
|
||||
|
||||
domain := &Domain{
|
||||
Name: "invalid_fqdn",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(domain)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
func TestCheckFdqnNumericTld(t *testing.T) {
|
||||
if checker.IsFqdn("abcd.1234") != checker.ResultNotFqdn {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFQDNValid(t *testing.T) {
|
||||
type Domain struct {
|
||||
Name string `checkers:"fqdn"`
|
||||
}
|
||||
|
||||
domain := &Domain{
|
||||
Name: "example.com",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(domain)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
func TestCheckFdqnLong(t *testing.T) {
|
||||
if checker.IsFqdn("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd.com") != checker.ResultNotFqdn {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFdqnInvalidCharacters(t *testing.T) {
|
||||
if checker.IsFqdn("ab_cd.com") != checker.ResultNotFqdn {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFdqnStaringWithHyphen(t *testing.T) {
|
||||
if checker.IsFqdn("-abcd.com") != checker.ResultNotFqdn {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFdqnStaringEndingWithHyphen(t *testing.T) {
|
||||
if checker.IsFqdn("abcd-.com") != checker.ResultNotFqdn {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFdqnStartingWithDot(t *testing.T) {
|
||||
if checker.IsFqdn(".abcd.com") != checker.ResultNotFqdn {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFdqnEndingWithDot(t *testing.T) {
|
||||
if checker.IsFqdn("abcd.com.") != checker.ResultNotFqdn {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFqdnNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Request struct {
|
||||
Domain int `checkers:"fqdn"`
|
||||
}
|
||||
|
||||
request := &Request{}
|
||||
|
||||
checker.Check(request)
|
||||
}
|
||||
|
||||
func TestCheckFqdnValid(t *testing.T) {
|
||||
type Request struct {
|
||||
Domain string `checkers:"fqdn"`
|
||||
}
|
||||
|
||||
request := &Request{
|
||||
Domain: "zdo.com",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
go.mod
4
go.mod
|
|
@ -1,3 +1,3 @@
|
|||
module github.com/cinar/checker/v2
|
||||
module github.com/cinar/checker
|
||||
|
||||
go 1.23.2
|
||||
go 1.22
|
||||
|
|
|
|||
67
gte.go
67
gte.go
|
|
@ -1,67 +0,0 @@
|
|||
// 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
139
gte_test.go
|
|
@ -1,139 +0,0 @@
|
|||
// 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)
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
// 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 "testing"
|
||||
|
||||
// FailIfNoPanic fails the test if there were no panic.
|
||||
func FailIfNoPanic(t *testing.T, message string) {
|
||||
t.Helper()
|
||||
|
||||
if r := recover(); r == nil {
|
||||
t.Fatal(message)
|
||||
}
|
||||
}
|
||||
36
hex.go
36
hex.go
|
|
@ -1,36 +0,0 @@
|
|||
// 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"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameHex is the name of the hex check.
|
||||
nameHex = "hex"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotHex indicates that the given string contains hex characters.
|
||||
ErrNotHex = NewCheckError("NOT_HEX")
|
||||
)
|
||||
|
||||
// IsHex checks if the given string consists of only hex characters.
|
||||
func IsHex(value string) (string, error) {
|
||||
return IsRegexp("^[0-9a-fA-F]+$", value)
|
||||
}
|
||||
|
||||
// isHex checks if the given string consists of only hex characters.
|
||||
func isHex(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsHex(value.Interface().(string))
|
||||
return value, err
|
||||
}
|
||||
|
||||
// makeAlphanumeric makes a checker function for the alphanumeric checker.
|
||||
func makeHex(_ string) CheckFunc[reflect.Value] {
|
||||
return isHex
|
||||
}
|
||||
76
hex_test.go
76
hex_test.go
|
|
@ -1,76 +0,0 @@
|
|||
// 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 ExampleIsHex() {
|
||||
_, err := v2.IsHex("0123456789abcdefABCDEF")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsHexInvalid(t *testing.T) {
|
||||
_, err := v2.IsHex("ONUR")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsHexValid(t *testing.T) {
|
||||
_, err := v2.IsHex("0123456789abcdefABCDEF")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckHexNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Car struct {
|
||||
Color int `checkers:"hex"`
|
||||
}
|
||||
|
||||
car := &Car{}
|
||||
|
||||
v2.CheckStruct(car)
|
||||
}
|
||||
|
||||
func TestCheckHexInvalid(t *testing.T) {
|
||||
type Car struct {
|
||||
Color string `checkers:"hex"`
|
||||
}
|
||||
|
||||
car := &Car{
|
||||
Color: "red",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(car)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckHexValid(t *testing.T) {
|
||||
type Car struct {
|
||||
Color string `checkers:"hex"`
|
||||
}
|
||||
|
||||
car := &Car{
|
||||
Color: "ABcd1234",
|
||||
}
|
||||
|
||||
errs, ok := v2.CheckStruct(car)
|
||||
if !ok {
|
||||
t.Fatal(errs)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +1,26 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"html"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameHTMLEscape is the name of the HTML escape normalizer.
|
||||
nameHTMLEscape = "html-escape"
|
||||
)
|
||||
// NormalizerHTMLEscape is the name of the normalizer.
|
||||
const NormalizerHTMLEscape = "html-escape"
|
||||
|
||||
// HTMLEscape applies HTML escaping to special characters.
|
||||
func HTMLEscape(value string) (string, error) {
|
||||
return html.EscapeString(value), nil
|
||||
// makeHTMLEscape makes a normalizer function for the HTML escape normalizer.
|
||||
func makeHTMLEscape(_ string) CheckFunc {
|
||||
return normalizeHTMLEscape
|
||||
}
|
||||
|
||||
// reflectHTMLEscape applies HTML escaping to special characters.
|
||||
func reflectHTMLEscape(value reflect.Value) (reflect.Value, error) {
|
||||
newValue, err := HTMLEscape(value.Interface().(string))
|
||||
return reflect.ValueOf(newValue), err
|
||||
}
|
||||
// normalizeHTMLEscape applies HTML escaping to special characters.
|
||||
// Uses html.EscapeString for the actual escape operation.
|
||||
func normalizeHTMLEscape(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
// makeHTMLEscape returns the HTML escape normalizer function.
|
||||
func makeHTMLEscape(_ string) CheckFunc[reflect.Value] {
|
||||
return reflectHTMLEscape
|
||||
value.SetString(html.EscapeString(value.String()))
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,24 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func TestHTMLEscape(t *testing.T) {
|
||||
input := "<tag> \"Checker\" & 'Library' </tag>"
|
||||
expected := "<tag> "Checker" & 'Library' </tag>"
|
||||
func TestNormalizeHTMLEscapeNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
actual, err := v2.HTMLEscape(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
type Comment struct {
|
||||
Body int `checkers:"html-escape"`
|
||||
}
|
||||
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
comment := &Comment{}
|
||||
|
||||
checker.Check(comment)
|
||||
}
|
||||
|
||||
func TestReflectHTMLEscape(t *testing.T) {
|
||||
func TestNormalizeHTMLEscape(t *testing.T) {
|
||||
type Comment struct {
|
||||
Body string `checkers:"html-escape"`
|
||||
}
|
||||
|
|
@ -34,14 +27,12 @@ func TestReflectHTMLEscape(t *testing.T) {
|
|||
Body: "<tag> \"Checker\" & 'Library' </tag>",
|
||||
}
|
||||
|
||||
expected := "<tag> "Checker" & 'Library' </tag>"
|
||||
|
||||
errs, ok := v2.CheckStruct(comment)
|
||||
if !ok {
|
||||
t.Fatalf("got unexpected errors %v", errs)
|
||||
_, valid := checker.Check(comment)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if comment.Body != expected {
|
||||
t.Fatalf("actual %s expected %s", comment.Body, expected)
|
||||
if comment.Body != "<tag> "Checker" & 'Library' </tag>" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,26 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"html"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// nameHTMLUnescape is the name of the HTML unescape normalizer.
|
||||
const nameHTMLUnescape = "html-unescape"
|
||||
// NormalizerHTMLUnescape is the name of the normalizer.
|
||||
const NormalizerHTMLUnescape = "html-unescape"
|
||||
|
||||
// HTMLUnescape applies HTML unescaping to special characters.
|
||||
func HTMLUnescape(value string) (string, error) {
|
||||
return html.UnescapeString(value), nil
|
||||
// makeHTMLUnescape makes a normalizer function for the HTML unscape normalizer.
|
||||
func makeHTMLUnescape(_ string) CheckFunc {
|
||||
return normalizeHTMLUnescape
|
||||
}
|
||||
|
||||
// reflectHTMLUnescape applies HTML unescaping to special characters.
|
||||
func reflectHTMLUnescape(value reflect.Value) (reflect.Value, error) {
|
||||
newValue, err := HTMLUnescape(value.Interface().(string))
|
||||
return reflect.ValueOf(newValue), err
|
||||
}
|
||||
// normalizeHTMLUnescape applies HTML unescaping to special characters.
|
||||
// Uses html.UnescapeString for the actual unescape operation.
|
||||
func normalizeHTMLUnescape(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
// makeHTMLUnescape returns the HTML unescape normalizer function.
|
||||
func makeHTMLUnescape(_ string) CheckFunc[reflect.Value] {
|
||||
return reflectHTMLUnescape
|
||||
value.SetString(html.UnescapeString(value.String()))
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,24 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func TestHTMLUnescape(t *testing.T) {
|
||||
input := "<tag> "Checker" & 'Library' </tag>"
|
||||
expected := "<tag> \"Checker\" & 'Library' </tag>"
|
||||
func TestNormalizeHTMLUnescapeNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
actual, err := v2.HTMLUnescape(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
type Comment struct {
|
||||
Body int `checkers:"html-unescape"`
|
||||
}
|
||||
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
comment := &Comment{}
|
||||
|
||||
checker.Check(comment)
|
||||
}
|
||||
|
||||
func TestReflectHTMLUnescape(t *testing.T) {
|
||||
func TestNormalizeHTMLUnescape(t *testing.T) {
|
||||
type Comment struct {
|
||||
Body string `checkers:"html-unescape"`
|
||||
}
|
||||
|
|
@ -34,14 +27,12 @@ func TestReflectHTMLUnescape(t *testing.T) {
|
|||
Body: "<tag> "Checker" & 'Library' </tag>",
|
||||
}
|
||||
|
||||
expected := "<tag> \"Checker\" & 'Library' </tag>"
|
||||
|
||||
errs, ok := v2.CheckStruct(comment)
|
||||
if !ok {
|
||||
t.Fatalf("got unexpected errors %v", errs)
|
||||
_, valid := checker.Check(comment)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if comment.Body != expected {
|
||||
t.Fatalf("actual %s expected %s", comment.Body, expected)
|
||||
if comment.Body != "<tag> \"Checker\" & 'Library' </tag>" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
50
ip.go
50
ip.go
|
|
@ -1,40 +1,36 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameIP is the name of the IP check.
|
||||
nameIP = "ip"
|
||||
)
|
||||
// CheckerIP is the name of the checker.
|
||||
const CheckerIP = "ip"
|
||||
|
||||
var (
|
||||
// ErrNotIP indicates that the given value is not a valid IP address.
|
||||
ErrNotIP = NewCheckError("NOT_IP")
|
||||
)
|
||||
// ResultNotIP indicates that the given value is not an IP address.
|
||||
const ResultNotIP = "NOT_IP"
|
||||
|
||||
// IsIP checks if the value is a valid IP address.
|
||||
func IsIP(value string) (string, error) {
|
||||
if net.ParseIP(value) == nil {
|
||||
return value, ErrNotIP
|
||||
// IsIP checks if the given value is an IP address.
|
||||
func IsIP(value string) Result {
|
||||
ip := net.ParseIP(value)
|
||||
if ip == nil {
|
||||
return ResultNotIP
|
||||
}
|
||||
return value, nil
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
|
||||
// checkIP checks if the value is a valid IP address.
|
||||
func checkIP(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsIP(value.Interface().(string))
|
||||
return value, err
|
||||
}
|
||||
|
||||
// makeIP makes a checker function for the IP checker.
|
||||
func makeIP(_ string) CheckFunc[reflect.Value] {
|
||||
// makeIP makes a checker function for the ip checker.
|
||||
func makeIP(_ string) CheckFunc {
|
||||
return checkIP
|
||||
}
|
||||
|
||||
// checkIP checks if the given value is an IP address.
|
||||
func checkIP(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsIP(value.String())
|
||||
}
|
||||
|
|
|
|||
69
ip_test.go
69
ip_test.go
|
|
@ -1,76 +1,69 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsIP() {
|
||||
_, err := v2.IsIP("192.168.1.1")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
result := checker.IsIP("2001:db8::68")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPInvalid(t *testing.T) {
|
||||
_, err := v2.IsIP("invalid-ip")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
if checker.IsIP("900.800.200.100") == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPValid(t *testing.T) {
|
||||
_, err := v2.IsIP("192.168.1.1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if checker.IsIP("2001:db8::68") != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIPNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
func TestCheckIpNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Network struct {
|
||||
Address int `checkers:"ip"`
|
||||
type Request struct {
|
||||
RemoteIP int `checkers:"ip"`
|
||||
}
|
||||
|
||||
network := &Network{}
|
||||
request := &Request{}
|
||||
|
||||
v2.CheckStruct(network)
|
||||
checker.Check(request)
|
||||
}
|
||||
|
||||
func TestCheckIPInvalid(t *testing.T) {
|
||||
type Network struct {
|
||||
Address string `checkers:"ip"`
|
||||
func TestCheckIpInvalid(t *testing.T) {
|
||||
type Request struct {
|
||||
RemoteIP string `checkers:"ip"`
|
||||
}
|
||||
|
||||
network := &Network{
|
||||
Address: "invalid-ip",
|
||||
request := &Request{
|
||||
RemoteIP: "900.800.200.100",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
_, valid := checker.Check(request)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIPValid(t *testing.T) {
|
||||
type Network struct {
|
||||
Address string `checkers:"ip"`
|
||||
type Request struct {
|
||||
RemoteIP string `checkers:"ip"`
|
||||
}
|
||||
|
||||
network := &Network{
|
||||
Address: "192.168.1.1",
|
||||
request := &Request{
|
||||
RemoteIP: "192.168.1.1",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
_, valid := checker.Check(request)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
51
ipv4.go
51
ipv4.go
|
|
@ -1,41 +1,40 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameIPv4 is the name of the IPv4 check.
|
||||
nameIPv4 = "ipv4"
|
||||
)
|
||||
// CheckerIPV4 is the name of the checker.
|
||||
const CheckerIPV4 = "ipv4"
|
||||
|
||||
var (
|
||||
// ErrNotIPv4 indicates that the given value is not a valid IPv4 address.
|
||||
ErrNotIPv4 = NewCheckError("NOT_IPV4")
|
||||
)
|
||||
// ResultNotIPV4 indicates that the given value is not an IPv4 address.
|
||||
const ResultNotIPV4 = "NOT_IP_V4"
|
||||
|
||||
// IsIPv4 checks if the value is a valid IPv4 address.
|
||||
func IsIPv4(value string) (string, error) {
|
||||
// IsIPV4 checks if the given value is an IPv4 address.
|
||||
func IsIPV4(value string) Result {
|
||||
ip := net.ParseIP(value)
|
||||
if ip == nil || ip.To4() == nil {
|
||||
return value, ErrNotIPv4
|
||||
if ip == nil {
|
||||
return ResultNotIPV4
|
||||
}
|
||||
return value, nil
|
||||
|
||||
if ip.To4() == nil {
|
||||
return ResultNotIPV4
|
||||
}
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
|
||||
// checkIPv4 checks if the value is a valid IPv4 address.
|
||||
func checkIPv4(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsIPv4(value.Interface().(string))
|
||||
return value, err
|
||||
// makeIPV4 makes a checker function for the ipV4 checker.
|
||||
func makeIPV4(_ string) CheckFunc {
|
||||
return checkIPV4
|
||||
}
|
||||
|
||||
// makeIPv4 makes a checker function for the IPv4 checker.
|
||||
func makeIPv4(_ string) CheckFunc[reflect.Value] {
|
||||
return checkIPv4
|
||||
// checkIPV4 checks if the given value is an IPv4 address.
|
||||
func checkIPV4(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsIPV4(value.String())
|
||||
}
|
||||
|
|
|
|||
103
ipv4_test.go
103
ipv4_test.go
|
|
@ -1,76 +1,75 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsIPv4() {
|
||||
_, err := v2.IsIPv4("192.168.1.1")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
func ExampleIsIPV4() {
|
||||
result := checker.IsIPV4("192.168.1.1")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPv4Invalid(t *testing.T) {
|
||||
_, err := v2.IsIPv4("2001:db8::1")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
func TestIsIPV4Invalid(t *testing.T) {
|
||||
if checker.IsIPV4("900.800.200.100") == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPv4Valid(t *testing.T) {
|
||||
_, err := v2.IsIPv4("192.168.1.1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
func TestIsIPV4InvalidV6(t *testing.T) {
|
||||
if checker.IsIPV4("2001:db8::68") == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIPv4NonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Network struct {
|
||||
Address int `checkers:"ipv4"`
|
||||
}
|
||||
|
||||
network := &Network{}
|
||||
|
||||
v2.CheckStruct(network)
|
||||
}
|
||||
|
||||
func TestCheckIPv4Invalid(t *testing.T) {
|
||||
type Network struct {
|
||||
Address string `checkers:"ipv4"`
|
||||
}
|
||||
|
||||
network := &Network{
|
||||
Address: "2001:db8::1",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
func TestIsIPV4Valid(t *testing.T) {
|
||||
if checker.IsIPV4("192.168.1.1") != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIPv4Valid(t *testing.T) {
|
||||
type Network struct {
|
||||
Address string `checkers:"ipv4"`
|
||||
func TestCheckIPV4NonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Request struct {
|
||||
RemoteIP int `checkers:"ipv4"`
|
||||
}
|
||||
|
||||
network := &Network{
|
||||
Address: "192.168.1.1",
|
||||
request := &Request{}
|
||||
|
||||
checker.Check(request)
|
||||
}
|
||||
|
||||
func TestCheckIPV4Invalid(t *testing.T) {
|
||||
type Request struct {
|
||||
RemoteIP string `checkers:"ipv4"`
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
request := &Request{
|
||||
RemoteIP: "900.800.200.100",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIPV4Valid(t *testing.T) {
|
||||
type Request struct {
|
||||
RemoteIP string `checkers:"ipv4"`
|
||||
}
|
||||
|
||||
request := &Request{
|
||||
RemoteIP: "192.168.1.1",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
ipv6.go
52
ipv6.go
|
|
@ -1,40 +1,40 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameIPv6 is the name of the IPv6 check.
|
||||
nameIPv6 = "ipv6"
|
||||
)
|
||||
// CheckerIPV6 is the name of the checker.
|
||||
const CheckerIPV6 = "ipv6"
|
||||
|
||||
var (
|
||||
// ErrNotIPv6 indicates that the given value is not a valid IPv6 address.
|
||||
ErrNotIPv6 = NewCheckError("NOT_IPV6")
|
||||
)
|
||||
// ResultNotIPV6 indicates that the given value is not an IPv6 address.
|
||||
const ResultNotIPV6 = "NOT_IP_V6"
|
||||
|
||||
// IsIPv6 checks if the value is a valid IPv6 address.
|
||||
func IsIPv6(value string) (string, error) {
|
||||
if net.ParseIP(value) == nil || net.ParseIP(value).To4() != nil {
|
||||
return value, ErrNotIPv6
|
||||
// IsIPV6 checks if the given value is an IPv6 address.
|
||||
func IsIPV6(value string) Result {
|
||||
ip := net.ParseIP(value)
|
||||
if ip == nil {
|
||||
return ResultNotIPV6
|
||||
}
|
||||
return value, nil
|
||||
|
||||
if ip.To4() != nil {
|
||||
return ResultNotIPV6
|
||||
}
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
|
||||
// checkIPv6 checks if the value is a valid IPv6 address.
|
||||
func checkIPv6(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsIPv6(value.Interface().(string))
|
||||
return value, err
|
||||
// makeIPV6 makes a checker function for the ipV6 checker.
|
||||
func makeIPV6(_ string) CheckFunc {
|
||||
return checkIPV6
|
||||
}
|
||||
|
||||
// makeIPv6 makes a checker function for the IPv6 checker.
|
||||
func makeIPv6(_ string) CheckFunc[reflect.Value] {
|
||||
return checkIPv6
|
||||
// checkIPV6 checks if the given value is an IPv6 address.
|
||||
func checkIPV6(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsIPV6(value.String())
|
||||
}
|
||||
|
|
|
|||
103
ipv6_test.go
103
ipv6_test.go
|
|
@ -1,76 +1,75 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsIPv6() {
|
||||
_, err := v2.IsIPv6("2001:db8::1")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
func ExampleIsIPV6() {
|
||||
result := checker.IsIPV6("2001:db8::68")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPv6Invalid(t *testing.T) {
|
||||
_, err := v2.IsIPv6("192.168.1.1")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
func TestIsIPV6Invalid(t *testing.T) {
|
||||
if checker.IsIPV6("900.800.200.100") == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPv6Valid(t *testing.T) {
|
||||
_, err := v2.IsIPv6("2001:db8::1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
func TestIsIPV6InvalidV4(t *testing.T) {
|
||||
if checker.IsIPV6("192.168.1.1") == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIPv6NonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Network struct {
|
||||
Address int `checkers:"ipv6"`
|
||||
}
|
||||
|
||||
network := &Network{}
|
||||
|
||||
v2.CheckStruct(network)
|
||||
}
|
||||
|
||||
func TestCheckIPv6Invalid(t *testing.T) {
|
||||
type Network struct {
|
||||
Address string `checkers:"ipv6"`
|
||||
}
|
||||
|
||||
network := &Network{
|
||||
Address: "192.168.1.1",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
func TestIsIPV6Valid(t *testing.T) {
|
||||
if checker.IsIPV6("2001:db8::68") != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIPv6Valid(t *testing.T) {
|
||||
type Network struct {
|
||||
Address string `checkers:"ipv6"`
|
||||
func TestCheckIPV6NonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Request struct {
|
||||
RemoteIP int `checkers:"ipv6"`
|
||||
}
|
||||
|
||||
network := &Network{
|
||||
Address: "2001:db8::1",
|
||||
request := &Request{}
|
||||
|
||||
checker.Check(request)
|
||||
}
|
||||
|
||||
func TestCheckIPV6Invalid(t *testing.T) {
|
||||
type Request struct {
|
||||
RemoteIP string `checkers:"ipv6"`
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
request := &Request{
|
||||
RemoteIP: "900.800.200.100",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIPV6Valid(t *testing.T) {
|
||||
type Request struct {
|
||||
RemoteIP string `checkers:"ipv6"`
|
||||
}
|
||||
|
||||
request := &Request{
|
||||
RemoteIP: "2001:db8::68",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
136
isbn.go
136
isbn.go
|
|
@ -1,43 +1,121 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameISBN is the name of the ISBN check.
|
||||
nameISBN = "isbn"
|
||||
)
|
||||
// Program to check for ISBN
|
||||
// https://www.geeksforgeeks.org/program-check-isbn/
|
||||
|
||||
var (
|
||||
// ErrNotISBN indicates that the given value is not a valid ISBN.
|
||||
ErrNotISBN = NewCheckError("NOT_ISBN")
|
||||
// How to Verify an ISBN
|
||||
// https://www.instructables.com/How-to-verify-a-ISBN/
|
||||
|
||||
// isbnRegex is the regular expression for validating ISBN-10 and ISBN-13.
|
||||
isbnRegex = regexp.MustCompile(`^(97(8|9))?\d{9}(\d|X)$`)
|
||||
)
|
||||
// CheckerISBN is the name of the checker.
|
||||
const CheckerISBN = "isbn"
|
||||
|
||||
// IsISBN checks if the value is a valid ISBN-10 or ISBN-13.
|
||||
func IsISBN(value string) (string, error) {
|
||||
if !isbnRegex.MatchString(value) {
|
||||
return value, ErrNotISBN
|
||||
// ResultNotISBN indicates that the given value is not a valid ISBN.
|
||||
const ResultNotISBN = "NOT_ISBN"
|
||||
|
||||
// IsISBN10 checks if the given value is a valid ISBN-10 number.
|
||||
func IsISBN10(value string) Result {
|
||||
value = strings.ReplaceAll(value, "-", "")
|
||||
|
||||
if len(value) != 10 {
|
||||
return ResultNotISBN
|
||||
}
|
||||
return value, nil
|
||||
|
||||
digits := []rune(value)
|
||||
sum := 0
|
||||
|
||||
for i, e := 0, len(digits); i < e; i++ {
|
||||
n := isbnDigitToInt(digits[i])
|
||||
sum += n * (e - i)
|
||||
}
|
||||
|
||||
if sum%11 != 0 {
|
||||
return ResultNotISBN
|
||||
}
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
|
||||
// checkISBN checks if the value is a valid ISBN-10 or ISBN-13.
|
||||
func checkISBN(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsISBN(value.Interface().(string))
|
||||
return value, err
|
||||
// IsISBN13 checks if the given value is a valid ISBN-13 number.
|
||||
func IsISBN13(value string) Result {
|
||||
value = strings.ReplaceAll(value, "-", "")
|
||||
|
||||
if len(value) != 13 {
|
||||
return ResultNotISBN
|
||||
}
|
||||
|
||||
digits := []rune(value)
|
||||
sum := 0
|
||||
|
||||
for i, d := range digits {
|
||||
n := isbnDigitToInt(d)
|
||||
if i%2 != 0 {
|
||||
n *= 3
|
||||
}
|
||||
|
||||
sum += n
|
||||
}
|
||||
|
||||
if sum%10 != 0 {
|
||||
return ResultNotISBN
|
||||
}
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
|
||||
// makeISBN makes a checker function for the ISBN checker.
|
||||
func makeISBN(_ string) CheckFunc[reflect.Value] {
|
||||
return checkISBN
|
||||
// IsISBN checks if the given value is a valid ISBN number.
|
||||
func IsISBN(value string) Result {
|
||||
value = strings.ReplaceAll(value, "-", "")
|
||||
|
||||
if len(value) == 10 {
|
||||
return IsISBN10(value)
|
||||
} else if len(value) == 13 {
|
||||
return IsISBN13(value)
|
||||
}
|
||||
|
||||
return ResultNotISBN
|
||||
}
|
||||
|
||||
// isbnDigitToInt returns the integer value of given ISBN digit.
|
||||
func isbnDigitToInt(r rune) int {
|
||||
if r == 'X' {
|
||||
return 10
|
||||
}
|
||||
|
||||
return int(r - '0')
|
||||
}
|
||||
|
||||
// makeISBN makes a checker function for the URL checker.
|
||||
func makeISBN(config string) CheckFunc {
|
||||
if config != "" && config != "10" && config != "13" {
|
||||
panic("invalid format")
|
||||
}
|
||||
|
||||
return func(value, parent reflect.Value) Result {
|
||||
return checkISBN(value, parent, config)
|
||||
}
|
||||
}
|
||||
|
||||
// checkISBN checks if the given value is a valid ISBN number.
|
||||
func checkISBN(value, _ reflect.Value, mode string) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
number := value.String()
|
||||
|
||||
switch mode {
|
||||
case "10":
|
||||
return IsISBN10(number)
|
||||
|
||||
case "13":
|
||||
return IsISBN13(number)
|
||||
|
||||
default:
|
||||
return IsISBN(number)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
187
isbn_test.go
187
isbn_test.go
|
|
@ -1,40 +1,118 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsISBN10() {
|
||||
result := checker.IsISBN10("1430248270")
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN10Valid(t *testing.T) {
|
||||
result := checker.IsISBN10("1430248270")
|
||||
if result != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN10ValidX(t *testing.T) {
|
||||
result := checker.IsISBN10("007462542X")
|
||||
if result != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN10ValidWithDashes(t *testing.T) {
|
||||
result := checker.IsISBN10("1-4302-4827-0")
|
||||
if result != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN10InvalidLength(t *testing.T) {
|
||||
result := checker.IsISBN10("143024827")
|
||||
if result != checker.ResultNotISBN {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN10InvalidCheck(t *testing.T) {
|
||||
result := checker.IsISBN10("1430248272")
|
||||
if result != checker.ResultNotISBN {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsISBN13() {
|
||||
result := checker.IsISBN13("9781430248279")
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN13Valid(t *testing.T) {
|
||||
result := checker.IsISBN13("9781430248279")
|
||||
if result != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN13ValidWithDashes(t *testing.T) {
|
||||
result := checker.IsISBN13("978-1-4302-4827-9")
|
||||
if result != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN13InvalidLength(t *testing.T) {
|
||||
result := checker.IsISBN13("978143024827")
|
||||
if result != checker.ResultNotISBN {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN13InvalidCheck(t *testing.T) {
|
||||
result := checker.IsISBN13("9781430248272")
|
||||
if result != checker.ResultNotISBN {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsISBN() {
|
||||
_, err := v2.IsISBN("1430248270")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
result := checker.IsISBN("1430248270")
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBNInvalid(t *testing.T) {
|
||||
_, err := v2.IsISBN("invalid-isbn")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
func TestIsISBNValid10(t *testing.T) {
|
||||
result := checker.IsISBN("1430248270")
|
||||
if result != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBNValid(t *testing.T) {
|
||||
_, err := v2.IsISBN("1430248270")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
func TestIsISBNValid13(t *testing.T) {
|
||||
result := checker.IsISBN("9781430248279")
|
||||
if result != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBNInvalidLenght(t *testing.T) {
|
||||
result := checker.IsISBN("978143024827")
|
||||
if result != checker.ResultNotISBN {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckISBNNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Book struct {
|
||||
ISBN int `checkers:"isbn"`
|
||||
|
|
@ -42,22 +120,7 @@ func TestCheckISBNNonString(t *testing.T) {
|
|||
|
||||
book := &Book{}
|
||||
|
||||
v2.CheckStruct(book)
|
||||
}
|
||||
|
||||
func TestCheckISBNInvalid(t *testing.T) {
|
||||
type Book struct {
|
||||
ISBN string `checkers:"isbn"`
|
||||
}
|
||||
|
||||
book := &Book{
|
||||
ISBN: "invalid-isbn",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(book)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
checker.Check(book)
|
||||
}
|
||||
|
||||
func TestCheckISBNValid(t *testing.T) {
|
||||
|
|
@ -66,11 +129,55 @@ func TestCheckISBNValid(t *testing.T) {
|
|||
}
|
||||
|
||||
book := &Book{
|
||||
ISBN: "9783161484100",
|
||||
ISBN: "1430248270",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(book)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
_, valid := checker.Check(book)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckISBNInvalid(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Book struct {
|
||||
ISBN string `checkers:"isbn:20"`
|
||||
}
|
||||
|
||||
book := &Book{
|
||||
ISBN: "1430248270",
|
||||
}
|
||||
|
||||
checker.Check(book)
|
||||
}
|
||||
|
||||
func TestCheckISBNValid10(t *testing.T) {
|
||||
type Book struct {
|
||||
ISBN string `checkers:"isbn:10"`
|
||||
}
|
||||
|
||||
book := &Book{
|
||||
ISBN: "1430248270",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(book)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckISBNValid13(t *testing.T) {
|
||||
type Book struct {
|
||||
ISBN string `checkers:"isbn:13"`
|
||||
}
|
||||
|
||||
book := &Book{
|
||||
ISBN: "9781430248279",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(book)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
<!-- gomarkdoc:embed:start -->
|
||||
|
||||
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
|
||||
|
||||
# locales
|
||||
|
||||
```go
|
||||
import "github.com/cinar/checker/v2/locales"
|
||||
```
|
||||
|
||||
Package locales provides the localized error messages for the check errors.
|
||||
|
||||
## Index
|
||||
|
||||
- [Constants](<#constants>)
|
||||
- [Variables](<#variables>)
|
||||
|
||||
|
||||
## Constants
|
||||
|
||||
<a name="EnUS"></a>
|
||||
|
||||
```go
|
||||
const (
|
||||
// EnUS is the en_us locale.
|
||||
EnUS = "en-US"
|
||||
)
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
<a name="EnUSMessages"></a>EnUSMessages is the map of en\-US messages.
|
||||
|
||||
```go
|
||||
var EnUSMessages = map[string]string{
|
||||
"NOT_ALPHANUMERIC": "Not an alphanumeric string.",
|
||||
"NOT_ASCII": "Can only contain ASCII characters.",
|
||||
"NOT_CIDR": "Not a valid CIDR notation.",
|
||||
"NOT_CREDIT_CARD": "Not a valid credit card number.",
|
||||
"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 }}.",
|
||||
"NOT_MIN_LEN": "Value cannot be less than {{ .min }}.",
|
||||
"NOT_TIME": "Not a valid time.",
|
||||
"REQUIRED": "Required value is missing.",
|
||||
"NOT_URL": "Not a valid URL.",
|
||||
}
|
||||
```
|
||||
|
||||
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
|
||||
|
||||
|
||||
<!-- gomarkdoc:embed:end -->
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package locales
|
||||
|
||||
const (
|
||||
// EnUS is the en_us locale.
|
||||
EnUS = "en-US"
|
||||
)
|
||||
|
||||
// EnUSMessages is the map of en-US messages.
|
||||
var EnUSMessages = map[string]string{
|
||||
"NOT_ALPHANUMERIC": "Not an alphanumeric string.",
|
||||
"NOT_ASCII": "Can only contain ASCII characters.",
|
||||
"NOT_CIDR": "Not a valid CIDR notation.",
|
||||
"NOT_CREDIT_CARD": "Not a valid credit card number.",
|
||||
"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 }}.",
|
||||
"NOT_MIN_LEN": "Value cannot be less than {{ .min }}.",
|
||||
"NOT_TIME": "Not a valid time.",
|
||||
"REQUIRED": "Required value is missing.",
|
||||
"NOT_URL": "Not a valid URL.",
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
// Package locales provides the localized error messages for the check errors.
|
||||
package locales
|
||||
35
lower.go
35
lower.go
|
|
@ -1,32 +1,25 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameLower is the name of the lower normalizer.
|
||||
nameLower = "lower"
|
||||
)
|
||||
// NormalizerLower is the name of the normalizer.
|
||||
const NormalizerLower = "lower"
|
||||
|
||||
// Lower maps all Unicode letters in the given value to their lower case.
|
||||
func Lower(value string) (string, error) {
|
||||
return strings.ToLower(value), nil
|
||||
// makeLower makes a normalizer function for the lower normalizer.
|
||||
func makeLower(_ string) CheckFunc {
|
||||
return normalizeLower
|
||||
}
|
||||
|
||||
// reflectLower maps all Unicode letters in the given value to their lower case.
|
||||
func reflectLower(value reflect.Value) (reflect.Value, error) {
|
||||
newValue, err := Lower(value.Interface().(string))
|
||||
return reflect.ValueOf(newValue), err
|
||||
}
|
||||
// normalizeLower maps all Unicode letters in the given value to their lower case.
|
||||
func normalizeLower(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
// makeLower returns the lower normalizer function.
|
||||
func makeLower(_ string) CheckFunc[reflect.Value] {
|
||||
return reflectLower
|
||||
value.SetString(strings.ToLower(value.String()))
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,50 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func TestLower(t *testing.T) {
|
||||
input := "CHECKER"
|
||||
expected := "checker"
|
||||
func TestNormalizeLowerNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
actual, err := v2.Lower(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
type User struct {
|
||||
Username int `checkers:"lower"`
|
||||
}
|
||||
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
user := &User{}
|
||||
|
||||
checker.Check(user)
|
||||
}
|
||||
|
||||
func TestNormalizeLowerResultValid(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"lower"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "chECker",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestReflectLower(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"lower"`
|
||||
func TestNormalizeLower(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"lower"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "CHECKER",
|
||||
user := &User{
|
||||
Username: "chECker",
|
||||
}
|
||||
|
||||
expected := "checker"
|
||||
checker.Check(user)
|
||||
|
||||
errs, ok := v2.CheckStruct(person)
|
||||
if !ok {
|
||||
t.Fatalf("got unexpected errors %v", errs)
|
||||
}
|
||||
|
||||
if person.Name != expected {
|
||||
t.Fatalf("actual %s expected %s", person.Name, expected)
|
||||
if user.Username != "checker" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
67
lte.go
67
lte.go
|
|
@ -1,67 +0,0 @@
|
|||
// 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
139
lte_test.go
|
|
@ -1,139 +0,0 @@
|
|||
// 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)
|
||||
}
|
||||
87
luhn.go
87
luhn.go
|
|
@ -1,61 +1,60 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameLUHN is the name of the LUHN check.
|
||||
nameLUHN = "luhn"
|
||||
)
|
||||
// CheckerLuhn is the name of the checker.
|
||||
const CheckerLuhn = "luhn"
|
||||
|
||||
var (
|
||||
// ErrNotLUHN indicates that the given value is not a valid LUHN number.
|
||||
ErrNotLUHN = NewCheckError("NOT_LUHN")
|
||||
)
|
||||
// ResultNotLuhn indicates that the given number is not valid based on the Luhn algorithm.
|
||||
const ResultNotLuhn = "NOT_LUHN"
|
||||
|
||||
// IsLUHN checks if the value is a valid LUHN number.
|
||||
func IsLUHN(value string) (string, error) {
|
||||
var sum int
|
||||
var alt bool
|
||||
// doubleTable is the values for the last digits of doubled digits added.
|
||||
var doubleTable = [10]int{0, 2, 4, 6, 8, 1, 3, 5, 7, 9}
|
||||
|
||||
for i := len(value) - 1; i >= 0; i-- {
|
||||
r := rune(value[i])
|
||||
if !unicode.IsDigit(r) {
|
||||
return value, ErrNotLUHN
|
||||
}
|
||||
// IsLuhn checks if the given number is valid based on the Luhn algorithm.
|
||||
func IsLuhn(number string) Result {
|
||||
digits := number[:len(number)-1]
|
||||
check := rune(number[len(number)-1])
|
||||
|
||||
n := int(r - '0')
|
||||
if alt {
|
||||
n *= 2
|
||||
if n > 9 {
|
||||
n -= 9
|
||||
}
|
||||
}
|
||||
sum += n
|
||||
alt = !alt
|
||||
if calculateLuhnCheckDigit(digits) != check {
|
||||
return ResultNotLuhn
|
||||
}
|
||||
|
||||
if sum%10 != 0 {
|
||||
return value, ErrNotLUHN
|
||||
return ResultValid
|
||||
}
|
||||
|
||||
// makeLuhn makes a checker function for the Luhn algorithm.
|
||||
func makeLuhn(_ string) CheckFunc {
|
||||
return checkLuhn
|
||||
}
|
||||
|
||||
// checkLuhn checks if the given number is valid based on the Luhn algorithm.
|
||||
func checkLuhn(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return value, nil
|
||||
return IsLuhn(value.String())
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// Calculates the Luhn algorighm check digit for the given number.
|
||||
func calculateLuhnCheckDigit(number string) rune {
|
||||
digits := []rune(number)
|
||||
check := 0
|
||||
|
||||
// makeLUHN makes a checker function for the LUHN checker.
|
||||
func makeLUHN(_ string) CheckFunc[reflect.Value] {
|
||||
return checkLUHN
|
||||
for i, j := 0, len(digits)-1; i <= j; i++ {
|
||||
d := int(digits[j-i] - '0')
|
||||
if i%2 == 0 {
|
||||
d = doubleTable[d]
|
||||
}
|
||||
|
||||
check += d
|
||||
}
|
||||
|
||||
check *= 9
|
||||
check %= 10
|
||||
|
||||
return rune('0' + check)
|
||||
}
|
||||
|
|
|
|||
121
luhn_test.go
121
luhn_test.go
|
|
@ -1,83 +1,72 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsLUHN() {
|
||||
_, err := v2.IsLUHN("4012888888881881")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
func ExampleIsLuhn() {
|
||||
result := checker.IsLuhn("4012888888881881")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLUHNInvalid(t *testing.T) {
|
||||
_, err := v2.IsLUHN("123456789")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
func TestIsLuhnValid(t *testing.T) {
|
||||
numbers := []string{
|
||||
"4012888888881881",
|
||||
"4222222222222",
|
||||
"5555555555554444",
|
||||
"5105105105105100",
|
||||
}
|
||||
|
||||
for _, number := range numbers {
|
||||
if checker.IsLuhn(number) != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLUHNInvalidDigits(t *testing.T) {
|
||||
_, err := v2.IsLUHN("ABCD")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
func TestCheckLuhnNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Order struct {
|
||||
CreditCard int `checkers:"luhn"`
|
||||
}
|
||||
|
||||
order := &Order{}
|
||||
|
||||
checker.Check(order)
|
||||
}
|
||||
|
||||
func TestCheckLuhnInvalid(t *testing.T) {
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"luhn"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: "4012888888881884",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(order)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLUHNValid(t *testing.T) {
|
||||
_, err := v2.IsLUHN("4012888888881881")
|
||||
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")
|
||||
func TestCheckLuhnValid(t *testing.T) {
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"luhn"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: "4012888888881881",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(order)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
45
mac.go
45
mac.go
|
|
@ -1,41 +1,36 @@
|
|||
// 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
|
||||
package checker
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameMAC is the name of the MAC check.
|
||||
nameMAC = "mac"
|
||||
)
|
||||
// CheckerMac is the name of the checker.
|
||||
const CheckerMac = "mac"
|
||||
|
||||
var (
|
||||
// ErrNotMAC indicates that the given value is not a valid MAC address.
|
||||
ErrNotMAC = NewCheckError("NOT_MAC")
|
||||
)
|
||||
// ResultNotMac indicates that the given value is not an MAC address.
|
||||
const ResultNotMac = "NOT_MAC"
|
||||
|
||||
// IsMAC checks if the value is a valid MAC address.
|
||||
func IsMAC(value string) (string, error) {
|
||||
// IsMac checks if the given value is a valid an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet IP over InfiniBand link-layer address.
|
||||
func IsMac(value string) Result {
|
||||
_, err := net.ParseMAC(value)
|
||||
if err != nil {
|
||||
return value, ErrNotMAC
|
||||
return ResultNotMac
|
||||
}
|
||||
return value, nil
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
|
||||
// checkMAC checks if the value is a valid MAC address.
|
||||
func checkMAC(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsMAC(value.Interface().(string))
|
||||
return value, err
|
||||
// makeMac makes a checker function for the ip checker.
|
||||
func makeMac(_ string) CheckFunc {
|
||||
return checkMac
|
||||
}
|
||||
|
||||
// makeMAC makes a checker function for the MAC checker.
|
||||
func makeMAC(_ string) CheckFunc[reflect.Value] {
|
||||
return checkMAC
|
||||
// checkMac checks if the given value is a valid an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet IP over InfiniBand link-layer address.
|
||||
func checkMac(value, _ reflect.Value) Result {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsMac(value.String())
|
||||
}
|
||||
|
|
|
|||
77
mac_test.go
77
mac_test.go
|
|
@ -1,76 +1,69 @@
|
|||
// 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
|
||||
package checker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsMAC() {
|
||||
_, err := v2.IsMAC("00:1A:2B:3C:4D:5E")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
func ExampleIsMac() {
|
||||
result := checker.IsMac("00:00:5e:00:53:01")
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMACInvalid(t *testing.T) {
|
||||
_, err := v2.IsMAC("invalid-mac")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
func TestIsMacInvalid(t *testing.T) {
|
||||
if checker.IsMac("1234") == checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMACValid(t *testing.T) {
|
||||
_, err := v2.IsMAC("00:1A:2B:3C:4D:5E")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
func TestIsMacValid(t *testing.T) {
|
||||
if checker.IsMac("00:00:5e:00:53:01") != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckMACNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
func TestCheckMacNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Device struct {
|
||||
MAC int `checkers:"mac"`
|
||||
type Network struct {
|
||||
HardwareAddress int `checkers:"mac"`
|
||||
}
|
||||
|
||||
device := &Device{}
|
||||
network := &Network{}
|
||||
|
||||
v2.CheckStruct(device)
|
||||
checker.Check(network)
|
||||
}
|
||||
|
||||
func TestCheckMACInvalid(t *testing.T) {
|
||||
type Device struct {
|
||||
MAC string `checkers:"mac"`
|
||||
func TestCheckMacInvalid(t *testing.T) {
|
||||
type Network struct {
|
||||
HardwareAddress string `checkers:"mac"`
|
||||
}
|
||||
|
||||
device := &Device{
|
||||
MAC: "invalid-mac",
|
||||
network := &Network{
|
||||
HardwareAddress: "1234",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(device)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
_, valid := checker.Check(network)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckMACValid(t *testing.T) {
|
||||
type Device struct {
|
||||
MAC string `checkers:"mac"`
|
||||
func TestCheckMacValid(t *testing.T) {
|
||||
type Network struct {
|
||||
HardwareAddress string `checkers:"mac"`
|
||||
}
|
||||
|
||||
device := &Device{
|
||||
MAC: "00:1A:2B:3C:4D:5E",
|
||||
network := &Network{
|
||||
HardwareAddress: "00:00:5e:00:53:01",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(device)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
_, valid := checker.Check(network)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
76
maker.go
76
maker.go
|
|
@ -1,76 +0,0 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MakeCheckFunc is a function that returns a check function using the given params.
|
||||
type MakeCheckFunc func(params string) CheckFunc[reflect.Value]
|
||||
|
||||
// makers provides a mapping of maker functions keyed by the check name.
|
||||
var makers = map[string]MakeCheckFunc{
|
||||
nameAlphanumeric: makeAlphanumeric,
|
||||
nameASCII: makeASCII,
|
||||
nameCIDR: makeCIDR,
|
||||
nameCreditCard: makeCreditCard,
|
||||
nameDigits: makeDigits,
|
||||
nameEmail: makeEmail,
|
||||
nameFQDN: makeFQDN,
|
||||
nameGte: makeGte,
|
||||
nameHex: makeHex,
|
||||
nameHTMLEscape: makeHTMLEscape,
|
||||
nameHTMLUnescape: makeHTMLUnescape,
|
||||
nameIP: makeIP,
|
||||
nameIPv4: makeIPv4,
|
||||
nameIPv6: makeIPv6,
|
||||
nameISBN: makeISBN,
|
||||
nameLower: makeLower,
|
||||
nameLte: makeLte,
|
||||
nameLUHN: makeLUHN,
|
||||
nameMAC: makeMAC,
|
||||
nameMaxLen: makeMaxLen,
|
||||
nameMinLen: makeMinLen,
|
||||
nameRegexp: makeRegexp,
|
||||
nameRequired: makeRequired,
|
||||
nameTime: makeTime,
|
||||
nameTitle: makeTitle,
|
||||
nameTrimLeft: makeTrimLeft,
|
||||
nameTrimRight: makeTrimRight,
|
||||
nameTrimSpace: makeTrimSpace,
|
||||
nameUpper: makeUpper,
|
||||
nameURL: makeURL,
|
||||
nameURLEscape: makeURLEscape,
|
||||
nameURLUnescape: makeURLUnescape,
|
||||
}
|
||||
|
||||
// RegisterMaker registers a new maker function with the given name.
|
||||
func RegisterMaker(name string, maker MakeCheckFunc) {
|
||||
makers[name] = maker
|
||||
}
|
||||
|
||||
// makeChecks take a checker config and returns the check functions.
|
||||
func makeChecks(config string) []CheckFunc[reflect.Value] {
|
||||
fields := strings.Fields(config)
|
||||
|
||||
checks := make([]CheckFunc[reflect.Value], len(fields))
|
||||
|
||||
for i, field := range fields {
|
||||
name, params, _ := strings.Cut(field, ":")
|
||||
|
||||
maker, ok := makers[name]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("check %s not found", name))
|
||||
}
|
||||
|
||||
checks[i] = maker(params)
|
||||
}
|
||||
|
||||
return checks
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
// 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"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker/v2/locales"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func TestMakeCheckersUnknown(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Person struct {
|
||||
Name string `checkers:"unknown"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "Onur",
|
||||
}
|
||||
|
||||
v2.CheckStruct(person)
|
||||
}
|
||||
|
||||
func ExampleRegisterMaker() {
|
||||
locales.EnUSMessages["NOT_FRUIT"] = "Not a fruit name."
|
||||
|
||||
v2.RegisterMaker("is-fruit", func(params string) v2.CheckFunc[reflect.Value] {
|
||||
return func(value reflect.Value) (reflect.Value, error) {
|
||||
stringValue := value.Interface().(string)
|
||||
|
||||
if stringValue == "apple" || stringValue == "banana" {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
return value, v2.NewCheckError("NOT_FRUIT")
|
||||
}
|
||||
})
|
||||
|
||||
type Item struct {
|
||||
Name string `checkers:"is-fruit"`
|
||||
}
|
||||
|
||||
person := &Item{
|
||||
Name: "banana",
|
||||
}
|
||||
|
||||
err, ok := v2.CheckStruct(person)
|
||||
if !ok {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterMaker(t *testing.T) {
|
||||
v2.RegisterMaker("unknown", func(params string) v2.CheckFunc[reflect.Value] {
|
||||
return func(value reflect.Value) (reflect.Value, error) {
|
||||
return value, nil
|
||||
}
|
||||
})
|
||||
|
||||
type Person struct {
|
||||
Name string `checkers:"unknown"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "Onur",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(person)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
40
max.go
Normal file
40
max.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// CheckerMax is the name of the checker.
|
||||
const CheckerMax = "max"
|
||||
|
||||
// ResultNotMax indicates that the given value is above the defined maximum.
|
||||
const ResultNotMax = "NOT_MIN"
|
||||
|
||||
// IsMax checks if the given value is below than the given maximum.
|
||||
func IsMax(value interface{}, max float64) Result {
|
||||
return checkMax(reflect.Indirect(reflect.ValueOf(value)), reflect.ValueOf(nil), max)
|
||||
}
|
||||
|
||||
// makeMax makes a checker function for the max checker.
|
||||
func makeMax(config string) CheckFunc {
|
||||
max, err := strconv.ParseFloat(config, 64)
|
||||
if err != nil {
|
||||
panic("unable to parse max")
|
||||
}
|
||||
|
||||
return func(value, parent reflect.Value) Result {
|
||||
return checkMax(value, parent, max)
|
||||
}
|
||||
}
|
||||
|
||||
// checkMax checks if the given value is less than the given maximum.
|
||||
func checkMax(value, _ reflect.Value, max float64) Result {
|
||||
n := numberOf(value)
|
||||
|
||||
if n > max {
|
||||
return ResultNotMax
|
||||
}
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
61
max_len.go
61
max_len.go
|
|
@ -1,61 +0,0 @@
|
|||
// 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"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameMaxLen is the name of the maximum length check.
|
||||
nameMaxLen = "max-len"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMaxLen indicates that the value's length is greater than the specified maximum.
|
||||
ErrMaxLen = NewCheckError("NOT_MAX_LEN")
|
||||
)
|
||||
|
||||
// MaxLen checks if the length of the given value (string, slice, or map) is at most n.
|
||||
// Returns an error if the length is greater than n.
|
||||
func MaxLen[T any](n int) CheckFunc[T] {
|
||||
return func(value T) (T, error) {
|
||||
v, ok := any(value).(reflect.Value)
|
||||
if !ok {
|
||||
v = reflect.ValueOf(value)
|
||||
}
|
||||
|
||||
v = reflect.Indirect(v)
|
||||
|
||||
if v.Len() > n {
|
||||
return value, newMaxLenError(n)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
// makeMaxLen creates a maximum length check function from a string parameter.
|
||||
// Panics if the parameter cannot be parsed as an integer.
|
||||
func makeMaxLen(params string) CheckFunc[reflect.Value] {
|
||||
n, err := strconv.Atoi(params)
|
||||
if err != nil {
|
||||
panic("unable to parse max length")
|
||||
}
|
||||
|
||||
return MaxLen[reflect.Value](n)
|
||||
}
|
||||
|
||||
// newMaxLenError creates a new maximum length error with the given maximum length.
|
||||
func newMaxLenError(n int) error {
|
||||
return NewCheckErrorWithData(
|
||||
ErrMaxLen.Code,
|
||||
map[string]interface{}{
|
||||
"max": n,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
// 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 (
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func TestMaxLenSuccess(t *testing.T) {
|
||||
value := "test"
|
||||
|
||||
check := v2.MaxLen[string](4)
|
||||
|
||||
result, err := check(value)
|
||||
if result != value {
|
||||
t.Fatalf("result (%s) is not the original value (%s)", result, value)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxLenError(t *testing.T) {
|
||||
value := "test test"
|
||||
|
||||
check := v2.MaxLen[string](5)
|
||||
|
||||
result, err := check(value)
|
||||
if result != value {
|
||||
t.Fatalf("result (%s) is not the original value (%s)", result, value)
|
||||
}
|
||||
|
||||
message := "Value cannot be greater than 5."
|
||||
|
||||
if err.Error() != message {
|
||||
t.Fatalf("expected %s actual %s", message, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestReflectMaxLenError(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"max-len:2"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "Onur",
|
||||
}
|
||||
|
||||
errs, ok := v2.CheckStruct(person)
|
||||
if ok {
|
||||
t.Fatalf("expected errors")
|
||||
}
|
||||
|
||||
if errs["Name"] == nil {
|
||||
t.Fatalf("expected maximum length error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReflectMaxLenInvalidMaxLen(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Person struct {
|
||||
Name string `checkers:"max-len:abcd"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "Onur",
|
||||
}
|
||||
|
||||
v2.CheckStruct(person)
|
||||
}
|
||||
|
||||
func TestReflectMaxLenInvalidType(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Person struct {
|
||||
Name int `checkers:"max-len:8"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: 1,
|
||||
}
|
||||
|
||||
v2.CheckStruct(person)
|
||||
}
|
||||
67
max_test.go
Normal file
67
max_test.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package checker_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsMax() {
|
||||
quantity := 5
|
||||
|
||||
result := checker.IsMax(quantity, 10)
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMaxValid(t *testing.T) {
|
||||
n := 5
|
||||
|
||||
if checker.IsMax(n, 10) != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckMaxInvalidConfig(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Order struct {
|
||||
Quantity int `checkers:"max:AB"`
|
||||
}
|
||||
|
||||
order := &Order{}
|
||||
|
||||
checker.Check(order)
|
||||
}
|
||||
|
||||
func TestCheckMaxValid(t *testing.T) {
|
||||
type Order struct {
|
||||
Quantity int `checkers:"max:10"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
Quantity: 5,
|
||||
}
|
||||
|
||||
_, valid := checker.Check(order)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckMaxInvalid(t *testing.T) {
|
||||
type Order struct {
|
||||
Quantity int `checkers:"max:10"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
Quantity: 20,
|
||||
}
|
||||
|
||||
_, valid := checker.Check(order)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
39
maxlength.go
Normal file
39
maxlength.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// CheckerMaxLength is the name of the checker.
|
||||
const CheckerMaxLength = "max-length"
|
||||
|
||||
// ResultNotMaxLength indicates that the length of the given value is above the defined number.
|
||||
const ResultNotMaxLength = "NOT_MAX_LENGTH"
|
||||
|
||||
// IsMaxLength checks if the length of the given value is less than the given maximum length.
|
||||
func IsMaxLength(value interface{}, maxLength int) Result {
|
||||
return checkMaxLength(reflect.Indirect(reflect.ValueOf(value)), reflect.ValueOf(nil), maxLength)
|
||||
}
|
||||
|
||||
// makeMaxLength makes a checker function for the max length checker.
|
||||
func makeMaxLength(config string) CheckFunc {
|
||||
maxLength, err := strconv.Atoi(config)
|
||||
if err != nil {
|
||||
panic("unable to parse max length value")
|
||||
}
|
||||
|
||||
return func(value, parent reflect.Value) Result {
|
||||
return checkMaxLength(value, parent, maxLength)
|
||||
}
|
||||
}
|
||||
|
||||
// checkMaxLength checks if the length of the given value is less than the given maximum length.
|
||||
// The function uses the reflect.Value.Len() function to determaxe the length of the value.
|
||||
func checkMaxLength(value, _ reflect.Value, maxLength int) Result {
|
||||
if value.Len() > maxLength {
|
||||
return ResultNotMaxLength
|
||||
}
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
67
maxlength_test.go
Normal file
67
maxlength_test.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package checker_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsMaxLength() {
|
||||
s := "1234"
|
||||
|
||||
result := checker.IsMaxLength(s, 4)
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMaxLengthValid(t *testing.T) {
|
||||
s := "1234"
|
||||
|
||||
if checker.IsMaxLength(s, 4) != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckMaxLengthInvalidConfig(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type User struct {
|
||||
Password string `checkers:"max-length:AB"`
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
|
||||
checker.Check(user)
|
||||
}
|
||||
|
||||
func TestCheckMaxLengthValid(t *testing.T) {
|
||||
type User struct {
|
||||
Password string `checkers:"max-length:4"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Password: "1234",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckMaxLengthInvalid(t *testing.T) {
|
||||
type User struct {
|
||||
Password string `checkers:"max-length:4"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Password: "123456",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
40
min.go
Normal file
40
min.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// CheckerMin is the name of the checker.
|
||||
const CheckerMin = "min"
|
||||
|
||||
// ResultNotMin indicates that the given value is below the defined minimum.
|
||||
const ResultNotMin = "NOT_MIN"
|
||||
|
||||
// IsMin checks if the given value is above than the given minimum.
|
||||
func IsMin(value interface{}, min float64) Result {
|
||||
return checkMin(reflect.Indirect(reflect.ValueOf(value)), reflect.ValueOf(nil), min)
|
||||
}
|
||||
|
||||
// makeMin makes a checker function for the min checker.
|
||||
func makeMin(config string) CheckFunc {
|
||||
min, err := strconv.ParseFloat(config, 64)
|
||||
if err != nil {
|
||||
panic("unable to parse min")
|
||||
}
|
||||
|
||||
return func(value, parent reflect.Value) Result {
|
||||
return checkMin(value, parent, min)
|
||||
}
|
||||
}
|
||||
|
||||
// checkMin checks if the given value is greather than the given minimum.
|
||||
func checkMin(value, _ reflect.Value, min float64) Result {
|
||||
n := numberOf(value)
|
||||
|
||||
if n < min {
|
||||
return ResultNotMin
|
||||
}
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
61
min_len.go
61
min_len.go
|
|
@ -1,61 +0,0 @@
|
|||
// 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"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameMinLen is the name of the minimum length check.
|
||||
nameMinLen = "min-len"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMinLen indicates that the value's length is less than the specified minimum.
|
||||
ErrMinLen = NewCheckError("NOT_MIN_LEN")
|
||||
)
|
||||
|
||||
// MinLen checks if the length of the given value (string, slice, or map) is at least n.
|
||||
// Returns an error if the length is less than n.
|
||||
func MinLen[T any](n int) CheckFunc[T] {
|
||||
return func(value T) (T, error) {
|
||||
v, ok := any(value).(reflect.Value)
|
||||
if !ok {
|
||||
v = reflect.ValueOf(value)
|
||||
}
|
||||
|
||||
v = reflect.Indirect(v)
|
||||
|
||||
if v.Len() < n {
|
||||
return value, newMinLenError(n)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
// makeMinLen creates a minimum length check function from a string parameter.
|
||||
// Panics if the parameter cannot be parsed as an integer.
|
||||
func makeMinLen(params string) CheckFunc[reflect.Value] {
|
||||
n, err := strconv.Atoi(params)
|
||||
if err != nil {
|
||||
panic("unable to parse min length")
|
||||
}
|
||||
|
||||
return MinLen[reflect.Value](n)
|
||||
}
|
||||
|
||||
// newMinLenError creates a new minimum length error with the given minimum value.
|
||||
func newMinLenError(n int) error {
|
||||
return NewCheckErrorWithData(
|
||||
ErrMinLen.Code,
|
||||
map[string]interface{}{
|
||||
"min": n,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
// 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 (
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func TestMinLenSuccess(t *testing.T) {
|
||||
value := "test"
|
||||
|
||||
check := v2.MinLen[string](4)
|
||||
|
||||
result, err := check(value)
|
||||
if result != value {
|
||||
t.Fatalf("result (%s) is not the original value (%s)", result, value)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinLenError(t *testing.T) {
|
||||
value := "test"
|
||||
|
||||
check := v2.MinLen[string](5)
|
||||
|
||||
result, err := check(value)
|
||||
if result != value {
|
||||
t.Fatalf("result (%s) is not the original value (%s)", result, value)
|
||||
}
|
||||
|
||||
message := "Value cannot be less than 5."
|
||||
|
||||
if err.Error() != message {
|
||||
t.Fatalf("expected %s actual %s", message, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestReflectMinLenError(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"trim min-len:8"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: " Onur ",
|
||||
}
|
||||
|
||||
errs, ok := v2.CheckStruct(person)
|
||||
if ok {
|
||||
t.Fatalf("expected errors")
|
||||
}
|
||||
|
||||
if errs["Name"] == nil {
|
||||
t.Fatalf("expected minimum length error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReflectMinLenInvalidMinLen(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Person struct {
|
||||
Name string `checkers:"min-len:abcd"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "Onur",
|
||||
}
|
||||
|
||||
v2.CheckStruct(person)
|
||||
}
|
||||
|
||||
func TestReflectMinLenInvalidType(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Person struct {
|
||||
Name int `checkers:"min-len:8"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: 1,
|
||||
}
|
||||
|
||||
v2.CheckStruct(person)
|
||||
}
|
||||
67
min_test.go
Normal file
67
min_test.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package checker_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsMin() {
|
||||
age := 45
|
||||
|
||||
result := checker.IsMin(age, 21)
|
||||
|
||||
if result != checker.ResultValid {
|
||||
// Send the mistakes back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMinValid(t *testing.T) {
|
||||
n := 45
|
||||
|
||||
if checker.IsMin(n, 21) != checker.ResultValid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckMinInvalidConfig(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type User struct {
|
||||
Age int `checkers:"min:AB"`
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
|
||||
checker.Check(user)
|
||||
}
|
||||
|
||||
func TestCheckMinValid(t *testing.T) {
|
||||
type User struct {
|
||||
Age int `checkers:"min:21"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Age: 45,
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckMinInvalid(t *testing.T) {
|
||||
type User struct {
|
||||
Age int `checkers:"min:21"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Age: 18,
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
39
minlenght.go
Normal file
39
minlenght.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// CheckerMinLength is the name of the checker.
|
||||
const CheckerMinLength = "min-length"
|
||||
|
||||
// ResultNotMinLength indicates that the length of the given value is below the defined number.
|
||||
const ResultNotMinLength = "NOT_MIN_LENGTH"
|
||||
|
||||
// IsMinLength checks if the length of the given value is greather than the given minimum length.
|
||||
func IsMinLength(value interface{}, minLength int) Result {
|
||||
return checkMinLength(reflect.Indirect(reflect.ValueOf(value)), reflect.ValueOf(nil), minLength)
|
||||
}
|
||||
|
||||
// makeMinLength makes a checker function for the min length checker.
|
||||
func makeMinLength(config string) CheckFunc {
|
||||
minLength, err := strconv.Atoi(config)
|
||||
if err != nil {
|
||||
panic("unable to parse min length value")
|
||||
}
|
||||
|
||||
return func(value, parent reflect.Value) Result {
|
||||
return checkMinLength(value, parent, minLength)
|
||||
}
|
||||
}
|
||||
|
||||
// checkMinLength checks if the length of the given value is greather than the given minimum length.
|
||||
// The function uses the reflect.Value.Len() function to determine the length of the value.
|
||||
func checkMinLength(value, _ reflect.Value, minLength int) Result {
|
||||
if value.Len() < minLength {
|
||||
return ResultNotMinLength
|
||||
}
|
||||
|
||||
return ResultValid
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue