Repository: getkin/kin-openapi (go)

Issue:
Panic while loading invalid document
Lib panics while loading invalid swagger definition with `LoadSwaggerFromData`:
```
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x1334b84]

goroutine 1 [running]:
github.com/getkin/kin-openapi/openapi3.(*SwaggerLoader).resolveResponseRef(0xc0000972c0, 0xc000099110, 0x0, 0x0, 0x0, 0x0)
	/Users/rsukho/go/src/github.com/getkin/kin-openapi/openapi3/swagger_loader.go:525 +0xe4
github.com/getkin/kin-openapi/openapi3.(*SwaggerLoader).resolvePathItemRef(0xc0000972c0, 0xc000099110, 0xc00015a530, 0x5, 0xc000152580, 0x0, 0x0, 0x8)
	/Users/rsukho/go/src/github.com/getkin/kin-openapi/openapi3/swagger_loader.go:849 +0x3df
github.com/getkin/kin-openapi/openapi3.(*SwaggerLoader).ResolveRefsIn(0xc0000972c0, 0xc000099110, 0x0, 0x900, 0x13c8920)
	/Users/rsukho/go/src/github.com/getkin/kin-openapi/openapi3/swagger_loader.go:217 +0x839
github.com/getkin/kin-openapi/openapi3.(*SwaggerLoader).loadSwaggerFromDataInternal(0xc0000972c0, 0xc0000d2900, 0x836, 0x900, 0x836, 0x900, 0x100742f)
	/Users/rsukho/go/src/github.com/getkin/kin-openapi/openapi3/swagger_loader.go:150 +0xe7
github.com/getkin/kin-openapi/openapi3.(*SwaggerLoader).LoadSwaggerFromData(...)
	/Users/rsukho/go/src/github.com/getkin/kin-openapi/openapi3/swagger_loader.go:142
main.main()
	/Users/rsukho/go/src/github.com/zzell/issue1/main.go:10 +0xc5
exit status 2
```

I made a small snippet to reproduce the issue:

```go
package main

import (
	"log"

	"github.com/getkin/kin-openapi/openapi3"
)

func main() {
	_, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData([]byte(doc))
	if err != nil {
		log.Fatal(err)
	}
}

var doc = `openapi: 3.0.0
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: 'http://petstore.swagger.io/v1'
paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      tags:
        - pets
      parameters:
        - name: limit
          in: query
          description: How many items to return at one time (max 100)
          required: false
          schema:
            type: integer
            format: int32
      responses:
        '200': # <--------------- PANIC HERE

    post:
      summary: Create a pet
      operationId: createPets
      tags:
        - pets
      responses:
        '201':
          description: Null response
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  '/pets/{petId}':
    get:
      summary: Info for a specific pet
      operationId: showPetById
      tags:
        - pets
      parameters:
        - name: petId
          in: path
          required: true
          description: The id of the pet to retrieve
          schema:
            type: string
      responses:
        '200':
          description: Expected response to a valid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pet'
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Pet:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string
    Pets:
      type: array
      items:
        $ref: '#/components/schemas/Pet'
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string`
```

Pull request:
handle the main ref dereferencing issues
Fixes #222

