-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
276 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
# cli-validation | ||
Library to help with flag validation of urfave/cli | ||
Library to help with flag validation of urfave/cli(v3 only) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
module github.com/dearchap/cli-validation | ||
|
||
go 1.19 | ||
|
||
require github.com/stretchr/testify v1.8.4 | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package validation | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
type Signed interface { | ||
~int | ~int8 | ~int16 | ~int32 | ~int64 | ||
} | ||
|
||
type Unsigned interface { | ||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ||
} | ||
|
||
type Integer interface { | ||
Signed | Unsigned | ||
} | ||
|
||
type Float interface { | ||
~float32 | ~float64 | ||
} | ||
|
||
type Ordered interface { | ||
Integer | Float | ||
} | ||
|
||
// ConditionOrError ir a helper function to make writing | ||
// validation functions much easier | ||
func ConditionOrError(cond bool, err error) error { | ||
if cond { | ||
return nil | ||
} | ||
return err | ||
} | ||
|
||
// ValidationChainAll allows one to chain a sequence of validation | ||
// functions to construct a single validation function. All the | ||
// individual validations must pass for the validation to succeed | ||
func ValidationChainAll[T any](fns ...func(T) error) func(T) error { | ||
return func(v T) error { | ||
for _, fn := range fns { | ||
if err := fn(v); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
// ValidationChainAny allows one to chain a sequence of validation | ||
// functions to construct a single validation function. Atleast one | ||
// of the individual validations must pass for the validation to succeed | ||
func ValidationChainAny[T any](fns ...func(T) error) func(T) error { | ||
return func(v T) error { | ||
var errs []error | ||
for _, fn := range fns { | ||
if err := fn(v); err == nil { | ||
return nil | ||
} else { | ||
errs = append(errs, err) | ||
} | ||
} | ||
return fmt.Errorf("%+v", errs) | ||
} | ||
} | ||
|
||
// Min means that the value to be checked needs to be atleast(and including) | ||
// the checked value | ||
func Min[T Ordered](c T) func(T) error { | ||
return func(v T) error { | ||
return ConditionOrError(v >= c, fmt.Errorf("%v is not less than %v", v, c)) | ||
} | ||
} | ||
|
||
// Max means that the value to be checked needs to be atmost(and including) | ||
// the checked value | ||
func Max[T Ordered](c T) func(T) error { | ||
return func(v T) error { | ||
return ConditionOrError(v <= c, fmt.Errorf("%v is not greater than %v", v, c)) | ||
} | ||
} | ||
|
||
// Max means that the value to be checked needs to be atmost(and including) | ||
// the checked value | ||
func RangeInclusive[T Ordered](a, b T) func(T) error { | ||
return ValidationChainAll[T](Min[T](a), Max[T](b)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
package validation | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
type testcase[T any] struct { | ||
name string | ||
f func(T) error | ||
input T | ||
errExpected bool | ||
} | ||
|
||
func test_with_input[T any](t *testing.T, tcases []testcase[T]) { | ||
r := require.New(t) | ||
for _, tc := range tcases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
err := tc.f(tc.input) | ||
if tc.errExpected { | ||
r.Error(err) | ||
} else { | ||
r.NoError(err) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestMin(t *testing.T) { | ||
|
||
inputs := []testcase[int]{ | ||
{ | ||
name: "min lower limit", | ||
f: Min[int](10), | ||
input: 10, | ||
}, | ||
{ | ||
name: "min normal", | ||
f: Min[int](10), | ||
input: 11, | ||
}, | ||
{ | ||
name: "min failure", | ||
f: Min[int](10), | ||
input: 8, | ||
errExpected: true, | ||
}, | ||
} | ||
|
||
test_with_input[int](t, inputs) | ||
} | ||
|
||
func TestMax(t *testing.T) { | ||
|
||
inputs := []testcase[float64]{ | ||
{ | ||
name: "max upper limit", | ||
f: Max[float64](10.0), | ||
input: 10.0, | ||
}, | ||
{ | ||
name: "max normal", | ||
f: Max[float64](10.0), | ||
input: 9.0, | ||
}, | ||
{ | ||
name: "max failure", | ||
f: Max[float64](10.0), | ||
input: 10.00001, | ||
errExpected: true, | ||
}, | ||
} | ||
|
||
test_with_input[float64](t, inputs) | ||
} | ||
|
||
func TestRange(t *testing.T) { | ||
|
||
inputs := []testcase[uint64]{ | ||
{ | ||
name: "lower limit", | ||
f: RangeInclusive[uint64](10, 16), | ||
input: 10, | ||
}, | ||
{ | ||
name: "upper limit", | ||
f: RangeInclusive[uint64](10, 16), | ||
input: 16, | ||
}, | ||
{ | ||
name: "lower limit failure", | ||
f: RangeInclusive[uint64](10, 16), | ||
input: 9, | ||
errExpected: true, | ||
}, | ||
{ | ||
name: "upper limit failure", | ||
f: RangeInclusive[uint64](10, 16), | ||
input: 17, | ||
errExpected: true, | ||
}, | ||
{ | ||
name: "normal", | ||
f: RangeInclusive[uint64](10, 16), | ||
input: 12, | ||
}, | ||
} | ||
|
||
test_with_input[uint64](t, inputs) | ||
} | ||
|
||
func TestDisjointRange(t *testing.T) { | ||
|
||
inputs := []testcase[int16]{ | ||
{ | ||
name: "lower limit lower range", | ||
f: ValidationChainAny[int16](RangeInclusive[int16](10, 16), RangeInclusive[int16](56, 67)), | ||
input: 10, | ||
}, | ||
{ | ||
name: "upper limit lower range", | ||
f: ValidationChainAny[int16](RangeInclusive[int16](10, 16), RangeInclusive[int16](56, 67)), | ||
input: 16, | ||
}, | ||
{ | ||
name: "lower limit upper range", | ||
f: ValidationChainAny[int16](RangeInclusive[int16](10, 16), RangeInclusive[int16](56, 67)), | ||
input: 56, | ||
}, | ||
{ | ||
name: "upper limit upper range", | ||
f: ValidationChainAny[int16](RangeInclusive[int16](10, 16), RangeInclusive[int16](56, 67)), | ||
input: 67, | ||
}, | ||
{ | ||
name: "normal lower range", | ||
f: ValidationChainAny[int16](RangeInclusive[int16](10, 16), RangeInclusive[int16](56, 67)), | ||
input: 13, | ||
}, | ||
{ | ||
name: "normal upper range", | ||
f: ValidationChainAny[int16](RangeInclusive[int16](10, 16), RangeInclusive[int16](56, 67)), | ||
input: 60, | ||
}, | ||
{ | ||
name: "failure below lower range", | ||
f: ValidationChainAny[int16](RangeInclusive[int16](10, 16), RangeInclusive[int16](56, 67)), | ||
input: 1, | ||
errExpected: true, | ||
}, | ||
{ | ||
name: "failure in between lower and upper range", | ||
f: ValidationChainAny[int16](RangeInclusive[int16](10, 16), RangeInclusive[int16](56, 67)), | ||
input: 20, | ||
errExpected: true, | ||
}, | ||
{ | ||
name: "failure above upper range", | ||
f: ValidationChainAny[int16](RangeInclusive[int16](10, 16), RangeInclusive[int16](56, 67)), | ||
input: 70, | ||
errExpected: true, | ||
}, | ||
} | ||
|
||
test_with_input[int16](t, inputs) | ||
} |