Skip to content

Commit

Permalink
feat(parseutil) Add Safe variants of ParseInt*
Browse files Browse the repository at this point in the history
These proposed variants allow parsing smaller data types (such as ints)
from larger data types (the int64 returned by ParseInt{,Slice}(...)),
validating that they are within the requested range prior to casting.
With the SafeParseIntRange(...) helper, we also allow validation of the
maximum expected number of elements in the slice.

Signed-off-by: Alexander Scheel <[email protected]>
  • Loading branch information
cipherboy committed Mar 22, 2022
1 parent 895daab commit f33f5fe
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 0 deletions.
52 changes: 52 additions & 0 deletions parseutil/parseutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -401,3 +402,54 @@ func ParseAddrs(addrs interface{}) ([]*sockaddr.SockAddrMarshaler, error) {

return out, nil
}

func SafeParseIntRange(in interface{}, min int64, max int64) (int64, error) {
raw, err := ParseInt(in)
if err != nil {
return 0, err
}

if raw < min || raw > max {
return 0, fmt.Errorf("error parsing int value; out of range [%v to %v]: %v", min, max, raw)
}

return raw, nil
}

func SafeParseInt(in interface{}) (int, error) {
raw, err := SafeParseIntRange(in, math.MinInt, math.MaxInt)
return int(raw), err
}

func SafeParseIntSliceRange(in interface{}, minValue int64, maxValue int64, elements int) ([]int64, error) {
raw, err := ParseIntSlice(in)
if err != nil {
return nil, err
}

if elements > 0 && len(raw) > elements {
return nil, fmt.Errorf("error parsing value from input: got %v but expected at most %v elements", len(raw), elements)
}

for index, value := range raw {
if value < minValue || value > maxValue {
return nil, fmt.Errorf("error parsing value from input: element %v was outside of range [%v to %v]: %v", index, minValue, maxValue, value)
}
}

return raw, nil
}

func SafeParseIntSlice(in interface{}, elements int) ([]int, error) {
raw, err := SafeParseIntSliceRange(in, math.MinInt, math.MaxInt, elements)
if err != nil || raw == nil {
return nil, err
}

var result = make([]int, len(raw))
for _, element := range raw {
result = append(result, int(element))
}

return result, nil
}
59 changes: 59 additions & 0 deletions parseutil/parseutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,121 +354,174 @@ func Test_ParseIntSlice(t *testing.T) {
testCases := []struct {
inp interface{}
valid bool
ranged bool
expected []int64
}{
// ParseInt
{
int(-1),
true,
false,
[]int64{-1},
},
{
int32(-1),
true,
false,
[]int64{-1},
},
{
int64(-1),
true,
false,
[]int64{-1},
},
{
uint(1),
true,
true,
[]int64{1},
},
{
uint32(1),
true,
true,
[]int64{1},
},
{
uint64(1),
true,
true,
[]int64{1},
},
{
json.Number("1"),
true,
true,
[]int64{1},
},
{
"1",
true,
true,
[]int64{1},
},
// ParseDirectIntSlice
{
[]int{1, -2, 3},
true,
false,
[]int64{1, -2, 3},
},
{
[]int32{1, -2, 3},
true,
false,
[]int64{1, -2, 3},
},
{
[]int64{1, -2, 3},
true,
false,
[]int64{1, -2, 3},
},
{
[]uint{1, 2, 3},
true,
true,
[]int64{1, 2, 3},
},
{
[]uint32{1, 2, 3},
true,
true,
[]int64{1, 2, 3},
},
{
[]uint64{1, 2, 3},
true,
true,
[]int64{1, 2, 3},
},
{
[]json.Number{json.Number("1"), json.Number("2"), json.Number("3")},
true,
true,
[]int64{1, 2, 3},
},
{
[]string{"1", "2", "3"},
true,
true,
[]int64{1, 2, 3},
},
// Comma separated list
{
"1",
true,
true,
[]int64{1},
},
{
"1,",
true,
true,
[]int64{1},
},
{
",1",
true,
true,
[]int64{1},
},
{
",1,",
true,
true,
[]int64{1},
},
{
"1,2",
true,
true,
[]int64{1, 2},
},
{
"1,2,3",
true,
true,
[]int64{1, 2, 3},
},
{
"1,3,5",
true,
true,
[]int64{1, 3, 5},
},
{
"1,3,5,7",
true,
false,
[]int64{1, 3, 5, 7},
},
{
"1,2,3,4,5,6,7,8,9,0",
true,
false,
[]int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
},
{
"1,1,1,1,1,1,1,1,1,1,1",
true,
false,
[]int64{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
},
{
"1,1,1,1,1,1,1,1,1,1",
true,
true,
[]int64{1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
},
}

for _, tc := range testCases {
Expand All @@ -485,6 +538,12 @@ func Test_ParseIntSlice(t *testing.T) {
}
if !equalInt64Slice(outp, tc.expected) {
t.Errorf("input %v parsed as %v, expected %v", tc.inp, outp, tc.expected)
continue
}
_, err = SafeParseIntSliceRange(tc.inp, 0 /* min */, 5 /* max */, 10 /* num elements */)
if err == nil != tc.ranged {
t.Errorf("no ranged slice error for %v", tc.inp)
continue
}
}
}
Expand Down

0 comments on commit f33f5fe

Please sign in to comment.