Patch:
diff --git a/openapi3/swagger_loader.go b/openapi3/swagger_loader.go
index 3925e0e..8376734 100644
--- a/openapi3/swagger_loader.go
+++ b/openapi3/swagger_loader.go
@@ -355,15 +355,16 @@ func (swaggerLoader *SwaggerLoader) resolveRefSwagger(swagger *Swagger, ref stri
 }
 
 func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component *HeaderRef, path *url.URL) error {
-	// Prevent infinite recursion
 	visited := swaggerLoader.visited
 	if _, isVisited := visited[component]; isVisited {
 		return nil
 	}
 	visited[component] = struct{}{}
 
-	// Resolve ref
 	const prefix = "#/components/headers/"
+	if component == nil {
+		return errors.New("invalid header: value MUST be a JSON object")
+	}
 	if ref := component.Ref; len(ref) > 0 {
 		if isSingleRefElement(ref) {
 			var header Header
@@ -400,15 +401,16 @@ func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component
 }
 
 func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, component *ParameterRef, documentPath *url.URL) error {
-	// Prevent infinite recursion
 	visited := swaggerLoader.visited
 	if _, isVisited := visited[component]; isVisited {
 		return nil
 	}
 	visited[component] = struct{}{}
 
-	// Resolve ref
 	const prefix = "#/components/parameters/"
+	if component == nil {
+		return errors.New("invalid parameter: value MUST be a JSON object")
+	}
 	ref := component.Ref
 	if len(ref) > 0 {
 		if isSingleRefElement(ref) {
@@ -461,15 +463,16 @@ func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, compon
 }
 
 func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, component *RequestBodyRef, path *url.URL) error {
-	// Prevent infinite recursion
 	visited := swaggerLoader.visited
 	if _, isVisited := visited[component]; isVisited {
 		return nil
 	}
 	visited[component] = struct{}{}
 
-	// Resolve ref
 	const prefix = "#/components/requestBodies/"
+	if component == nil {
+		return errors.New("invalid requestBody: value MUST be a JSON object")
+	}
 	if ref := component.Ref; len(ref) > 0 {
 		if isSingleRefElement(ref) {
 			var requestBody RequestBody
@@ -514,16 +517,17 @@ func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, comp
 }
 
 func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, component *ResponseRef, documentPath *url.URL) error {
-	// Prevent infinite recursion
 	visited := swaggerLoader.visited
 	if _, isVisited := visited[component]; isVisited {
 		return nil
 	}
 	visited[component] = struct{}{}
 
-	// Resolve ref
-	ref := component.Ref
 	const prefix = "#/components/responses/"
+	if component == nil {
+		return errors.New("invalid response: value MUST be a JSON object")
+	}
+	ref := component.Ref
 	if len(ref) > 0 {
 
 		if isSingleRefElement(ref) {
@@ -588,15 +592,16 @@ func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, compone
 }
 
 func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component *SchemaRef, documentPath *url.URL) error {
-	// Prevent infinite recursion
 	visited := swaggerLoader.visited
 	if _, isVisited := visited[component]; isVisited {
 		return nil
 	}
 	visited[component] = struct{}{}
 
-	// Resolve ref
 	const prefix = "#/components/schemas/"
+	if component == nil {
+		return errors.New("invalid schema: value MUST be a JSON object")
+	}
 	ref := component.Ref
 	if len(ref) > 0 {
 		if isSingleRefElement(ref) {
@@ -673,15 +678,16 @@ func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component
 }
 
 func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, component *SecuritySchemeRef, path *url.URL) error {
-	// Prevent infinite recursion
 	visited := swaggerLoader.visited
 	if _, isVisited := visited[component]; isVisited {
 		return nil
 	}
 	visited[component] = struct{}{}
 
-	// Resolve ref
 	const prefix = "#/components/securitySchemes/"
+	if component == nil {
+		return errors.New("invalid securityScheme: value MUST be a JSON object")
+	}
 	if ref := component.Ref; len(ref) > 0 {
 		if isSingleRefElement(ref) {
 			var scheme SecurityScheme
@@ -709,7 +715,6 @@ func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, c
 }
 
 func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, component *ExampleRef, path *url.URL) error {
-	// Prevent infinite recursion
 	visited := swaggerLoader.visited
 	if _, isVisited := visited[component]; isVisited {
 		return nil
@@ -717,6 +722,9 @@ func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, componen
 	visited[component] = struct{}{}
 
 	const prefix = "#/components/examples/"
+	if component == nil {
+		return errors.New("invalid example: value MUST be a JSON object")
+	}
 	if ref := component.Ref; len(ref) > 0 {
 		if isSingleRefElement(ref) {
 			var example Example
@@ -744,7 +752,6 @@ func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, componen
 }
 
 func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *LinkRef, path *url.URL) error {
-	// Prevent infinite recursion
 	visited := swaggerLoader.visited
 	if _, isVisited := visited[component]; isVisited {
 		return nil
@@ -752,6 +759,9 @@ func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *
 	visited[component] = struct{}{}
 
 	const prefix = "#/components/links/"
+	if component == nil {
+		return errors.New("invalid link: value MUST be a JSON object")
+	}
 	if ref := component.Ref; len(ref) > 0 {
 		if isSingleRefElement(ref) {
 			var link Link
@@ -779,7 +789,6 @@ func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *
 }
 
 func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypoint string, pathItem *PathItem, documentPath *url.URL) (err error) {
-	// Prevent infinite recursion
 	visited := swaggerLoader.visitedFiles
 	key := "_"
 	if documentPath != nil {
@@ -791,6 +800,10 @@ func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypo
 	}
 	visited[key] = struct{}{}
 
+	const prefix = "#/paths/"
+	if pathItem == nil {
+		return errors.New("invalid path item: value MUST be a JSON object")
+	}
 	ref := pathItem.Ref
 	if ref != "" {
 		if isSingleRefElement(ref) {
@@ -804,7 +817,6 @@ func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypo
 				return
 			}
 
-			prefix := "#/paths/"
 			if !strings.HasPrefix(ref, prefix) {
 				err = fmt.Errorf("expected prefix '%s' in URI '%s'", prefix, ref)
 				return


Test patch:
diff --git a/openapi3/refs_test.go b/openapi3/refs_test.go
new file mode 100644
index 0000000..ed12b89
--- /dev/null
+++ b/openapi3/refs_test.go
@@ -0,0 +1,111 @@
+package openapi3
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestIssue222(t *testing.T) {
+	spec := `
+openapi: 3.0.0
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+  license:
+    name: MIT
+servers:
+  - url: 'http://petstore.swagger.io/v1'
+paths:
+  /pets:
+    get:
+      summary: List all pets
+      operationId: listPets
+      tags:
+        - pets
+      parameters:
+        - name: limit
+          in: query
+          description: How many items to return at one time (max 100)
+          required: false
+          schema:
+            type: integer
+            format: int32
+      responses:
+        '200': # <--------------- PANIC HERE
+
+    post:
+      summary: Create a pet
+      operationId: createPets
+      tags:
+        - pets
+      responses:
+        '201':
+          description: Null response
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+  '/pets/{petId}':
+    get:
+      summary: Info for a specific pet
+      operationId: showPetById
+      tags:
+        - pets
+      parameters:
+        - name: petId
+          in: path
+          required: true
+          description: The id of the pet to retrieve
+          schema:
+            type: string
+      responses:
+        '200':
+          description: Expected response to a valid request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Pet'
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+components:
+  schemas:
+    Pet:
+      type: object
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: integer
+          format: int64
+        name:
+          type: string
+        tag:
+          type: string
+    Pets:
+      type: array
+      items:
+        $ref: '#/components/schemas/Pet'
+    Error:
+      type: object
+      required:
+        - code
+        - message
+      properties:
+        code:
+          type: integer
+          format: int32
+        message:
+          type: string
+`
+
+	_, err := NewSwaggerLoader().LoadSwaggerFromData([]byte(spec))
+	require.EqualError(t, err, `invalid response: value MUST be a JSON object`)
+}