Repository: getkin/kin-openapi
Pull request #549 (2022-05-30 16:11:49)

Follow up to #540 with more tests
Signed-off-by: Pierre Fenoll <pierrefenoll@gmail.com>


Patch:
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 0408d8d..94edf75 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -795,19 +795,19 @@ func parsePrimitive(raw string, schema *openapi3.SchemaRef) (interface{}, error)
 	case "integer":
 		v, err := strconv.ParseFloat(raw, 64)
 		if err != nil {
-			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid integer", Cause: err}
+			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err}
 		}
 		return v, nil
 	case "number":
 		v, err := strconv.ParseFloat(raw, 64)
 		if err != nil {
-			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid number", Cause: err}
+			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err}
 		}
 		return v, nil
 	case "boolean":
 		v, err := strconv.ParseBool(raw)
 		if err != nil {
-			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid number", Cause: err}
+			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err}
 		}
 		return v, nil
 	case "string":


Test patch:
diff --git a/openapi3/internalize_refs_test.go b/openapi3/internalize_refs_test.go
index d6264d4..b1ca846 100644
--- a/openapi3/internalize_refs_test.go
+++ b/openapi3/internalize_refs_test.go
@@ -2,6 +2,7 @@ package openapi3
 
 import (
 	"context"
+	"io/ioutil"
 	"regexp"
 	"testing"
 
@@ -9,8 +10,10 @@ import (
 )
 
 func TestInternalizeRefs(t *testing.T) {
-	var regexpRef = regexp.MustCompile(`"\$ref":`)
-	var regexpRefInternal = regexp.MustCompile(`"\$ref":"\#`)
+	ctx := context.Background()
+
+	regexpRef := regexp.MustCompile(`"\$ref":`)
+	regexpRefInternal := regexp.MustCompile(`"\$ref":"#`)
 
 	tests := []struct {
 		filename string
@@ -28,13 +31,15 @@ func TestInternalizeRefs(t *testing.T) {
 			sl.IsExternalRefsAllowed = true
 			doc, err := sl.LoadFromFile(test.filename)
 			require.NoError(t, err, "loading test file")
+			err = doc.Validate(ctx)
+			require.NoError(t, err, "validating spec")
 
 			// Internalize the references
-			doc.InternalizeRefs(context.Background(), DefaultRefNameResolver)
+			doc.InternalizeRefs(ctx, nil)
 
 			// Validate the internalized spec
-			err = doc.Validate(context.Background())
-			require.Nil(t, err, "validating internalized spec")
+			err = doc.Validate(ctx)
+			require.NoError(t, err, "validating internalized spec")
 
 			data, err := doc.MarshalJSON()
 			require.NoError(t, err, "marshalling internalized spec")
@@ -48,8 +53,13 @@ func TestInternalizeRefs(t *testing.T) {
 			// load from data, but with the path set to the current directory
 			doc2, err := sl.LoadFromData(data)
 			require.NoError(t, err, "reloading spec")
-			err = doc2.Validate(context.Background())
-			require.Nil(t, err, "validating reloaded spec")
+			err = doc2.Validate(ctx)
+			require.NoError(t, err, "validating reloaded spec")
+
+			// compare with expected
+			data0, err := ioutil.ReadFile(test.filename + ".internalized.yml")
+			require.NoError(t, err)
+			require.JSONEq(t, string(data), string(data0))
 		})
 	}
 }
diff --git a/openapi3/testdata/callbacks.yml.internalized.yml b/openapi3/testdata/callbacks.yml.internalized.yml
new file mode 100644
index 0000000..866cb5c
--- /dev/null
+++ b/openapi3/testdata/callbacks.yml.internalized.yml
@@ -0,0 +1,131 @@
+{
+  "components": {
+    "callbacks": {
+      "MyCallbackEvent": {
+        "{$request.query.queryUrl}": {
+          "post": {
+            "requestBody": {
+              "content": {
+                "application/json": {
+                  "schema": {
+                    "$ref": "#/components/schemas/SomeOtherPayload"
+                  }
+                }
+              },
+              "description": "Callback payload"
+            },
+            "responses": {
+              "200": {
+                "description": "callback successfully processed"
+              }
+            }
+          }
+        }
+      }
+    },
+    "schemas": {
+      "SomeOtherPayload": {
+        "type": "boolean"
+      },
+      "SomePayload": {
+        "type": "object"
+      }
+    }
+  },
+  "info": {
+    "title": "Callback refd",
+    "version": "1.2.3"
+  },
+  "openapi": "3.1.0",
+  "paths": {
+    "/other": {
+      "post": {
+        "callbacks": {
+          "myEvent": {
+            "$ref": "#/components/callbacks/MyCallbackEvent"
+          }
+        },
+        "parameters": [
+          {
+            "description": "bla\nbla\nbla\n",
+            "in": "query",
+            "name": "queryUrl",
+            "required": true,
+            "schema": {
+              "example": "https://example.com",
+              "format": "uri",
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "201": {
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "object"
+                }
+              }
+            },
+            "description": ""
+          }
+        }
+      }
+    },
+    "/trans": {
+      "post": {
+        "callbacks": {
+          "transactionCallback": {
+            "http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}": {
+              "post": {
+                "requestBody": {
+                  "content": {
+                    "application/json": {
+                      "schema": {
+                        "$ref": "#/components/schemas/SomePayload"
+                      }
+                    }
+                  },
+                  "description": "Callback payload"
+                },
+                "responses": {
+                  "200": {
+                    "description": "callback successfully processed"
+                  }
+                }
+              }
+            }
+          }
+        },
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "properties": {
+                  "email": {
+                    "format": "email"
+                  },
+                  "id": {
+                    "type": "string"
+                  }
+                }
+              }
+            }
+          }
+        },
+        "responses": {
+          "201": {
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "object"
+                }
+              }
+            },
+            "description": "subscription successfully created"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml b/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml
new file mode 100644
index 0000000..d1260eb
--- /dev/null
+++ b/openapi3/testdata/recursiveRef/openapi.yml.internalized.yml
@@ -0,0 +1,68 @@
+{
+  "components": {
+    "parameters": {
+      "number": {
+        "in": "query",
+        "name": "someNumber",
+        "schema": {
+          "type": "string"
+        }
+      }
+    },
+    "schemas": {
+      "Bar": {
+        "example": "bar",
+        "type": "string"
+      },
+      "Foo": {
+        "properties": {
+          "bar": {
+            "$ref": "#/components/schemas/Bar"
+          }
+        },
+        "type": "object"
+      },
+      "Foo2": {
+        "properties": {
+          "foo": {
+            "$ref": "#/components/schemas/Foo"
+          }
+        },
+        "type": "object"
+      }
+    }
+  },
+  "info": {
+    "title": "Recursive refs example",
+    "version": "1.0"
+  },
+  "openapi": "3.0.3",
+  "paths": {
+    "/foo": {
+      "get": {
+        "responses": {
+          "200": {
+            "content": {
+              "application/json": {
+                "schema": {
+                  "properties": {
+                    "foo2": {
+                      "$ref": "#/components/schemas/Foo2"
+                    }
+                  },
+                  "type": "object"
+                }
+              }
+            },
+            "description": "OK"
+          }
+        }
+      },
+      "parameters": [
+        {
+          "$ref": "#/components/parameters/number"
+        }
+      ]
+    }
+  }
+}
diff --git a/openapi3/testdata/spec.yaml.internalized.yml b/openapi3/testdata/spec.yaml.internalized.yml
new file mode 100644
index 0000000..feca4a0
--- /dev/null
+++ b/openapi3/testdata/spec.yaml.internalized.yml
@@ -0,0 +1,36 @@
+{
+  "components": {
+    "schemas": {
+      "Test": {
+        "properties": {
+          "test": {
+            "$ref": "#/components/schemas/b"
+          }
+        },
+        "type": "object"
+      },
+      "a": {
+        "type": "string"
+      },
+      "b": {
+        "description": "I use a local reference.",
+        "properties": {
+          "name": {
+            "$ref": "#/components/schemas/a"
+          }
+        },
+        "type": "object"
+      }
+    }
+  },
+  "info": {
+    "license": {
+      "name": "MIT"
+    },
+    "title": "Some Swagger",
+    "version": "1.0.0"
+  },
+  "openapi": "3.0.1",
+  "paths": {
+  }
+}
diff --git a/openapi3/testdata/testref.openapi.yml.internalized.yml b/openapi3/testdata/testref.openapi.yml.internalized.yml
new file mode 100644
index 0000000..e35a500
--- /dev/null
+++ b/openapi3/testdata/testref.openapi.yml.internalized.yml
@@ -0,0 +1,19 @@
+{
+  "components": {
+    "schemas": {
+      "AnotherTestSchema": {
+        "type": "string"
+      },
+      "CustomTestSchema": {
+        "type": "string"
+      }
+    }
+  },
+  "info": {
+    "title": "OAI Specification w/ refs in YAML",
+    "version": "1"
+  },
+  "openapi": "3.0.0",
+  "paths": {
+  }
+}
diff --git a/openapi3filter/validation_error_test.go b/openapi3filter/validation_error_test.go
index aae26e5..10d46f8 100644
--- a/openapi3filter/validation_error_test.go
+++ b/openapi3filter/validation_error_test.go
@@ -187,7 +187,7 @@ func getValidationTests(t *testing.T) []*validationTest {
 				Title: `parameter "status" in query is required`},
 		},
 		{
-			name: "error - wrong query string parameter type",
+			name: "error - wrong query string parameter type as integer",
 			args: validationArgs{
 				r: newPetstoreRequest(t, http.MethodGet, "/pet/findByIds?ids=1,notAnInt", nil),
 			},
@@ -195,8 +195,7 @@ func getValidationTests(t *testing.T) []*validationTest {
 			wantErrParamIn: "query",
 			// This is a nested ParseError. The outer error is a KindOther with no details.
 			// So we'd need to look at the inner one which is a KindInvalidFormat. So just check the error body.
-			wantErrBody: `parameter "ids" in query has an error: path 1: value notAnInt: an invalid integer: ` +
-				"strconv.ParseFloat: parsing \"notAnInt\": invalid syntax",
+			wantErrBody: `parameter "ids" in query has an error: path 1: value notAnInt: an invalid integer: invalid syntax`,
 			// TODO: Should we treat query params of the wrong type like a 404 instead of a 400?
 			wantErrResponse: &ValidationError{Status: http.StatusBadRequest,
 				Title: `parameter "ids" in query is invalid: notAnInt is an invalid integer`},