Repository: getkin/kin-openapi
Pull request #745 (2023-01-18 20:18:19)

openapi3: fix error phrase in security scheme
Encountered this while trying to validate a schema with "oauth2" type for security scheme.


Patch:
diff --git a/openapi3/schema.go b/openapi3/schema.go
index 9d21c00..25d244a 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -1077,6 +1077,19 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf
 	switch value := value.(type) {
 	case bool:
 		return schema.visitJSONBoolean(settings, value)
+	case json.Number:
+		valueFloat64, err := value.Float64()
+		if err != nil {
+			return &SchemaError{
+				Value:                 value,
+				Schema:                schema,
+				SchemaField:           "type",
+				Reason:                "cannot convert json.Number to float64",
+				customizeMessageError: settings.customizeMessageError,
+				Origin:                err,
+			}
+		}
+		return schema.visitJSONNumber(settings, valueFloat64)
 	case int:
 		return schema.visitJSONNumber(settings, float64(value))
 	case int32:
diff --git a/openapi3/security_scheme.go b/openapi3/security_scheme.go
index f9a0838..788c73e 100644
--- a/openapi3/security_scheme.go
+++ b/openapi3/security_scheme.go
@@ -194,7 +194,7 @@ func (ss *SecurityScheme) Validate(ctx context.Context, opts ...ValidationOption
 	} else if len(ss.In) > 0 {
 		return fmt.Errorf("security scheme of type %q can't have 'in'", ss.Type)
 	} else if len(ss.Name) > 0 {
-		return errors.New("security scheme of type 'apiKey' can't have 'name'")
+		return fmt.Errorf("security scheme of type %q can't have 'name'", ss.Type)
 	}
 
 	// Validate "format"
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 44abacb..384b512 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -1030,7 +1030,9 @@ func plainBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Schem
 
 func jsonBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
 	var value interface{}
-	if err := json.NewDecoder(body).Decode(&value); err != nil {
+	dec := json.NewDecoder(body)
+	dec.UseNumber()
+	if err := dec.Decode(&value); err != nil {
 		return nil, &ParseError{Kind: KindInvalidFormat, Cause: err}
 	}
 	return value, nil


Test patch:
diff --git a/openapi3filter/issue733_test.go b/openapi3filter/issue733_test.go
new file mode 100644
index 0000000..0d2214b
--- /dev/null
+++ b/openapi3filter/issue733_test.go
@@ -0,0 +1,109 @@
+package openapi3filter_test
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"math"
+	"math/big"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/getkin/kin-openapi/openapi3filter"
+	"github.com/getkin/kin-openapi/routers/gorillamux"
+)
+
+func TestIntMax(t *testing.T) {
+	spec := `
+openapi: 3.0.0
+info:
+  version: 1.0.0
+  title: test large integer value
+paths:
+  /test:
+    post:
+      requestBody:
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                testInteger:
+                  type: integer
+                  format: int64
+                testDefault:
+                  type: boolean
+                  default: false
+      responses:
+        '200':
+          description: Successful response
+`[1:]
+
+	loader := openapi3.NewLoader()
+
+	doc, err := loader.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+
+	err = doc.Validate(loader.Context)
+	require.NoError(t, err)
+
+	router, err := gorillamux.NewRouter(doc)
+	require.NoError(t, err)
+
+	testOne := func(value *big.Int, pass bool) {
+		valueString := value.String()
+
+		req, err := http.NewRequest(http.MethodPost, "/test", bytes.NewReader([]byte(`{"testInteger":`+valueString+`}`)))
+		require.NoError(t, err)
+		req.Header.Set("Content-Type", "application/json")
+
+		route, pathParams, err := router.FindRoute(req)
+		require.NoError(t, err)
+
+		err = openapi3filter.ValidateRequest(
+			context.Background(),
+			&openapi3filter.RequestValidationInput{
+				Request:    req,
+				PathParams: pathParams,
+				Route:      route,
+			})
+		if pass {
+			require.NoError(t, err)
+
+			dec := json.NewDecoder(req.Body)
+			dec.UseNumber()
+			var jsonAfter map[string]interface{}
+			err = dec.Decode(&jsonAfter)
+			require.NoError(t, err)
+
+			valueAfter := jsonAfter["testInteger"]
+			require.IsType(t, json.Number(""), valueAfter)
+			assert.Equal(t, valueString, string(valueAfter.(json.Number)))
+		} else {
+			if assert.Error(t, err) {
+				var serr *openapi3.SchemaError
+				if assert.ErrorAs(t, err, &serr) {
+					assert.Equal(t, "number must be an int64", serr.Reason)
+				}
+			}
+		}
+	}
+
+	bigMaxInt64 := big.NewInt(math.MaxInt64)
+	bigMaxInt64Plus1 := new(big.Int).Add(bigMaxInt64, big.NewInt(1))
+	bigMinInt64 := big.NewInt(math.MinInt64)
+	bigMinInt64Minus1 := new(big.Int).Sub(bigMinInt64, big.NewInt(1))
+
+	testOne(bigMaxInt64, true)
+	// XXX not yet fixed
+	// testOne(bigMaxInt64Plus1, false)
+	testOne(bigMaxInt64Plus1, true)
+	testOne(bigMinInt64, true)
+	// XXX not yet fixed
+	// testOne(bigMinInt64Minus1, false)
+	testOne(bigMinInt64Minus1, true)
+}
diff --git a/openapi3filter/req_resp_decoder_test.go b/openapi3filter/req_resp_decoder_test.go
index 1e71e0a..449ba0e 100644
--- a/openapi3filter/req_resp_decoder_test.go
+++ b/openapi3filter/req_resp_decoder_test.go
@@ -1226,7 +1226,7 @@ func TestDecodeBody(t *testing.T) {
 				WithProperty("d", openapi3.NewObjectSchema().WithProperty("d1", openapi3.NewStringSchema())).
 				WithProperty("f", openapi3.NewStringSchema().WithFormat("binary")).
 				WithProperty("g", openapi3.NewStringSchema()),
-			want: map[string]interface{}{"a": "a1", "b": float64(10), "c": []interface{}{"c1", "c2"}, "d": map[string]interface{}{"d1": "d1"}, "f": "foo", "g": "g1"},
+			want: map[string]interface{}{"a": "a1", "b": json.Number("10"), "c": []interface{}{"c1", "c2"}, "d": map[string]interface{}{"d1": "d1"}, "f": "foo", "g": "g1"},
 		},
 		{
 			name: "multipartExtraPart",