Version v2. (#158)
# Describe Request Version v2. # Change Type New version.
This commit is contained in:
parent
ceffc4932d
commit
bdb9c8ea9b
145 changed files with 1684 additions and 7569 deletions
|
|
@ -1,4 +1,3 @@
|
|||
output: "{{.Dir}}/README.md"
|
||||
output: "{{.Dir}}/DOC.md"
|
||||
repository:
|
||||
url: https://github.com/cinar/checker
|
||||
exclude-dirs: "./v2/..."
|
||||
|
|
@ -249,7 +249,7 @@ var (
|
|||
```
|
||||
|
||||
<a name="Check"></a>
|
||||
## func [Check](<https://github.com/cinar/checker/blob/main/v2/checker.go#L32>)
|
||||
## func [Check](<https://github.com/cinar/checker/blob/main/checker.go#L32>)
|
||||
|
||||
```go
|
||||
func Check[T any](value T, checks ...CheckFunc[T]) (T, error)
|
||||
|
|
@ -294,7 +294,7 @@ Onur Cinar
|
|||
</details>
|
||||
|
||||
<a name="CheckStruct"></a>
|
||||
## func [CheckStruct](<https://github.com/cinar/checker/blob/main/v2/checker.go#L62>)
|
||||
## func [CheckStruct](<https://github.com/cinar/checker/blob/main/checker.go#L62>)
|
||||
|
||||
```go
|
||||
func CheckStruct(st any) (map[string]error, bool)
|
||||
|
|
@ -345,7 +345,7 @@ Onur Cinar
|
|||
</details>
|
||||
|
||||
<a name="CheckWithConfig"></a>
|
||||
## func [CheckWithConfig](<https://github.com/cinar/checker/blob/main/v2/checker.go#L47>)
|
||||
## func [CheckWithConfig](<https://github.com/cinar/checker/blob/main/checker.go#L47>)
|
||||
|
||||
```go
|
||||
func CheckWithConfig[T any](value T, config string) (T, error)
|
||||
|
|
@ -354,7 +354,7 @@ func CheckWithConfig[T any](value T, config string) (T, error)
|
|||
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.
|
||||
|
||||
<a name="HTMLEscape"></a>
|
||||
## func [HTMLEscape](<https://github.com/cinar/checker/blob/main/v2/html_escape.go#L19>)
|
||||
## func [HTMLEscape](<https://github.com/cinar/checker/blob/main/html_escape.go#L19>)
|
||||
|
||||
```go
|
||||
func HTMLEscape(value string) (string, error)
|
||||
|
|
@ -363,7 +363,7 @@ func HTMLEscape(value string) (string, error)
|
|||
HTMLEscape applies HTML escaping to special characters.
|
||||
|
||||
<a name="HTMLUnescape"></a>
|
||||
## func [HTMLUnescape](<https://github.com/cinar/checker/blob/main/v2/html_unescape.go#L17>)
|
||||
## func [HTMLUnescape](<https://github.com/cinar/checker/blob/main/html_unescape.go#L17>)
|
||||
|
||||
```go
|
||||
func HTMLUnescape(value string) (string, error)
|
||||
|
|
@ -372,7 +372,7 @@ func HTMLUnescape(value string) (string, error)
|
|||
HTMLUnescape applies HTML unescaping to special characters.
|
||||
|
||||
<a name="IsASCII"></a>
|
||||
## func [IsASCII](<https://github.com/cinar/checker/blob/main/v2/ascii.go#L24>)
|
||||
## func [IsASCII](<https://github.com/cinar/checker/blob/main/ascii.go#L24>)
|
||||
|
||||
```go
|
||||
func IsASCII(value string) (string, error)
|
||||
|
|
@ -406,7 +406,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsAlphanumeric"></a>
|
||||
## func [IsAlphanumeric](<https://github.com/cinar/checker/blob/main/v2/alphanumeric.go#L24>)
|
||||
## func [IsAlphanumeric](<https://github.com/cinar/checker/blob/main/alphanumeric.go#L24>)
|
||||
|
||||
```go
|
||||
func IsAlphanumeric(value string) (string, error)
|
||||
|
|
@ -440,7 +440,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsAmexCreditCard"></a>
|
||||
## func [IsAmexCreditCard](<https://github.com/cinar/checker/blob/main/v2/credit_card.go#L80>)
|
||||
## func [IsAmexCreditCard](<https://github.com/cinar/checker/blob/main/credit_card.go#L80>)
|
||||
|
||||
```go
|
||||
func IsAmexCreditCard(number string) (string, error)
|
||||
|
|
@ -473,7 +473,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsAnyCreditCard"></a>
|
||||
## func [IsAnyCreditCard](<https://github.com/cinar/checker/blob/main/v2/credit_card.go#L75>)
|
||||
## func [IsAnyCreditCard](<https://github.com/cinar/checker/blob/main/credit_card.go#L75>)
|
||||
|
||||
```go
|
||||
func IsAnyCreditCard(number string) (string, error)
|
||||
|
|
@ -506,7 +506,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsCIDR"></a>
|
||||
## func [IsCIDR](<https://github.com/cinar/checker/blob/main/v2/cidr.go#L24>)
|
||||
## func [IsCIDR](<https://github.com/cinar/checker/blob/main/cidr.go#L24>)
|
||||
|
||||
```go
|
||||
func IsCIDR(value string) (string, error)
|
||||
|
|
@ -540,7 +540,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsDigits"></a>
|
||||
## func [IsDigits](<https://github.com/cinar/checker/blob/main/v2/digits.go#L24>)
|
||||
## func [IsDigits](<https://github.com/cinar/checker/blob/main/digits.go#L24>)
|
||||
|
||||
```go
|
||||
func IsDigits(value string) (string, error)
|
||||
|
|
@ -574,7 +574,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsDinersCreditCard"></a>
|
||||
## func [IsDinersCreditCard](<https://github.com/cinar/checker/blob/main/v2/credit_card.go#L85>)
|
||||
## func [IsDinersCreditCard](<https://github.com/cinar/checker/blob/main/credit_card.go#L85>)
|
||||
|
||||
```go
|
||||
func IsDinersCreditCard(number string) (string, error)
|
||||
|
|
@ -607,7 +607,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsDiscoverCreditCard"></a>
|
||||
## func [IsDiscoverCreditCard](<https://github.com/cinar/checker/blob/main/v2/credit_card.go#L90>)
|
||||
## func [IsDiscoverCreditCard](<https://github.com/cinar/checker/blob/main/credit_card.go#L90>)
|
||||
|
||||
```go
|
||||
func IsDiscoverCreditCard(number string) (string, error)
|
||||
|
|
@ -640,7 +640,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsEmail"></a>
|
||||
## func [IsEmail](<https://github.com/cinar/checker/blob/main/v2/email.go#L24>)
|
||||
## func [IsEmail](<https://github.com/cinar/checker/blob/main/email.go#L24>)
|
||||
|
||||
```go
|
||||
func IsEmail(value string) (string, error)
|
||||
|
|
@ -674,7 +674,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsFQDN"></a>
|
||||
## func [IsFQDN](<https://github.com/cinar/checker/blob/main/v2/fqdn.go#L27>)
|
||||
## func [IsFQDN](<https://github.com/cinar/checker/blob/main/fqdn.go#L27>)
|
||||
|
||||
```go
|
||||
func IsFQDN(value string) (string, error)
|
||||
|
|
@ -708,7 +708,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsHex"></a>
|
||||
## func [IsHex](<https://github.com/cinar/checker/blob/main/v2/hex.go#L23>)
|
||||
## func [IsHex](<https://github.com/cinar/checker/blob/main/hex.go#L23>)
|
||||
|
||||
```go
|
||||
func IsHex(value string) (string, error)
|
||||
|
|
@ -742,7 +742,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsIP"></a>
|
||||
## func [IsIP](<https://github.com/cinar/checker/blob/main/v2/ip.go#L24>)
|
||||
## func [IsIP](<https://github.com/cinar/checker/blob/main/ip.go#L24>)
|
||||
|
||||
```go
|
||||
func IsIP(value string) (string, error)
|
||||
|
|
@ -776,7 +776,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsIPv4"></a>
|
||||
## func [IsIPv4](<https://github.com/cinar/checker/blob/main/v2/ipv4.go#L24>)
|
||||
## func [IsIPv4](<https://github.com/cinar/checker/blob/main/ipv4.go#L24>)
|
||||
|
||||
```go
|
||||
func IsIPv4(value string) (string, error)
|
||||
|
|
@ -810,7 +810,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsIPv6"></a>
|
||||
## func [IsIPv6](<https://github.com/cinar/checker/blob/main/v2/ipv6.go#L24>)
|
||||
## func [IsIPv6](<https://github.com/cinar/checker/blob/main/ipv6.go#L24>)
|
||||
|
||||
```go
|
||||
func IsIPv6(value string) (string, error)
|
||||
|
|
@ -844,7 +844,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsISBN"></a>
|
||||
## func [IsISBN](<https://github.com/cinar/checker/blob/main/v2/isbn.go#L27>)
|
||||
## func [IsISBN](<https://github.com/cinar/checker/blob/main/isbn.go#L27>)
|
||||
|
||||
```go
|
||||
func IsISBN(value string) (string, error)
|
||||
|
|
@ -878,7 +878,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsJcbCreditCard"></a>
|
||||
## func [IsJcbCreditCard](<https://github.com/cinar/checker/blob/main/v2/credit_card.go#L95>)
|
||||
## func [IsJcbCreditCard](<https://github.com/cinar/checker/blob/main/credit_card.go#L95>)
|
||||
|
||||
```go
|
||||
func IsJcbCreditCard(number string) (string, error)
|
||||
|
|
@ -911,7 +911,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsLUHN"></a>
|
||||
## func [IsLUHN](<https://github.com/cinar/checker/blob/main/v2/luhn.go#L24>)
|
||||
## func [IsLUHN](<https://github.com/cinar/checker/blob/main/luhn.go#L24>)
|
||||
|
||||
```go
|
||||
func IsLUHN(value string) (string, error)
|
||||
|
|
@ -945,7 +945,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsMAC"></a>
|
||||
## func [IsMAC](<https://github.com/cinar/checker/blob/main/v2/mac.go#L24>)
|
||||
## func [IsMAC](<https://github.com/cinar/checker/blob/main/mac.go#L24>)
|
||||
|
||||
```go
|
||||
func IsMAC(value string) (string, error)
|
||||
|
|
@ -979,7 +979,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsMasterCardCreditCard"></a>
|
||||
## func [IsMasterCardCreditCard](<https://github.com/cinar/checker/blob/main/v2/credit_card.go#L100>)
|
||||
## func [IsMasterCardCreditCard](<https://github.com/cinar/checker/blob/main/credit_card.go#L100>)
|
||||
|
||||
```go
|
||||
func IsMasterCardCreditCard(number string) (string, error)
|
||||
|
|
@ -1012,7 +1012,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsRegexp"></a>
|
||||
## func [IsRegexp](<https://github.com/cinar/checker/blob/main/v2/regexp.go#L20>)
|
||||
## func [IsRegexp](<https://github.com/cinar/checker/blob/main/regexp.go#L20>)
|
||||
|
||||
```go
|
||||
func IsRegexp(expression, value string) (string, error)
|
||||
|
|
@ -1046,7 +1046,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsURL"></a>
|
||||
## func [IsURL](<https://github.com/cinar/checker/blob/main/v2/url.go#L24>)
|
||||
## func [IsURL](<https://github.com/cinar/checker/blob/main/url.go#L24>)
|
||||
|
||||
```go
|
||||
func IsURL(value string) (string, error)
|
||||
|
|
@ -1080,7 +1080,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsUnionPayCreditCard"></a>
|
||||
## func [IsUnionPayCreditCard](<https://github.com/cinar/checker/blob/main/v2/credit_card.go#L105>)
|
||||
## func [IsUnionPayCreditCard](<https://github.com/cinar/checker/blob/main/credit_card.go#L105>)
|
||||
|
||||
```go
|
||||
func IsUnionPayCreditCard(number string) (string, error)
|
||||
|
|
@ -1113,7 +1113,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="IsVisaCreditCard"></a>
|
||||
## func [IsVisaCreditCard](<https://github.com/cinar/checker/blob/main/v2/credit_card.go#L110>)
|
||||
## func [IsVisaCreditCard](<https://github.com/cinar/checker/blob/main/credit_card.go#L110>)
|
||||
|
||||
```go
|
||||
func IsVisaCreditCard(number string) (string, error)
|
||||
|
|
@ -1146,7 +1146,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="Lower"></a>
|
||||
## func [Lower](<https://github.com/cinar/checker/blob/main/v2/lower.go#L19>)
|
||||
## func [Lower](<https://github.com/cinar/checker/blob/main/lower.go#L19>)
|
||||
|
||||
```go
|
||||
func Lower(value string) (string, error)
|
||||
|
|
@ -1155,7 +1155,7 @@ func Lower(value string) (string, error)
|
|||
Lower maps all Unicode letters in the given value to their lower case.
|
||||
|
||||
<a name="ReflectCheckWithConfig"></a>
|
||||
## func [ReflectCheckWithConfig](<https://github.com/cinar/checker/blob/main/v2/checker.go#L55>)
|
||||
## func [ReflectCheckWithConfig](<https://github.com/cinar/checker/blob/main/checker.go#L55>)
|
||||
|
||||
```go
|
||||
func ReflectCheckWithConfig(value reflect.Value, config string) (reflect.Value, error)
|
||||
|
|
@ -1164,7 +1164,7 @@ func ReflectCheckWithConfig(value reflect.Value, config string) (reflect.Value,
|
|||
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.
|
||||
|
||||
<a name="RegisterLocale"></a>
|
||||
## func [RegisterLocale](<https://github.com/cinar/checker/blob/main/v2/check_error.go#L80>)
|
||||
## func [RegisterLocale](<https://github.com/cinar/checker/blob/main/check_error.go#L80>)
|
||||
|
||||
```go
|
||||
func RegisterLocale(locale string, messages map[string]string)
|
||||
|
|
@ -1173,7 +1173,7 @@ func RegisterLocale(locale string, messages map[string]string)
|
|||
RegisterLocale registers the localized error messages for the given locale.
|
||||
|
||||
<a name="RegisterMaker"></a>
|
||||
## func [RegisterMaker](<https://github.com/cinar/checker/blob/main/v2/maker.go#L51>)
|
||||
## func [RegisterMaker](<https://github.com/cinar/checker/blob/main/maker.go#L51>)
|
||||
|
||||
```go
|
||||
func RegisterMaker(name string, maker MakeCheckFunc)
|
||||
|
|
@ -1232,7 +1232,7 @@ func main() {
|
|||
</details>
|
||||
|
||||
<a name="Required"></a>
|
||||
## func [Required](<https://github.com/cinar/checker/blob/main/v2/required.go#L22>)
|
||||
## func [Required](<https://github.com/cinar/checker/blob/main/required.go#L22>)
|
||||
|
||||
```go
|
||||
func Required[T any](value T) (T, error)
|
||||
|
|
@ -1241,7 +1241,7 @@ func Required[T any](value T) (T, error)
|
|||
Required checks if the given value of type T is its zero value. It returns an error if the value is zero.
|
||||
|
||||
<a name="Title"></a>
|
||||
## func [Title](<https://github.com/cinar/checker/blob/main/v2/title.go#L20>)
|
||||
## func [Title](<https://github.com/cinar/checker/blob/main/title.go#L20>)
|
||||
|
||||
```go
|
||||
func Title(value string) (string, error)
|
||||
|
|
@ -1250,7 +1250,7 @@ func Title(value string) (string, error)
|
|||
Title returns the value of the string with the first letter of each word in upper case.
|
||||
|
||||
<a name="TrimLeft"></a>
|
||||
## func [TrimLeft](<https://github.com/cinar/checker/blob/main/v2/trim_left.go#L19>)
|
||||
## func [TrimLeft](<https://github.com/cinar/checker/blob/main/trim_left.go#L19>)
|
||||
|
||||
```go
|
||||
func TrimLeft(value string) (string, error)
|
||||
|
|
@ -1259,7 +1259,7 @@ func TrimLeft(value string) (string, error)
|
|||
TrimLeft returns the value of the string with whitespace removed from the beginning.
|
||||
|
||||
<a name="TrimRight"></a>
|
||||
## func [TrimRight](<https://github.com/cinar/checker/blob/main/v2/trim_right.go#L19>)
|
||||
## func [TrimRight](<https://github.com/cinar/checker/blob/main/trim_right.go#L19>)
|
||||
|
||||
```go
|
||||
func TrimRight(value string) (string, error)
|
||||
|
|
@ -1268,7 +1268,7 @@ func TrimRight(value string) (string, error)
|
|||
TrimRight returns the value of the string with whitespace removed from the end.
|
||||
|
||||
<a name="TrimSpace"></a>
|
||||
## func [TrimSpace](<https://github.com/cinar/checker/blob/main/v2/trim_space.go#L19>)
|
||||
## func [TrimSpace](<https://github.com/cinar/checker/blob/main/trim_space.go#L19>)
|
||||
|
||||
```go
|
||||
func TrimSpace(value string) (string, error)
|
||||
|
|
@ -1277,7 +1277,7 @@ func TrimSpace(value string) (string, error)
|
|||
TrimSpace returns the value of the string with whitespace removed from both ends.
|
||||
|
||||
<a name="URLEscape"></a>
|
||||
## func [URLEscape](<https://github.com/cinar/checker/blob/main/v2/url_escape.go#L17>)
|
||||
## func [URLEscape](<https://github.com/cinar/checker/blob/main/url_escape.go#L17>)
|
||||
|
||||
```go
|
||||
func URLEscape(value string) (string, error)
|
||||
|
|
@ -1286,7 +1286,7 @@ func URLEscape(value string) (string, error)
|
|||
URLEscape applies URL escaping to special characters.
|
||||
|
||||
<a name="URLUnescape"></a>
|
||||
## func [URLUnescape](<https://github.com/cinar/checker/blob/main/v2/url_unescape.go#L17>)
|
||||
## func [URLUnescape](<https://github.com/cinar/checker/blob/main/url_unescape.go#L17>)
|
||||
|
||||
```go
|
||||
func URLUnescape(value string) (string, error)
|
||||
|
|
@ -1295,7 +1295,7 @@ func URLUnescape(value string) (string, error)
|
|||
URLUnescape applies URL unescaping to special characters.
|
||||
|
||||
<a name="Upper"></a>
|
||||
## func [Upper](<https://github.com/cinar/checker/blob/main/v2/upper.go#L19>)
|
||||
## func [Upper](<https://github.com/cinar/checker/blob/main/upper.go#L19>)
|
||||
|
||||
```go
|
||||
func Upper(value string) (string, error)
|
||||
|
|
@ -1304,7 +1304,7 @@ func Upper(value string) (string, error)
|
|||
Upper maps all Unicode letters in the given value to their upper case.
|
||||
|
||||
<a name="CheckError"></a>
|
||||
## type [CheckError](<https://github.com/cinar/checker/blob/main/v2/check_error.go#L16-L22>)
|
||||
## type [CheckError](<https://github.com/cinar/checker/blob/main/check_error.go#L16-L22>)
|
||||
|
||||
CheckError defines the check error.
|
||||
|
||||
|
|
@ -1319,7 +1319,7 @@ type CheckError struct {
|
|||
```
|
||||
|
||||
<a name="NewCheckError"></a>
|
||||
### func [NewCheckError](<https://github.com/cinar/checker/blob/main/v2/check_error.go#L35>)
|
||||
### func [NewCheckError](<https://github.com/cinar/checker/blob/main/check_error.go#L35>)
|
||||
|
||||
```go
|
||||
func NewCheckError(code string) *CheckError
|
||||
|
|
@ -1328,7 +1328,7 @@ func NewCheckError(code string) *CheckError
|
|||
NewCheckError creates a new check error with the given code.
|
||||
|
||||
<a name="NewCheckErrorWithData"></a>
|
||||
### func [NewCheckErrorWithData](<https://github.com/cinar/checker/blob/main/v2/check_error.go#L43>)
|
||||
### func [NewCheckErrorWithData](<https://github.com/cinar/checker/blob/main/check_error.go#L43>)
|
||||
|
||||
```go
|
||||
func NewCheckErrorWithData(code string, data map[string]interface{}) *CheckError
|
||||
|
|
@ -1337,7 +1337,7 @@ func NewCheckErrorWithData(code string, data map[string]interface{}) *CheckError
|
|||
NewCheckErrorWithData creates a new check error with the given code and data.
|
||||
|
||||
<a name="CheckError.Error"></a>
|
||||
### func \(\*CheckError\) [Error](<https://github.com/cinar/checker/blob/main/v2/check_error.go#L51>)
|
||||
### func \(\*CheckError\) [Error](<https://github.com/cinar/checker/blob/main/check_error.go#L51>)
|
||||
|
||||
```go
|
||||
func (c *CheckError) Error() string
|
||||
|
|
@ -1346,7 +1346,7 @@ func (c *CheckError) Error() string
|
|||
Error returns the error message for the check.
|
||||
|
||||
<a name="CheckError.ErrorWithLocale"></a>
|
||||
### func \(\*CheckError\) [ErrorWithLocale](<https://github.com/cinar/checker/blob/main/v2/check_error.go#L65>)
|
||||
### func \(\*CheckError\) [ErrorWithLocale](<https://github.com/cinar/checker/blob/main/check_error.go#L65>)
|
||||
|
||||
```go
|
||||
func (c *CheckError) ErrorWithLocale(locale string) string
|
||||
|
|
@ -1355,7 +1355,7 @@ func (c *CheckError) ErrorWithLocale(locale string) string
|
|||
ErrorWithLocale returns the localized error message for the check with the given locale.
|
||||
|
||||
<a name="CheckError.Is"></a>
|
||||
### func \(\*CheckError\) [Is](<https://github.com/cinar/checker/blob/main/v2/check_error.go#L56>)
|
||||
### func \(\*CheckError\) [Is](<https://github.com/cinar/checker/blob/main/check_error.go#L56>)
|
||||
|
||||
```go
|
||||
func (c *CheckError) Is(target error) bool
|
||||
|
|
@ -1364,7 +1364,7 @@ func (c *CheckError) Is(target error) bool
|
|||
Is reports whether the check error is the same as the target error.
|
||||
|
||||
<a name="CheckFunc"></a>
|
||||
## type [CheckFunc](<https://github.com/cinar/checker/blob/main/v2/check_func.go#L11>)
|
||||
## type [CheckFunc](<https://github.com/cinar/checker/blob/main/check_func.go#L11>)
|
||||
|
||||
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.
|
||||
|
||||
|
|
@ -1373,7 +1373,7 @@ type CheckFunc[T any] func(value T) (T, error)
|
|||
```
|
||||
|
||||
<a name="MakeRegexpChecker"></a>
|
||||
### func [MakeRegexpChecker](<https://github.com/cinar/checker/blob/main/v2/regexp.go#L29>)
|
||||
### func [MakeRegexpChecker](<https://github.com/cinar/checker/blob/main/regexp.go#L29>)
|
||||
|
||||
```go
|
||||
func MakeRegexpChecker(expression string, invalidError error) CheckFunc[reflect.Value]
|
||||
|
|
@ -1382,7 +1382,7 @@ func MakeRegexpChecker(expression string, invalidError error) CheckFunc[reflect.
|
|||
MakeRegexpChecker makes a regexp checker for the given regexp expression with the given invalid result.
|
||||
|
||||
<a name="MaxLen"></a>
|
||||
### func [MaxLen](<https://github.com/cinar/checker/blob/main/v2/max_len.go#L25>)
|
||||
### func [MaxLen](<https://github.com/cinar/checker/blob/main/max_len.go#L25>)
|
||||
|
||||
```go
|
||||
func MaxLen[T any](n int) CheckFunc[T]
|
||||
|
|
@ -1391,7 +1391,7 @@ func MaxLen[T any](n int) CheckFunc[T]
|
|||
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.
|
||||
|
||||
<a name="MinLen"></a>
|
||||
### func [MinLen](<https://github.com/cinar/checker/blob/main/v2/min_len.go#L25>)
|
||||
### func [MinLen](<https://github.com/cinar/checker/blob/main/min_len.go#L25>)
|
||||
|
||||
```go
|
||||
func MinLen[T any](n int) CheckFunc[T]
|
||||
|
|
@ -1400,7 +1400,7 @@ func MinLen[T any](n int) CheckFunc[T]
|
|||
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.
|
||||
|
||||
<a name="MakeCheckFunc"></a>
|
||||
## type [MakeCheckFunc](<https://github.com/cinar/checker/blob/main/v2/maker.go#L15>)
|
||||
## type [MakeCheckFunc](<https://github.com/cinar/checker/blob/main/maker.go#L15>)
|
||||
|
||||
MakeCheckFunc is a function that returns a check function using the given params.
|
||||
|
||||
|
|
@ -3,41 +3,41 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// tagAlphanumeric is the tag of the checker.
|
||||
const tagAlphanumeric = "alphanumeric"
|
||||
const (
|
||||
// nameAlphanumeric is the name of the alphanumeric check.
|
||||
nameAlphanumeric = "alphanumeric"
|
||||
)
|
||||
|
||||
// ErrNotAlphanumeric indicates that the given string contains non-alphanumeric characters.
|
||||
var ErrNotAlphanumeric = errors.New("please use only letters and numbers")
|
||||
var (
|
||||
// ErrNotAlphanumeric indicates that the given string contains non-alphanumeric characters.
|
||||
ErrNotAlphanumeric = NewCheckError("NOT_ALPHANUMERIC")
|
||||
)
|
||||
|
||||
// IsAlphanumeric checks if the given string consists of only alphanumeric characters.
|
||||
func IsAlphanumeric(value string) error {
|
||||
func IsAlphanumeric(value string) (string, error) {
|
||||
for _, c := range value {
|
||||
if !unicode.IsDigit(c) && !unicode.IsLetter(c) {
|
||||
return ErrNotAlphanumeric
|
||||
return value, ErrNotAlphanumeric
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeAlphanumeric makes a checker function for the alphanumeric checker.
|
||||
func makeAlphanumeric(_ string) CheckFunc {
|
||||
return checkAlphanumeric
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// checkAlphanumeric checks if the given string consists of only alphanumeric characters.
|
||||
func checkAlphanumeric(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsAlphanumeric(value.String())
|
||||
func isAlphanumeric(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsAlphanumeric(value.Interface().(string))
|
||||
return value, err
|
||||
}
|
||||
|
||||
// makeAlphanumeric makes a checker function for the alphanumeric checker.
|
||||
func makeAlphanumeric(_ string) CheckFunc[reflect.Value] {
|
||||
return isAlphanumeric
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,71 +3,74 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsAlphanumeric() {
|
||||
err := checker.IsAlphanumeric("ABcd1234")
|
||||
_, err := v2.IsAlphanumeric("ABcd1234")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAlphanumericInvalid(t *testing.T) {
|
||||
if checker.IsAlphanumeric("-/") == nil {
|
||||
t.Fail()
|
||||
_, err := v2.IsAlphanumeric("-/")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAlphanumericValid(t *testing.T) {
|
||||
if checker.IsAlphanumeric("ABcd1234") != nil {
|
||||
t.Fail()
|
||||
_, err := v2.IsAlphanumeric("ABcd1234")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAlphanumericNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type User struct {
|
||||
Username int `checkers:"alphanumeric"`
|
||||
type Person struct {
|
||||
Name int `checkers:"alphanumeric"`
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
person := &Person{}
|
||||
|
||||
checker.Check(user)
|
||||
v2.CheckStruct(person)
|
||||
}
|
||||
|
||||
func TestCheckAlphanumericInvalid(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"alphanumeric"`
|
||||
type Person struct {
|
||||
Name string `checkers:"alphanumeric"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "user-/",
|
||||
person := &Person{
|
||||
Name: "name-/",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(person)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAlphanumericValid(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"alphanumeric"`
|
||||
type Person struct {
|
||||
Name string `checkers:"alphanumeric"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "ABcd1234",
|
||||
person := &Person{
|
||||
Name: "ABcd1234",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
errs, ok := v2.CheckStruct(person)
|
||||
if !ok {
|
||||
t.Fatal(errs)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
40
ascii.go
40
ascii.go
|
|
@ -3,41 +3,41 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// tagASCII is the tag of the checker.
|
||||
const tagASCII = "ascii"
|
||||
const (
|
||||
// nameASCII is the name of the ASCII check.
|
||||
nameASCII = "ascii"
|
||||
)
|
||||
|
||||
// ErrNotASCII indicates that the given string contains non-ASCII characters.
|
||||
var ErrNotASCII = errors.New("please use standard English characters only")
|
||||
var (
|
||||
// ErrNotASCII indicates that the given string contains non-ASCII characters.
|
||||
ErrNotASCII = NewCheckError("NOT_ASCII")
|
||||
)
|
||||
|
||||
// IsASCII checks if the given string consists of only ASCII characters.
|
||||
func IsASCII(value string) error {
|
||||
func IsASCII(value string) (string, error) {
|
||||
for _, c := range value {
|
||||
if c > unicode.MaxASCII {
|
||||
return ErrNotASCII
|
||||
return value, ErrNotASCII
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeASCII makes a checker function for the ASCII checker.
|
||||
func makeASCII(_ string) CheckFunc {
|
||||
return checkASCII
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// checkASCII checks if the given string consists of only ASCII characters.
|
||||
func checkASCII(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsASCII(value.String())
|
||||
func isASCII(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsASCII(value.Interface().(string))
|
||||
return value, err
|
||||
}
|
||||
|
||||
// makeASCII makes a checker function for the ASCII checker.
|
||||
func makeASCII(_ string) CheckFunc[reflect.Value] {
|
||||
return isASCII
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,43 +3,46 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsASCII() {
|
||||
err := checker.IsASCII("Checker")
|
||||
_, err := v2.IsASCII("Checker")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsASCIIInvalid(t *testing.T) {
|
||||
if checker.IsASCII("𝄞 Music!") == nil {
|
||||
t.Fail()
|
||||
_, err := v2.IsASCII("𝄞 Music!")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsASCIIValid(t *testing.T) {
|
||||
if checker.IsASCII("Checker") != nil {
|
||||
t.Fail()
|
||||
_, err := v2.IsASCII("Checker")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckASCIINonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type User struct {
|
||||
Age int `checkers:"ascii"`
|
||||
Username int `checkers:"ascii"`
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
|
||||
checker.Check(user)
|
||||
v2.CheckStruct(user)
|
||||
}
|
||||
|
||||
func TestCheckASCIIInvalid(t *testing.T) {
|
||||
|
|
@ -51,9 +54,9 @@ func TestCheckASCIIInvalid(t *testing.T) {
|
|||
Username: "𝄞 Music!",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(user)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,8 +69,8 @@ func TestCheckASCIIValid(t *testing.T) {
|
|||
Username: "checker",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(user)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
220
checker.go
220
checker.go
|
|
@ -1,10 +1,10 @@
|
|||
// Package checker is a Go library for validating user input through struct tags.
|
||||
//
|
||||
// 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 checker
|
||||
|
||||
// Package v2 Checker is a Go library for validating user input through checker rules provided in struct tags.
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -12,77 +12,60 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// CheckFunc defines the signature for the checker functions.
|
||||
type CheckFunc func(value, parent reflect.Value) error
|
||||
const (
|
||||
// checkerTag is the name of the field tag used for checker.
|
||||
checkerTag = "checkers"
|
||||
|
||||
// MakeFunc defines the signature for the checker maker functions.
|
||||
type MakeFunc func(params string) CheckFunc
|
||||
// sliceConfigPrefix is the prefix used to distinguish slice-level checks from item-level checks.
|
||||
sliceConfigPrefix = "@"
|
||||
)
|
||||
|
||||
// Errors provides a mapping of the checker errors keyed by the field names.
|
||||
type Errors map[string]error
|
||||
|
||||
type checkerJob struct {
|
||||
Parent reflect.Value
|
||||
// checkStructJob defines a check strcut job.
|
||||
type checkStructJob struct {
|
||||
Name string
|
||||
Value reflect.Value
|
||||
Config string
|
||||
}
|
||||
|
||||
// makers provides a mapping of the maker functions keyed by the respective checker names.
|
||||
var makers = map[string]MakeFunc{
|
||||
tagAlphanumeric: makeAlphanumeric,
|
||||
tagASCII: makeASCII,
|
||||
tagCreditCard: makeCreditCard,
|
||||
tagCidr: makeCidr,
|
||||
tagDigits: makeDigits,
|
||||
tagEmail: makeEmail,
|
||||
tagFqdn: makeFqdn,
|
||||
tagIP: makeIP,
|
||||
tagIPV4: makeIPV4,
|
||||
tagIPV6: makeIPV6,
|
||||
tagISBN: makeISBN,
|
||||
tagLuhn: makeLuhn,
|
||||
tagMac: makeMac,
|
||||
tagMax: makeMax,
|
||||
tagMaxLength: makeMaxLength,
|
||||
tagMin: makeMin,
|
||||
tagMinLength: makeMinLength,
|
||||
tagRegexp: makeRegexp,
|
||||
tagRequired: makeRequired,
|
||||
tagSame: makeSame,
|
||||
tagURL: makeURL,
|
||||
tagHTMLEscape: makeHTMLEscape,
|
||||
tagHTMLUnescape: makeHTMLUnescape,
|
||||
tagLower: makeLower,
|
||||
tagUpper: makeUpper,
|
||||
tagTitle: makeTitle,
|
||||
tagTrim: makeTrim,
|
||||
tagTrimLeft: makeTrimLeft,
|
||||
tagTrimRight: makeTrimRight,
|
||||
tagURLEscape: makeURLEscape,
|
||||
tagURLUnescape: makeURLUnescape,
|
||||
}
|
||||
// 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
|
||||
|
||||
// Register registers the given checker name and the maker function.
|
||||
func Register(name string, maker MakeFunc) {
|
||||
makers[name] = maker
|
||||
}
|
||||
|
||||
// Check function checks the given struct based on the checkers listed in field tag names.
|
||||
func Check(s interface{}) (Errors, bool) {
|
||||
root := reflect.Indirect(reflect.ValueOf(s))
|
||||
if root.Kind() != reflect.Struct {
|
||||
panic("expecting struct")
|
||||
for _, check := range checks {
|
||||
value, err = check(value)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
errs := Errors{}
|
||||
return value, err
|
||||
}
|
||||
|
||||
jobs := []checkerJob{
|
||||
// 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{
|
||||
{
|
||||
Parent: reflect.ValueOf(nil),
|
||||
Name: "",
|
||||
Value: root,
|
||||
Config: "",
|
||||
Name: "",
|
||||
Value: reflect.Indirect(reflect.ValueOf(st)),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -90,73 +73,82 @@ func Check(s interface{}) (Errors, bool) {
|
|||
job := jobs[0]
|
||||
jobs = jobs[1:]
|
||||
|
||||
if job.Value.Kind() == reflect.Struct {
|
||||
switch job.Value.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < job.Value.NumField(); i++ {
|
||||
field := job.Value.Type().Field(i)
|
||||
addJob := field.Type.Kind() == reflect.Struct
|
||||
config := ""
|
||||
|
||||
if !addJob {
|
||||
config = field.Tag.Get("checkers")
|
||||
addJob = config != ""
|
||||
}
|
||||
name := fieldName(job.Name, field)
|
||||
value := reflect.Indirect(job.Value.FieldByIndex(field.Index))
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
jobs = append(jobs, &checkStructJob{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Config: field.Tag.Get(checkerTag),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for _, checker := range initCheckers(job.Config) {
|
||||
if err := checker(job.Value, job.Parent); err != nil {
|
||||
errs[job.Name] = err
|
||||
break
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// initCheckers initializes the checkers provided in the config.
|
||||
func initCheckers(config string) []CheckFunc {
|
||||
fields := strings.Fields(config)
|
||||
checkers := make([]CheckFunc, len(fields))
|
||||
// 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
|
||||
|
||||
for i, field := range fields {
|
||||
name, params, _ := strings.Cut(field, ":")
|
||||
// Use json tag if present
|
||||
if jsonTag, ok := field.Tag.Lookup("json"); ok {
|
||||
name = jsonTag
|
||||
}
|
||||
|
||||
maker, ok := makers[name]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("checker %s is unknown", name))
|
||||
// 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)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
return strings.Join(sliceFileds, " "), strings.Join(itemFields, " ")
|
||||
}
|
||||
|
|
|
|||
280
checker_test.go
280
checker_test.go
|
|
@ -3,178 +3,196 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func TestInitCheckersUnknown(t *testing.T) {
|
||||
defer FailIfNoPanic(t)
|
||||
func ExampleCheck() {
|
||||
name := " Onur Cinar "
|
||||
|
||||
initCheckers("unknown")
|
||||
name, err := v2.Check(name, v2.TrimSpace, v2.Required)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(name)
|
||||
// Output: Onur Cinar
|
||||
}
|
||||
|
||||
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) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
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) {
|
||||
func ExampleCheckStruct() {
|
||||
type Person struct {
|
||||
Name string `checkers:"required"`
|
||||
}
|
||||
|
||||
person := &Person{}
|
||||
|
||||
errors, valid := Check(person)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if len(errors) != 1 {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if errors["Name"] != ErrRequired {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckValid(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"required"`
|
||||
Name string `checkers:"trim required"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "Onur",
|
||||
Name: " Onur Cinar ",
|
||||
}
|
||||
|
||||
errors, valid := Check(person)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
errs, ok := v2.CheckStruct(person)
|
||||
if !ok {
|
||||
fmt.Println(errs)
|
||||
return
|
||||
}
|
||||
|
||||
if len(errors) != 0 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckNoStruct(t *testing.T) {
|
||||
defer FailIfNoPanic(t)
|
||||
func TestCheckTrimSpaceRequiredMissing(t *testing.T) {
|
||||
input := " "
|
||||
expected := ""
|
||||
|
||||
s := "unknown"
|
||||
Check(s)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckNestedStruct(t *testing.T) {
|
||||
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) {
|
||||
type Address struct {
|
||||
Street string `checkers:"required"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string `checkers:"required"`
|
||||
Home Address
|
||||
Name string `checkers:"required"`
|
||||
Address *Address
|
||||
}
|
||||
|
||||
person := &Person{}
|
||||
|
||||
errors, valid := Check(person)
|
||||
if valid {
|
||||
t.Fail()
|
||||
person := &Person{
|
||||
Name: "Onur Cinar",
|
||||
Address: &Address{
|
||||
Street: "1234 Main",
|
||||
},
|
||||
}
|
||||
|
||||
if len(errors) != 2 {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if errors["Name"] != ErrRequired {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if errors["Home.Street"] != ErrRequired {
|
||||
t.Fail()
|
||||
errors, ok := v2.CheckStruct(person)
|
||||
if !ok {
|
||||
t.Fatalf("got unexpected errors %v", errors)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
func TestCheckStructRequiredMissing(t *testing.T) {
|
||||
type Address struct {
|
||||
Street string `checkers:"required"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string `checkers:"required"`
|
||||
Home Address
|
||||
Name string `checkers:"required"`
|
||||
Address *Address
|
||||
}
|
||||
|
||||
person := &Person{}
|
||||
person := &Person{
|
||||
Name: "",
|
||||
Address: &Address{
|
||||
Street: "",
|
||||
},
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
Check(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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
40
cidr.go
40
cidr.go
|
|
@ -3,40 +3,40 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// tagCidr is the tag of the checker.
|
||||
const tagCidr = "cidr"
|
||||
const (
|
||||
// nameCIDR is the name of the CIDR check.
|
||||
nameCIDR = "cidr"
|
||||
)
|
||||
|
||||
// ErrNotCidr indicates that the given value is not a valid CIDR.
|
||||
var ErrNotCidr = errors.New("please enter a valid CIDR")
|
||||
var (
|
||||
// ErrNotCIDR indicates that the given value is not a valid CIDR.
|
||||
ErrNotCIDR = NewCheckError("NOT_CIDR")
|
||||
)
|
||||
|
||||
// IsCidr checker checks if the value is a valid CIDR notation IP address and prefix length.
|
||||
func IsCidr(value string) error {
|
||||
// IsCIDR checks if the value is a valid CIDR notation IP address and prefix length.
|
||||
func IsCIDR(value string) (string, error) {
|
||||
_, _, err := net.ParseCIDR(value)
|
||||
if err != nil {
|
||||
return ErrNotCidr
|
||||
return value, ErrNotCIDR
|
||||
}
|
||||
|
||||
return nil
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// makeCidr makes a checker function for the ip checker.
|
||||
func makeCidr(_ string) CheckFunc {
|
||||
return checkCidr
|
||||
// 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
|
||||
}
|
||||
|
||||
// checkCidr checker checks if the value is a valid CIDR notation IP address and prefix length.
|
||||
func checkCidr(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsCidr(value.String())
|
||||
// makeCIDR makes a checker function for the CIDR checker.
|
||||
func makeCIDR(_ string) CheckFunc[reflect.Value] {
|
||||
return checkCIDR
|
||||
}
|
||||
|
|
|
|||
47
cidr_test.go
47
cidr_test.go
|
|
@ -3,35 +3,38 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsCidr() {
|
||||
err := checker.IsCidr("2001:db8::/32")
|
||||
func ExampleIsCIDR() {
|
||||
_, err := v2.IsCIDR("2001:db8::/32")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCidrInvalid(t *testing.T) {
|
||||
if checker.IsCidr("900.800.200.100//24") == nil {
|
||||
t.Fail()
|
||||
func TestIsCIDRInvalid(t *testing.T) {
|
||||
_, err := v2.IsCIDR("900.800.200.100//24")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCidrValid(t *testing.T) {
|
||||
if checker.IsCidr("2001:db8::/32") != nil {
|
||||
t.Fail()
|
||||
func TestIsCIDRValid(t *testing.T) {
|
||||
_, err := v2.IsCIDR("2001:db8::/32")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCidrNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
func TestCheckCIDRNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Network struct {
|
||||
Subnet int `checkers:"cidr"`
|
||||
|
|
@ -39,10 +42,10 @@ func TestCheckCidrNonString(t *testing.T) {
|
|||
|
||||
network := &Network{}
|
||||
|
||||
checker.Check(network)
|
||||
v2.CheckStruct(network)
|
||||
}
|
||||
|
||||
func TestCheckCidrInvalid(t *testing.T) {
|
||||
func TestCheckCIDRInvalid(t *testing.T) {
|
||||
type Network struct {
|
||||
Subnet string `checkers:"cidr"`
|
||||
}
|
||||
|
|
@ -51,13 +54,13 @@ func TestCheckCidrInvalid(t *testing.T) {
|
|||
Subnet: "900.800.200.100//24",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(network)
|
||||
if valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCidrValid(t *testing.T) {
|
||||
func TestCheckCIDRValid(t *testing.T) {
|
||||
type Network struct {
|
||||
Subnet string `checkers:"cidr"`
|
||||
}
|
||||
|
|
@ -66,8 +69,8 @@ func TestCheckCidrValid(t *testing.T) {
|
|||
Subnet: "192.0.2.0/24",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(network)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
130
credit_card.go
130
credit_card.go
|
|
@ -3,113 +3,116 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// tagCreditCard is the tag of the checker.
|
||||
const tagCreditCard = "credit-card"
|
||||
const (
|
||||
// nameCreditCard is the name of the credit card check.
|
||||
nameCreditCard = "credit-card"
|
||||
)
|
||||
|
||||
// ErrNotCreditCard indicates that the given value is not a valid credit card number.
|
||||
var ErrNotCreditCard = errors.New("please enter a valid credit card number")
|
||||
var (
|
||||
// ErrNotCreditCard indicates that the given value is not a valid credit card number.
|
||||
ErrNotCreditCard = NewCheckError("NOT_CREDIT_CARD")
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
|
||||
// anyCreditCardPattern is the regexp for any credit card.
|
||||
var anyCreditCardPattern = regexp.MustCompile(strings.Join([]string{
|
||||
amexExpression,
|
||||
dinersExpression,
|
||||
discoverExpression,
|
||||
jcbExpression,
|
||||
masterCardExpression,
|
||||
unionPayExpression,
|
||||
visaExpression,
|
||||
}, "|"))
|
||||
// anyCreditCardPattern is the regexp for any credit card.
|
||||
anyCreditCardPattern = regexp.MustCompile(strings.Join([]string{
|
||||
amexExpression,
|
||||
dinersExpression,
|
||||
discoverExpression,
|
||||
jcbExpression,
|
||||
masterCardExpression,
|
||||
unionPayExpression,
|
||||
visaExpression,
|
||||
}, "|"))
|
||||
|
||||
// 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,
|
||||
}
|
||||
// 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,
|
||||
}
|
||||
)
|
||||
|
||||
// IsAnyCreditCard checks if the given value is a valid credit card number.
|
||||
func IsAnyCreditCard(number string) error {
|
||||
func IsAnyCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, anyCreditCardPattern)
|
||||
}
|
||||
|
||||
// IsAmexCreditCard checks if the given valie is a valid AMEX credit card.
|
||||
func IsAmexCreditCard(number string) error {
|
||||
func IsAmexCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, amexPattern)
|
||||
}
|
||||
|
||||
// IsDinersCreditCard checks if the given valie is a valid Diners credit card.
|
||||
func IsDinersCreditCard(number string) error {
|
||||
func IsDinersCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, dinersPattern)
|
||||
}
|
||||
|
||||
// IsDiscoverCreditCard checks if the given valie is a valid Discover credit card.
|
||||
func IsDiscoverCreditCard(number string) error {
|
||||
func IsDiscoverCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, discoverPattern)
|
||||
}
|
||||
|
||||
// IsJcbCreditCard checks if the given valie is a valid JCB 15 credit card.
|
||||
func IsJcbCreditCard(number string) error {
|
||||
func IsJcbCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, jcbPattern)
|
||||
}
|
||||
|
||||
// IsMasterCardCreditCard checks if the given valie is a valid MasterCard credit card.
|
||||
func IsMasterCardCreditCard(number string) error {
|
||||
func IsMasterCardCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, masterCardPattern)
|
||||
}
|
||||
|
||||
// IsUnionPayCreditCard checks if the given valie is a valid UnionPay credit card.
|
||||
func IsUnionPayCreditCard(number string) error {
|
||||
func IsUnionPayCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, unionPayPattern)
|
||||
}
|
||||
|
||||
// IsVisaCreditCard checks if the given valie is a valid Visa credit card.
|
||||
func IsVisaCreditCard(number string) error {
|
||||
func IsVisaCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, visaPattern)
|
||||
}
|
||||
|
||||
// makeCreditCard makes a checker function for the credit card checker.
|
||||
func makeCreditCard(config string) CheckFunc {
|
||||
func makeCreditCard(config string) CheckFunc[reflect.Value] {
|
||||
patterns := []*regexp.Regexp{}
|
||||
|
||||
if config != "" {
|
||||
|
|
@ -125,7 +128,7 @@ func makeCreditCard(config string) CheckFunc {
|
|||
patterns = append(patterns, anyCreditCardPattern)
|
||||
}
|
||||
|
||||
return func(value, _ reflect.Value) error {
|
||||
return func(value reflect.Value) (reflect.Value, error) {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
|
@ -133,20 +136,21 @@ func makeCreditCard(config string) CheckFunc {
|
|||
number := value.String()
|
||||
|
||||
for _, pattern := range patterns {
|
||||
if isCreditCard(number, pattern) == nil {
|
||||
return nil
|
||||
_, err := isCreditCard(number, pattern)
|
||||
if err == nil {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
return ErrNotCreditCard
|
||||
return value, ErrNotCreditCard
|
||||
}
|
||||
}
|
||||
|
||||
// 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) error {
|
||||
func isCreditCard(number string, pattern *regexp.Regexp) (string, error) {
|
||||
if !pattern.MatchString(number) {
|
||||
return ErrNotCreditCard
|
||||
return number, ErrNotCreditCard
|
||||
}
|
||||
|
||||
return IsLuhn(number)
|
||||
return IsLUHN(number)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
// Test numbers from https://stripe.com/docs/testing
|
||||
|
|
@ -35,7 +35,7 @@ func changeToInvalidLuhn(number string) string {
|
|||
}
|
||||
|
||||
func ExampleIsAnyCreditCard() {
|
||||
err := checker.IsAnyCreditCard("6011111111111117")
|
||||
_, err := v2.IsAnyCreditCard("6011111111111117")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
|
|
@ -43,25 +43,26 @@ func ExampleIsAnyCreditCard() {
|
|||
}
|
||||
|
||||
func TestIsAnyCreditCardValid(t *testing.T) {
|
||||
if checker.IsAnyCreditCard(amexCard) != nil {
|
||||
t.Fail()
|
||||
_, err := v2.IsAnyCreditCard(amexCard)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAnyCreditCardInvalidPattern(t *testing.T) {
|
||||
if checker.IsAnyCreditCard(invalidCard) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsAnyCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAnyCreditCardInvalidLuhn(t *testing.T) {
|
||||
if checker.IsAnyCreditCard(changeToInvalidLuhn(amexCard)) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsAnyCreditCard(changeToInvalidLuhn(amexCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsAmexCreditCard() {
|
||||
err := checker.IsAmexCreditCard("378282246310005")
|
||||
_, err := v2.IsAmexCreditCard("378282246310005")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
|
|
@ -69,75 +70,75 @@ func ExampleIsAmexCreditCard() {
|
|||
}
|
||||
|
||||
func TestIsAmexCreditCardValid(t *testing.T) {
|
||||
if checker.IsAmexCreditCard(amexCard) != nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsAmexCreditCard(amexCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAmexCreditCardInvalidPattern(t *testing.T) {
|
||||
if checker.IsAmexCreditCard(invalidCard) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsAmexCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAmexCreditCardInvalidLuhn(t *testing.T) {
|
||||
if checker.IsAmexCreditCard(changeToInvalidLuhn(amexCard)) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsAmexCreditCard(changeToInvalidLuhn(amexCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsDinersCreditCard() {
|
||||
err := checker.IsDinersCreditCard("36227206271667")
|
||||
_, err := v2.IsDinersCreditCard("36227206271667")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
func TestIsDinersCreditCardValid(t *testing.T) {
|
||||
if checker.IsDinersCreditCard(dinersCard) != nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsDinersCreditCard(dinersCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDinersCreditCardInvalidPattern(t *testing.T) {
|
||||
if checker.IsDinersCreditCard(invalidCard) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsDinersCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDinersCreditCardInvalidLuhn(t *testing.T) {
|
||||
if checker.IsDinersCreditCard(changeToInvalidLuhn(dinersCard)) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsDinersCreditCard(changeToInvalidLuhn(dinersCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsDiscoverCreditCard() {
|
||||
err := checker.IsDiscoverCreditCard("6011111111111117")
|
||||
_, err := v2.IsDiscoverCreditCard("6011111111111117")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
func TestIsDiscoverCreditCardValid(t *testing.T) {
|
||||
if checker.IsDiscoverCreditCard(discoverCard) != nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsDiscoverCreditCard(discoverCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDiscoverCreditCardInvalidPattern(t *testing.T) {
|
||||
if checker.IsDiscoverCreditCard(invalidCard) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsDiscoverCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDiscoverCreditCardInvalidLuhn(t *testing.T) {
|
||||
if checker.IsDiscoverCreditCard(changeToInvalidLuhn(discoverCard)) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsDiscoverCreditCard(changeToInvalidLuhn(discoverCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsJcbCreditCard() {
|
||||
err := checker.IsJcbCreditCard("3530111333300000")
|
||||
_, err := v2.IsJcbCreditCard("3530111333300000")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
|
|
@ -145,25 +146,25 @@ func ExampleIsJcbCreditCard() {
|
|||
}
|
||||
|
||||
func TestIsJcbCreditCardValid(t *testing.T) {
|
||||
if checker.IsJcbCreditCard(jcbCard) != nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsJcbCreditCard(jcbCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJcbCreditCardInvalidPattern(t *testing.T) {
|
||||
if checker.IsJcbCreditCard(invalidCard) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsJcbCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJcbCreditCardInvalidLuhn(t *testing.T) {
|
||||
if checker.IsJcbCreditCard(changeToInvalidLuhn(jcbCard)) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsJcbCreditCard(changeToInvalidLuhn(jcbCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsMasterCardCreditCard() {
|
||||
err := checker.IsMasterCardCreditCard("5555555555554444")
|
||||
_, err := v2.IsMasterCardCreditCard("5555555555554444")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
|
|
@ -171,25 +172,25 @@ func ExampleIsMasterCardCreditCard() {
|
|||
}
|
||||
|
||||
func TestIsMasterCardCreditCardValid(t *testing.T) {
|
||||
if checker.IsMasterCardCreditCard(masterCard) != nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsMasterCardCreditCard(masterCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMasterCardCreditCardInvalidPattern(t *testing.T) {
|
||||
if checker.IsMasterCardCreditCard(invalidCard) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsMasterCardCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMasterCardCreditCardInvalidLuhn(t *testing.T) {
|
||||
if checker.IsMasterCardCreditCard(changeToInvalidLuhn(masterCard)) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsMasterCardCreditCard(changeToInvalidLuhn(masterCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsUnionPayCreditCard() {
|
||||
err := checker.IsUnionPayCreditCard("6200000000000005")
|
||||
_, err := v2.IsUnionPayCreditCard("6200000000000005")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
|
|
@ -197,50 +198,50 @@ func ExampleIsUnionPayCreditCard() {
|
|||
}
|
||||
|
||||
func TestIsUnionPayCreditCardValid(t *testing.T) {
|
||||
if checker.IsUnionPayCreditCard(unionPayCard) != nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsUnionPayCreditCard(unionPayCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUnionPayCreditCardInvalidPattern(t *testing.T) {
|
||||
if checker.IsUnionPayCreditCard(invalidCard) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsUnionPayCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUnionPayCreditCardInvalidLuhn(t *testing.T) {
|
||||
if checker.IsUnionPayCreditCard(changeToInvalidLuhn(unionPayCard)) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsUnionPayCreditCard(changeToInvalidLuhn(unionPayCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsVisaCreditCard() {
|
||||
err := checker.IsVisaCreditCard("4111111111111111")
|
||||
_, err := v2.IsVisaCreditCard("4111111111111111")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
func TestIsVisaCreditCardValid(t *testing.T) {
|
||||
if checker.IsVisaCreditCard(visaCard) != nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsVisaCreditCard(visaCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsVisaCreditCardInvalidPattern(t *testing.T) {
|
||||
if checker.IsVisaCreditCard(invalidCard) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsVisaCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsVisaCreditCardInvalidLuhn(t *testing.T) {
|
||||
if checker.IsVisaCreditCard(changeToInvalidLuhn(visaCard)) == nil {
|
||||
t.Fail()
|
||||
if _, err := v2.IsVisaCreditCard(changeToInvalidLuhn(visaCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCreditCardNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
defer FailIfNoPanic(t, "expected panic for non-string credit card")
|
||||
|
||||
type Order struct {
|
||||
CreditCard int `checkers:"credit-card"`
|
||||
|
|
@ -248,7 +249,7 @@ func TestCheckCreditCardNonString(t *testing.T) {
|
|||
|
||||
order := &Order{}
|
||||
|
||||
checker.Check(order)
|
||||
v2.CheckStruct(order)
|
||||
}
|
||||
|
||||
func TestCheckCreditCardValid(t *testing.T) {
|
||||
|
|
@ -260,7 +261,7 @@ func TestCheckCreditCardValid(t *testing.T) {
|
|||
CreditCard: amexCard,
|
||||
}
|
||||
|
||||
_, valid := checker.Check(order)
|
||||
_, valid := v2.CheckStruct(order)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
|
|
@ -275,14 +276,14 @@ func TestCheckCreditCardInvalid(t *testing.T) {
|
|||
CreditCard: invalidCard,
|
||||
}
|
||||
|
||||
_, valid := checker.Check(order)
|
||||
_, valid := v2.CheckStruct(order)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCreditCardMultipleUnknown(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
defer FailIfNoPanic(t, "expected panic for unknown credit card")
|
||||
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card:amex,unknown"`
|
||||
|
|
@ -292,7 +293,7 @@ func TestCheckCreditCardMultipleUnknown(t *testing.T) {
|
|||
CreditCard: amexCard,
|
||||
}
|
||||
|
||||
checker.Check(order)
|
||||
v2.CheckStruct(order)
|
||||
}
|
||||
|
||||
func TestCheckCreditCardMultipleInvalid(t *testing.T) {
|
||||
|
|
@ -304,7 +305,7 @@ func TestCheckCreditCardMultipleInvalid(t *testing.T) {
|
|||
CreditCard: discoverCard,
|
||||
}
|
||||
|
||||
_, valid := checker.Check(order)
|
||||
_, valid := v2.CheckStruct(order)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
|
|
|
|||
43
digits.go
43
digits.go
|
|
@ -3,41 +3,40 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// tagDigits is the tag of the checker.
|
||||
const tagDigits = "digits"
|
||||
const (
|
||||
// nameDigits is the name of the digits check.
|
||||
nameDigits = "digits"
|
||||
)
|
||||
|
||||
// ErrNotDigits indicates that the given string contains non-digit characters.
|
||||
var ErrNotDigits = errors.New("please enter a valid number")
|
||||
var (
|
||||
// ErrNotDigits indicates that the given value is not a valid digits string.
|
||||
ErrNotDigits = NewCheckError("NOT_DIGITS")
|
||||
)
|
||||
|
||||
// IsDigits checks if the given string consists of only digit characters.
|
||||
func IsDigits(value string) error {
|
||||
for _, c := range value {
|
||||
if !unicode.IsDigit(c) {
|
||||
return ErrNotDigits
|
||||
// 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
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
return 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
|
||||
}
|
||||
|
||||
// makeDigits makes a checker function for the digits checker.
|
||||
func makeDigits(_ string) CheckFunc {
|
||||
func makeDigits(_ string) CheckFunc[reflect.Value] {
|
||||
return checkDigits
|
||||
}
|
||||
|
||||
// checkDigits checks if the given string consists of only digit characters.
|
||||
func checkDigits(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsDigits(value.String())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,71 +3,74 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsDigits() {
|
||||
err := checker.IsDigits("1234")
|
||||
_, err := v2.IsDigits("123456")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDigitsInvalid(t *testing.T) {
|
||||
if checker.IsDigits("checker") == nil {
|
||||
t.Fail()
|
||||
_, err := v2.IsDigits("123a456")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDigitsValid(t *testing.T) {
|
||||
if checker.IsDigits("1234") != nil {
|
||||
t.Fail()
|
||||
_, err := v2.IsDigits("123456")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckDigitsNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type User struct {
|
||||
ID int `checkers:"digits"`
|
||||
type Code struct {
|
||||
Value int `checkers:"digits"`
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
code := &Code{}
|
||||
|
||||
checker.Check(user)
|
||||
v2.CheckStruct(code)
|
||||
}
|
||||
|
||||
func TestCheckDigitsInvalid(t *testing.T) {
|
||||
type User struct {
|
||||
ID string `checkers:"digits"`
|
||||
type Code struct {
|
||||
Value string `checkers:"digits"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
ID: "checker",
|
||||
code := &Code{
|
||||
Value: "123a456",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(code)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckDigitsValid(t *testing.T) {
|
||||
type User struct {
|
||||
ID string `checkers:"digits"`
|
||||
type Code struct {
|
||||
Value string `checkers:"digits"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
ID: "1234",
|
||||
code := &Code{
|
||||
Value: "123456",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(code)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
131
email.go
131
email.go
|
|
@ -3,126 +3,39 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/mail"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// tagEmail is the tag of the checker.
|
||||
const tagEmail = "email"
|
||||
const (
|
||||
// nameEmail is the name of the email check.
|
||||
nameEmail = "email"
|
||||
)
|
||||
|
||||
// ErrNotEmail indicates that the given string is not a valid email.
|
||||
var ErrNotEmail = errors.New("please enter a valid email address")
|
||||
var (
|
||||
// ErrNotEmail indicates that the given value is not a valid email address.
|
||||
ErrNotEmail = NewCheckError("NOT_EMAIL")
|
||||
)
|
||||
|
||||
// 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) error {
|
||||
atIndex := strings.LastIndex(email, "@")
|
||||
if atIndex == -1 || atIndex == len(email)-1 {
|
||||
return ErrNotEmail
|
||||
// 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
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
domain := email[atIndex+1:]
|
||||
if isValidEmailDomain(domain) != nil {
|
||||
return ErrNotEmail
|
||||
}
|
||||
|
||||
return isValidEmailUser(email[:atIndex])
|
||||
// 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
|
||||
}
|
||||
|
||||
// makeEmail makes a checker function for the email checker.
|
||||
func makeEmail(_ string) CheckFunc {
|
||||
func makeEmail(_ string) CheckFunc[reflect.Value] {
|
||||
return checkEmail
|
||||
}
|
||||
|
||||
// checkEmail checks if the given string is an email address.
|
||||
func checkEmail(value, _ reflect.Value) error {
|
||||
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) error {
|
||||
if len(domain) > 255 {
|
||||
return ErrNotEmail
|
||||
}
|
||||
|
||||
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) error {
|
||||
// Cannot be empty user
|
||||
if user == "" || len(user) > 64 {
|
||||
return ErrNotEmail
|
||||
}
|
||||
|
||||
// Cannot start or end with dot
|
||||
if user[0] == '.' || user[len(user)-1] == '.' {
|
||||
return ErrNotEmail
|
||||
}
|
||||
|
||||
return isValidEmailUserCharacters(user)
|
||||
}
|
||||
|
||||
// isValidEmailUserCharacters if the email user characters are valid.
|
||||
func isValidEmailUserCharacters(user string) error {
|
||||
quoted := false
|
||||
start := true
|
||||
prev := ' '
|
||||
|
||||
for _, c := range user {
|
||||
// Cannot have a double dot unless quoted
|
||||
if !quoted && c == '.' && prev == '.' {
|
||||
return ErrNotEmail
|
||||
}
|
||||
|
||||
if start {
|
||||
start = false
|
||||
|
||||
if c == '"' {
|
||||
quoted = true
|
||||
prev = c
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !quoted {
|
||||
if c == '.' {
|
||||
start = true
|
||||
} else if !notQuotedChars.MatchString(string(c)) {
|
||||
return ErrNotEmail
|
||||
}
|
||||
} else {
|
||||
if c == '"' && prev != '\\' {
|
||||
quoted = false
|
||||
}
|
||||
}
|
||||
|
||||
prev = c
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
103
email_test.go
103
email_test.go
|
|
@ -3,23 +3,38 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsEmail() {
|
||||
err := checker.IsEmail("user@zdo.com")
|
||||
_, err := v2.IsEmail("test@example.com")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckEmailNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type User struct {
|
||||
Email int `checkers:"email"`
|
||||
|
|
@ -27,7 +42,22 @@ func TestCheckEmailNonString(t *testing.T) {
|
|||
|
||||
user := &User{}
|
||||
|
||||
checker.Check(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")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckEmailValid(t *testing.T) {
|
||||
|
|
@ -36,64 +66,11 @@ func TestCheckEmailValid(t *testing.T) {
|
|||
}
|
||||
|
||||
user := &User{
|
||||
Email: "user@zdo.com",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
|
||||
_, 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) != nil {
|
||||
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) == nil {
|
||||
t.Fatal(email)
|
||||
}
|
||||
_, ok := v2.CheckStruct(user)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
88
fqdn.go
88
fqdn.go
|
|
@ -3,75 +3,41 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// tagFqdn is the tag of the checker.
|
||||
const tagFqdn = "fqdn"
|
||||
const (
|
||||
// nameFQDN is the name of the FQDN check.
|
||||
nameFQDN = "fqdn"
|
||||
)
|
||||
|
||||
// ErrNotFqdn indicates that the given string is not a valid FQDN.
|
||||
var ErrNotFqdn = errors.New("please enter a valid domain name")
|
||||
var (
|
||||
// ErrNotFQDN indicates that the given value is not a valid FQDN.
|
||||
ErrNotFQDN = NewCheckError("FQDN")
|
||||
|
||||
// Valid characters excluding full-width characters.
|
||||
var fqdnValidChars = regexp.MustCompile("^[a-z0-9\u00a1-\uff00\uff06-\uffff\\-]+$")
|
||||
// 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,}$`)
|
||||
)
|
||||
|
||||
// IsFqdn checks if the given string is a fully qualified domain name.
|
||||
func IsFqdn(domain string) error {
|
||||
parts := strings.Split(domain, ".")
|
||||
|
||||
// Require TLD
|
||||
if len(parts) < 2 {
|
||||
return ErrNotFqdn
|
||||
}
|
||||
|
||||
tld := parts[len(parts)-1]
|
||||
|
||||
// Should be all numeric TLD
|
||||
if IsDigits(tld) == nil {
|
||||
return ErrNotFqdn
|
||||
}
|
||||
|
||||
// Short TLD
|
||||
if len(tld) < 2 {
|
||||
return ErrNotFqdn
|
||||
}
|
||||
|
||||
for _, part := range parts {
|
||||
// Cannot be more than 63 characters
|
||||
if len(part) > 63 {
|
||||
return ErrNotFqdn
|
||||
}
|
||||
|
||||
// Check for valid characters
|
||||
if !fqdnValidChars.MatchString(part) {
|
||||
return ErrNotFqdn
|
||||
}
|
||||
|
||||
// Should not start or end with a hyphen (-) character.
|
||||
if part[0] == '-' || part[len(part)-1] == '-' {
|
||||
return ErrNotFqdn
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
// 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
|
||||
}
|
||||
|
||||
// makeFqdn makes a checker function for the fqdn checker.
|
||||
func makeFqdn(_ string) CheckFunc {
|
||||
return checkFqdn
|
||||
// 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
|
||||
}
|
||||
|
||||
// checkFqdn checks if the given string is a fully qualified domain name.
|
||||
func checkFqdn(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsFqdn(value.String())
|
||||
}
|
||||
// makeFQDN makes a checker function for the FQDN checker.
|
||||
func makeFQDN(_ string) CheckFunc[reflect.Value] {
|
||||
return checkFQDN
|
||||
}
|
||||
124
fqdn_test.go
124
fqdn_test.go
|
|
@ -3,98 +3,74 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsFqdn() {
|
||||
err := checker.IsFqdn("zdo.com")
|
||||
func ExampleIsFQDN() {
|
||||
_, err := v2.IsFQDN("example.com")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFdqnWithoutTld(t *testing.T) {
|
||||
if checker.IsFqdn("abcd") == nil {
|
||||
t.Fail()
|
||||
func TestIsFQDNInvalid(t *testing.T) {
|
||||
_, err := v2.IsFQDN("invalid_fqdn")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFdqnShortTld(t *testing.T) {
|
||||
if checker.IsFqdn("abcd.c") == nil {
|
||||
t.Fail()
|
||||
func TestIsFQDNValid(t *testing.T) {
|
||||
_, err := v2.IsFQDN("example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFdqnNumericTld(t *testing.T) {
|
||||
if checker.IsFqdn("abcd.1234") == nil {
|
||||
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 TestCheckFdqnLong(t *testing.T) {
|
||||
if checker.IsFqdn("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd.com") == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFdqnInvalidCharacters(t *testing.T) {
|
||||
if checker.IsFqdn("ab_cd.com") == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFdqnStaringWithHyphen(t *testing.T) {
|
||||
if checker.IsFqdn("-abcd.com") == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFdqnStaringEndingWithHyphen(t *testing.T) {
|
||||
if checker.IsFqdn("abcd-.com") == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFdqnStartingWithDot(t *testing.T) {
|
||||
if checker.IsFqdn(".abcd.com") == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFdqnEndingWithDot(t *testing.T) {
|
||||
if checker.IsFqdn("abcd.com.") == nil {
|
||||
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()
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
go.mod
4
go.mod
|
|
@ -1,3 +1,3 @@
|
|||
module github.com/cinar/checker
|
||||
module github.com/cinar/checker/v2
|
||||
|
||||
go 1.22
|
||||
go 1.23.2
|
||||
|
|
|
|||
|
|
@ -3,29 +3,30 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"html"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// tagHTMLEscape is the tag of the normalizer.
|
||||
const tagHTMLEscape = "html-escape"
|
||||
const (
|
||||
// nameHTMLEscape is the name of the HTML escape normalizer.
|
||||
nameHTMLEscape = "html-escape"
|
||||
)
|
||||
|
||||
// makeHTMLEscape makes a normalizer function for the HTML escape normalizer.
|
||||
func makeHTMLEscape(_ string) CheckFunc {
|
||||
return normalizeHTMLEscape
|
||||
// HTMLEscape applies HTML escaping to special characters.
|
||||
func HTMLEscape(value string) (string, error) {
|
||||
return html.EscapeString(value), nil
|
||||
}
|
||||
|
||||
// normalizeHTMLEscape applies HTML escaping to special characters.
|
||||
// Uses html.EscapeString for the actual escape operation.
|
||||
func normalizeHTMLEscape(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
value.SetString(html.EscapeString(value.String()))
|
||||
|
||||
return nil
|
||||
// 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
|
||||
}
|
||||
|
||||
// makeHTMLEscape returns the HTML escape normalizer function.
|
||||
func makeHTMLEscape(_ string) CheckFunc[reflect.Value] {
|
||||
return reflectHTMLEscape
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,26 +3,29 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func TestNormalizeHTMLEscapeNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
type Comment struct {
|
||||
Body int `checkers:"html-escape"`
|
||||
func TestHTMLEscape(t *testing.T) {
|
||||
input := "<tag> \"Checker\" & 'Library' </tag>"
|
||||
expected := "<tag> "Checker" & 'Library' </tag>"
|
||||
|
||||
actual, err := v2.HTMLEscape(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
comment := &Comment{}
|
||||
|
||||
checker.Check(comment)
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeHTMLEscape(t *testing.T) {
|
||||
func TestReflectHTMLEscape(t *testing.T) {
|
||||
type Comment struct {
|
||||
Body string `checkers:"html-escape"`
|
||||
}
|
||||
|
|
@ -31,12 +34,14 @@ func TestNormalizeHTMLEscape(t *testing.T) {
|
|||
Body: "<tag> \"Checker\" & 'Library' </tag>",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(comment)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
expected := "<tag> "Checker" & 'Library' </tag>"
|
||||
|
||||
errs, ok := v2.CheckStruct(comment)
|
||||
if !ok {
|
||||
t.Fatalf("got unexpected errors %v", errs)
|
||||
}
|
||||
|
||||
if comment.Body != "<tag> "Checker" & 'Library' </tag>" {
|
||||
t.Fail()
|
||||
if comment.Body != expected {
|
||||
t.Fatalf("actual %s expected %s", comment.Body, expected)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,29 +3,28 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"html"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// tagHTMLUnescape is the tag of the normalizer.
|
||||
const tagHTMLUnescape = "html-unescape"
|
||||
// nameHTMLUnescape is the name of the HTML unescape normalizer.
|
||||
const nameHTMLUnescape = "html-unescape"
|
||||
|
||||
// makeHTMLUnescape makes a normalizer function for the HTML unscape normalizer.
|
||||
func makeHTMLUnescape(_ string) CheckFunc {
|
||||
return normalizeHTMLUnescape
|
||||
// HTMLUnescape applies HTML unescaping to special characters.
|
||||
func HTMLUnescape(value string) (string, error) {
|
||||
return html.UnescapeString(value), nil
|
||||
}
|
||||
|
||||
// normalizeHTMLUnescape applies HTML unescaping to special characters.
|
||||
// Uses html.UnescapeString for the actual unescape operation.
|
||||
func normalizeHTMLUnescape(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
value.SetString(html.UnescapeString(value.String()))
|
||||
|
||||
return nil
|
||||
// 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
|
||||
}
|
||||
|
||||
// makeHTMLUnescape returns the HTML unescape normalizer function.
|
||||
func makeHTMLUnescape(_ string) CheckFunc[reflect.Value] {
|
||||
return reflectHTMLUnescape
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,29 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func TestNormalizeHTMLUnescapeNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
func TestHTMLUnescape(t *testing.T) {
|
||||
input := "<tag> "Checker" & 'Library' </tag>"
|
||||
expected := "<tag> \"Checker\" & 'Library' </tag>"
|
||||
|
||||
type Comment struct {
|
||||
Body int `checkers:"html-unescape"`
|
||||
actual, err := v2.HTMLUnescape(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
comment := &Comment{}
|
||||
|
||||
checker.Check(comment)
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeHTMLUnescape(t *testing.T) {
|
||||
func TestReflectHTMLUnescape(t *testing.T) {
|
||||
type Comment struct {
|
||||
Body string `checkers:"html-unescape"`
|
||||
}
|
||||
|
|
@ -32,12 +34,14 @@ func TestNormalizeHTMLUnescape(t *testing.T) {
|
|||
Body: "<tag> "Checker" & 'Library' </tag>",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(comment)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
expected := "<tag> \"Checker\" & 'Library' </tag>"
|
||||
|
||||
errs, ok := v2.CheckStruct(comment)
|
||||
if !ok {
|
||||
t.Fatalf("got unexpected errors %v", errs)
|
||||
}
|
||||
|
||||
if comment.Body != "<tag> \"Checker\" & 'Library' </tag>" {
|
||||
t.Fail()
|
||||
if comment.Body != expected {
|
||||
t.Fatalf("actual %s expected %s", comment.Body, expected)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
46
ip.go
46
ip.go
|
|
@ -3,40 +3,38 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// tagIP is the tag of the checker.
|
||||
const tagIP = "ip"
|
||||
const (
|
||||
// nameIP is the name of the IP check.
|
||||
nameIP = "ip"
|
||||
)
|
||||
|
||||
// ErrNotIP indicates that the given value is not an IP address.
|
||||
var ErrNotIP = errors.New("please enter a valid IP address")
|
||||
var (
|
||||
// ErrNotIP indicates that the given value is not a valid IP address.
|
||||
ErrNotIP = NewCheckError("NOT_IP")
|
||||
)
|
||||
|
||||
// IsIP checks if the given value is an IP address.
|
||||
func IsIP(value string) error {
|
||||
ip := net.ParseIP(value)
|
||||
if ip == nil {
|
||||
return ErrNotIP
|
||||
// IsIP checks if the value is a valid IP address.
|
||||
func IsIP(value string) (string, error) {
|
||||
if net.ParseIP(value) == nil {
|
||||
return value, ErrNotIP
|
||||
}
|
||||
|
||||
return nil
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// makeIP makes a checker function for the ip checker.
|
||||
func makeIP(_ string) CheckFunc {
|
||||
// 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] {
|
||||
return checkIP
|
||||
}
|
||||
|
||||
// checkIP checks if the given value is an IP address.
|
||||
func checkIP(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsIP(value.String())
|
||||
}
|
||||
|
|
|
|||
61
ip_test.go
61
ip_test.go
|
|
@ -3,71 +3,74 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsIP() {
|
||||
err := checker.IsIP("2001:db8::68")
|
||||
_, err := v2.IsIP("192.168.1.1")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPInvalid(t *testing.T) {
|
||||
if checker.IsIP("900.800.200.100") == nil {
|
||||
t.Fail()
|
||||
_, err := v2.IsIP("invalid-ip")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPValid(t *testing.T) {
|
||||
if checker.IsIP("2001:db8::68") != nil {
|
||||
t.Fail()
|
||||
_, err := v2.IsIP("192.168.1.1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIpNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
func TestCheckIPNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Request struct {
|
||||
RemoteIP int `checkers:"ip"`
|
||||
type Network struct {
|
||||
Address int `checkers:"ip"`
|
||||
}
|
||||
|
||||
request := &Request{}
|
||||
network := &Network{}
|
||||
|
||||
checker.Check(request)
|
||||
v2.CheckStruct(network)
|
||||
}
|
||||
|
||||
func TestCheckIpInvalid(t *testing.T) {
|
||||
type Request struct {
|
||||
RemoteIP string `checkers:"ip"`
|
||||
func TestCheckIPInvalid(t *testing.T) {
|
||||
type Network struct {
|
||||
Address string `checkers:"ip"`
|
||||
}
|
||||
|
||||
request := &Request{
|
||||
RemoteIP: "900.800.200.100",
|
||||
network := &Network{
|
||||
Address: "invalid-ip",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIPValid(t *testing.T) {
|
||||
type Request struct {
|
||||
RemoteIP string `checkers:"ip"`
|
||||
type Network struct {
|
||||
Address string `checkers:"ip"`
|
||||
}
|
||||
|
||||
request := &Request{
|
||||
RemoteIP: "192.168.1.1",
|
||||
network := &Network{
|
||||
Address: "192.168.1.1",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
47
ipv4.go
47
ipv4.go
|
|
@ -3,44 +3,39 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// tagIPV4 is the tag of the checker.
|
||||
const tagIPV4 = "ipv4"
|
||||
const (
|
||||
// nameIPv4 is the name of the IPv4 check.
|
||||
nameIPv4 = "ipv4"
|
||||
)
|
||||
|
||||
// ErrNotIPV4 indicates that the given value is not an IPv4 address.
|
||||
var ErrNotIPV4 = errors.New("please enter a valid IPv4 address")
|
||||
var (
|
||||
// ErrNotIPv4 indicates that the given value is not a valid IPv4 address.
|
||||
ErrNotIPv4 = NewCheckError("NOT_IPV4")
|
||||
)
|
||||
|
||||
// IsIPV4 checks if the given value is an IPv4 address.
|
||||
func IsIPV4(value string) error {
|
||||
// IsIPv4 checks if the value is a valid IPv4 address.
|
||||
func IsIPv4(value string) (string, error) {
|
||||
ip := net.ParseIP(value)
|
||||
if ip == nil {
|
||||
return ErrNotIPV4
|
||||
if ip == nil || ip.To4() == nil {
|
||||
return value, ErrNotIPv4
|
||||
}
|
||||
|
||||
if ip.To4() == nil {
|
||||
return ErrNotIPV4
|
||||
}
|
||||
|
||||
return nil
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// makeIPV4 makes a checker function for the ipV4 checker.
|
||||
func makeIPV4(_ string) CheckFunc {
|
||||
return checkIPV4
|
||||
// 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
|
||||
}
|
||||
|
||||
// checkIPV4 checks if the given value is an IPv4 address.
|
||||
func checkIPV4(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsIPV4(value.String())
|
||||
// makeIPv4 makes a checker function for the IPv4 checker.
|
||||
func makeIPv4(_ string) CheckFunc[reflect.Value] {
|
||||
return checkIPv4
|
||||
}
|
||||
|
|
|
|||
95
ipv4_test.go
95
ipv4_test.go
|
|
@ -3,77 +3,74 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsIPV4() {
|
||||
err := checker.IsIPV4("192.168.1.1")
|
||||
func ExampleIsIPv4() {
|
||||
_, err := v2.IsIPv4("192.168.1.1")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPV4Invalid(t *testing.T) {
|
||||
if checker.IsIPV4("900.800.200.100") == nil {
|
||||
t.Fail()
|
||||
func TestIsIPv4Invalid(t *testing.T) {
|
||||
_, err := v2.IsIPv4("2001:db8::1")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPV4InvalidV6(t *testing.T) {
|
||||
if checker.IsIPV4("2001:db8::68") == nil {
|
||||
t.Fail()
|
||||
func TestIsIPv4Valid(t *testing.T) {
|
||||
_, err := v2.IsIPv4("192.168.1.1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPV4Valid(t *testing.T) {
|
||||
if checker.IsIPV4("192.168.1.1") != nil {
|
||||
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 TestCheckIPV4NonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Request struct {
|
||||
RemoteIP int `checkers:"ipv4"`
|
||||
func TestCheckIPv4Valid(t *testing.T) {
|
||||
type Network struct {
|
||||
Address string `checkers:"ipv4"`
|
||||
}
|
||||
|
||||
request := &Request{}
|
||||
|
||||
checker.Check(request)
|
||||
}
|
||||
|
||||
func TestCheckIPV4Invalid(t *testing.T) {
|
||||
type Request struct {
|
||||
RemoteIP string `checkers:"ipv4"`
|
||||
network := &Network{
|
||||
Address: "192.168.1.1",
|
||||
}
|
||||
|
||||
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()
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
48
ipv6.go
48
ipv6.go
|
|
@ -3,44 +3,38 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// tagIPV6 is the tag of the checker.
|
||||
const tagIPV6 = "ipv6"
|
||||
const (
|
||||
// nameIPv6 is the name of the IPv6 check.
|
||||
nameIPv6 = "ipv6"
|
||||
)
|
||||
|
||||
// ErrNotIPV6 indicates that the given value is not an IPv6 address.
|
||||
var ErrNotIPV6 = errors.New("please enter a valid IPv6 address")
|
||||
var (
|
||||
// ErrNotIPv6 indicates that the given value is not a valid IPv6 address.
|
||||
ErrNotIPv6 = NewCheckError("NOT_IPV6")
|
||||
)
|
||||
|
||||
// IsIPV6 checks if the given value is an IPv6 address.
|
||||
func IsIPV6(value string) error {
|
||||
ip := net.ParseIP(value)
|
||||
if ip == nil {
|
||||
return ErrNotIPV6
|
||||
// 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
|
||||
}
|
||||
|
||||
if ip.To4() != nil {
|
||||
return ErrNotIPV6
|
||||
}
|
||||
|
||||
return nil
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// makeIPV6 makes a checker function for the ipV6 checker.
|
||||
func makeIPV6(_ string) CheckFunc {
|
||||
return checkIPV6
|
||||
// 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
|
||||
}
|
||||
|
||||
// checkIPV6 checks if the given value is an IPv6 address.
|
||||
func checkIPV6(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsIPV6(value.String())
|
||||
// makeIPv6 makes a checker function for the IPv6 checker.
|
||||
func makeIPv6(_ string) CheckFunc[reflect.Value] {
|
||||
return checkIPv6
|
||||
}
|
||||
|
|
|
|||
96
ipv6_test.go
96
ipv6_test.go
|
|
@ -3,78 +3,74 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsIPV6() {
|
||||
err := checker.IsIPV6("2001:db8::68")
|
||||
|
||||
func ExampleIsIPv6() {
|
||||
_, err := v2.IsIPv6("2001:db8::1")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPV6Invalid(t *testing.T) {
|
||||
if checker.IsIPV6("900.800.200.100") == nil {
|
||||
t.Fail()
|
||||
func TestIsIPv6Invalid(t *testing.T) {
|
||||
_, err := v2.IsIPv6("192.168.1.1")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPV6InvalidV4(t *testing.T) {
|
||||
if checker.IsIPV6("192.168.1.1") == nil {
|
||||
t.Fail()
|
||||
func TestIsIPv6Valid(t *testing.T) {
|
||||
_, err := v2.IsIPv6("2001:db8::1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsIPV6Valid(t *testing.T) {
|
||||
if checker.IsIPV6("2001:db8::68") != nil {
|
||||
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 TestCheckIPV6NonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type Request struct {
|
||||
RemoteIP int `checkers:"ipv6"`
|
||||
func TestCheckIPv6Valid(t *testing.T) {
|
||||
type Network struct {
|
||||
Address string `checkers:"ipv6"`
|
||||
}
|
||||
|
||||
request := &Request{}
|
||||
|
||||
checker.Check(request)
|
||||
}
|
||||
|
||||
func TestCheckIPV6Invalid(t *testing.T) {
|
||||
type Request struct {
|
||||
RemoteIP string `checkers:"ipv6"`
|
||||
network := &Network{
|
||||
Address: "2001:db8::1",
|
||||
}
|
||||
|
||||
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()
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
132
isbn.go
132
isbn.go
|
|
@ -3,125 +3,41 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Program to check for ISBN
|
||||
// https://www.geeksforgeeks.org/program-check-isbn/
|
||||
const (
|
||||
// nameISBN is the name of the ISBN check.
|
||||
nameISBN = "isbn"
|
||||
)
|
||||
|
||||
// How to Verify an ISBN
|
||||
// https://www.instructables.com/How-to-verify-a-ISBN/
|
||||
var (
|
||||
// ErrNotISBN indicates that the given value is not a valid ISBN.
|
||||
ErrNotISBN = NewCheckError("NOT_ISBN")
|
||||
|
||||
// tagISBN is the tag of the checker.
|
||||
const tagISBN = "isbn"
|
||||
// isbnRegex is the regular expression for validating ISBN-10 and ISBN-13.
|
||||
isbnRegex = regexp.MustCompile(`^(97(8|9))?\d{9}(\d|X)$`)
|
||||
)
|
||||
|
||||
// ErrNotISBN indicates that the given value is not a valid ISBN.
|
||||
var ErrNotISBN = errors.New("please enter a valid ISBN number")
|
||||
|
||||
// IsISBN10 checks if the given value is a valid ISBN-10 number.
|
||||
func IsISBN10(value string) error {
|
||||
value = strings.ReplaceAll(value, "-", "")
|
||||
|
||||
if len(value) != 10 {
|
||||
return ErrNotISBN
|
||||
// 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
|
||||
}
|
||||
|
||||
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 ErrNotISBN
|
||||
}
|
||||
|
||||
return nil
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// IsISBN13 checks if the given value is a valid ISBN-13 number.
|
||||
func IsISBN13(value string) error {
|
||||
value = strings.ReplaceAll(value, "-", "")
|
||||
|
||||
if len(value) != 13 {
|
||||
return ErrNotISBN
|
||||
}
|
||||
|
||||
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 ErrNotISBN
|
||||
}
|
||||
|
||||
return nil
|
||||
// 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
|
||||
}
|
||||
|
||||
// IsISBN checks if the given value is a valid ISBN number.
|
||||
func IsISBN(value string) error {
|
||||
value = strings.ReplaceAll(value, "-", "")
|
||||
|
||||
if len(value) == 10 {
|
||||
return IsISBN10(value)
|
||||
} else if len(value) == 13 {
|
||||
return IsISBN13(value)
|
||||
}
|
||||
|
||||
return ErrNotISBN
|
||||
}
|
||||
|
||||
// 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) error {
|
||||
return checkISBN(value, parent, config)
|
||||
}
|
||||
}
|
||||
|
||||
// checkISBN checks if the given value is a valid ISBN number.
|
||||
func checkISBN(value, _ reflect.Value, mode string) error {
|
||||
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)
|
||||
}
|
||||
// makeISBN makes a checker function for the ISBN checker.
|
||||
func makeISBN(_ string) CheckFunc[reflect.Value] {
|
||||
return checkISBN
|
||||
}
|
||||
|
|
|
|||
179
isbn_test.go
179
isbn_test.go
|
|
@ -3,122 +3,38 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsISBN10() {
|
||||
err := checker.IsISBN10("1430248270")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN10Valid(t *testing.T) {
|
||||
err := checker.IsISBN10("1430248270")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN10ValidX(t *testing.T) {
|
||||
err := checker.IsISBN10("007462542X")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN10ValidWithDashes(t *testing.T) {
|
||||
err := checker.IsISBN10("1-4302-4827-0")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN10InvalidLength(t *testing.T) {
|
||||
err := checker.IsISBN10("143024827")
|
||||
if !errors.Is(err, checker.ErrNotISBN) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN10InvalidCheck(t *testing.T) {
|
||||
err := checker.IsISBN10("1430248272")
|
||||
if !errors.Is(err, checker.ErrNotISBN) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsISBN13() {
|
||||
err := checker.IsISBN13("9781430248279")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN13Valid(t *testing.T) {
|
||||
err := checker.IsISBN13("9781430248279")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN13ValidWithDashes(t *testing.T) {
|
||||
err := checker.IsISBN13("978-1-4302-4827-9")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN13InvalidLength(t *testing.T) {
|
||||
err := checker.IsISBN13("978143024827")
|
||||
if !errors.Is(err, checker.ErrNotISBN) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBN13InvalidCheck(t *testing.T) {
|
||||
err := checker.IsISBN13("9781430248272")
|
||||
if !errors.Is(err, checker.ErrNotISBN) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsISBN() {
|
||||
err := checker.IsISBN("1430248270")
|
||||
_, err := v2.IsISBN("1430248270")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBNValid10(t *testing.T) {
|
||||
err := checker.IsISBN("1430248270")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
func TestIsISBNInvalid(t *testing.T) {
|
||||
_, err := v2.IsISBN("invalid-isbn")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBNValid13(t *testing.T) {
|
||||
err := checker.IsISBN("9781430248279")
|
||||
func TestIsISBNValid(t *testing.T) {
|
||||
_, err := v2.IsISBN("1430248270")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsISBNInvalidLenght(t *testing.T) {
|
||||
err := checker.IsISBN("978143024827")
|
||||
if err != checker.ErrNotISBN {
|
||||
t.Fail()
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckISBNNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Book struct {
|
||||
ISBN int `checkers:"isbn"`
|
||||
|
|
@ -126,7 +42,22 @@ func TestCheckISBNNonString(t *testing.T) {
|
|||
|
||||
book := &Book{}
|
||||
|
||||
checker.Check(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")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckISBNValid(t *testing.T) {
|
||||
|
|
@ -135,55 +66,11 @@ func TestCheckISBNValid(t *testing.T) {
|
|||
}
|
||||
|
||||
book := &Book{
|
||||
ISBN: "1430248270",
|
||||
ISBN: "9783161484100",
|
||||
}
|
||||
|
||||
_, 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()
|
||||
_, ok := v2.CheckStruct(book)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
lower.go
32
lower.go
|
|
@ -3,28 +3,30 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// tagLower is the tag of the normalizer.
|
||||
const tagLower = "lower"
|
||||
const (
|
||||
// nameLower is the name of the lower normalizer.
|
||||
nameLower = "lower"
|
||||
)
|
||||
|
||||
// makeLower makes a normalizer function for the lower normalizer.
|
||||
func makeLower(_ string) CheckFunc {
|
||||
return normalizeLower
|
||||
// Lower maps all Unicode letters in the given value to their lower case.
|
||||
func Lower(value string) (string, error) {
|
||||
return strings.ToLower(value), nil
|
||||
}
|
||||
|
||||
// normalizeLower maps all Unicode letters in the given value to their lower case.
|
||||
func normalizeLower(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
value.SetString(strings.ToLower(value.String()))
|
||||
|
||||
return nil
|
||||
// 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
|
||||
}
|
||||
|
||||
// makeLower returns the lower normalizer function.
|
||||
func makeLower(_ string) CheckFunc[reflect.Value] {
|
||||
return reflectLower
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,53 +3,45 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func TestNormalizeLowerNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
func TestLower(t *testing.T) {
|
||||
input := "CHECKER"
|
||||
expected := "checker"
|
||||
|
||||
type User struct {
|
||||
Username int `checkers:"lower"`
|
||||
actual, err := v2.Lower(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
|
||||
checker.Check(user)
|
||||
}
|
||||
|
||||
func TestNormalizeLowerErrValid(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"lower"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "chECker",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeLower(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"lower"`
|
||||
func TestReflectLower(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"lower"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "chECker",
|
||||
person := &Person{
|
||||
Name: "CHECKER",
|
||||
}
|
||||
|
||||
checker.Check(user)
|
||||
expected := "checker"
|
||||
|
||||
if user.Username != "checker" {
|
||||
t.Fail()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
89
luhn.go
89
luhn.go
|
|
@ -3,64 +3,59 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// tagLuhn is the tag of the checker.
|
||||
const tagLuhn = "luhn"
|
||||
const (
|
||||
// nameLUHN is the name of the LUHN check.
|
||||
nameLUHN = "luhn"
|
||||
)
|
||||
|
||||
// ErrNotLuhn indicates that the given number is not valid based on the Luhn algorithm.
|
||||
var ErrNotLuhn = errors.New("please enter a valid LUHN")
|
||||
var (
|
||||
// ErrNotLUHN indicates that the given value is not a valid LUHN number.
|
||||
ErrNotLUHN = NewCheckError("NOT_LUHN")
|
||||
)
|
||||
|
||||
// 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}
|
||||
// IsLUHN checks if the value is a valid LUHN number.
|
||||
func IsLUHN(value string) (string, error) {
|
||||
var sum int
|
||||
var alt bool
|
||||
|
||||
// IsLuhn checks if the given number is valid based on the Luhn algorithm.
|
||||
func IsLuhn(number string) error {
|
||||
digits := number[:len(number)-1]
|
||||
check := rune(number[len(number)-1])
|
||||
|
||||
if calculateLuhnCheckDigit(digits) != check {
|
||||
return ErrNotLuhn
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsLuhn(value.String())
|
||||
}
|
||||
|
||||
// Calculates the Luhn algorighm check digit for the given number.
|
||||
func calculateLuhnCheckDigit(number string) rune {
|
||||
digits := []rune(number)
|
||||
check := 0
|
||||
|
||||
for i, j := 0, len(digits)-1; i <= j; i++ {
|
||||
d := int(digits[j-i] - '0')
|
||||
if i%2 == 0 {
|
||||
d = doubleTable[d]
|
||||
for i := len(value) - 1; i >= 0; i-- {
|
||||
r := rune(value[i])
|
||||
if !unicode.IsDigit(r) {
|
||||
return value, ErrNotLUHN
|
||||
}
|
||||
|
||||
check += d
|
||||
n := int(r - '0')
|
||||
if alt {
|
||||
n *= 2
|
||||
if n > 9 {
|
||||
n -= 9
|
||||
}
|
||||
}
|
||||
sum += n
|
||||
alt = !alt
|
||||
}
|
||||
|
||||
check *= 9
|
||||
check %= 10
|
||||
if sum%10 != 0 {
|
||||
return value, ErrNotLUHN
|
||||
}
|
||||
|
||||
return rune('0' + check)
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// checkLUHN checks if the value is a valid LUHN number.
|
||||
func checkLUHN(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsLUHN(value.Interface().(string))
|
||||
return value, err
|
||||
}
|
||||
|
||||
// makeLUHN makes a checker function for the LUHN checker.
|
||||
func makeLUHN(_ string) CheckFunc[reflect.Value] {
|
||||
return checkLUHN
|
||||
}
|
||||
|
|
|
|||
113
luhn_test.go
113
luhn_test.go
|
|
@ -3,74 +3,81 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsLuhn() {
|
||||
err := checker.IsLuhn("4012888888881881")
|
||||
func ExampleIsLUHN() {
|
||||
_, err := v2.IsLUHN("4012888888881881")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLuhnValid(t *testing.T) {
|
||||
numbers := []string{
|
||||
"4012888888881881",
|
||||
"4222222222222",
|
||||
"5555555555554444",
|
||||
"5105105105105100",
|
||||
}
|
||||
|
||||
for _, number := range numbers {
|
||||
if checker.IsLuhn(number) != nil {
|
||||
t.Fail()
|
||||
}
|
||||
func TestIsLUHNInvalid(t *testing.T) {
|
||||
_, err := v2.IsLUHN("123456789")
|
||||
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 TestIsLUHNInvalidDigits(t *testing.T) {
|
||||
_, err := v2.IsLUHN("ABCD")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLuhnValid(t *testing.T) {
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"luhn"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: "4012888888881881",
|
||||
}
|
||||
|
||||
_, 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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
41
mac.go
41
mac.go
|
|
@ -3,40 +3,39 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// tagMac is the tag of the checker.
|
||||
const tagMac = "mac"
|
||||
const (
|
||||
// nameMAC is the name of the MAC check.
|
||||
nameMAC = "mac"
|
||||
)
|
||||
|
||||
// ErrNotMac indicates that the given value is not an MAC address.
|
||||
var ErrNotMac = errors.New("please enter a valid MAC address")
|
||||
var (
|
||||
// ErrNotMAC indicates that the given value is not a valid MAC address.
|
||||
ErrNotMAC = NewCheckError("NOT_MAC")
|
||||
)
|
||||
|
||||
// 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) error {
|
||||
// IsMAC checks if the value is a valid MAC address.
|
||||
func IsMAC(value string) (string, error) {
|
||||
_, err := net.ParseMAC(value)
|
||||
if err != nil {
|
||||
return ErrNotMac
|
||||
return value, ErrNotMAC
|
||||
}
|
||||
|
||||
return nil
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// makeMac makes a checker function for the ip checker.
|
||||
func makeMac(_ string) CheckFunc {
|
||||
return checkMac
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsMac(value.String())
|
||||
// makeMAC makes a checker function for the MAC checker.
|
||||
func makeMAC(_ string) CheckFunc[reflect.Value] {
|
||||
return checkMAC
|
||||
}
|
||||
|
|
|
|||
69
mac_test.go
69
mac_test.go
|
|
@ -3,71 +3,74 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsMac() {
|
||||
err := checker.IsMac("00:00:5e:00:53:01")
|
||||
func ExampleIsMAC() {
|
||||
_, err := v2.IsMAC("00:1A:2B:3C:4D:5E")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMacInvalid(t *testing.T) {
|
||||
if checker.IsMac("1234") == nil {
|
||||
t.Fail()
|
||||
func TestIsMACInvalid(t *testing.T) {
|
||||
_, err := v2.IsMAC("invalid-mac")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMacValid(t *testing.T) {
|
||||
if checker.IsMac("00:00:5e:00:53:01") != nil {
|
||||
t.Fail()
|
||||
func TestIsMACValid(t *testing.T) {
|
||||
_, err := v2.IsMAC("00:1A:2B:3C:4D:5E")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckMacNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
func TestCheckMACNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Network struct {
|
||||
HardwareAddress int `checkers:"mac"`
|
||||
type Device struct {
|
||||
MAC int `checkers:"mac"`
|
||||
}
|
||||
|
||||
network := &Network{}
|
||||
device := &Device{}
|
||||
|
||||
checker.Check(network)
|
||||
v2.CheckStruct(device)
|
||||
}
|
||||
|
||||
func TestCheckMacInvalid(t *testing.T) {
|
||||
type Network struct {
|
||||
HardwareAddress string `checkers:"mac"`
|
||||
func TestCheckMACInvalid(t *testing.T) {
|
||||
type Device struct {
|
||||
MAC string `checkers:"mac"`
|
||||
}
|
||||
|
||||
network := &Network{
|
||||
HardwareAddress: "1234",
|
||||
device := &Device{
|
||||
MAC: "invalid-mac",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(network)
|
||||
if valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(device)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckMacValid(t *testing.T) {
|
||||
type Network struct {
|
||||
HardwareAddress string `checkers:"mac"`
|
||||
func TestCheckMACValid(t *testing.T) {
|
||||
type Device struct {
|
||||
MAC string `checkers:"mac"`
|
||||
}
|
||||
|
||||
network := &Network{
|
||||
HardwareAddress: "00:00:5e:00:53:01",
|
||||
device := &Device{
|
||||
MAC: "00:1A:2B:3C:4D:5E",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(network)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(device)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
43
max.go
43
max.go
|
|
@ -1,43 +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 checker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// tagMax is the tag of the checker.
|
||||
const tagMax = "max"
|
||||
|
||||
// IsMax checks if the given value is below than the given maximum.
|
||||
func IsMax(value interface{}, max float64) error {
|
||||
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) error {
|
||||
return checkMax(value, parent, max)
|
||||
}
|
||||
}
|
||||
|
||||
// checkMax checks if the given value is less than the given maximum.
|
||||
func checkMax(value, _ reflect.Value, max float64) error {
|
||||
n := numberOf(value)
|
||||
|
||||
if n > max {
|
||||
return fmt.Errorf("please enter a number less than %g", max)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
71
max_test.go
71
max_test.go
|
|
@ -1,71 +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 checker_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsMax() {
|
||||
quantity := 5
|
||||
|
||||
err := checker.IsMax(quantity, 10)
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMaxValid(t *testing.T) {
|
||||
n := 5
|
||||
|
||||
if checker.IsMax(n, 10) != nil {
|
||||
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()
|
||||
}
|
||||
}
|
||||
42
maxlength.go
42
maxlength.go
|
|
@ -1,42 +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 checker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// tagMaxLength is the tag of the checker.
|
||||
const tagMaxLength = "max-length"
|
||||
|
||||
// IsMaxLength checks if the length of the given value is less than the given maximum length.
|
||||
func IsMaxLength(value interface{}, maxLength int) error {
|
||||
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) error {
|
||||
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) error {
|
||||
if value.Len() > maxLength {
|
||||
return fmt.Errorf("please enter %d characters or less", maxLength-1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,71 +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 checker_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsMaxLength() {
|
||||
s := "1234"
|
||||
|
||||
err := checker.IsMaxLength(s, 4)
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMaxLengthValid(t *testing.T) {
|
||||
s := "1234"
|
||||
|
||||
if checker.IsMaxLength(s, 4) != nil {
|
||||
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()
|
||||
}
|
||||
}
|
||||
43
min.go
43
min.go
|
|
@ -1,43 +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 checker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// tagMin is the tag of the checker.
|
||||
const tagMin = "min"
|
||||
|
||||
// IsMin checks if the given value is above than the given minimum.
|
||||
func IsMin(value interface{}, min float64) error {
|
||||
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) error {
|
||||
return checkMin(value, parent, min)
|
||||
}
|
||||
}
|
||||
|
||||
// checkMin checks if the given value is greather than the given minimum.
|
||||
func checkMin(value, _ reflect.Value, min float64) error {
|
||||
n := numberOf(value)
|
||||
|
||||
if n < min {
|
||||
return fmt.Errorf("please enter a number less than %g", min)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
71
min_test.go
71
min_test.go
|
|
@ -1,71 +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 checker_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsMin() {
|
||||
age := 45
|
||||
|
||||
err := checker.IsMin(age, 21)
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMinValid(t *testing.T) {
|
||||
n := 45
|
||||
|
||||
if checker.IsMin(n, 21) != nil {
|
||||
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()
|
||||
}
|
||||
}
|
||||
42
minlenght.go
42
minlenght.go
|
|
@ -1,42 +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 checker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// tagMinLength is the tag of the checker.
|
||||
const tagMinLength = "min-length"
|
||||
|
||||
// IsMinLength checks if the length of the given value is greather than the given minimum length.
|
||||
func IsMinLength(value interface{}, minLength int) error {
|
||||
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) error {
|
||||
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) error {
|
||||
if value.Len() < minLength {
|
||||
return fmt.Errorf("please enter at least %d characters", minLength)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,71 +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 checker_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func ExampleIsMinLength() {
|
||||
s := "1234"
|
||||
|
||||
err := checker.IsMinLength(s, 4)
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMinLengthValid(t *testing.T) {
|
||||
s := "1234"
|
||||
|
||||
if checker.IsMinLength(s, 4) != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckMinLengthInvalidConfig(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type User struct {
|
||||
Password string `checkers:"min-length:AB"`
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
|
||||
checker.Check(user)
|
||||
}
|
||||
|
||||
func TestCheckMinLengthValid(t *testing.T) {
|
||||
type User struct {
|
||||
Password string `checkers:"min-length:4"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Password: "1234",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckMinLengthInvalid(t *testing.T) {
|
||||
type User struct {
|
||||
Password string `checkers:"min-length:4"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Password: "12",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
49
regexp.go
49
regexp.go
|
|
@ -3,50 +3,45 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// tagRegexp is the tag of the checker.
|
||||
const tagRegexp = "regexp"
|
||||
// nameRegexp is the name of the regexp check.
|
||||
const nameRegexp = "regexp"
|
||||
|
||||
// ErrNotMatch indicates that the given string does not match the regexp pattern.
|
||||
var ErrNotMatch = errors.New("please enter a valid input")
|
||||
var ErrNotMatch = NewCheckError("REGEXP")
|
||||
|
||||
// MakeRegexpMaker makes a regexp checker maker for the given regexp expression with the given invalid result.
|
||||
func MakeRegexpMaker(expression string, invalidError error) MakeFunc {
|
||||
return func(_ string) CheckFunc {
|
||||
return MakeRegexpChecker(expression, invalidError)
|
||||
// IsRegexp checks if the given string matches the given regexp expression.
|
||||
func IsRegexp(expression, value string) (string, error) {
|
||||
if !regexp.MustCompile(expression).MatchString(value) {
|
||||
return value, ErrNotMatch
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// MakeRegexpChecker makes a regexp checker for the given regexp expression with the given invalid result.
|
||||
func MakeRegexpChecker(expression string, invalidError error) CheckFunc {
|
||||
pattern := regexp.MustCompile(expression)
|
||||
func MakeRegexpChecker(expression string, invalidError error) CheckFunc[reflect.Value] {
|
||||
return func(value reflect.Value) (reflect.Value, error) {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return func(value, parent reflect.Value) error {
|
||||
return checkRegexp(value, pattern, invalidError)
|
||||
_, err := IsRegexp(expression, value.String())
|
||||
if err != nil {
|
||||
return value, invalidError
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
// makeRegexp makes a checker function for the regexp.
|
||||
func makeRegexp(config string) CheckFunc {
|
||||
func makeRegexp(config string) CheckFunc[reflect.Value] {
|
||||
return MakeRegexpChecker(config, ErrNotMatch)
|
||||
}
|
||||
|
||||
// checkRegexp checks if the given string matches the regexp pattern.
|
||||
func checkRegexp(value reflect.Value, pattern *regexp.Regexp, invalidError error) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
if !pattern.MatchString(value.String()) {
|
||||
return invalidError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,38 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsRegexp() {
|
||||
_, err := v2.IsRegexp("^[0-9a-fA-F]+$", "ABcd1234")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRegexpInvalid(t *testing.T) {
|
||||
_, err := v2.IsRegexp("^[0-9a-fA-F]+$", "Onur")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRegexpValid(t *testing.T) {
|
||||
_, err := v2.IsRegexp("^[0-9a-fA-F]+$", "ABcd1234")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRegexpNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type User struct {
|
||||
Username int `checkers:"regexp:^[A-Za-z]$"`
|
||||
|
|
@ -22,7 +42,7 @@ func TestCheckRegexpNonString(t *testing.T) {
|
|||
|
||||
user := &User{}
|
||||
|
||||
checker.Check(user)
|
||||
v2.CheckStruct(user)
|
||||
}
|
||||
|
||||
func TestCheckRegexpInvalid(t *testing.T) {
|
||||
|
|
@ -34,9 +54,9 @@ func TestCheckRegexpInvalid(t *testing.T) {
|
|||
Username: "abcd1234",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(user)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -49,34 +69,8 @@ func TestCheckRegexpValid(t *testing.T) {
|
|||
Username: "abcd",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeRegexpChecker(t *testing.T) {
|
||||
checkHex := checker.MakeRegexpChecker("^[A-Fa-f0-9]+$", errors.New("Not Hex"))
|
||||
|
||||
err := checkHex(reflect.ValueOf("f0f0f0"), reflect.ValueOf(nil))
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeRegexpMaker(t *testing.T) {
|
||||
checker.Register("hex", checker.MakeRegexpMaker("^[A-Fa-f0-9]+$", errors.New("Not Hex")))
|
||||
|
||||
type Theme struct {
|
||||
Color string `checkers:"hex"`
|
||||
}
|
||||
|
||||
theme := &Theme{
|
||||
Color: "f0f0f0",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(theme)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(user)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
required.go
52
required.go
|
|
@ -3,40 +3,40 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
import "reflect"
|
||||
|
||||
const (
|
||||
// nameRequired is the name of the required check.
|
||||
nameRequired = "required"
|
||||
)
|
||||
|
||||
// tagRequired is the tag of the checker.
|
||||
const tagRequired = "required"
|
||||
var (
|
||||
// ErrRequired indicates that a required value was missing.
|
||||
ErrRequired = NewCheckError("REQUIRED")
|
||||
)
|
||||
|
||||
// ErrRequired indicates that the required value is missing.
|
||||
var ErrRequired = errors.New("is required")
|
||||
|
||||
// IsRequired checks if the given required value is present.
|
||||
func IsRequired(v interface{}) error {
|
||||
return checkRequired(reflect.ValueOf(v), reflect.ValueOf(nil))
|
||||
// Required checks if the given value of type T is its zero value. It
|
||||
// returns an error if the value is zero.
|
||||
func Required[T any](value T) (T, error) {
|
||||
_, err := reflectRequired(reflect.ValueOf(value))
|
||||
return value, err
|
||||
}
|
||||
|
||||
// makeRequired makes a checker function for required.
|
||||
func makeRequired(_ string) CheckFunc {
|
||||
return checkRequired
|
||||
}
|
||||
// reflectRequired checks if the given value is its zero value. It
|
||||
// returns an error if the value is zero.
|
||||
func reflectRequired(value reflect.Value) (reflect.Value, error) {
|
||||
var err error
|
||||
|
||||
// checkRequired checks if the required value is present.
|
||||
func checkRequired(value, _ reflect.Value) error {
|
||||
if value.IsZero() {
|
||||
return ErrRequired
|
||||
err = ErrRequired
|
||||
}
|
||||
|
||||
k := value.Kind()
|
||||
|
||||
if (k == reflect.Array || k == reflect.Map || k == reflect.Slice) && value.Len() == 0 {
|
||||
return ErrRequired
|
||||
}
|
||||
|
||||
return nil
|
||||
return value, err
|
||||
}
|
||||
|
||||
// makeRequired returns the required check function.
|
||||
func makeRequired(_ string) CheckFunc[reflect.Value] {
|
||||
return reflectRequired
|
||||
}
|
||||
|
|
|
|||
150
required_test.go
150
required_test.go
|
|
@ -3,145 +3,39 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsRequired() {
|
||||
var name string
|
||||
func TestRequiredSuccess(t *testing.T) {
|
||||
value := "test"
|
||||
|
||||
result, err := v2.Required(value)
|
||||
|
||||
if result != value {
|
||||
t.Fatalf("result (%s) is not the original value (%s)", result, value)
|
||||
}
|
||||
|
||||
err := checker.IsRequired(name)
|
||||
if err != nil {
|
||||
// Send the err back to the user
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRequired(t *testing.T) {
|
||||
s := "valid"
|
||||
func TestRequiredMissing(t *testing.T) {
|
||||
var value string
|
||||
|
||||
if checker.IsRequired(s) != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRequiredUninitializedString(t *testing.T) {
|
||||
var s string
|
||||
|
||||
if checker.IsRequired(s) != checker.ErrRequired {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRequiredEmptyString(t *testing.T) {
|
||||
s := ""
|
||||
|
||||
if checker.IsRequired(s) == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRequiredUninitializedNumber(t *testing.T) {
|
||||
var n int
|
||||
|
||||
if checker.IsRequired(n) != checker.ErrRequired {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRequiredValidSlice(t *testing.T) {
|
||||
s := []int{1}
|
||||
|
||||
if checker.IsRequired(s) != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRequiredUninitializedSlice(t *testing.T) {
|
||||
var s []int
|
||||
|
||||
if checker.IsRequired(s) != checker.ErrRequired {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRequiredEmptySlice(t *testing.T) {
|
||||
s := make([]int, 0)
|
||||
|
||||
if checker.IsRequired(s) != checker.ErrRequired {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRequiredValidArray(t *testing.T) {
|
||||
s := [1]int{1}
|
||||
|
||||
if checker.IsRequired(s) != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRequiredEmptyArray(t *testing.T) {
|
||||
s := [1]int{}
|
||||
|
||||
if checker.IsRequired(s) != checker.ErrRequired {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRequiredValidMap(t *testing.T) {
|
||||
m := map[string]string{
|
||||
"a": "b",
|
||||
}
|
||||
|
||||
if checker.IsRequired(m) != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRequiredUninitializedMap(t *testing.T) {
|
||||
var m map[string]string
|
||||
|
||||
if checker.IsRequired(m) != checker.ErrRequired {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRequiredEmptyMap(t *testing.T) {
|
||||
m := map[string]string{}
|
||||
|
||||
if checker.IsRequired(m) != checker.ErrRequired {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRequiredValid(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"required"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "checker",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRequiredInvalid(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"required"`
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if valid {
|
||||
t.Fail()
|
||||
result, err := v2.Required(value)
|
||||
|
||||
if result != value {
|
||||
t.Fatalf("result (%s) is not the original value (%s)", result, value)
|
||||
}
|
||||
|
||||
if !errors.Is(err, v2.ErrRequired) {
|
||||
t.Fatalf("got unexpected error %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
41
same.go
41
same.go
|
|
@ -1,41 +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 checker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// tagSame is the tag of the checker.
|
||||
const tagSame = "same"
|
||||
|
||||
// ErrNotSame indicates that the given two values are not equal to each other.
|
||||
var ErrNotSame = errors.New("does not match the other")
|
||||
|
||||
// makeSame makes a checker function for the same checker.
|
||||
func makeSame(config string) CheckFunc {
|
||||
return func(value, parent reflect.Value) error {
|
||||
return checkSame(value, parent, config)
|
||||
}
|
||||
}
|
||||
|
||||
// checkSame checks if the given value is equal to the value of the field with the given name.
|
||||
func checkSame(value, parent reflect.Value, name string) error {
|
||||
other := parent.FieldByName(name)
|
||||
|
||||
if !other.IsValid() {
|
||||
panic("other field not found")
|
||||
}
|
||||
|
||||
other = reflect.Indirect(other)
|
||||
|
||||
if !value.Equal(other) {
|
||||
return ErrNotSame
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
62
same_test.go
62
same_test.go
|
|
@ -1,62 +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 checker_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func TestSameValid(t *testing.T) {
|
||||
type User struct {
|
||||
Password string
|
||||
Confirm string `checkers:"same:Password"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Password: "1234",
|
||||
Confirm: "1234",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameInvalid(t *testing.T) {
|
||||
type User struct {
|
||||
Password string
|
||||
Confirm string `checkers:"same:Password"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Password: "1234",
|
||||
Confirm: "12",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameInvalidName(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type User struct {
|
||||
Password string
|
||||
Confirm string `checkers:"same:Unknown"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Password: "1234",
|
||||
Confirm: "1234",
|
||||
}
|
||||
|
||||
checker.Check(user)
|
||||
}
|
||||
|
|
@ -19,13 +19,14 @@ tasks:
|
|||
lint:
|
||||
cmds:
|
||||
- go vet ./...
|
||||
- go run github.com/securego/gosec/v2/cmd/gosec@v2.20.0 -exclude-dir=v2 ./...
|
||||
- go run github.com/securego/gosec/v2/cmd/gosec@v2.20.0 ./...
|
||||
- go run honnef.co/go/tools/cmd/staticcheck@v0.5.1 ./...
|
||||
- go run github.com/mgechev/revive@v1.3.4 -config=revive.toml ./...
|
||||
|
||||
test:
|
||||
cmds:
|
||||
- go test -cover -coverprofile=coverage.out ./...
|
||||
- go tool cover -func=coverage.out
|
||||
|
||||
docs:
|
||||
cmds:
|
||||
|
|
|
|||
|
|
@ -1,15 +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 checker
|
||||
|
||||
import "testing"
|
||||
|
||||
// FailIfNoPanic fails if test didn't panic. Use this function with the defer.
|
||||
func FailIfNoPanic(t *testing.T) {
|
||||
if r := recover(); r == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +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 checker_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func TestFailIfNoPanicValid(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
panic("")
|
||||
}
|
||||
|
||||
func TestFailIfNoPanicInvalid(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
defer checker.FailIfNoPanic(nil)
|
||||
}
|
||||
38
title.go
38
title.go
|
|
@ -3,7 +3,7 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
|
@ -11,24 +11,17 @@ import (
|
|||
"unicode"
|
||||
)
|
||||
|
||||
// tagTitle is the tag of the normalizer.
|
||||
const tagTitle = "title"
|
||||
|
||||
// makeTitle makes a normalizer function for the title normalizer.
|
||||
func makeTitle(_ string) CheckFunc {
|
||||
return normalizeTitle
|
||||
}
|
||||
|
||||
// normalizeTitle maps the first letter of each word to their upper case.
|
||||
func normalizeTitle(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
const (
|
||||
// nameTitle is the name of the title normalizer.
|
||||
nameTitle = "title"
|
||||
)
|
||||
|
||||
// Title returns the value of the string with the first letter of each word in upper case.
|
||||
func Title(value string) (string, error) {
|
||||
var sb strings.Builder
|
||||
begin := true
|
||||
|
||||
for _, c := range value.String() {
|
||||
for _, c := range value {
|
||||
if unicode.IsLetter(c) {
|
||||
if begin {
|
||||
c = unicode.ToUpper(c)
|
||||
|
|
@ -43,7 +36,16 @@ func normalizeTitle(value, _ reflect.Value) error {
|
|||
sb.WriteRune(c)
|
||||
}
|
||||
|
||||
value.SetString(sb.String())
|
||||
|
||||
return nil
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
// reflectTitle returns the value of the string with the first letter of each word in upper case.
|
||||
func reflectTitle(value reflect.Value) (reflect.Value, error) {
|
||||
newValue, err := Title(value.Interface().(string))
|
||||
return reflect.ValueOf(newValue), err
|
||||
}
|
||||
|
||||
// makeTitle returns the title normalizer function.
|
||||
func makeTitle(_ string) CheckFunc[reflect.Value] {
|
||||
return reflectTitle
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,53 +3,45 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func TestNormalizeTitleNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
func TestTitle(t *testing.T) {
|
||||
input := "the checker"
|
||||
expected := "The Checker"
|
||||
|
||||
type Book struct {
|
||||
Chapter int `checkers:"title"`
|
||||
actual, err := v2.Title(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
book := &Book{}
|
||||
|
||||
checker.Check(book)
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeTitleErrValid(t *testing.T) {
|
||||
func TestReflectTitle(t *testing.T) {
|
||||
type Book struct {
|
||||
Chapter string `checkers:"title"`
|
||||
}
|
||||
|
||||
book := &Book{
|
||||
Chapter: "THE checker",
|
||||
Chapter: "the checker",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(book)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeTitle(t *testing.T) {
|
||||
type Book struct {
|
||||
Chapter string `checkers:"title"`
|
||||
}
|
||||
|
||||
book := &Book{
|
||||
Chapter: "THE checker",
|
||||
}
|
||||
|
||||
checker.Check(book)
|
||||
|
||||
if book.Chapter != "The Checker" {
|
||||
t.Fail()
|
||||
expected := "The Checker"
|
||||
|
||||
errs, ok := v2.CheckStruct(book)
|
||||
if !ok {
|
||||
t.Fatalf("got unexpected errors %v", errs)
|
||||
}
|
||||
|
||||
if book.Chapter != expected {
|
||||
t.Fatalf("actual %s expected %s", book.Chapter, expected)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
30
trim.go
30
trim.go
|
|
@ -1,30 +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 checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// tagTrim is the tag of the normalizer.
|
||||
const tagTrim = "trim"
|
||||
|
||||
// makeTrim makes a normalizer function for the trim normalizer.
|
||||
func makeTrim(_ string) CheckFunc {
|
||||
return normalizeTrim
|
||||
}
|
||||
|
||||
// normalizeTrim removes the whitespaces at the beginning and at the end of the given value.
|
||||
func normalizeTrim(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
value.SetString(strings.Trim(value.String(), " \t"))
|
||||
|
||||
return nil
|
||||
}
|
||||
32
trim_left.go
32
trim_left.go
|
|
@ -3,28 +3,30 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// tagTrimLeft is the tag of the normalizer.
|
||||
const tagTrimLeft = "trim-left"
|
||||
const (
|
||||
// nameTrimLeft is the name of the trim left normalizer.
|
||||
nameTrimLeft = "trim-left"
|
||||
)
|
||||
|
||||
// makeTrimLeft makes a normalizer function for the trim left normalizer.
|
||||
func makeTrimLeft(_ string) CheckFunc {
|
||||
return normalizeTrimLeft
|
||||
// TrimLeft returns the value of the string with whitespace removed from the beginning.
|
||||
func TrimLeft(value string) (string, error) {
|
||||
return strings.TrimLeft(value, " \t"), nil
|
||||
}
|
||||
|
||||
// normalizeTrim removes the whitespaces at the beginning of the given value.
|
||||
func normalizeTrimLeft(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
value.SetString(strings.TrimLeft(value.String(), " \t"))
|
||||
|
||||
return nil
|
||||
// reflectTrimLeft returns the value of the string with whitespace removed from the beginning.
|
||||
func reflectTrimLeft(value reflect.Value) (reflect.Value, error) {
|
||||
newValue, err := TrimLeft(value.Interface().(string))
|
||||
return reflect.ValueOf(newValue), err
|
||||
}
|
||||
|
||||
// makeTrimLeft returns the trim left normalizer function.
|
||||
func makeTrimLeft(_ string) CheckFunc[reflect.Value] {
|
||||
return reflectTrimLeft
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,53 +3,45 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func TestNormalizeTrimLeftNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
func TestTrimLeft(t *testing.T) {
|
||||
input := " test "
|
||||
expected := "test "
|
||||
|
||||
type User struct {
|
||||
Username int `checkers:"trim-left"`
|
||||
actual, err := v2.TrimLeft(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
|
||||
checker.Check(user)
|
||||
}
|
||||
|
||||
func TestNormalizeTrimLeftErrValid(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"trim-left"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: " normalizer ",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeTrimLeft(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"trim-left"`
|
||||
func TestReflectTrimLeft(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"trim-left"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: " normalizer ",
|
||||
person := &Person{
|
||||
Name: " test ",
|
||||
}
|
||||
|
||||
checker.Check(user)
|
||||
expected := "test "
|
||||
|
||||
if user.Username != "normalizer " {
|
||||
t.Fail()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,28 +3,30 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// tagTrimRight is the tag of the normalizer.
|
||||
const tagTrimRight = "trim-right"
|
||||
const (
|
||||
// nameTrimRight is the name of the trim right normalizer.
|
||||
nameTrimRight = "trim-right"
|
||||
)
|
||||
|
||||
// makeTrimRight makes a normalizer function for the trim right normalizer.
|
||||
func makeTrimRight(_ string) CheckFunc {
|
||||
return normalizeTrimRight
|
||||
// TrimRight returns the value of the string with whitespace removed from the end.
|
||||
func TrimRight(value string) (string, error) {
|
||||
return strings.TrimRight(value, " \t"), nil
|
||||
}
|
||||
|
||||
// normalizeTrimRight removes the whitespaces at the end of the given value.
|
||||
func normalizeTrimRight(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
value.SetString(strings.TrimRight(value.String(), " \t"))
|
||||
|
||||
return nil
|
||||
// reflectTrimRight returns the value of the string with whitespace removed from the end.
|
||||
func reflectTrimRight(value reflect.Value) (reflect.Value, error) {
|
||||
newValue, err := TrimRight(value.Interface().(string))
|
||||
return reflect.ValueOf(newValue), err
|
||||
}
|
||||
|
||||
// makeTrimRight returns the trim right normalizer function.
|
||||
func makeTrimRight(_ string) CheckFunc[reflect.Value] {
|
||||
return reflectTrimRight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,53 +3,45 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func TestNormalizeTrimRightNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
func TestTrimRight(t *testing.T) {
|
||||
input := " test "
|
||||
expected := " test"
|
||||
|
||||
type User struct {
|
||||
Username int `checkers:"trim-right"`
|
||||
actual, err := v2.TrimRight(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
|
||||
checker.Check(user)
|
||||
}
|
||||
|
||||
func TestNormalizeTrimRightErrValid(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"trim-right"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: " normalizer ",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeTrimRight(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"trim-right"`
|
||||
func TestReflectTrimRight(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"trim-right"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: " normalizer ",
|
||||
person := &Person{
|
||||
Name: " test ",
|
||||
}
|
||||
|
||||
checker.Check(user)
|
||||
expected := " test"
|
||||
|
||||
if user.Username != " normalizer" {
|
||||
t.Fail()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
trim_test.go
55
trim_test.go
|
|
@ -1,55 +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 checker_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
)
|
||||
|
||||
func TestNormalizeTrimNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
|
||||
type User struct {
|
||||
Username int `checkers:"trim"`
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
|
||||
checker.Check(user)
|
||||
}
|
||||
|
||||
func TestNormalizeTrimErrValid(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"trim"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: " normalizer ",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeTrim(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"trim"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: " normalizer ",
|
||||
}
|
||||
|
||||
checker.Check(user)
|
||||
|
||||
if user.Username != "normalizer" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
32
upper.go
32
upper.go
|
|
@ -3,28 +3,30 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// tagUpper is the tag of the normalizer.
|
||||
const tagUpper = "upper"
|
||||
const (
|
||||
// nameUpper is the name of the upper normalizer.
|
||||
nameUpper = "upper"
|
||||
)
|
||||
|
||||
// makeUpper makes a normalizer function for the upper normalizer.
|
||||
func makeUpper(_ string) CheckFunc {
|
||||
return normalizeUpper
|
||||
// Upper maps all Unicode letters in the given value to their upper case.
|
||||
func Upper(value string) (string, error) {
|
||||
return strings.ToUpper(value), nil
|
||||
}
|
||||
|
||||
// normalizeUpper maps all Unicode letters in the given value to their upper case.
|
||||
func normalizeUpper(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
value.SetString(strings.ToUpper(value.String()))
|
||||
|
||||
return nil
|
||||
// reflectUpper maps all Unicode letters in the given value to their upper case.
|
||||
func reflectUpper(value reflect.Value) (reflect.Value, error) {
|
||||
newValue, err := Upper(value.Interface().(string))
|
||||
return reflect.ValueOf(newValue), err
|
||||
}
|
||||
|
||||
// makeUpper returns the upper normalizer function.
|
||||
func makeUpper(_ string) CheckFunc[reflect.Value] {
|
||||
return reflectUpper
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,53 +3,45 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func TestNormalizeUpperNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
func TestUpper(t *testing.T) {
|
||||
input := "checker"
|
||||
expected := "CHECKER"
|
||||
|
||||
type User struct {
|
||||
Username int `checkers:"upper"`
|
||||
actual, err := v2.Upper(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
|
||||
checker.Check(user)
|
||||
}
|
||||
|
||||
func TestNormalizeUpperErrValid(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"upper"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "chECker",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(user)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeUpper(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"upper"`
|
||||
func TestReflectUpper(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"upper"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "chECker",
|
||||
person := &Person{
|
||||
Name: "checker",
|
||||
}
|
||||
|
||||
checker.Check(user)
|
||||
expected := "CHECKER"
|
||||
|
||||
if user.Username != "CHECKER" {
|
||||
t.Fail()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
49
url.go
49
url.go
|
|
@ -3,48 +3,39 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// tagURL is the tag of the checker.
|
||||
const tagURL = "url"
|
||||
const (
|
||||
// nameURL is the name of the URL check.
|
||||
nameURL = "url"
|
||||
)
|
||||
|
||||
// ErrNotURL indicates that the given value is not a valid URL.
|
||||
var ErrNotURL = errors.New("please enter a valid URL")
|
||||
var (
|
||||
// ErrNotURL indicates that the given value is not a valid URL.
|
||||
ErrNotURL = NewCheckError("NOT_URL")
|
||||
)
|
||||
|
||||
// IsURL checks if the given value is a valid URL.
|
||||
func IsURL(value string) error {
|
||||
url, err := url.ParseRequestURI(value)
|
||||
// IsURL checks if the value is a valid URL.
|
||||
func IsURL(value string) (string, error) {
|
||||
_, err := url.ParseRequestURI(value)
|
||||
if err != nil {
|
||||
return ErrNotURL
|
||||
return value, ErrNotURL
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
if url.Scheme == "" {
|
||||
return ErrNotURL
|
||||
}
|
||||
|
||||
if url.Host == "" {
|
||||
return ErrNotURL
|
||||
}
|
||||
|
||||
return nil
|
||||
// checkURL checks if the value is a valid URL.
|
||||
func checkURL(value reflect.Value) (reflect.Value, error) {
|
||||
_, err := IsURL(value.Interface().(string))
|
||||
return value, err
|
||||
}
|
||||
|
||||
// makeURL makes a checker function for the URL checker.
|
||||
func makeURL(_ string) CheckFunc {
|
||||
func makeURL(_ string) CheckFunc[reflect.Value] {
|
||||
return checkURL
|
||||
}
|
||||
|
||||
// checkURL checks if the given value is a valid URL.
|
||||
func checkURL(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
return IsURL(value.String())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,29 +3,28 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// tagURLEscape is the tag of the normalizer.
|
||||
const tagURLEscape = "url-escape"
|
||||
// nameURLEscape is the name of the URL escape normalizer.
|
||||
const nameURLEscape = "url-escape"
|
||||
|
||||
// makeURLEscape makes a normalizer function for the URL escape normalizer.
|
||||
func makeURLEscape(_ string) CheckFunc {
|
||||
return normalizeURLEscape
|
||||
// URLEscape applies URL escaping to special characters.
|
||||
func URLEscape(value string) (string, error) {
|
||||
return url.QueryEscape(value), nil
|
||||
}
|
||||
|
||||
// normalizeURLEscape applies URL escaping to special characters.
|
||||
// Uses net.url.QueryEscape for the actual escape operation.
|
||||
func normalizeURLEscape(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
value.SetString(url.QueryEscape(value.String()))
|
||||
|
||||
return nil
|
||||
// reflectURLEscape applies URL escaping to special characters.
|
||||
func reflectURLEscape(value reflect.Value) (reflect.Value, error) {
|
||||
newValue, err := URLEscape(value.Interface().(string))
|
||||
return reflect.ValueOf(newValue), err
|
||||
}
|
||||
|
||||
// makeURLEscape returns the URL escape normalizer function.
|
||||
func makeURLEscape(_ string) CheckFunc[reflect.Value] {
|
||||
return reflectURLEscape
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,29 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func TestNormalizeURLEscapeNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
func TestURLEscape(t *testing.T) {
|
||||
input := "param1/param2 = 1 + 2 & 3 + 4"
|
||||
expected := "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4"
|
||||
|
||||
type Request struct {
|
||||
Query int `checkers:"url-escape"`
|
||||
actual, err := v2.URLEscape(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
request := &Request{}
|
||||
|
||||
checker.Check(request)
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeURLEscape(t *testing.T) {
|
||||
func TestReflectURLEscape(t *testing.T) {
|
||||
type Request struct {
|
||||
Query string `checkers:"url-escape"`
|
||||
}
|
||||
|
|
@ -32,12 +34,14 @@ func TestNormalizeURLEscape(t *testing.T) {
|
|||
Query: "param1/param2 = 1 + 2 & 3 + 4",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
expected := "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4"
|
||||
|
||||
errs, ok := v2.CheckStruct(request)
|
||||
if !ok {
|
||||
t.Fatalf("got unexpected errors %v", errs)
|
||||
}
|
||||
|
||||
if request.Query != "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4" {
|
||||
t.Fail()
|
||||
if request.Query != expected {
|
||||
t.Fatalf("actual %s expected %s", request.Query, expected)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
97
url_test.go
97
url_test.go
|
|
@ -3,103 +3,74 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleIsURL() {
|
||||
err := checker.IsURL("https://zdo.com")
|
||||
_, err := v2.IsURL("https://example.com")
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsURLValid(t *testing.T) {
|
||||
err := checker.IsURL("https://zdo.com")
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsURLInvalid(t *testing.T) {
|
||||
err := checker.IsURL("https:://index.html")
|
||||
_, err := v2.IsURL("invalid-url")
|
||||
if err == nil {
|
||||
t.Fail()
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsURLValid(t *testing.T) {
|
||||
_, err := v2.IsURL("https://example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckURLNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Bookmark struct {
|
||||
URL int `checkers:"url"`
|
||||
type Website struct {
|
||||
Link int `checkers:"url"`
|
||||
}
|
||||
|
||||
bookmark := &Bookmark{}
|
||||
website := &Website{}
|
||||
|
||||
checker.Check(bookmark)
|
||||
}
|
||||
|
||||
func TestCheckURLValid(t *testing.T) {
|
||||
type Bookmark struct {
|
||||
URL string `checkers:"url"`
|
||||
}
|
||||
|
||||
bookmark := &Bookmark{
|
||||
URL: "https://zdo.com",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(bookmark)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
v2.CheckStruct(website)
|
||||
}
|
||||
|
||||
func TestCheckURLInvalid(t *testing.T) {
|
||||
type Bookmark struct {
|
||||
URL string `checkers:"url"`
|
||||
type Website struct {
|
||||
Link string `checkers:"url"`
|
||||
}
|
||||
|
||||
bookmark := &Bookmark{
|
||||
URL: "zdo.com/index.html",
|
||||
website := &Website{
|
||||
Link: "invalid-url",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(bookmark)
|
||||
if valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(website)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckURLWithoutSchema(t *testing.T) {
|
||||
type Bookmark struct {
|
||||
URL string `checkers:"url"`
|
||||
func TestCheckURLValid(t *testing.T) {
|
||||
type Website struct {
|
||||
Link string `checkers:"url"`
|
||||
}
|
||||
|
||||
bookmark := &Bookmark{
|
||||
URL: "//zdo.com/index.html",
|
||||
website := &Website{
|
||||
Link: "https://example.com",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(bookmark)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckURLWithoutHost(t *testing.T) {
|
||||
type Bookmark struct {
|
||||
URL string `checkers:"url"`
|
||||
}
|
||||
|
||||
bookmark := &Bookmark{
|
||||
URL: "https:://index.html",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(bookmark)
|
||||
if valid {
|
||||
t.Fail()
|
||||
_, ok := v2.CheckStruct(website)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,32 +3,29 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker
|
||||
package v2
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// tagURLUnescape is the tag of the normalizer.
|
||||
const tagURLUnescape = "url-unescape"
|
||||
// nameURLUnescape is the name of the URL unescape normalizer.
|
||||
const nameURLUnescape = "url-unescape"
|
||||
|
||||
// makeURLUnescape makes a normalizer function for the URL unscape normalizer.
|
||||
func makeURLUnescape(_ string) CheckFunc {
|
||||
return normalizeURLUnescape
|
||||
// URLUnescape applies URL unescaping to special characters.
|
||||
func URLUnescape(value string) (string, error) {
|
||||
unescaped, err := url.QueryUnescape(value)
|
||||
return unescaped, err
|
||||
}
|
||||
|
||||
// normalizeURLUnescape applies URL unescaping to special characters.
|
||||
// Uses url.QueryUnescape for the actual unescape operation.
|
||||
func normalizeURLUnescape(value, _ reflect.Value) error {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
unescaped, err := url.QueryUnescape(value.String())
|
||||
if err == nil {
|
||||
value.SetString(unescaped)
|
||||
}
|
||||
|
||||
return nil
|
||||
// reflectURLUnescape applies URL unescaping to special characters.
|
||||
func reflectURLUnescape(value reflect.Value) (reflect.Value, error) {
|
||||
newValue, err := URLUnescape(value.Interface().(string))
|
||||
return reflect.ValueOf(newValue), err
|
||||
}
|
||||
|
||||
// makeURLUnescape returns the URL unescape normalizer function.
|
||||
func makeURLUnescape(_ string) CheckFunc[reflect.Value] {
|
||||
return reflectURLUnescape
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,29 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
// https://github.com/cinar/checker
|
||||
|
||||
package checker_test
|
||||
package v2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cinar/checker"
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func TestNormalizeURLUnescapeNonString(t *testing.T) {
|
||||
defer checker.FailIfNoPanic(t)
|
||||
func TestURLUnescape(t *testing.T) {
|
||||
input := "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4"
|
||||
expected := "param1/param2 = 1 + 2 & 3 + 4"
|
||||
|
||||
type Request struct {
|
||||
Query int `checkers:"url-unescape"`
|
||||
actual, err := v2.URLUnescape(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
request := &Request{}
|
||||
|
||||
checker.Check(request)
|
||||
if actual != expected {
|
||||
t.Fatalf("actual %s expected %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeURLUnescape(t *testing.T) {
|
||||
func TestReflectURLUnescape(t *testing.T) {
|
||||
type Request struct {
|
||||
Query string `checkers:"url-unescape"`
|
||||
}
|
||||
|
|
@ -32,12 +34,14 @@ func TestNormalizeURLUnescape(t *testing.T) {
|
|||
Query: "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4",
|
||||
}
|
||||
|
||||
_, valid := checker.Check(request)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
expected := "param1/param2 = 1 + 2 & 3 + 4"
|
||||
|
||||
errs, ok := v2.CheckStruct(request)
|
||||
if !ok {
|
||||
t.Fatalf("got unexpected errors %v", errs)
|
||||
}
|
||||
|
||||
if request.Query != "param1/param2 = 1 + 2 & 3 + 4" {
|
||||
t.Fail()
|
||||
if request.Query != expected {
|
||||
t.Fatalf("actual %s expected %s", request.Query, expected)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
output: "{{.Dir}}/DOC.md"
|
||||
repository:
|
||||
url: https://github.com/cinar/checker
|
||||
244
v2/README.md
244
v2/README.md
|
|
@ -1,244 +0,0 @@
|
|||
[](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.
|
||||
|
||||
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.
|
||||
|
||||
## Usage
|
||||
|
||||
To begin using the Checker library, install it with the following command:
|
||||
|
||||
```bash
|
||||
go get github.com/cinar/checker/v2
|
||||
```
|
||||
|
||||
Then, import the library into your source file as shown below:
|
||||
|
||||
```golang
|
||||
import (
|
||||
checker "github.com/cinar/checker/v2"
|
||||
)
|
||||
```
|
||||
|
||||
### 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:
|
||||
|
||||
```golang
|
||||
type Person struct {
|
||||
Name string `checkers:"trim required"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: " Onur Cinar ",
|
||||
}
|
||||
|
||||
errors, valid := checker.CheckStruct(person)
|
||||
if !valid {
|
||||
// Handle validation errors
|
||||
}
|
||||
```
|
||||
|
||||
### 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
|
||||
}
|
||||
```
|
||||
|
||||
### Validating Individual User Input
|
||||
|
||||
For simpler validation, you can call individual checker functions. Here is an example:
|
||||
|
||||
```golang
|
||||
name := "Onur Cinar"
|
||||
|
||||
err := checker.IsRequired(name)
|
||||
if err != nil {
|
||||
// Handle validation error
|
||||
}
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
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:
|
||||
|
||||
```golang
|
||||
type Person struct {
|
||||
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.
|
||||
- [`hex`](DOC.md#IsHex): Ensures the string contains only hex 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.
|
||||
- [`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.
|
||||
- [`url`](DOC.md#IsURL): Ensures the string 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.
|
||||
|
||||
# Custom Checkers and Normalizers
|
||||
|
||||
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:
|
||||
|
||||
```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")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```golang
|
||||
type Item struct {
|
||||
Name string `checkers:"is-fruit"`
|
||||
}
|
||||
|
||||
item := &Item{
|
||||
Name: "banana",
|
||||
}
|
||||
|
||||
errors, valid := v2.CheckStruct(item)
|
||||
if !valid {
|
||||
fmt.Println(errors)
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```golang
|
||||
type Person struct {
|
||||
Name string `checkers:"required"`
|
||||
Emails []string `checkers:"@max-len:2 max-len:64"`
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```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 := 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.]
|
||||
}
|
||||
```
|
||||
|
||||
# Contributing to the Project
|
||||
|
||||
Anyone can contribute to Checkers library. Please make sure to read our [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md) guide first. Follow the [How to Contribute to Checker](./CONTRIBUTING.md) to contribute.
|
||||
|
||||
# License
|
||||
|
||||
This library is free to use, modify, and distribute under the terms of the MIT license. The full license text can be found in the [LICENSE](./LICENSE) file.
|
||||
|
||||
The MIT license is a permissive license that allows you to do almost anything with the library, as long as you retain the copyright notice and the license text. This means that you can use the library in commercial products, modify it, and redistribute it without having to ask for permission from the authors.
|
||||
|
||||
The [LICENSE](./LICENSE) file is located in the root directory of the library. You can open it in a text editor to read the full license text.
|
||||
|
|
@ -1,43 +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"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameAlphanumeric is the name of the alphanumeric check.
|
||||
nameAlphanumeric = "alphanumeric"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotAlphanumeric indicates that the given string contains non-alphanumeric characters.
|
||||
ErrNotAlphanumeric = NewCheckError("NOT_ALPHANUMERIC")
|
||||
)
|
||||
|
||||
// IsAlphanumeric checks if the given string consists of only alphanumeric characters.
|
||||
func IsAlphanumeric(value string) (string, error) {
|
||||
for _, c := range value {
|
||||
if !unicode.IsDigit(c) && !unicode.IsLetter(c) {
|
||||
return value, ErrNotAlphanumeric
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// makeAlphanumeric makes a checker function for the alphanumeric checker.
|
||||
func makeAlphanumeric(_ string) CheckFunc[reflect.Value] {
|
||||
return isAlphanumeric
|
||||
}
|
||||
|
|
@ -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 ExampleIsAlphanumeric() {
|
||||
_, err := v2.IsAlphanumeric("ABcd1234")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAlphanumericInvalid(t *testing.T) {
|
||||
_, err := v2.IsAlphanumeric("-/")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAlphanumericValid(t *testing.T) {
|
||||
_, err := v2.IsAlphanumeric("ABcd1234")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAlphanumericNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Person struct {
|
||||
Name int `checkers:"alphanumeric"`
|
||||
}
|
||||
|
||||
person := &Person{}
|
||||
|
||||
v2.CheckStruct(person)
|
||||
}
|
||||
|
||||
func TestCheckAlphanumericInvalid(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"alphanumeric"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "name-/",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(person)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAlphanumericValid(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `checkers:"alphanumeric"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "ABcd1234",
|
||||
}
|
||||
|
||||
errs, ok := v2.CheckStruct(person)
|
||||
if !ok {
|
||||
t.Fatal(errs)
|
||||
}
|
||||
}
|
||||
43
v2/ascii.go
43
v2/ascii.go
|
|
@ -1,43 +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"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameASCII is the name of the ASCII check.
|
||||
nameASCII = "ascii"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotASCII indicates that the given string contains non-ASCII characters.
|
||||
ErrNotASCII = NewCheckError("NOT_ASCII")
|
||||
)
|
||||
|
||||
// IsASCII checks if the given string consists of only ASCII characters.
|
||||
func IsASCII(value string) (string, error) {
|
||||
for _, c := range value {
|
||||
if c > unicode.MaxASCII {
|
||||
return value, ErrNotASCII
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// makeASCII makes a checker function for the ASCII checker.
|
||||
func makeASCII(_ string) CheckFunc[reflect.Value] {
|
||||
return isASCII
|
||||
}
|
||||
|
|
@ -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 ExampleIsASCII() {
|
||||
_, err := v2.IsASCII("Checker")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsASCIIInvalid(t *testing.T) {
|
||||
_, err := v2.IsASCII("𝄞 Music!")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsASCIIValid(t *testing.T) {
|
||||
_, err := v2.IsASCII("Checker")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckASCIINonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type User struct {
|
||||
Username int `checkers:"ascii"`
|
||||
}
|
||||
|
||||
user := &User{}
|
||||
|
||||
v2.CheckStruct(user)
|
||||
}
|
||||
|
||||
func TestCheckASCIIInvalid(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"ascii"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "𝄞 Music!",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(user)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckASCIIValid(t *testing.T) {
|
||||
type User struct {
|
||||
Username string `checkers:"ascii"`
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Username: "checker",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(user)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
154
v2/checker.go
154
v2/checker.go
|
|
@ -1,154 +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 Checker is a Go library for validating user input through checker rules provided in struct tags.
|
||||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// checkerTag is the name of the field tag used for checker.
|
||||
checkerTag = "checkers"
|
||||
|
||||
// sliceConfigPrefix is the prefix used to distinguish slice-level checks from item-level checks.
|
||||
sliceConfigPrefix = "@"
|
||||
)
|
||||
|
||||
// checkStructJob defines a check strcut job.
|
||||
type checkStructJob struct {
|
||||
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
|
||||
|
||||
for _, check := range checks {
|
||||
value, err = check(value)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return value, err
|
||||
}
|
||||
|
||||
// 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{
|
||||
{
|
||||
Name: "",
|
||||
Value: reflect.Indirect(reflect.ValueOf(st)),
|
||||
},
|
||||
}
|
||||
|
||||
for len(jobs) > 0 {
|
||||
job := jobs[0]
|
||||
jobs = jobs[1:]
|
||||
|
||||
switch job.Value.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < job.Value.NumField(); i++ {
|
||||
field := job.Value.Type().Field(i)
|
||||
|
||||
name := fieldName(job.Name, field)
|
||||
value := reflect.Indirect(job.Value.FieldByIndex(field.Index))
|
||||
|
||||
jobs = append(jobs, &checkStructJob{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Config: field.Tag.Get(checkerTag),
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(sliceFileds, " "), strings.Join(itemFields, " ")
|
||||
}
|
||||
|
|
@ -1,198 +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"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
func ExampleCheck() {
|
||||
name := " Onur Cinar "
|
||||
|
||||
name, err := v2.Check(name, v2.TrimSpace, v2.Required)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(name)
|
||||
// Output: Onur Cinar
|
||||
}
|
||||
|
||||
func ExampleCheckStruct() {
|
||||
type Person struct {
|
||||
Name string `checkers:"trim required"`
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: " Onur Cinar ",
|
||||
}
|
||||
|
||||
errs, ok := v2.CheckStruct(person)
|
||||
if !ok {
|
||||
fmt.Println(errs)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckTrimSpaceRequiredMissing(t *testing.T) {
|
||||
input := " "
|
||||
expected := ""
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
type Address struct {
|
||||
Street string `checkers:"required"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string `checkers:"required"`
|
||||
Address *Address
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "Onur Cinar",
|
||||
Address: &Address{
|
||||
Street: "1234 Main",
|
||||
},
|
||||
}
|
||||
|
||||
errors, ok := v2.CheckStruct(person)
|
||||
if !ok {
|
||||
t.Fatalf("got unexpected errors %v", errors)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckStructRequiredMissing(t *testing.T) {
|
||||
type Address struct {
|
||||
Street string `checkers:"required"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string `checkers:"required"`
|
||||
Address *Address
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "",
|
||||
Address: &Address{
|
||||
Street: "",
|
||||
},
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
42
v2/cidr.go
42
v2/cidr.go
|
|
@ -1,42 +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 (
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameCIDR is the name of the CIDR check.
|
||||
nameCIDR = "cidr"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotCIDR indicates that the given value is not a valid CIDR.
|
||||
ErrNotCIDR = NewCheckError("NOT_CIDR")
|
||||
)
|
||||
|
||||
// IsCIDR checks if the value is a valid CIDR notation IP address and prefix length.
|
||||
func IsCIDR(value string) (string, error) {
|
||||
_, _, err := net.ParseCIDR(value)
|
||||
if err != nil {
|
||||
return value, ErrNotCIDR
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// 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 CIDR checker.
|
||||
func makeCIDR(_ string) CheckFunc[reflect.Value] {
|
||||
return checkCIDR
|
||||
}
|
||||
|
|
@ -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 ExampleIsCIDR() {
|
||||
_, err := v2.IsCIDR("2001:db8::/32")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCIDRInvalid(t *testing.T) {
|
||||
_, err := v2.IsCIDR("900.800.200.100//24")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCIDRValid(t *testing.T) {
|
||||
_, err := v2.IsCIDR("2001:db8::/32")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCIDRNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic")
|
||||
|
||||
type Network struct {
|
||||
Subnet int `checkers:"cidr"`
|
||||
}
|
||||
|
||||
network := &Network{}
|
||||
|
||||
v2.CheckStruct(network)
|
||||
}
|
||||
|
||||
func TestCheckCIDRInvalid(t *testing.T) {
|
||||
type Network struct {
|
||||
Subnet string `checkers:"cidr"`
|
||||
}
|
||||
|
||||
network := &Network{
|
||||
Subnet: "900.800.200.100//24",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if ok {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCIDRValid(t *testing.T) {
|
||||
type Network struct {
|
||||
Subnet string `checkers:"cidr"`
|
||||
}
|
||||
|
||||
network := &Network{
|
||||
Subnet: "192.0.2.0/24",
|
||||
}
|
||||
|
||||
_, ok := v2.CheckStruct(network)
|
||||
if !ok {
|
||||
t.Fatal("expected valid")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,156 +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"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// nameCreditCard is the name of the credit card check.
|
||||
nameCreditCard = "credit-card"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotCreditCard indicates that the given value is not a valid credit card number.
|
||||
ErrNotCreditCard = NewCheckError("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)
|
||||
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
|
||||
// anyCreditCardPattern is the regexp for any credit card.
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
// IsAnyCreditCard checks if the given value is a valid credit card number.
|
||||
func IsAnyCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, anyCreditCardPattern)
|
||||
}
|
||||
|
||||
// IsAmexCreditCard checks if the given valie is a valid AMEX credit card.
|
||||
func IsAmexCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, amexPattern)
|
||||
}
|
||||
|
||||
// IsDinersCreditCard checks if the given valie is a valid Diners credit card.
|
||||
func IsDinersCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, dinersPattern)
|
||||
}
|
||||
|
||||
// IsDiscoverCreditCard checks if the given valie is a valid Discover credit card.
|
||||
func IsDiscoverCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, discoverPattern)
|
||||
}
|
||||
|
||||
// IsJcbCreditCard checks if the given valie is a valid JCB 15 credit card.
|
||||
func IsJcbCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, jcbPattern)
|
||||
}
|
||||
|
||||
// IsMasterCardCreditCard checks if the given valie is a valid MasterCard credit card.
|
||||
func IsMasterCardCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, masterCardPattern)
|
||||
}
|
||||
|
||||
// IsUnionPayCreditCard checks if the given valie is a valid UnionPay credit card.
|
||||
func IsUnionPayCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, unionPayPattern)
|
||||
}
|
||||
|
||||
// IsVisaCreditCard checks if the given valie is a valid Visa credit card.
|
||||
func IsVisaCreditCard(number string) (string, error) {
|
||||
return isCreditCard(number, visaPattern)
|
||||
}
|
||||
|
||||
// makeCreditCard makes a checker function for the credit card checker.
|
||||
func makeCreditCard(config string) CheckFunc[reflect.Value] {
|
||||
patterns := []*regexp.Regexp{}
|
||||
|
||||
if config != "" {
|
||||
for _, card := range strings.Split(config, ",") {
|
||||
pattern, ok := creditCardPatterns[card]
|
||||
if !ok {
|
||||
panic("unknown credit card name")
|
||||
}
|
||||
|
||||
patterns = append(patterns, pattern)
|
||||
}
|
||||
} else {
|
||||
patterns = append(patterns, anyCreditCardPattern)
|
||||
}
|
||||
|
||||
return func(value reflect.Value) (reflect.Value, error) {
|
||||
if value.Kind() != reflect.String {
|
||||
panic("string expected")
|
||||
}
|
||||
|
||||
number := value.String()
|
||||
|
||||
for _, pattern := range patterns {
|
||||
_, err := isCreditCard(number, pattern)
|
||||
if err == nil {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
return value, ErrNotCreditCard
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if !pattern.MatchString(number) {
|
||||
return number, ErrNotCreditCard
|
||||
}
|
||||
|
||||
return IsLUHN(number)
|
||||
}
|
||||
|
|
@ -1,312 +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 (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
v2 "github.com/cinar/checker/v2"
|
||||
)
|
||||
|
||||
// Test numbers from https://stripe.com/docs/testing
|
||||
var invalidCard = "1234123412341234"
|
||||
var amexCard = "378282246310005"
|
||||
var dinersCard = "36227206271667"
|
||||
var discoverCard = "6011111111111117"
|
||||
var jcbCard = "3530111333300000"
|
||||
var masterCard = "5555555555554444"
|
||||
var unionPayCard = "6200000000000005"
|
||||
var visaCard = "4111111111111111"
|
||||
|
||||
// changeToInvalidLuhn increments the luhn digit to make the number invalid. It assumes that the given number is valid.
|
||||
func changeToInvalidLuhn(number string) string {
|
||||
luhn, err := strconv.Atoi(number[len(number)-1:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
luhn = (luhn + 1) % 10
|
||||
|
||||
return number[:len(number)-1] + strconv.Itoa(luhn)
|
||||
}
|
||||
|
||||
func ExampleIsAnyCreditCard() {
|
||||
_, err := v2.IsAnyCreditCard("6011111111111117")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAnyCreditCardValid(t *testing.T) {
|
||||
_, err := v2.IsAnyCreditCard(amexCard)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAnyCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsAnyCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAnyCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsAnyCreditCard(changeToInvalidLuhn(amexCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsAmexCreditCard() {
|
||||
_, err := v2.IsAmexCreditCard("378282246310005")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAmexCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsAmexCreditCard(amexCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAmexCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsAmexCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsAmexCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsAmexCreditCard(changeToInvalidLuhn(amexCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsDinersCreditCard() {
|
||||
_, err := v2.IsDinersCreditCard("36227206271667")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
func TestIsDinersCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsDinersCreditCard(dinersCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDinersCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsDinersCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDinersCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsDinersCreditCard(changeToInvalidLuhn(dinersCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsDiscoverCreditCard() {
|
||||
_, err := v2.IsDiscoverCreditCard("6011111111111117")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
func TestIsDiscoverCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsDiscoverCreditCard(discoverCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDiscoverCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsDiscoverCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDiscoverCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsDiscoverCreditCard(changeToInvalidLuhn(discoverCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsJcbCreditCard() {
|
||||
_, err := v2.IsJcbCreditCard("3530111333300000")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJcbCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsJcbCreditCard(jcbCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJcbCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsJcbCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJcbCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsJcbCreditCard(changeToInvalidLuhn(jcbCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsMasterCardCreditCard() {
|
||||
_, err := v2.IsMasterCardCreditCard("5555555555554444")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMasterCardCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsMasterCardCreditCard(masterCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMasterCardCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsMasterCardCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMasterCardCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsMasterCardCreditCard(changeToInvalidLuhn(masterCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsUnionPayCreditCard() {
|
||||
_, err := v2.IsUnionPayCreditCard("6200000000000005")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUnionPayCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsUnionPayCreditCard(unionPayCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUnionPayCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsUnionPayCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUnionPayCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsUnionPayCreditCard(changeToInvalidLuhn(unionPayCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleIsVisaCreditCard() {
|
||||
_, err := v2.IsVisaCreditCard("4111111111111111")
|
||||
|
||||
if err != nil {
|
||||
// Send the errors back to the user
|
||||
}
|
||||
}
|
||||
func TestIsVisaCreditCardValid(t *testing.T) {
|
||||
if _, err := v2.IsVisaCreditCard(visaCard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsVisaCreditCardInvalidPattern(t *testing.T) {
|
||||
if _, err := v2.IsVisaCreditCard(invalidCard); err == nil {
|
||||
t.Error("expected error for invalid card pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsVisaCreditCardInvalidLuhn(t *testing.T) {
|
||||
if _, err := v2.IsVisaCreditCard(changeToInvalidLuhn(visaCard)); err == nil {
|
||||
t.Error("expected error for invalid Luhn")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCreditCardNonString(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic for non-string credit card")
|
||||
|
||||
type Order struct {
|
||||
CreditCard int `checkers:"credit-card"`
|
||||
}
|
||||
|
||||
order := &Order{}
|
||||
|
||||
v2.CheckStruct(order)
|
||||
}
|
||||
|
||||
func TestCheckCreditCardValid(t *testing.T) {
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: amexCard,
|
||||
}
|
||||
|
||||
_, valid := v2.CheckStruct(order)
|
||||
if !valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCreditCardInvalid(t *testing.T) {
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: invalidCard,
|
||||
}
|
||||
|
||||
_, valid := v2.CheckStruct(order)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCreditCardMultipleUnknown(t *testing.T) {
|
||||
defer FailIfNoPanic(t, "expected panic for unknown credit card")
|
||||
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card:amex,unknown"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: amexCard,
|
||||
}
|
||||
|
||||
v2.CheckStruct(order)
|
||||
}
|
||||
|
||||
func TestCheckCreditCardMultipleInvalid(t *testing.T) {
|
||||
type Order struct {
|
||||
CreditCard string `checkers:"credit-card:amex,visa"`
|
||||
}
|
||||
|
||||
order := &Order{
|
||||
CreditCard: discoverCard,
|
||||
}
|
||||
|
||||
_, valid := v2.CheckStruct(order)
|
||||
if valid {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
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