Skip to content

Commit

Permalink
add In and NotIn rules for strings
Browse files Browse the repository at this point in the history
  • Loading branch information
slessard authored Apr 20, 2023
1 parent 5f25116 commit 44595f5
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,8 @@ The following rules are provided in the `validation` package:

* `In(...interface{})`: checks if a value can be found in the given list of values.
* `NotIn(...interface{})`: checks if a value is NOT among the given list of values.
* `StringIn(...interface{})`: checks if a value can be found in the given list of values, optionally case sensitive.
* `StringNotIn(...interface{})`: checks if a value is NOT among the given list of values, optionally case sensitive.
* `Length(min, max int)`: checks if the length of a value is within the specified range.
This rule should only be used for validating strings, slices, maps, and arrays.
* `RuneLength(min, max int)`: checks if the length of a string is within the specified range.
Expand Down
70 changes: 70 additions & 0 deletions string_in.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2016 Qiang Xue. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package validation

import (
"strings"
)

// StringIn returns a validation rule that checks if a value can be found in the given list of values.
// == or strings.EqualFold will be used to determine if two values are equal.
// An empty value is considered valid. Use the Required rule to make sure a value is not empty.
func StringIn(isCaseSensitive bool, values ...string) StringInRule {
return StringInRule{
isCaseSensitive: isCaseSensitive,
elements: values,
err: ErrInInvalid,
}
}

// StringInRule is a validation rule that validates if a value can be found in the given list of values.
type StringInRule struct {
isCaseSensitive bool
elements []string
err Error
}

// Validate checks if the given value is valid or not.
func (r StringInRule) Validate(value interface{}) error {
_, isStringPtr := value.(*string)
indirectValue, isNil := Indirect(value)

if isNil && isStringPtr {
return nil
}
valueAsString, err := EnsureString(indirectValue)
if err != nil {
return err
}
if IsEmpty(indirectValue) {
return nil
}

for _, e := range r.elements {
if r.isCaseSensitive {
if e == valueAsString {
return nil
}
} else {
if strings.EqualFold(e, valueAsString) {
return nil
}
}
}

return r.err
}

// Error sets the error message for the rule.
func (r StringInRule) Error(message string) StringInRule {
r.err = r.err.SetMessage(message)
return r
}

// ErrorObject sets the error struct for the rule.
func (r StringInRule) ErrorObject(err Error) StringInRule {
r.err = err
return r
}
72 changes: 72 additions & 0 deletions string_in_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2016 Qiang Xue. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package validation

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestStringIn(t *testing.T) {
var v1 = "A"
var v2 *string
var v3 = "a"
tests := []struct {
tag string
isCaseSensitive bool
values []string
value interface{}
err string
}{
{"t0", true, []string{"A", "B"}, "", ""},
{"t1", true, []string{"A", "B"}, "A", ""},
{"t2", true, []string{"A", "B"}, "B", ""},
{"t3", true, []string{"A", "B"}, "C", "must be a valid value"},
{"t4", true, []string{"A", "B"}, 4, "must be either a string or byte slice"},
{"t5", true, []string{}, "C", "must be a valid value"},
{"t6", true, []string{"A", "B"}, &v1, ""},
{"t7", true, []string{"A", "B"}, v2, ""},
{"t8", true, []string{"A", "B"}, (*int)(nil), "must be either a string or byte slice"},
{"t9", false, []string{"A", "B"}, "", ""},
{"t10", false, []string{"A", "B"}, "A", ""},
{"t11", false, []string{"A", "B"}, "B", ""},
{"t12", false, []string{"A", "B"}, "C", "must be a valid value"},
{"t13", false, []string{"A", "B"}, "a", ""},
{"t14", false, []string{"A", "B"}, "b", ""},
{"t15", false, []string{"A", "B"}, "c", "must be a valid value"},
{"t16", false, []string{"A", "B"}, 4, "must be either a string or byte slice"},
{"t17", false, []string{}, "c", "must be a valid value"},
{"t18", false, []string{"A", "B"}, &v3, ""},
{"t19", false, []string{"A", "B"}, v2, ""},
}

for _, test := range tests {
t.Run(test.tag, func(t *testing.T) {
r := StringIn(test.isCaseSensitive, test.values...)
err := r.Validate(test.value)
assertError(t, test.err, err, test.tag)
})
}
}

func Test_StringInRule_Error(t *testing.T) {
r := StringIn(true, "A", "B", "C")
val := "D"
assert.Equal(t, "must be a valid value", r.Validate(val).Error())
r = r.Error("123")
assert.Equal(t, "123", r.err.Message())
}

