Repository: getkin/kin-openapi (go)

Issue:
Local references not resolved correctly in external files
From https://swagger.io/docs/specification/using-ref/:

> Local Reference – $ref: '#/definitions/myElement' # means go to the root of the current document and then find elements definitions and myElement one after one.

However in kin-openapi when the specification includes a Remote Reference to a document with a Local reference, the local references in the document cannot be found.

Example:

*ext.json*
```json
{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "definitions": {
        "a": {
            "type": "string"
        },
        "b": {
            "type": "object",
            "properties": {
                "name": {
                    "$ref": "#/definitions/a"
                }
            }
        }
    }
}
```

*spec.yaml*
```yaml
openapi: 3.0.1
components:
  schemas:
    Test:
      type: object
      properties:
        test:
          $ref: 'ext.json#/definitions/b'
```

Loading the spec (package openapi3) raises an error because it can't find `definitions` when trying to resolve `#/definitions/a` in ext.json. Code to reproduce is available in https://gist.github.com/roee88/626d93f9e73776c7f1ec3aa59d7db988#file-extref_test-go. 

Changing `#/definitions/a` to `ext.json#/definitions/a` in the `ext.json` file bypass the problem but this is not an acceptable solution because often you don't have control of the referenced files (plus I suspect it makes them invalid when used independently but unsure). 

Pull request description:
Fix following refs to non-openapi3 root documents (but that are sub-documents)
Fixes #344

Patch:
diff --git a/.gitignore b/.gitignore
index a5bf17c..31ab03f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,4 +18,3 @@
 
  # IntelliJ / GoLand
 .idea
