Skip to content

Commit f20a39f

Browse files
authored
Add range limits to inferred Schemas of int types. (#30)
1 parent 2a1c0a4 commit f20a39f

File tree

2 files changed

+82
-5
lines changed

2 files changed

+82
-5
lines changed

jsonschema/infer.go

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"log/slog"
1212
"maps"
13+
"math"
1314
"math/big"
1415
"reflect"
1516
"regexp"
@@ -99,6 +100,11 @@ func ForType(t reflect.Type, opts *ForOptions) (*Schema, error) {
99100
return s, nil
100101
}
101102

103+
// Helper to create a *float64 pointer from a value
104+
func f64Ptr(f float64) *float64 {
105+
return &f
106+
}
107+
102108
func forType(t reflect.Type, seen map[reflect.Type]bool, ignore bool, schemas map[reflect.Type]*Schema) (*Schema, error) {
103109
// Follow pointers: the schema for *T is almost the same as for T, except that
104110
// an explicit JSON "null" is allowed for the pointer.
@@ -131,10 +137,42 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, ignore bool, schemas ma
131137
case reflect.Bool:
132138
s.Type = "boolean"
133139

134-
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
135-
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
136-
reflect.Uintptr:
140+
case reflect.Int, reflect.Int64:
141+
s.Type = "integer"
142+
143+
case reflect.Uint, reflect.Uint64, reflect.Uintptr:
144+
s.Type = "integer"
145+
s.Minimum = f64Ptr(0)
146+
147+
case reflect.Int8:
148+
s.Type = "integer"
149+
s.Minimum = f64Ptr(math.MinInt8)
150+
s.Maximum = f64Ptr(math.MaxInt8)
151+
152+
case reflect.Uint8:
153+
s.Type = "integer"
154+
s.Minimum = f64Ptr(0)
155+
s.Maximum = f64Ptr(math.MaxUint8)
156+
157+
case reflect.Int16:
158+
s.Type = "integer"
159+
s.Minimum = f64Ptr(math.MinInt16)
160+
s.Maximum = f64Ptr(math.MaxInt16)
161+
162+
case reflect.Uint16:
163+
s.Type = "integer"
164+
s.Minimum = f64Ptr(0)
165+
s.Maximum = f64Ptr(math.MaxUint16)
166+
167+
case reflect.Int32:
168+
s.Type = "integer"
169+
s.Minimum = f64Ptr(math.MinInt32)
170+
s.Maximum = f64Ptr(math.MaxInt32)
171+
172+
case reflect.Uint32:
137173
s.Type = "integer"
174+
s.Minimum = f64Ptr(0)
175+
s.Maximum = f64Ptr(math.MaxUint32)
138176

139177
case reflect.Float32, reflect.Float64:
140178
s.Type = "number"

jsonschema/infer_test.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package jsonschema_test
66

77
import (
88
"log/slog"
9+
"math"
910
"math/big"
1011
"reflect"
1112
"strings"
@@ -49,12 +50,46 @@ func TestFor(t *testing.T) {
4950
want *jsonschema.Schema
5051
}
5152

53+
f64Ptr := jsonschema.Ptr[float64]
54+
5255
tests := func(ignore bool) []test {
5356
return []test{
5457
{"string", forType[string](ignore), &schema{Type: "string"}},
58+
{
59+
"int8",
60+
forType[int8](ignore),
61+
&schema{Type: "integer", Minimum: f64Ptr(math.MinInt8), Maximum: f64Ptr(math.MaxInt8)},
62+
},
63+
{
64+
"uint8",
65+
forType[uint8](ignore),
66+
&schema{Type: "integer", Minimum: f64Ptr(0), Maximum: f64Ptr(math.MaxUint8)},
67+
},
68+
{
69+
"int16",
70+
forType[int16](ignore),
71+
&schema{Type: "integer", Minimum: f64Ptr(math.MinInt16), Maximum: f64Ptr(math.MaxInt16)},
72+
},
73+
{
74+
"uint16",
75+
forType[uint16](ignore),
76+
&schema{Type: "integer", Minimum: f64Ptr(0), Maximum: f64Ptr(math.MaxUint16)},
77+
},
78+
{
79+
"int32",
80+
forType[int32](ignore),
81+
&schema{Type: "integer", Minimum: f64Ptr(math.MinInt32), Maximum: f64Ptr(math.MaxInt32)},
82+
},
83+
{
84+
"uint32",
85+
forType[uint32](ignore),
86+
&schema{Type: "integer", Minimum: f64Ptr(0), Maximum: f64Ptr(math.MaxUint32)},
87+
},
88+
{"int64", forType[int64](ignore), &schema{Type: "integer"}},
89+
{"uint64", forType[uint64](ignore), &schema{Type: "integer", Minimum: f64Ptr(0)}},
5590
{"int", forType[int](ignore), &schema{Type: "integer"}},
56-
{"int16", forType[int16](ignore), &schema{Type: "integer"}},
57-
{"uint32", forType[int16](ignore), &schema{Type: "integer"}},
91+
{"uint", forType[uint](ignore), &schema{Type: "integer", Minimum: f64Ptr(0)}},
92+
{"uintptr", forType[uintptr](ignore), &schema{Type: "integer", Minimum: f64Ptr(0)}},
5893
{"float64", forType[float64](ignore), &schema{Type: "number"}},
5994
{"bool", forType[bool](ignore), &schema{Type: "boolean"}},
6095
{"time", forType[time.Time](ignore), &schema{Type: "string"}},
@@ -65,6 +100,10 @@ func TestFor(t *testing.T) {
65100
Type: "object",
66101
AdditionalProperties: &schema{Type: "integer"},
67102
}},
103+
{"int8map", forType[map[string]int8](ignore), &schema{
104+
Type: "object",
105+
AdditionalProperties: &schema{Type: "integer", Minimum: f64Ptr(math.MinInt8), Maximum: f64Ptr(math.MaxInt8)},
106+
}},
68107
{"anymap", forType[map[string]any](ignore), &schema{
69108
Type: "object",
70109
AdditionalProperties: &schema{},

0 commit comments

Comments
 (0)