Skip to content

Commit 9cba433

Browse files
authored
Merge pull request #1728 from shuvamk/fix/string-numeric-parse
fix(validate): parse numeric string constraints with strconv.ParseFloat
2 parents 183b5b8 + f3208e0 commit 9cba433

2 files changed

Lines changed: 55 additions & 4 deletions

File tree

validate/string.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package validate
22

33
import (
4-
"fmt"
4+
"math"
5+
"strconv"
56
"strings"
67
"unicode"
78

@@ -166,11 +167,16 @@ func (t String) Validate(v string) error {
166167
}
167168

168169
func (t String) validateNumeric(v string) error {
169-
// Parse string as float64
170-
var val float64
171-
if _, err := fmt.Sscanf(v, "%f", &val); err != nil {
170+
val, err := strconv.ParseFloat(v, 64)
171+
if err != nil {
172172
return errors.Wrap(err, "parse as number")
173173
}
174+
if math.IsNaN(val) {
175+
return errors.Errorf("value %f is not a number", val)
176+
}
177+
if math.IsInf(val, 0) {
178+
return errors.Errorf("value %f is infinite", val)
179+
}
174180

175181
if t.MinNumericSet && val < t.MinNumeric {
176182
return errors.Errorf("value %f less than minimum %f", val, t.MinNumeric)

validate/string_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,48 @@ func TestString_Validate(t *testing.T) {
112112
require.Error(t, v.Validate(s), "%q should be invalid", s)
113113
}
114114
}
115+
116+
func TestString_ValidateNumeric(t *testing.T) {
117+
minMax := String{MinNumeric: 1, MinNumericSet: true, MaxNumeric: 10, MaxNumericSet: true}
118+
minOnly := String{MinNumeric: 1, MinNumericSet: true}
119+
maxOnly := String{MaxNumeric: 10, MaxNumericSet: true}
120+
121+
require.True(t, minOnly.Set())
122+
require.True(t, maxOnly.Set())
123+
124+
for _, tc := range []struct {
125+
Name string
126+
Validator String
127+
Value string
128+
Valid bool
129+
}{
130+
{Name: "Min", Validator: minMax, Value: "1", Valid: true},
131+
{Name: "Max", Validator: minMax, Value: "10", Valid: true},
132+
{Name: "Fraction", Validator: minMax, Value: "5.5", Valid: true},
133+
{Name: "Sign", Validator: minMax, Value: "+5", Valid: true},
134+
{Name: "LeadingZeroes", Validator: minMax, Value: "007", Valid: true},
135+
{Name: "Exponent", Validator: minMax, Value: "1e1", Valid: true},
136+
{Name: "BelowMin", Validator: minMax, Value: "0", Valid: false},
137+
{Name: "BelowMinFraction", Validator: minMax, Value: "0.5", Valid: false},
138+
{Name: "AboveMax", Validator: minMax, Value: "11", Valid: false},
139+
{Name: "Empty", Validator: minMax, Value: "", Valid: false},
140+
{Name: "NotANumber", Validator: minMax, Value: "abc", Valid: false},
141+
{Name: "TrailingGarbage", Validator: minMax, Value: "5abc", Valid: false},
142+
{Name: "GroupSeparator", Validator: minMax, Value: "5,000", Valid: false},
143+
{Name: "SecondDot", Validator: minMax, Value: "1.2.3", Valid: false},
144+
{Name: "LeadingSpace", Validator: minMax, Value: " 5", Valid: false},
145+
{Name: "TrailingSpace", Validator: minMax, Value: "5 ", Valid: false},
146+
{Name: "NaN", Validator: minMax, Value: "NaN", Valid: false},
147+
{Name: "NaNLower", Validator: minMax, Value: "nan", Valid: false},
148+
{Name: "PosInfMinOnly", Validator: minOnly, Value: "Inf", Valid: false},
149+
{Name: "PosInfinityMinOnly", Validator: minOnly, Value: "Infinity", Valid: false},
150+
{Name: "NaNMinOnly", Validator: minOnly, Value: "NaN", Valid: false},
151+
{Name: "NegInfMaxOnly", Validator: maxOnly, Value: "-Inf", Valid: false},
152+
{Name: "NaNMaxOnly", Validator: maxOnly, Value: "NaN", Valid: false},
153+
} {
154+
t.Run(tc.Name, func(t *testing.T) {
155+
valid := tc.Validator.Validate(tc.Value) == nil
156+
require.Equal(t, tc.Valid, valid, "%+v: %q", tc.Validator, tc.Value)
157+
})
158+
}
159+
}

0 commit comments

Comments
 (0)