Repository: getkin/kin-openapi (go)

Issue:
InternalizeRefs does not internalize parameters of paths
Calling `.InternalizeRefs` on a schema that contains parameters on the path level does not seem to resolve those parameters correctly. The resulting schema still contains the reference to the external file. When moving the parameters below the operation level, the behavior is as expected.

<details>
  <summary>Schema</summary>

  ```yaml
  openapi: 3.0.0
  info:
    title: foo
    version: 0.0.0
  paths:
    /{external1}:
      parameters:
        - in: path
          name: external1
          required: true
          schema:
            $ref: './components.yaml#/components/schemas/External1'
      get:
        responses:
          '204':
            description: No content
  ```
</details>

<details>
  <summary>components.yaml</summary>

  ```yaml
  components:
    schemas:
      External1:
        type: string
  ```
</details>

<details>
  <summary>Code</summary>

  ```go
  package main

  import (
    "context"
    "encoding/json"
    "log"
    "os"

    "github.com/getkin/kin-openapi/openapi3"
  )

  func main() {
    loader := openapi3.NewLoader()
    loader.IsExternalRefsAllowed = true
    doc, err := loader.LoadFromFile("openapi.yaml")
    if err != nil {
      log.Fatalf("load: %v", err)
    }

    doc.InternalizeRefs(context.Background(), nil)

    result, err := json.MarshalIndent(doc, "", "  ")
    if err != nil {
      log.Fatalf("marshal: %v", err)
    }

    err = os.WriteFile("result.json", result, 0644)
    if err != nil {
      log.Fatalf("result: %v", err)
    }
  }
  ```
</details>

<details>
  <summary>Expected</summary>

  ```json
  {
    "components": {
      "schemas": {
        "External1": {
          "type": "string"
        }
      }
    },
    "info": {
      "title": "foo",
      "version": "0.0.0"
    },
    "openapi": "3.0.0",
    "paths": {
      "/{external1}": {
        "get": {
          "responses": {
            "204": {
              "description": "No content"
            }
          }
        },
        "parameters": [
          {
            "in": "path",
            "name": "external1",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/External1"
            }
          }
        ]
      }
    }
  }
  ```
</details>

<details>
  <summary>Got</summary>

  ```json
  {
    "info": {
      "title": "foo",
      "version": "0.0.0"
    },
    "openapi": "3.0.0",
    "paths": {
      "/{external1}": {
        "get": {
          "responses": {
            "204": {
              "description": "No content"
            }
          }
        },
        "parameters": [
          {
            "in": "path",
            "name": "external1",
            "required": true,
            "schema": {
              "$ref": "./components.yaml#/components/schemas/External1"
            }
          }
        ]
      }
    }
  }
  ```
</details>

Pull request:
openapi3: internalize refs for path parameters
This is an attempt to close #959.

It adapts the code path for `ops.Parameters` to the same logic as the code path for `op.Parameters` (the path parameters), which already uses `derefParameter` to resolve external references in the operations parameters.

Tests as described in the bug ticket are included.

Patch:
diff --git a/openapi3/internalize_refs.go b/openapi3/internalize_refs.go
index e313e55..191c14f 100644
--- a/openapi3/internalize_refs.go
+++ b/openapi3/internalize_refs.go
@@ -351,7 +351,10 @@ func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameReso
 		ops.Ref = ""
 
 		for _, param := range ops.Parameters {
-			doc.addParameterToSpec(param, refNameResolver, pathIsExternal)
+			isExternal := doc.addParameterToSpec(param, refNameResolver, pathIsExternal)
+			if param.Value != nil {
+				doc.derefParameter(*param.Value, refNameResolver, pathIsExternal || isExternal)
+			}
 		}
 
 		for _, op := range ops.Operations() {


Test patch:
diff --git a/openapi3/internalize_refs_test.go b/openapi3/internalize_refs_test.go
index b5ceb69..90e73f2 100644
--- a/openapi3/internalize_refs_test.go
+++ b/openapi3/internalize_refs_test.go
@@ -23,6 +23,7 @@ func TestInternalizeRefs(t *testing.T) {
 		{"testdata/spec.yaml"},
 		{"testdata/callbacks.yml"},
 		{"testdata/issue831/testref.internalizepath.openapi.yml"},
+		{"testdata/issue959/openapi.yml"},
 	}
 
 	for _, test := range tests {
diff --git a/openapi3/testdata/issue959/components.yml b/openapi3/testdata/issue959/components.yml
new file mode 100644
index 0000000..c7095e9
--- /dev/null
+++ b/openapi3/testdata/issue959/components.yml
@@ -0,0 +1,4 @@
+components:
+  schemas:
+    External1:
+      type: string
\ No newline at end of file
diff --git a/openapi3/testdata/issue959/openapi.yml b/openapi3/testdata/issue959/openapi.yml
new file mode 100644
index 0000000..de5bbfe
--- /dev/null
+++ b/openapi3/testdata/issue959/openapi.yml
@@ -0,0 +1,16 @@
+openapi: 3.0.0
+info:
+  title: foo
+  version: 0.0.0
+paths:
+  /{external1}:
+    parameters:
+      - in: path
+        name: external1
+        required: true
+        schema:
+          $ref: './components.yml#/components/schemas/External1'
+    get:
+      responses:
+        '204':
+          description: No content
\ No newline at end of file
diff --git a/openapi3/testdata/issue959/openapi.yml.internalized.yml b/openapi3/testdata/issue959/openapi.yml.internalized.yml
new file mode 100644
index 0000000..3cbe674
--- /dev/null
+++ b/openapi3/testdata/issue959/openapi.yml.internalized.yml
@@ -0,0 +1,35 @@
+{
+  "components": {
+    "schemas": {
+      "External1": {
+        "type": "string"
+      }
+    }
+  },
+  "info": {
+    "title": "foo",
+    "version": "0.0.0"
+  },
+  "openapi": "3.0.0",
+  "paths": {
+    "/{external1}": {
+      "get": {
+        "responses": {
+          "204": {
+            "description": "No content"
+          }
+        }
+      },
+      "parameters": [
+        {
+          "in": "path",
+          "name": "external1",
+          "required": true,
+          "schema": {
+            "$ref": "#/components/schemas/External1"
+          }
+        }
+      ]
+    }
+  }
+}
\ No newline at end of file