func TestStringInRule_ErrorObject(t *testing.T) {
r := StringIn(true, "A", "B", "C")

err := NewError("code", "abc")
r = r.ErrorObject(err)

assert.Equal(t, err, r.err)
assert.Equal(t, err.Code(), r.err.Code())
assert.Equal(t, err.Message(), r.err.Message())
}
67 changes: 67 additions & 0 deletions string_not_in.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2018 Qiang Xue, Google LLC. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package validation

import "strings"

// StringNotIn returns a validation rule that checks if a value is absent from the given list of values.
// An empty value is considered valid. Use the Required rule to make sure a value is not empty.
func StringNotIn(isCaseSensitive bool, values ...string) StringNotInRule {
return StringNotInRule{
isCaseSensitive: isCaseSensitive,
elements: values,
err: ErrNotInInvalid,
}
}

// StringNotInRule is a validation rule that checks if a value is absent from the given list of values.
type StringNotInRule struct {
isCaseSensitive bool
elements []string
err Error
}

// Validate checks if the given value is valid or not.
func (r StringNotInRule) Validate(value interface{}) error {
_, isStringPtr := value.(*string)
indirectValue, isNil := Indirect(value)

if isNil && isStringPtr {
return nil
}
valueAsString, err := EnsureString(indirectValue)
if err != nil {
return err
}
if IsEmpty(indirectValue) {
return nil
}

for _, e := range r.elements {
if r.isCaseSensitive {
if e == valueAsString {
return r.err
}
} else {
if strings.EqualFold(e, valueAsString) {
return r.err
}
}
}

return nil
}

// Error sets the error message for the rule.
func (r StringNotInRule) Error(message string) StringNotInRule {
r.err = r.err.SetMessage(message)
return r
}

// ErrorObject sets the error struct for the rule.
func (r StringNotInRule) ErrorObject(err Error) StringNotInRule {
r.err = err
return r
}
71 changes: 71 additions & 0 deletions string_not_in_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2016 Qiang Xue, Google LLC. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package validation

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestStringNotIn(t *testing.T) {
var v1 = "A"
var v2 *string
var v3 = "a"
var tests = []struct {
tag string
isCaseSensitive bool
values []string
value interface{}
err string
}{
{"t0", true, []string{"A", "B"}, "", ""},
{"t1", true, []string{"A", "B"}, "A", "must not be in list"},
{"t2", true, []string{"A", "B"}, "B", "must not be in list"},
{"t3", true, []string{"A", "B"}, "C", ""},
{"t4", true, []string{"A", "B"}, 4, "must be either a string or byte slice"},
{"t5", true, []string{}, "C", ""},
{"t6", true, []string{"A", "B"}, &v1, "must not be in list"},
{"t7", true, []string{"A", "B"}, v2, ""},
{"t8", true, []string{"A", "B"}, (*int)(nil), "must be either a string or byte slice"},
{"t9", false, []string{"A", "B"}, "", ""},
{"t10", false, []string{"A", "B"}, "A", "must not be in list"},
{"t11", false, []string{"A", "B"}, "B", "must not be in list"},
{"t12", false, []string{"A", "B"}, "C", ""},
{"t13", false, []string{"A", "B"}, "a", "must not be in list"},
{"t14", false, []string{"A", "B"}, "b", "must not be in list"},
{"t15", false, []string{"A", "B"}, "c", ""},
{"t16", false, []string{"A", "B"}, 4, "must be either a string or byte slice"},
{"t17", false, []string{}, "c", ""},
{"t18", false, []string{"A", "B"}, &v3, "must not be in list"},
{"t19", false, []string{"A", "B"}, v2, ""},
}

for _, test := range tests {
t.Run(test.tag, func(t *testing.T) {
r := StringNotIn(test.isCaseSensitive, test.values...)
err := r.Validate(test.value)
assertError(t, test.err, err, test.tag)
})
}
}

func Test_StringNotInRule_Error(t *testing.T) {
r := StringNotIn(true, "A", "B", "C")
assert.Equal(t, "must not be in list", r.Validate("A").Error())
r = r.Error("123")
assert.Equal(t, "123", r.err.Message())
}

func TestStringNotInRule_ErrorObject(t *testing.T) {
r := StringNotIn(true, "A", "B", "C")

err := NewError("code", "abc")
r = r.ErrorObject(err)

assert.Equal(t, err, r.err)
assert.Equal(t, err.Code(), r.err.Code())
assert.Equal(t, err.Message(), r.err.Message())
}

0 comments on commit 44595f5

Please sign in to comment.