Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial functions #1

Merged
merged 7 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(current for v3 only). This ensures that urfave/cli codebase remains compact and clean of optional features
abitrolly marked this conversation as resolved.
Show resolved Hide resolved
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/urfave/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 testWithInput[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) {

dearchap marked this conversation as resolved.
Show resolved Hide resolved
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,
},
}

testWithInput[int](t, inputs)
}

func TestMax(t *testing.T) {

dearchap marked this conversation as resolved.
Show resolved Hide resolved
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,
},
}

testWithInput[float64](t, inputs)
}

func TestRange(t *testing.T) {

dearchap marked this conversation as resolved.
Show resolved Hide resolved
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,
},
}

testWithInput[uint64](t, inputs)
}

func TestDisjointRange(t *testing.T) {

dearchap marked this conversation as resolved.
Show resolved Hide resolved
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,
},
}

testWithInput[int16](t, inputs)
}