Skip to content

Commit

Permalink
Adding min/max constraints to number type. #42
Browse files Browse the repository at this point in the history
  • Loading branch information
danielfireman committed Sep 27, 2017
1 parent d51781f commit 05bb587
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 15 deletions.
2 changes: 1 addition & 1 deletion schema/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (f *Field) Decode(value string) (interface{}, error) {
case BooleanType:
return castBoolean(value, f.TrueValues, f.FalseValues)
case NumberType:
return castNumber(f.DecimalChar, f.GroupChar, f.BareNumber, value)
return castNumber(f.DecimalChar, f.GroupChar, f.BareNumber, value, f.Constraints)
case DateType:
return castDate(f.Format, value)
case ObjectType:
Expand Down
2 changes: 1 addition & 1 deletion schema/infer.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func findType(value string, checkOrder []string) string {
return IntegerType
}
case NumberType:
if _, err := castNumber(defaultDecimalChar, defaultGroupChar, defaultBareNumber, value); err == nil {
if _, err := castNumber(defaultDecimalChar, defaultGroupChar, defaultBareNumber, value, Constraints{}); err == nil {
return NumberType
}
case DateType:
Expand Down
26 changes: 24 additions & 2 deletions schema/number.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strings"
)

func castNumber(decimalChar, groupChar string, bareNumber bool, value string) (float64, error) {
func castNumber(decimalChar, groupChar string, bareNumber bool, value string, c Constraints) (float64, error) {
dc := decimalChar
if groupChar != "" {
dc = decimalChar
Expand All @@ -25,7 +25,29 @@ func castNumber(decimalChar, groupChar string, bareNumber bool, value string) (f
return 0, err
}
}
return strconv.ParseFloat(v, 64)
returned, err := strconv.ParseFloat(v, 64)
if err != nil {
return 0, err
}
if c.Maximum != "" {
max, err := strconv.ParseFloat(c.Maximum, 64)
if err != nil {
return 0, fmt.Errorf("invalid maximum number: %v", c.Maximum)
}
if returned > max {
return 0, fmt.Errorf("constraint check error: integer:%f > maximum:%f", returned, max)
}
}
if c.Minimum != "" {
min, err := strconv.ParseFloat(c.Minimum, 64)
if err != nil {
return 0, fmt.Errorf("invalid minimum integer: %v", c.Minimum)
}
if returned < min {
return 0, fmt.Errorf("constraint check error: integer:%f > minimum:%f", returned, min)
}
}
return returned, nil
}

var bareNumberRegexp = regexp.MustCompile(`((^[0-9]+\.?[0-9]*)|([0-9]+\.?[0-9]*$))`)
Expand Down
37 changes: 26 additions & 11 deletions schema/number_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestCastNumber(t *testing.T) {
}
for _, d := range data {
t.Run(d.desc, func(t *testing.T) {
got, err := castNumber(d.dc, d.gc, d.bn, d.number)
got, err := castNumber(d.dc, d.gc, d.bn, d.number, Constraints{})
if err != nil {
t.Fatalf("err want:nil got:%q", err)
}
Expand All @@ -42,7 +42,7 @@ func TestCastNumber(t *testing.T) {
}
})
t.Run("NaN", func(t *testing.T) {
got, err := castNumber(defaultDecimalChar, defaultGroupChar, defaultBareNumber, "NaN")
got, err := castNumber(defaultDecimalChar, defaultGroupChar, defaultBareNumber, "NaN", Constraints{})
if err != nil {
t.Fatalf("err want:nil got:%q", err)
}
Expand All @@ -51,7 +51,7 @@ func TestCastNumber(t *testing.T) {
}
})
t.Run("INF", func(t *testing.T) {
got, err := castNumber(defaultDecimalChar, defaultGroupChar, defaultBareNumber, "INF")
got, err := castNumber(defaultDecimalChar, defaultGroupChar, defaultBareNumber, "INF", Constraints{})
if err != nil {
t.Fatalf("err want:nil got:%q", err)
}
Expand All @@ -60,27 +60,42 @@ func TestCastNumber(t *testing.T) {
}
})
t.Run("NegativeINF", func(t *testing.T) {
got, err := castNumber(defaultDecimalChar, defaultGroupChar, defaultBareNumber, "-INF")
got, err := castNumber(defaultDecimalChar, defaultGroupChar, defaultBareNumber, "-INF", Constraints{})
if err != nil {
t.Fatalf("err want:nil got:%q", err)
}
if !math.IsInf(got, -1) {
t.Fatalf("val want:-Inf got:%f", got)
}
})
t.Run("ValidMaximum", func(t *testing.T) {
if _, err := castNumber(defaultDecimalChar, defaultGroupChar, defaultBareNumber, "2", Constraints{Maximum: "2"}); err != nil {
t.Fatalf("err want:nil got:%q", err)
}
})
t.Run("ValidMinimum", func(t *testing.T) {
if _, err := castNumber(defaultDecimalChar, defaultGroupChar, defaultBareNumber, "2", Constraints{Minimum: "2"}); err != nil {
t.Fatalf("err want:nil got:%q", err)
}
})
t.Run("Error", func(t *testing.T) {
data := []struct {
desc string
number string
dc string
gc string
bn bool
desc string
number string
dc string
gc string
bn bool
constraints Constraints
}{
{"InvalidNumberToStrip_TooManyNumbers", "+10.10++10", defaultDecimalChar, defaultGroupChar, notBareNumber},
{"InvalidNumberToStrip_TooManyNumbers", "+10.10++10", defaultDecimalChar, defaultGroupChar, notBareNumber, Constraints{}},
{"NumBiggerThanMaximum", "3", defaultDecimalChar, defaultGroupChar, notBareNumber, Constraints{Maximum: "2"}},
{"InvalidMaximum", "1", defaultDecimalChar, defaultGroupChar, notBareNumber, Constraints{Maximum: "boo"}},
{"NumSmallerThanMinimum", "1", defaultDecimalChar, defaultGroupChar, notBareNumber, Constraints{Minimum: "2"}},
{"InvalidMinimum", "1", defaultDecimalChar, defaultGroupChar, notBareNumber, Constraints{Minimum: "boo"}},
}
for _, d := range data {
t.Run(d.desc, func(t *testing.T) {
if _, err := castNumber(defaultDecimalChar, defaultGroupChar, defaultBareNumber, d.number); err == nil {
if _, err := castNumber(defaultDecimalChar, defaultGroupChar, defaultBareNumber, d.number, d.constraints); err == nil {
t.Fatalf("err want:err got:nil")
}
})
Expand Down

0 comments on commit 05bb587

Please sign in to comment.