Skip to content

Commit 1e0451a

Browse files
committed
[format/int32,int64] add support, per OpenAPI 3
Note that for the time being, these formats apply not only to integer types, but numbers too. https://github.com/OAI/OpenAPI-Specification/blob/3.1.0/versions/3.1.0.md#dataTypes Refs #75
1 parent dde88bf commit 1e0451a

File tree

3 files changed

+125
-0
lines changed

3 files changed

+125
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Package jsonschema provides json-schema compilation and validation.
3737
- uri, uriref, uri-template(limited validation)
3838
- json-pointer, relative-json-pointer
3939
- regex, format
40+
- int32, int64
4041
- implements following contentEncoding (supports [user-defined](https://pkg.go.dev/github.com/santhosh-tekuri/jsonschema/v5/#example-package-UserDefinedContent))
4142
- base64
4243
- implements following contentMediaType (supports [user-defined](https://pkg.go.dev/github.com/santhosh-tekuri/jsonschema/v5/#example-package-UserDefinedContent))

format.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package jsonschema
22

33
import (
4+
"encoding/json"
45
"errors"
6+
"math"
7+
"math/big"
58
"net"
69
"net/mail"
710
"net/url"
@@ -37,6 +40,8 @@ var Formats = map[string]func(interface{}) bool{
3740
"json-pointer": isJSONPointer,
3841
"relative-json-pointer": isRelativeJSONPointer,
3942
"uuid": isUUID,
43+
"int32": isInt32, // OpenAPI
44+
"int64": isInt64, // OpenAPI
4045
}
4146

4247
// isDateTime tells whether given string is a valid date representation
@@ -565,3 +570,63 @@ func isUUID(v interface{}) bool {
565570
}
566571
return true
567572
}
573+
574+
// isInt32 tells whether given integer is a valid 32-bit one
575+
// as specified in OpenAPI 3.1.0.
576+
//
577+
// see https://github.com/OAI/OpenAPI-Specification/blob/3.1.0/versions/3.1.0.md#data-types, for details
578+
func isInt32(v interface{}) bool {
579+
var bv *big.Rat
580+
switch v := v.(type) {
581+
case json.Number:
582+
// v.Int64() and big.Int.SetString fail with .0 fractionals, which is inconsistent with basic integer test
583+
var ok bool
584+
if bv, ok = new(big.Rat).SetString(v.String()); !ok {
585+
return false
586+
}
587+
case int:
588+
bv = new(big.Rat).SetInt64(int64(v))
589+
case int32:
590+
return true
591+
case int64:
592+
bv = new(big.Rat).SetInt64(v)
593+
case float64:
594+
if bv = new(big.Rat).SetFloat64(v); bv == nil {
595+
return false
596+
}
597+
default:
598+
return true
599+
}
600+
if !bv.IsInt() {
601+
return false
602+
}
603+
bl := new(big.Rat)
604+
return bv.Cmp(bl.SetInt64(math.MinInt32)) != -1 && bv.Cmp(bl.SetInt64(math.MaxInt32)) != 1
605+
}
606+
607+
// isInt64 tells whether given integer is a valid 64-bit one
608+
// as specified in OpenAPI 3.1.0.
609+
//
610+
// see https://github.com/OAI/OpenAPI-Specification/blob/3.1.0/versions/3.1.0.md#data-types, for details
611+
func isInt64(v interface{}) bool {
612+
var bv *big.Rat
613+
switch v := v.(type) {
614+
case json.Number:
615+
// v.Int64() and big.Int.SetString fail with .0 fractionals, which is inconsistent with basic integer test
616+
var ok bool
617+
if bv, ok = new(big.Rat).SetString(v.String()); !ok {
618+
return false
619+
}
620+
case float64:
621+
if bv = new(big.Rat).SetFloat64(v); bv == nil {
622+
return false
623+
}
624+
default:
625+
return true
626+
}
627+
if !bv.IsInt() {
628+
return false
629+
}
630+
bl := new(big.Rat)
631+
return bv.Cmp(bl.SetInt64(math.MinInt64)) != -1 && bv.Cmp(bl.SetInt64(math.MaxInt64)) != 1
632+
}

format_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package jsonschema
22

33
import (
4+
"encoding/json"
5+
"fmt"
6+
"math"
47
"strings"
58
"testing"
69
)
@@ -353,3 +356,59 @@ func TestIsUUID(t *testing.T) {
353356
}
354357
}
355358
}
359+
360+
func TestIsInt32(t *testing.T) {
361+
tests := []struct {
362+
val interface{}
363+
valid bool
364+
}{
365+
{1, true},
366+
{1.0, true},
367+
{1.2, false},
368+
{-0, true},
369+
{math.MinInt32, true},
370+
{int32(math.MinInt32), true},
371+
{int64(math.MinInt64), false},
372+
{math.NaN(), false},
373+
{math.Inf(1), false},
374+
{json.Number("1"), true},
375+
{json.Number("1.0"), true},
376+
{json.Number("-1.01"), false},
377+
{json.Number(fmt.Sprint(math.MaxInt32)), true},
378+
{json.Number(fmt.Sprint(math.MaxInt64)), false},
379+
{"string", true},
380+
}
381+
for i, test := range tests {
382+
if test.valid != isInt32(test.val) {
383+
t.Errorf("#%d: %q, valid %t, got valid %t", i, test.val, test.valid, !test.valid)
384+
}
385+
}
386+
}
387+
388+
func TestIsInt64(t *testing.T) {
389+
tests := []struct {
390+
val interface{}
391+
valid bool
392+
}{
393+
{1, true},
394+
{1.0, true},
395+
{1.2, false},
396+
{0, true},
397+
{int32(math.MinInt32), true},
398+
{int64(math.MinInt64), true},
399+
{math.NaN(), false},
400+
{math.Inf(-1), false},
401+
{json.Number("1"), true},
402+
{json.Number("1.0"), true},
403+
{json.Number("-1.01"), false},
404+
{json.Number(fmt.Sprint(math.MaxInt32)), true},
405+
{json.Number(fmt.Sprint(math.MaxInt64)), true},
406+
{json.Number("9223372036854775808"), false},
407+
{"string", true},
408+
}
409+
for i, test := range tests {
410+
if test.valid != isInt64(test.val) {
411+
t.Errorf("#%d: %q, valid %t, got valid %t", i, test.val, test.valid, !test.valid)
412+
}
413+
}
414+
}

0 commit comments

Comments
 (0)