Skip to content

Commit 8764c3e

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 8764c3e

File tree

3 files changed

+122
-0
lines changed

3 files changed

+122
-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: 56 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,56 @@ 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+
{math.MinInt64, false},
371+
{math.NaN(), false},
372+
{math.Inf(1), false},
373+
{json.Number("1"), true},
374+
{json.Number("1.0"), true},
375+
{json.Number("-1.01"), false},
376+
{json.Number(fmt.Sprint(math.MaxInt32)), true},
377+
{json.Number(fmt.Sprint(math.MaxInt64)), false},
378+
}
379+
for i, test := range tests {
380+
if test.valid != isInt32(test.val) {
381+
t.Errorf("#%d: %q, valid %t, got valid %t", i, test.val, test.valid, !test.valid)
382+
}
383+
}
384+
}
385+
386+
func TestIsInt64(t *testing.T) {
387+
tests := []struct {
388+
val interface{}
389+
valid bool
390+
}{
391+
{1, true},
392+
{1.0, true},
393+
{1.2, false},
394+
{0, true},
395+
{math.MinInt32, true},
396+
{math.MinInt64, true},
397+
{math.NaN(), false},
398+
{math.Inf(-1), false},
399+
{json.Number("1"), true},
400+
{json.Number("1.0"), true},
401+
{json.Number("-1.01"), false},
402+
{json.Number(fmt.Sprint(math.MaxInt32)), true},
403+
{json.Number(fmt.Sprint(math.MaxInt64)), true},
404+
{json.Number("9223372036854775808"), false},
405+
}
406+
for i, test := range tests {
407+
if test.valid != isInt64(test.val) {
408+
t.Errorf("#%d: %q, valid %t, got valid %t", i, test.val, test.valid, !test.valid)
409+
}
410+
}
411+
}

0 commit comments

Comments
 (0)