Repository: getkin/kin-openapi
Pull request #755 (2023-01-28 06:07:58)

openapi3: fix integer enum schema validation after json.Number PR
The integer enum could not be validated correctly.
see.
https://github.com/k2tzumi/kin-openapi/actions/runs/4030301397/jobs/6928881789#step:18:19

Added appropriate conversion process since it was comparing `json.Number` with `float64`


Patch:
diff --git a/openapi3/schema.go b/openapi3/schema.go
index 415c281..4e2bf14 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -1143,8 +1143,20 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf
 func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, value interface{}) (err error) {
 	if enum := schema.Enum; len(enum) != 0 {
 		for _, v := range enum {
-			if reflect.DeepEqual(v, value) {
-				return
+			switch c := value.(type) {
+			case json.Number:
+				var f float64
+				f, err = strconv.ParseFloat(c.String(), 64)
+				if err != nil {
+					return err
+				}
+				if v == f {
+					return
+				}
+			default:
+				if reflect.DeepEqual(v, value) {
+					return
+				}
 			}
 		}
 		if settings.failfast {
diff --git a/openapi3/security_scheme.go b/openapi3/security_scheme.go
index 788c73e..76cc21f 100644
--- a/openapi3/security_scheme.go
+++ b/openapi3/security_scheme.go
@@ -363,8 +363,8 @@ func (flow *OAuthFlow) Validate(ctx context.Context, opts ...ValidationOption) e
 		}
 	}
 
-	if v := flow.Scopes; len(v) == 0 {
-		return errors.New("field 'scopes' is empty or missing")
+	if flow.Scopes == nil {
+		return errors.New("field 'scopes' is missing")
 	}
 
 	return validateExtensions(ctx, flow.Extensions)


Test patch:
diff --git a/openapi3/security_scheme_test.go b/openapi3/security_scheme_test.go
index 48ea046..790414c 100644
--- a/openapi3/security_scheme_test.go
+++ b/openapi3/security_scheme_test.go
@@ -197,7 +197,7 @@ var securitySchemeExamples = []securitySchemeExample{
     }
   }
 }`),
-		valid: false,
+		valid: true,
 	},
 
 	{
diff --git a/openapi3filter/validation_enum_test.go b/openapi3filter/validation_enum_test.go
new file mode 100644
index 0000000..898c402
--- /dev/null
+++ b/openapi3filter/validation_enum_test.go
@@ -0,0 +1,175 @@
+package openapi3filter
+
+import (
+	"bytes"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
+)
+
+func TestValidationWithIntegerEnum(t *testing.T) {
+	const spec = `
+openapi: 3.0.0
+info:
+  title: Example integer enum
+  version: '0.1'
+paths:
+  /sample:
+    put:
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                exenum:
+                  type: integer
+                  enum:
+                    - 0
+                    - 1
+                    - 2
+                    - 3
+                  example: 0
+                  nullable: true
+      responses:
+        '200':
+          description: Ok
+`
+
+	loader := openapi3.NewLoader()
+	doc, err := loader.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+
+	router, err := legacyrouter.NewRouter(doc)
+	require.NoError(t, err)
+
+	tests := []struct {
+		data    []byte
+		wantErr bool
+	}{
+		{
+			[]byte(`{"exenum": 1}`),
+			false,
+		},
+		{
+			[]byte(`{"exenum": "1"}`),
+			true,
+		},
+		{
+			[]byte(`{"exenum": null}`),
+			false,
+		},
+		{
+			[]byte(`{}`),
+			false,
+		},
+	}
+
+	for _, tt := range tests {
+		body := bytes.NewReader(tt.data)
+		req, err := http.NewRequest("PUT", "/sample", body)
+		require.NoError(t, err)
+		req.Header.Add(headerCT, "application/json")
+
+		route, pathParams, err := router.FindRoute(req)
+		require.NoError(t, err)
+
+		requestValidationInput := &RequestValidationInput{
+			Request:    req,
+			PathParams: pathParams,
+			Route:      route,
+		}
+		err = ValidateRequest(loader.Context, requestValidationInput)
+		if tt.wantErr {
+			require.Error(t, err)
+		} else {
+			require.NoError(t, err)
+		}
+	}
+}
+
+func TestValidationWithStringEnum(t *testing.T) {
+	const spec = `
+openapi: 3.0.0
+info:
+  title: Example string enum
+  version: '0.1'
+paths:
+  /sample:
+    put:
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                exenum:
+                  type: string
+                  enum:
+                    - "0"
+                    - "1"
+                    - "2"
+                    - "3"
+                  example: "0"
+      responses:
+        '200':
+          description: Ok
+`
+
+	loader := openapi3.NewLoader()
+	doc, err := loader.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+
+	router, err := legacyrouter.NewRouter(doc)
+	require.NoError(t, err)
+
+	tests := []struct {
+		data    []byte
+		wantErr bool
+	}{
+		{
+			[]byte(`{"exenum": "1"}`),
+			false,
+		},
+		{
+			[]byte(`{"exenum": 1}`),
+			true,
+		},
+		{
+			[]byte(`{"exenum": null}`),
+			true,
+		},
+		{
+			[]byte(`{}`),
+			false,
+		},
+	}
+
+	for _, tt := range tests {
+		body := bytes.NewReader(tt.data)
+		req, err := http.NewRequest("PUT", "/sample", body)
+		require.NoError(t, err)
+		req.Header.Add(headerCT, "application/json")
+
+		route, pathParams, err := router.FindRoute(req)
+		require.NoError(t, err)
+
+		requestValidationInput := &RequestValidationInput{
+			Request:    req,
+			PathParams: pathParams,
+			Route:      route,
+		}
+		err = ValidateRequest(loader.Context, requestValidationInput)
+		if tt.wantErr {
+			require.Error(t, err)
+		} else {
+			require.NoError(t, err)
+		}
+	}
+}