-
diff --git a/openapi3/callback.go b/openapi3/callback.go
index 3342331..b216d1a 100644
--- a/openapi3/callback.go
+++ b/openapi3/callback.go
@@ -13,7 +13,7 @@ var _ jsonpointer.JSONPointable = (*Callbacks)(nil)
 
 func (c Callbacks) JSONLookup(token string) (interface{}, error) {
 	ref, ok := c[token]
-	if ref == nil || ok == false {
+	if ref == nil || !ok {
 		return nil, fmt.Errorf("object has no field %q", token)
 	}
 
diff --git a/openapi3/examples.go b/openapi3/examples.go
index 5f6255b..98f79b8 100644
--- a/openapi3/examples.go
+++ b/openapi3/examples.go
@@ -13,7 +13,7 @@ var _ jsonpointer.JSONPointable = (*Examples)(nil)
 
 func (e Examples) JSONLookup(token string) (interface{}, error) {
 	ref, ok := e[token]
-	if ref == nil || ok == false {
+	if ref == nil || !ok {
 		return nil, fmt.Errorf("object has no field %q", token)
 	}
 
diff --git a/openapi3/header.go b/openapi3/header.go
index 3adb2ea..bab30a4 100644
--- a/openapi3/header.go
+++ b/openapi3/header.go
@@ -14,7 +14,7 @@ var _ jsonpointer.JSONPointable = (*Headers)(nil)
 
 func (h Headers) JSONLookup(token string) (interface{}, error) {
 	ref, ok := h[token]
-	if ref == nil || ok == false {
+	if ref == nil || !ok {
 		return nil, fmt.Errorf("object has no field %q", token)
 	}
 
diff --git a/openapi3/schema.go b/openapi3/schema.go
index 9c3103a..22cb842 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -1266,7 +1266,7 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [
 			Value:       value,
 			Schema:      schema,
 			SchemaField: "uniqueItems",
-			Reason:      fmt.Sprintf("duplicate items found"),
+			Reason:      "duplicate items found",
 		}
 		if !settings.multiError {
 			return err
diff --git a/openapi3/swagger_loader.go b/openapi3/swagger_loader.go
index 8ae8d04..2cb951e 100644
--- a/openapi3/swagger_loader.go
+++ b/openapi3/swagger_loader.go
@@ -63,6 +63,11 @@ func (swaggerLoader *SwaggerLoader) LoadSwaggerFromURI(location *url.URL) (*Swag
 	return swaggerLoader.loadSwaggerFromURIInternal(location)
 }
 
+// LoadSwaggerFromFile loads a spec from a local file path
+func (swaggerLoader *SwaggerLoader) LoadSwaggerFromFile(path string) (*Swagger, error) {
+	return swaggerLoader.LoadSwaggerFromURI(&url.URL{Path: path})
+}
+
 func (swaggerLoader *SwaggerLoader) loadSwaggerFromURIInternal(location *url.URL) (*Swagger, error) {
 	data, err := swaggerLoader.readURL(location)
 	if err != nil {
@@ -71,35 +76,41 @@ func (swaggerLoader *SwaggerLoader) loadSwaggerFromURIInternal(location *url.URL
 	return swaggerLoader.loadSwaggerFromDataWithPathInternal(data, location)
 }
 
-// loadSingleElementFromURI reads the data from ref and unmarshals to the passed element.
-func (swaggerLoader *SwaggerLoader) loadSingleElementFromURI(ref string, rootPath *url.URL, element json.Unmarshaler) error {
+func (swaggerLoader *SwaggerLoader) allowsExternalRefs(ref string) (err error) {
 	if !swaggerLoader.IsExternalRefsAllowed {
-		return fmt.Errorf("encountered non-allowed external reference: %q", ref)
+		err = fmt.Errorf("encountered disallowed external reference: %q", ref)
+	}
+	return
+}
+
+// loadSingleElementFromURI reads the data from ref and unmarshals to the passed element.
+func (swaggerLoader *SwaggerLoader) loadSingleElementFromURI(ref string, rootPath *url.URL, element json.Unmarshaler) (*url.URL, error) {
+	if err := swaggerLoader.allowsExternalRefs(ref); err != nil {
+		return nil, err
 	}
 
 	parsedURL, err := url.Parse(ref)
 	if err != nil {
-		return err
+		return nil, err
 	}
-
-	if parsedURL.Fragment != "" {
-		return errors.New("references to files which contain more than one element definition are not supported")
+	if fragment := parsedURL.Fragment; fragment != "" {
+		return nil, fmt.Errorf("unexpected ref fragment %q", fragment)
 	}
 
 	resolvedPath, err := resolvePath(rootPath, parsedURL)
 	if err != nil {
-		return fmt.Errorf("could not resolve path: %v", err)
+		return nil, fmt.Errorf("could not resolve path: %v", err)
 	}
 
 	data, err := swaggerLoader.readURL(resolvedPath)
 	if err != nil {
-		return err
+		return nil, err
 	}
 	if err := yaml.Unmarshal(data, element); err != nil {
-		return err
+		return nil, err
 	}
 
-	return nil
+	return resolvedPath, nil
 }
 
 func (swaggerLoader *SwaggerLoader) readURL(location *url.URL) ([]byte, error) {
@@ -121,28 +132,9 @@ func (swaggerLoader *SwaggerLoader) readURL(location *url.URL) ([]byte, error) {
 	return ioutil.ReadFile(location.Path)
 }
 
-// LoadSwaggerFromFile loads a spec from a local file path
-func (swaggerLoader *SwaggerLoader) LoadSwaggerFromFile(path string) (*Swagger, error) {
-	swaggerLoader.resetVisitedPathItemRefs()
-	return swaggerLoader.loadSwaggerFromFileInternal(path)
-}
-
-func (swaggerLoader *SwaggerLoader) loadSwaggerFromFileInternal(path string) (*Swagger, error) {
-	pathAsURL := &url.URL{Path: path}
-	data, err := swaggerLoader.readURL(pathAsURL)
-	if err != nil {
-		return nil, err
-	}
-	return swaggerLoader.loadSwaggerFromDataWithPathInternal(data, pathAsURL)
-}
-
 // LoadSwaggerFromData loads a spec from a byte array
 func (swaggerLoader *SwaggerLoader) LoadSwaggerFromData(data []byte) (*Swagger, error) {
 	swaggerLoader.resetVisitedPathItemRefs()
-	return swaggerLoader.loadSwaggerFromDataInternal(data)
-}
-
-func (swaggerLoader *SwaggerLoader) loadSwaggerFromDataInternal(data []byte) (*Swagger, error) {
 	doc := &Swagger{}
 	if err := yaml.Unmarshal(data, doc); err != nil {
 		return nil, err
@@ -239,15 +231,11 @@ func (swaggerLoader *SwaggerLoader) ResolveRefsIn(swagger *Swagger, path *url.UR
 	return
 }
 
-func copyURL(basePath *url.URL) (*url.URL, error) {
-	return url.Parse(basePath.String())
-}
-
 func join(basePath *url.URL, relativePath *url.URL) (*url.URL, error) {
 	if basePath == nil {
 		return relativePath, nil
 	}
-	newPath, err := copyURL(basePath)
+	newPath, err := url.Parse(basePath.String())
 	if err != nil {
 		return nil, fmt.Errorf("cannot copy path: %q", basePath.String())
 	}
@@ -292,18 +280,34 @@ func (swaggerLoader *SwaggerLoader) resolveComponent(
 		return nil, fmt.Errorf("expected fragment prefix '#/' in URI %q", ref)
 	}
 
-	var cursor interface{}
-	cursor = swagger
-	for _, pathPart := range strings.Split(fragment[1:], "/") {
-		pathPart = unescapeRefString(pathPart)
+	drill := func(cursor interface{}) (interface{}, error) {
+		for _, pathPart := range strings.Split(fragment[1:], "/") {
+			pathPart = unescapeRefString(pathPart)
 
-		if cursor, err = drillIntoSwaggerField(cursor, pathPart); err != nil {
-			e := failedToResolveRefFragmentPart(ref, pathPart)
-			return nil, fmt.Errorf("%s: %s", e.Error(), err.Error())
+			if cursor, err = drillIntoSwaggerField(cursor, pathPart); err != nil {
+				e := failedToResolveRefFragmentPart(ref, pathPart)
+				return nil, fmt.Errorf("%s: %s", e.Error(), err.Error())
+			}
+			if cursor == nil {
+				return nil, failedToResolveRefFragmentPart(ref, pathPart)
+			}
+		}
+		return cursor, nil
+	}
+	var cursor interface{}
+	if cursor, err = drill(swagger); err != nil {
+		var err2 error
+		data, err2 := swaggerLoader.readURL(path)
+		if err2 != nil {
+			return nil, err
 		}
-		if cursor == nil {
-			return nil, failedToResolveRefFragmentPart(ref, pathPart)
+		if err2 = yaml.Unmarshal(data, &cursor); err2 != nil {
+			return nil, err
+		}
+		if cursor, err2 = drill(cursor); err2 != nil || cursor == nil {
+			return nil, err
 		}
+		err = nil
 	}
 
 	switch {
@@ -388,33 +392,34 @@ func drillIntoSwaggerField(cursor interface{}, fieldName string) (interface{}, e
 }
 
 func (swaggerLoader *SwaggerLoader) resolveRefSwagger(swagger *Swagger, ref string, path *url.URL) (*Swagger, string, *url.URL, error) {
-	componentPath := path
-	if !strings.HasPrefix(ref, "#") {
-		if !swaggerLoader.IsExternalRefsAllowed {
-			return nil, "", nil, fmt.Errorf("encountered non-allowed external reference: %q", ref)
-		}
-		parsedURL, err := url.Parse(ref)
-		if err != nil {
-			return nil, "", nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL)
-		}
-		fragment := parsedURL.Fragment
-		parsedURL.Fragment = ""
+	if ref != "" && ref[0] == '#' {
+		return swagger, ref, path, nil
+	}
 
-		resolvedPath, err := resolvePath(path, parsedURL)
-		if err != nil {
-			return nil, "", nil, fmt.Errorf("error resolving path: %v", err)
-		}
+	if err := swaggerLoader.allowsExternalRefs(ref); err != nil {
+		return nil, "", nil, err
+	}
 
-		if swagger, err = swaggerLoader.loadSwaggerFromURIInternal(resolvedPath); err != nil {
-			return nil, "", nil, fmt.Errorf("error resolving reference %q: %v", ref, err)
-		}
-		ref = "#" + fragment
-		componentPath = resolvedPath
+	parsedURL, err := url.Parse(ref)
+	if err != nil {
+		return nil, "", nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL)
+	}
+	fragment := parsedURL.Fragment
+	parsedURL.Fragment = ""
+
+	var resolvedPath *url.URL
+	if resolvedPath, err = resolvePath(path, parsedURL); err != nil {
+		return nil, "", nil, fmt.Errorf("error resolving path: %v", err)
+	}
+
+	if swagger, err = swaggerLoader.loadSwaggerFromURIInternal(resolvedPath); err != nil {
+		return nil, "", nil, fmt.Errorf("error resolving reference %q: %v", ref, err)
 	}
-	return swagger, ref, componentPath, nil
+
+	return swagger, "#" + fragment, resolvedPath, nil
 }
 
-func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component *HeaderRef, documentPath *url.URL) error {
+func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component *HeaderRef, documentPath *url.URL) (err error) {
 	if component != nil && component.Value != nil {
 		if swaggerLoader.visitedHeader == nil {
 			swaggerLoader.visitedHeader = make(map[*Header]struct{})
@@ -425,17 +430,15 @@ func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component
 		swaggerLoader.visitedHeader[component.Value] = struct{}{}
 	}
 
-	const prefix = "#/components/headers/"
 	if component == nil {
 		return errors.New("invalid header: value MUST be an object")
 	}
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
 			var header Header
-			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &header); err != nil {
+			if documentPath, err = swaggerLoader.loadSingleElementFromURI(ref, documentPath, &header); err != nil {
 				return err
 			}
-
 			component.Value = &header
 		} else {
 			var resolved HeaderRef
@@ -453,6 +456,7 @@ func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component
 	if value == nil {
 		return nil
 	}
+
 	if schema := value.Schema; schema != nil {
 		if err := swaggerLoader.resolveSchemaRef(swagger, schema, documentPath); err != nil {
 			return err
@@ -461,7 +465,7 @@ func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component
 	return nil
 }
 
-func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, component *ParameterRef, documentPath *url.URL) error {
+func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, component *ParameterRef, documentPath *url.URL) (err error) {
 	if component != nil && component.Value != nil {
 		if swaggerLoader.visitedParameter == nil {
 			swaggerLoader.visitedParameter = make(map[*Parameter]struct{})
@@ -472,7 +476,6 @@ func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, compon
 		swaggerLoader.visitedParameter[component.Value] = struct{}{}
 	}
 
-	const prefix = "#/components/parameters/"
 	if component == nil {
 		return errors.New("invalid parameter: value MUST be an object")
 	}
@@ -480,7 +483,7 @@ func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, compon
 	if ref != "" {
 		if isSingleRefElement(ref) {
 			var param Parameter
-			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &param); err != nil {
+			if documentPath, err = swaggerLoader.loadSingleElementFromURI(ref, documentPath, &param); err != nil {
 				return err
 			}
 			component.Value = &param
@@ -501,30 +504,25 @@ func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, compon
 		return nil
 	}
 
-	refDocumentPath, err := referencedDocumentPath(documentPath, ref)
-	if err != nil {
-		return err
-	}
-
 	if value.Content != nil && value.Schema != nil {
 		return errors.New("cannot contain both schema and content in a parameter")
 	}
 	for _, contentType := range value.Content {
 		if schema := contentType.Schema; schema != nil {
-			if err := swaggerLoader.resolveSchemaRef(swagger, schema, refDocumentPath); err != nil {
+			if err := swaggerLoader.resolveSchemaRef(swagger, schema, documentPath); err != nil {
 				return err
 			}
 		}
 	}
 	if schema := value.Schema; schema != nil {
-		if err := swaggerLoader.resolveSchemaRef(swagger, schema, refDocumentPath); err != nil {
+		if err := swaggerLoader.resolveSchemaRef(swagger, schema, documentPath); err != nil {
 			return err
 		}
 	}
 	return nil
 }
 
-func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, component *RequestBodyRef, documentPath *url.URL) error {
+func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, component *RequestBodyRef, documentPath *url.URL) (err error) {
 	if component != nil && component.Value != nil {
 		if swaggerLoader.visitedRequestBody == nil {
 			swaggerLoader.visitedRequestBody = make(map[*RequestBody]struct{})
@@ -535,17 +533,15 @@ func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, comp
 		swaggerLoader.visitedRequestBody[component.Value] = struct{}{}
 	}
 
-	const prefix = "#/components/requestBodies/"
 	if component == nil {
 		return errors.New("invalid requestBody: value MUST be an object")
 	}
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
 			var requestBody RequestBody
-			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &requestBody); err != nil {
+			if documentPath, err = swaggerLoader.loadSingleElementFromURI(ref, documentPath, &requestBody); err != nil {
 				return err
 			}
-
 			component.Value = &requestBody
 		} else {
 			var resolved RequestBodyRef
@@ -563,6 +559,7 @@ func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, comp
 	if value == nil {
 		return nil
 	}
+
 	for _, contentType := range value.Content {
 		for name, example := range contentType.Examples {
 			if err := swaggerLoader.resolveExampleRef(swagger, example, documentPath); err != nil {
@@ -579,7 +576,7 @@ func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, comp
 	return nil
 }
 
-func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, component *ResponseRef, documentPath *url.URL) error {
+func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, component *ResponseRef, documentPath *url.URL) (err error) {
 	if component != nil && component.Value != nil {
 		if swaggerLoader.visitedResponse == nil {
 			swaggerLoader.visitedResponse = make(map[*Response]struct{})
@@ -590,7 +587,6 @@ func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, compone
 		swaggerLoader.visitedResponse[component.Value] = struct{}{}
 	}
 
-	const prefix = "#/components/responses/"
 	if component == nil {
 		return errors.New("invalid response: value MUST be an object")
 	}
@@ -598,7 +594,7 @@ func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, compone
 	if ref != "" {
 		if isSingleRefElement(ref) {
 			var resp Response
-			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &resp); err != nil {
+			if documentPath, err = swaggerLoader.loadSingleElementFromURI(ref, documentPath, &resp); err != nil {
 				return err
 			}
 			component.Value = &resp
@@ -614,17 +610,13 @@ func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, compone
 			component.Value = resolved.Value
 		}
 	}
-	refDocumentPath, err := referencedDocumentPath(documentPath, ref)
-	if err != nil {
-		return err
-	}
-
 	value := component.Value
 	if value == nil {
 		return nil
 	}
+
 	for _, header := range value.Headers {
-		if err := swaggerLoader.resolveHeaderRef(swagger, header, refDocumentPath); err != nil {
+		if err := swaggerLoader.resolveHeaderRef(swagger, header, documentPath); err != nil {
 			return err
 		}
 	}
@@ -633,27 +625,27 @@ func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, compone
 			continue
 		}
 		for name, example := range contentType.Examples {
-			if err := swaggerLoader.resolveExampleRef(swagger, example, refDocumentPath); err != nil {
+			if err := swaggerLoader.resolveExampleRef(swagger, example, documentPath); err != nil {
 				return err
 			}
 			contentType.Examples[name] = example
 		}
 		if schema := contentType.Schema; schema != nil {
-			if err := swaggerLoader.resolveSchemaRef(swagger, schema, refDocumentPath); err != nil {
+			if err := swaggerLoader.resolveSchemaRef(swagger, schema, documentPath); err != nil {
 				return err
 			}
 			contentType.Schema = schema
 		}
 	}
 	for _, link := range value.Links {
-		if err := swaggerLoader.resolveLinkRef(swagger, link, refDocumentPath); err != nil {
+		if err := swaggerLoader.resolveLinkRef(swagger, link, documentPath); err != nil {
 			return err
 		}
 	}
 	return nil
 }
 
-func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component *SchemaRef, documentPath *url.URL) error {
+func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component *SchemaRef, documentPath *url.URL) (err error) {
 	if component != nil && component.Value != nil {
 		if swaggerLoader.visitedSchema == nil {
 			swaggerLoader.visitedSchema = make(map[*Schema]struct{})
@@ -664,7 +656,6 @@ func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component
 		swaggerLoader.visitedSchema[component.Value] = struct{}{}
 	}
 
-	const prefix = "#/components/schemas/"
 	if component == nil {
 		return errors.New("invalid schema: value MUST be an object")
 	}
@@ -672,7 +663,7 @@ func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component
 	if ref != "" {
 		if isSingleRefElement(ref) {
 			var schema Schema
-			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &schema); err != nil {
+			if documentPath, err = swaggerLoader.loadSingleElementFromURI(ref, documentPath, &schema); err != nil {
 				return err
 			}
 			component.Value = &schema
@@ -688,12 +679,6 @@ func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component
 			component.Value = resolved.Value
 		}
 	}
-
-	refDocumentPath, err := referencedDocumentPath(documentPath, ref)
-	if err != nil {
-		return err
-	}
-
 	value := component.Value
 	if value == nil {
 		return nil
@@ -701,45 +686,44 @@ func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component
 
 	// ResolveRefs referred schemas
 	if v := value.Items; v != nil {
-		if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
+		if err := swaggerLoader.resolveSchemaRef(swagger, v, documentPath); err != nil {
 			return err
 		}
 	}
 	for _, v := range value.Properties {
-		if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
+		if err := swaggerLoader.resolveSchemaRef(swagger, v, documentPath); err != nil {
 			return err
 		}
 	}
 	if v := value.AdditionalProperties; v != nil {
-		if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
+		if err := swaggerLoader.resolveSchemaRef(swagger, v, documentPath); err != nil {
 			return err
 		}
 	}
 	if v := value.Not; v != nil {
-		if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
+		if err := swaggerLoader.resolveSchemaRef(swagger, v, documentPath); err != nil {
 			return err
 		}
 	}
 	for _, v := range value.AllOf {
-		if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
+		if err := swaggerLoader.resolveSchemaRef(swagger, v, documentPath); err != nil {
 			return err
 		}
 	}
 	for _, v := range value.AnyOf {
-		if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
+		if err := swaggerLoader.resolveSchemaRef(swagger, v, documentPath); err != nil {
 			return err
 		}
 	}
 	for _, v := range value.OneOf {
-		if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil {
+		if err := swaggerLoader.resolveSchemaRef(swagger, v, documentPath); err != nil {
 			return err
 		}
 	}
-
 	return nil
 }
 
-func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, component *SecuritySchemeRef, documentPath *url.URL) error {
+func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, component *SecuritySchemeRef, documentPath *url.URL) (err error) {
 	if component != nil && component.Value != nil {
 		if swaggerLoader.visitedSecurityScheme == nil {
 			swaggerLoader.visitedSecurityScheme = make(map[*SecurityScheme]struct{})
@@ -750,17 +734,15 @@ func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, c
 		swaggerLoader.visitedSecurityScheme[component.Value] = struct{}{}
 	}
 
-	const prefix = "#/components/securitySchemes/"
 	if component == nil {
 		return errors.New("invalid securityScheme: value MUST be an object")
 	}
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
 			var scheme SecurityScheme
-			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &scheme); err != nil {
+			if documentPath, err = swaggerLoader.loadSingleElementFromURI(ref, documentPath, &scheme); err != nil {
 				return err
 			}
-
 			component.Value = &scheme
 		} else {
 			var resolved SecuritySchemeRef
@@ -777,7 +759,7 @@ func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, c
 	return nil
 }
 
-func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, component *ExampleRef, documentPath *url.URL) error {
+func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, component *ExampleRef, documentPath *url.URL) (err error) {
 	if component != nil && component.Value != nil {
 		if swaggerLoader.visitedExample == nil {
 			swaggerLoader.visitedExample = make(map[*Example]struct{})
@@ -788,17 +770,15 @@ func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, componen
 		swaggerLoader.visitedExample[component.Value] = struct{}{}
 	}
 
-	const prefix = "#/components/examples/"
 	if component == nil {
 		return errors.New("invalid example: value MUST be an object")
 	}
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
 			var example Example
-			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &example); err != nil {
+			if documentPath, err = swaggerLoader.loadSingleElementFromURI(ref, documentPath, &example); err != nil {
 				return err
 			}
-
 			component.Value = &example
 		} else {
 			var resolved ExampleRef
@@ -815,7 +795,7 @@ func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, componen
 	return nil
 }
 
-func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *LinkRef, documentPath *url.URL) error {
+func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *LinkRef, documentPath *url.URL) (err error) {
 	if component != nil && component.Value != nil {
 		if swaggerLoader.visitedLink == nil {
 			swaggerLoader.visitedLink = make(map[*Link]struct{})
@@ -826,17 +806,15 @@ func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *
 		swaggerLoader.visitedLink[component.Value] = struct{}{}
 	}
 
-	const prefix = "#/components/links/"
 	if component == nil {
 		return errors.New("invalid link: value MUST be an object")
 	}
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
 			var link Link
-			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &link); err != nil {
+			if documentPath, err = swaggerLoader.loadSingleElementFromURI(ref, documentPath, &link); err != nil {
 				return err
 			}
-
 			component.Value = &link
 		} else {
 			var resolved LinkRef
@@ -864,7 +842,6 @@ func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypo
 	}
 	swaggerLoader.visitedPathItemRefs[key] = struct{}{}
 
-	const prefix = "#/paths/"
 	if pathItem == nil {
 		return errors.New("invalid path item: value MUST be an object")
 	}
@@ -872,7 +849,7 @@ func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypo
 	if ref != "" {
 		if isSingleRefElement(ref) {
 			var p PathItem
-			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &p); err != nil {
+			if documentPath, err = swaggerLoader.loadSingleElementFromURI(ref, documentPath, &p); err != nil {
 				return err
 			}
 			*pathItem = p
@@ -881,11 +858,11 @@ func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypo
 				return
 			}
 
-			if !strings.HasPrefix(ref, prefix) {
-				err = fmt.Errorf("expected prefix %q in URI %q", prefix, ref)
-				return
+			rest := strings.TrimPrefix(ref, "#/paths/")
+			if rest == ref {
+				return fmt.Errorf(`expected prefix "#/paths/" in URI %q`, ref)
 			}
-			id := unescapeRefString(ref[len(prefix):])
+			id := unescapeRefString(rest)
 
 			definitions := swagger.Paths
 			if definitions == nil {
@@ -900,55 +877,31 @@ func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypo
 		}
 	}
 
-	refDocumentPath, err := referencedDocumentPath(documentPath, ref)
-	if err != nil {
-		return err
-	}
-
 	for _, parameter := range pathItem.Parameters {
-		if err = swaggerLoader.resolveParameterRef(swagger, parameter, refDocumentPath); err != nil {
+		if err = swaggerLoader.resolveParameterRef(swagger, parameter, documentPath); err != nil {
 			return
 		}
 	}
 	for _, operation := range pathItem.Operations() {
 		for _, parameter := range operation.Parameters {
-			if err = swaggerLoader.resolveParameterRef(swagger, parameter, refDocumentPath); err != nil {
+			if err = swaggerLoader.resolveParameterRef(swagger, parameter, documentPath); err != nil {
 				return
 			}
 		}
 		if requestBody := operation.RequestBody; requestBody != nil {
-			if err = swaggerLoader.resolveRequestBodyRef(swagger, requestBody, refDocumentPath); err != nil {
+			if err = swaggerLoader.resolveRequestBodyRef(swagger, requestBody, documentPath); err != nil {
 				return
 			}
 		}
 		for _, response := range operation.Responses {
-			if err = swaggerLoader.resolveResponseRef(swagger, response, refDocumentPath); err != nil {
+			if err = swaggerLoader.resolveResponseRef(swagger, response, documentPath); err != nil {
 				return
 			}
 		}
 	}
-
-	return nil
+	return
 }
 
 func unescapeRefString(ref string) string {
 	return strings.Replace(strings.Replace(ref, "~1", "/", -1), "~0", "~", -1)
 }
-
-func referencedDocumentPath(documentPath *url.URL, ref string) (*url.URL, error) {
-	if documentPath == nil {
-		return nil, nil
-	}
-
-	newDocumentPath, err := copyURL(documentPath)
-	if err != nil {
-		return nil, err
-	}
-	refPath, err := url.Parse(ref)
-	if err != nil {
-		return nil, err
-	}
-	newDocumentPath.Path = path.Join(path.Dir(newDocumentPath.Path), path.Dir(refPath.Path)) + "/"
-
-	return newDocumentPath, nil
-}
diff --git a/openapi3filter/validation_error_encoder.go b/openapi3filter/validation_error_encoder.go
index 47f9cd9..707b22d 100644
--- a/openapi3filter/validation_error_encoder.go
+++ b/openapi3filter/validation_error_encoder.go
@@ -152,7 +152,7 @@ func convertSchemaError(e *RequestError, innerErr *openapi3.SchemaError) *Valida
 			strings.Join(enums, ", "))
 		value := fmt.Sprintf("%v", innerErr.Value)
 		if e.Parameter != nil &&
-			(e.Parameter.Explode == nil || *e.Parameter.Explode == true) &&
+			(e.Parameter.Explode == nil || *e.Parameter.Explode) &&
 			(e.Parameter.Style == "" || e.Parameter.Style == "form") &&
 			strings.Contains(value, ",") {
 			parts := strings.Split(value, ",")
diff --git a/routers/gorillamux/router.go b/routers/gorillamux/router.go
index 6bdda1a..896f5fa 100644
--- a/routers/gorillamux/router.go
+++ b/routers/gorillamux/router.go
@@ -135,9 +135,7 @@ func orderedPaths(paths map[string]*openapi3.PathItem) []string {
 	for c := 0; c <= max; c++ {
 		if ps, ok := vars[c]; ok {
 			sort.Strings(ps)
-			for _, p := range ps {
-				ordered = append(ordered, p)
-			}
+			ordered = append(ordered, ps...)
 		}
 	}
 	return ordered


Test patch:
diff --git a/openapi3/issue344_test.go b/openapi3/issue344_test.go
new file mode 100644
index 0000000..8a53394
--- /dev/null
+++ b/openapi3/issue344_test.go
@@ -0,0 +1,20 @@
+package openapi3
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestIssue344(t *testing.T) {
+	sl := NewSwaggerLoader()
+	sl.IsExternalRefsAllowed = true
+
+	doc, err := sl.LoadSwaggerFromFile("testdata/spec.yaml")
+	require.NoError(t, err)
+
+	err = doc.Validate(sl.Context)
+	require.NoError(t, err)
+
+	require.Equal(t, "string", doc.Components.Schemas["Test"].Value.Properties["test"].Value.Properties["name"].Value.Type)
+}
diff --git a/openapi3/swagger_loader_referenced_document_path_test.go b/openapi3/swagger_loader_referenced_document_path_test.go
deleted file mode 100644
index de219c3..0000000
--- a/openapi3/swagger_loader_referenced_document_path_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package openapi3
-
-import (
-	"net/url"
-	"testing"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestReferencedDocumentPath(t *testing.T) {
-	httpURL, err := url.Parse("http://example.com/path/to/schemas/test1.yaml")
-	require.NoError(t, err)
-
-	fileURL, err := url.Parse("path/to/schemas/test1.yaml")
-	require.NoError(t, err)
-
-	refEmpty := ""
-	refNoComponent := "moreschemas/test2.yaml"
-	refWithComponent := "moreschemas/test2.yaml#/components/schemas/someobject"
-
-	for _, test := range []struct {
-		path          *url.URL
-		ref, expected string
-	}{
-		{
-			path:     httpURL,
-			ref:      refEmpty,
-			expected: "http://example.com/path/to/schemas/",
-		},
-		{
-			path:     httpURL,
-			ref:      refNoComponent,
-			expected: "http://example.com/path/to/schemas/moreschemas/",
-		},
-		{
-			path:     httpURL,
-			ref:      refWithComponent,
-			expected: "http://example.com/path/to/schemas/moreschemas/",
-		},
-		{
-			path:     fileURL,
-			ref:      refEmpty,
-			expected: "path/to/schemas/",
-		},
-		{
-			path:     fileURL,
-			ref:      refNoComponent,
-			expected: "path/to/schemas/moreschemas/",
-		},
-		{
-			path:     fileURL,
-			ref:      refWithComponent,
-			expected: "path/to/schemas/moreschemas/",
-		},
-	} {
-		result, err := referencedDocumentPath(test.path, test.ref)
-		require.NoError(t, err)
-		require.Equal(t, test.expected, result.String())
-	}
-}
diff --git a/openapi3/swagger_loader_relative_refs_test.go b/openapi3/swagger_loader_relative_refs_test.go
index 8f07468..cb5ab3f 100644
--- a/openapi3/swagger_loader_relative_refs_test.go
+++ b/openapi3/swagger_loader_relative_refs_test.go
@@ -814,25 +814,25 @@ func TestLoadSpecWithRelativeDocumentRefs(t *testing.T) {
 
 const relativeSchemaDocsRefTemplate = `
 openapi: 3.0.0
-info: 
+info:
   title: ""
   version: "1.0"
 paths: {}
-components: 
-  schemas: 
-    TestSchema: 
+components:
+  schemas:
+    TestSchema:
       $ref: relativeDocs/CustomTestSchema.yml
 `
 
 const relativeResponseDocsRefTemplate = `
 openapi: 3.0.0
-info: 
+info:
   title: ""
   version: "1.0"
 paths: {}
-components: 
-  responses: 
-    TestResponse: 
+components:
+  responses:
+    TestResponse:
       $ref: relativeDocs/CustomTestResponse.yml
 `
 
@@ -844,7 +844,7 @@ info:
 paths: {}
 components:
   parameters:
-    TestParameter: 
+    TestParameter:
       $ref: relativeDocs/CustomTestParameter.yml
 `
 
@@ -921,6 +921,8 @@ func TestLoadSpecWithRelativeDocumentRefs2(t *testing.T) {
 
 	// check header
 	require.Equal(t, "header", nestedDirPath.Patch.Responses["200"].Value.Headers["X-Rate-Limit-Reset"].Value.Description)
+	require.Equal(t, "header1", nestedDirPath.Patch.Responses["200"].Value.Headers["X-Another"].Value.Description)
+	require.Equal(t, "header2", nestedDirPath.Patch.Responses["200"].Value.Headers["X-And-Another"].Value.Description)
 
 	// check request body
 	require.Equal(t, "example request", nestedDirPath.Patch.RequestBody.Value.Description)
@@ -939,6 +941,8 @@ func TestLoadSpecWithRelativeDocumentRefs2(t *testing.T) {
 
 	// check header
 	require.Equal(t, "header", nestedDirPath.Patch.Responses["200"].Value.Headers["X-Rate-Limit-Reset"].Value.Description)
+	require.Equal(t, "header1", nestedDirPath.Patch.Responses["200"].Value.Headers["X-Another"].Value.Description)
+	require.Equal(t, "header2", nestedDirPath.Patch.Responses["200"].Value.Headers["X-And-Another"].Value.Description)
 
 	// check request body
 	require.Equal(t, "example request", moreNestedDirPath.Patch.RequestBody.Value.Description)
diff --git a/openapi3/testdata/ext.json b/openapi3/testdata/ext.json
new file mode 100644
index 0000000..df227e6
--- /dev/null
+++ b/openapi3/testdata/ext.json
@@ -0,0 +1,17 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "definitions": {
+        "a": {
+            "type": "string"
+        },
+        "b": {
+            "type": "object",
+            "description": "I use a local reference.",
+            "properties": {
+                "name": {
+                    "$ref": "#/definitions/a"
+                }
+            }
+        }
+    }
+}
diff --git a/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader.yml b/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader.yml
index 9d12ac3..e5cf2b1 100644
--- a/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader.yml
+++ b/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader.yml
@@ -1,1 +1,1 @@
-description: header
\ No newline at end of file
+description: header
diff --git a/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader1.yml b/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader1.yml
new file mode 100644
index 0000000..4a5d8f9
--- /dev/null
+++ b/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader1.yml
@@ -0,0 +1,1 @@
+description: header1
diff --git a/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader1bis.yml b/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader1bis.yml
new file mode 100644
index 0000000..532e792
--- /dev/null
+++ b/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader1bis.yml
@@ -0,0 +1,2 @@
+header:
+  description: header1
diff --git a/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader2.yml b/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader2.yml
new file mode 100644
index 0000000..71536c5
--- /dev/null
+++ b/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader2.yml
@@ -0,0 +1,1 @@
+description: header2
diff --git a/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader2bis.yml b/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader2bis.yml
new file mode 100644
index 0000000..c14ae47
--- /dev/null
+++ b/openapi3/testdata/relativeDocsUseDocumentPath/CustomTestHeader2bis.yml
@@ -0,0 +1,2 @@
+header:
+  description: header2
diff --git a/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/CustomTestPath.yml b/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/CustomTestPath.yml
index 45046c4..6e60880 100644
--- a/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/CustomTestPath.yml
+++ b/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/CustomTestPath.yml
@@ -10,6 +10,10 @@ patch:
       headers:
         X-Rate-Limit-Reset:
           $ref: "../../../CustomTestHeader.yml"
+        X-Another:
+          $ref: ../../../CustomTestHeader1.yml
+        X-And-Another:
+          $ref: ../../../CustomTestHeader2.yml
       content:
         application/json:
           schema:
diff --git a/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/morenested/CustomTestPath.yml b/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/morenested/CustomTestPath.yml
index 647852f..35c5ccf 100644
--- a/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/morenested/CustomTestPath.yml
+++ b/openapi3/testdata/relativeDocsUseDocumentPath/openapi/paths/nesteddir/morenested/CustomTestPath.yml
@@ -10,6 +10,10 @@ patch:
       headers:
         X-Rate-Limit-Reset:
           $ref: "../../../../CustomTestHeader.yml"
+        X-Another:
+          $ref: '../../../../CustomTestHeader1bis.yml#/header'
+        X-And-Another:
+          $ref: '../../../../CustomTestHeader2bis.yml#/header'
       content:
         application/json:
           schema:
diff --git a/openapi3/testdata/spec.yaml b/openapi3/testdata/spec.yaml
new file mode 100644
index 0000000..781312f
--- /dev/null
+++ b/openapi3/testdata/spec.yaml
@@ -0,0 +1,14 @@
+openapi: 3.0.1
+info:
+  version: 1.0.0
+  title: Some Swagger
+  license:
+    name: MIT
+paths: {}
+components:
+  schemas:
+    Test:
+      type: object
+      properties:
+        test:
+          $ref: 'ext.json#/definitions/b'
diff --git a/openapi3gen/openapi3gen_test.go b/openapi3gen/openapi3gen_test.go
index 1422017..a975b2a 100644
--- a/openapi3gen/openapi3gen_test.go
+++ b/openapi3gen/openapi3gen_test.go
@@ -25,7 +25,7 @@ func TestExportedNonTagged(t *testing.T) {
 	type Bla struct {
 		A          string
 		Another    string `json:"another"`
-		yetAnother string
+		yetAnother string // unused because unexported
 		EvenAYaml  string `yaml:"even_a_yaml"`
 	}