Skip to content

Commit

Permalink
Add initial functions
Browse files Browse the repository at this point in the history
  • Loading branch information
dearchap committed Jun 27, 2023
1 parent 884dff1 commit 345560e
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
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)
11 changes: 11 additions & 0 deletions go.mod
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
)
10 changes: 10 additions & 0 deletions go.sum
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=
87 changes: 87 additions & 0 deletions validation.go
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))
}
167 changes: 167 additions & 0 deletions validation_test.go
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)
}

0 comments on commit 345560e

Please sign in to comment.