Skip to content

Commit 01abc03

Browse files
authored
Format values generated for min/max validations. (goadesign#1335)
So that large integer values don't get formatted using exponential notation. Fix goadesign#1327
1 parent a4dea70 commit 01abc03

File tree

2 files changed

+70
-8
lines changed

2 files changed

+70
-8
lines changed

goagen/codegen/validation.go

+29-8
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import (
44
"bytes"
55
"errors"
66
"fmt"
7+
"math"
78
"strings"
89
"text/template"
910

1011
"github.com/goadesign/goa/design"
11-
"github.com/goadesign/goa/dslengine"
1212
)
1313

1414
var (
@@ -328,6 +328,9 @@ func (v *Validator) recurseAttribute(att, catt *design.AttributeDefinition, n, t
328328
// error. It initializes that variable in case a validation fails.
329329
// Note: we do not want to recurse here, recursion is done by the marshaler/unmarshaler code.
330330
func ValidationChecker(att *design.AttributeDefinition, nonzero, required, hasDefault bool, target, context string, depth int, private bool) string {
331+
if att.Validation == nil {
332+
return ""
333+
}
331334
t := target
332335
isPointer := private || (!required && !hasDefault && !nonzero)
333336
if isPointer && att.Type.IsPrimitive() {
@@ -346,14 +349,12 @@ func ValidationChecker(att *design.AttributeDefinition, nonzero, required, hasDe
346349
"depth": depth,
347350
"private": private,
348351
}
349-
res := validationsCode(att.Validation, data)
352+
res := validationsCode(att, data)
350353
return strings.Join(res, "\n")
351354
}
352355

353-
func validationsCode(validation *dslengine.ValidationDefinition, data map[string]interface{}) (res []string) {
354-
if validation == nil {
355-
return nil
356-
}
356+
func validationsCode(att *design.AttributeDefinition, data map[string]interface{}) (res []string) {
357+
validation := att.Validation
357358
if values := validation.Values; values != nil {
358359
data["values"] = values
359360
if val := RunTemplate(enumValT, data); val != "" {
@@ -373,15 +374,23 @@ func validationsCode(validation *dslengine.ValidationDefinition, data map[string
373374
}
374375
}
375376
if min := validation.Minimum; min != nil {
376-
data["min"] = *min
377+
if att.Type == design.Integer {
378+
data["min"] = renderInteger(*min)
379+
} else {
380+
data["min"] = fmt.Sprintf("%f", *min)
381+
}
377382
data["isMin"] = true
378383
delete(data, "max")
379384
if val := RunTemplate(minMaxValT, data); val != "" {
380385
res = append(res, val)
381386
}
382387
}
383388
if max := validation.Maximum; max != nil {
384-
data["max"] = *max
389+
if att.Type == design.Integer {
390+
data["max"] = renderInteger(*max)
391+
} else {
392+
data["max"] = fmt.Sprintf("%f", *max)
393+
}
385394
data["isMin"] = false
386395
delete(data, "min")
387396
if val := RunTemplate(minMaxValT, data); val != "" {
@@ -418,6 +427,18 @@ func validationsCode(validation *dslengine.ValidationDefinition, data map[string
418427
return
419428
}
420429

430+
// renderInteger renders a max or min value properly, taking into account
431+
// overflows due to casting from a float value.
432+
func renderInteger(f float64) string {
433+
if f > math.Nextafter(float64(math.MaxInt64), 0) {
434+
return fmt.Sprintf("%d", int64(math.MaxInt64))
435+
}
436+
if f < math.Nextafter(float64(math.MinInt64), 0) {
437+
return fmt.Sprintf("%d", int64(math.MinInt64))
438+
}
439+
return fmt.Sprintf("%d", int64(f))
440+
}
441+
421442
// oneof produces code that compares target with each element of vals and ORs
422443
// the result, e.g. "target == 1 || target == 2".
423444
func oneof(target string, vals []interface{}) string {

goagen/codegen/validation_test.go

+41
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package codegen_test
22

33
import (
4+
"math"
45
"strings"
56

67
"github.com/goadesign/goa/design"
@@ -72,6 +73,34 @@ var _ = Describe("validation code generation", func() {
7273
})
7374
})
7475

76+
Context("of max value math.MaxInt64", func() {
77+
BeforeEach(func() {
78+
attType = design.Integer
79+
max := float64(math.MaxInt64)
80+
validation = &dslengine.ValidationDefinition{
81+
Maximum: &max,
82+
}
83+
})
84+
85+
It("produces the validation go code", func() {
86+
Ω(code).Should(Equal(maxValCode))
87+
})
88+
})
89+
90+
Context("of min value math.MinInt64", func() {
91+
BeforeEach(func() {
92+
attType = design.Integer
93+
min := float64(math.MinInt64)
94+
validation = &dslengine.ValidationDefinition{
95+
Minimum: &min,
96+
}
97+
})
98+
99+
It("produces the validation go code", func() {
100+
Ω(code).Should(Equal(minminValCode))
101+
})
102+
})
103+
75104
Context("of array min length 1", func() {
76105
BeforeEach(func() {
77106
attType = &design.Array{
@@ -343,6 +372,18 @@ const (
343372
}
344373
}`
345374

375+
maxValCode = ` if val != nil {
376+
if *val > 9223372036854775807 {
377+
err = goa.MergeErrors(err, goa.InvalidRangeError(` + "`" + `context` + "`" + `, *val, 9223372036854775807, false))
378+
}
379+
}`
380+
381+
minminValCode = ` if val != nil {
382+
if *val < -9223372036854775808 {
383+
err = goa.MergeErrors(err, goa.InvalidRangeError(` + "`" + `context` + "`" + `, *val, -9223372036854775808, true))
384+
}
385+
}`
386+
346387
arrayMinLengthValCode = ` if val != nil {
347388
if len(val) < 1 {
348389
err = goa.MergeErrors(err, goa.InvalidLengthError(` + "`" + `context` + "`" + `, val, len(val), 1, true))

0 commit comments

Comments
 (0)