Skip to content

Commit 2f18550

Browse files
committed
jsonschema: better errors unmarshaling ints
Distinguish among non-numbers, non-integral floating-point values, and unrepresentable integers. Change-Id: I0d60b92d6b4dd0baa536476931eb75e42ed5366a Reviewed-on: https://go-review.googlesource.com/c/tools/+/669695 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 635622b commit 2f18550

File tree

2 files changed

+64
-40
lines changed

2 files changed

+64
-40
lines changed

internal/mcp/internal/jsonschema/schema.go

+51-32
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ package jsonschema
88

99
import (
1010
"bytes"
11-
"cmp"
1211
"encoding/json"
1312
"errors"
1413
"fmt"
1514
"iter"
15+
"math"
1616
)
1717

1818
// A Schema is a JSON schema object.
@@ -177,14 +177,14 @@ func (s *Schema) UnmarshalJSON(data []byte) error {
177177
ms := struct {
178178
Type json.RawMessage `json:"type,omitempty"`
179179
Const json.RawMessage `json:"const,omitempty"`
180-
MinLength *float64 `json:"minLength,omitempty"`
181-
MaxLength *float64 `json:"maxLength,omitempty"`
182-
MinItems *float64 `json:"minItems,omitempty"`
183-
MaxItems *float64 `json:"maxItems,omitempty"`
184-
MinProperties *float64 `json:"minProperties,omitempty"`
185-
MaxProperties *float64 `json:"maxProperties,omitempty"`
186-
MinContains *float64 `json:"minContains,omitempty"`
187-
MaxContains *float64 `json:"maxContains,omitempty"`
180+
MinLength *integer `json:"minLength,omitempty"`
181+
MaxLength *integer `json:"maxLength,omitempty"`
182+
MinItems *integer `json:"minItems,omitempty"`
183+
MaxItems *integer `json:"maxItems,omitempty"`
184+
MinProperties *integer `json:"minProperties,omitempty"`
185+
MaxProperties *integer `json:"maxProperties,omitempty"`
186+
MinContains *integer `json:"minContains,omitempty"`
187+
MaxContains *integer `json:"maxContains,omitempty"`
188188

189189
*schemaWithoutMethods
190190
}{
@@ -219,33 +219,52 @@ func (s *Schema) UnmarshalJSON(data []byte) error {
219219
}
220220
}
221221

222-
// Store integer properties as ints.
223-
setInt := func(name string, dst **int, src *float64) error {
224-
if src == nil {
225-
return nil
222+
set := func(dst **int, src *integer) {
223+
if src != nil {
224+
*dst = Ptr(int(*src))
226225
}
227-
i := int(*src)
228-
if float64(i) != *src {
229-
return fmt.Errorf("%s: %f is not an int", name, *src)
230-
}
231-
*dst = &i
232-
return nil
233226
}
234227

235-
err = cmp.Or(
236-
setInt("minLength", &s.MinLength, ms.MinLength),
237-
setInt("maxLength", &s.MaxLength, ms.MaxLength),
238-
setInt("minItems", &s.MinItems, ms.MinItems),
239-
setInt("maxItems", &s.MaxItems, ms.MaxItems),
240-
setInt("minProperties", &s.MinProperties, ms.MinProperties),
241-
setInt("maxProperties", &s.MaxProperties, ms.MaxProperties),
242-
setInt("minContains", &s.MinContains, ms.MinContains),
243-
setInt("maxContains", &s.MaxContains, ms.MaxContains),
244-
)
245-
if err != nil {
246-
return err
247-
}
228+
set(&s.MinLength, ms.MinLength)
229+
set(&s.MaxLength, ms.MaxLength)
230+
set(&s.MinItems, ms.MinItems)
231+
set(&s.MaxItems, ms.MaxItems)
232+
set(&s.MinProperties, ms.MinProperties)
233+
set(&s.MaxProperties, ms.MaxProperties)
234+
set(&s.MinContains, ms.MinContains)
235+
set(&s.MaxContains, ms.MaxContains)
236+
237+
return nil
238+
}
248239

240+
type integer int32 // for the integer-valued fields of Schema
241+
242+
func (ip *integer) UnmarshalJSON(data []byte) error {
243+
if len(data) == 0 {
244+
// nothing to do
245+
return nil
246+
}
247+
// If there is a decimal point, src is a floating-point number.
248+
var i int64
249+
if bytes.ContainsRune(data, '.') {
250+
var f float64
251+
if err := json.Unmarshal(data, &f); err != nil {
252+
return errors.New("not a number")
253+
}
254+
i = int64(f)
255+
if float64(i) != f {
256+
return errors.New("not an integer value")
257+
}
258+
} else {
259+
if err := json.Unmarshal(data, &i); err != nil {
260+
return errors.New("cannot be unmarshaled into an int")
261+
}
262+
}
263+
// Ensure behavior is the same on both 32-bit and 64-bit systems.
264+
if i < math.MinInt32 || i > math.MaxInt32 {
265+
return errors.New("integer is out of range")
266+
}
267+
*ip = integer(i)
249268
return nil
250269
}
251270

internal/mcp/internal/jsonschema/schema_test.go

+13-8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package jsonschema
66

77
import (
88
"encoding/json"
9+
"fmt"
10+
"math"
911
"regexp"
1012
"testing"
1113
)
@@ -86,14 +88,17 @@ func TestUnmarshalErrors(t *testing.T) {
8688
}{
8789
{`1`, "cannot unmarshal number"},
8890
{`{"type":1}`, `invalid value for "type"`},
89-
{`{"minLength":1.5}`, `minLength:.*not an int`},
90-
{`{"maxLength":1.5}`, `maxLength:.*not an int`},
91-
{`{"minItems":1.5}`, `minItems:.*not an int`},
92-
{`{"maxItems":1.5}`, `maxItems:.*not an int`},
93-
{`{"minProperties":1.5}`, `minProperties:.*not an int`},
94-
{`{"maxProperties":1.5}`, `maxProperties:.*not an int`},
95-
{`{"minContains":1.5}`, `minContains:.*not an int`},
96-
{`{"maxContains":1.5}`, `maxContains:.*not an int`},
91+
{`{"minLength":1.5}`, `not an integer value`},
92+
{`{"maxLength":1.5}`, `not an integer value`},
93+
{`{"minItems":1.5}`, `not an integer value`},
94+
{`{"maxItems":1.5}`, `not an integer value`},
95+
{`{"minProperties":1.5}`, `not an integer value`},
96+
{`{"maxProperties":1.5}`, `not an integer value`},
97+
{`{"minContains":1.5}`, `not an integer value`},
98+
{`{"maxContains":1.5}`, `not an integer value`},
99+
{fmt.Sprintf(`{"maxContains":%d}`, int64(math.MaxInt32+1)), `out of range`},
100+
{`{"minLength":9e99}`, `cannot be unmarshaled`},
101+
{`{"minLength":"1.5"}`, `not a number`},
97102
} {
98103
var s Schema
99104
err := json.Unmarshal([]byte(tt.in), &s)

0 commit comments

Comments
 (0)