Repository: getkin/kin-openapi
Pull request #345 (2021-04-22 19:37:54)

[Bugfix] fail readURL on http code > 399
 if external reference returned http code > 399 should return an error


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..517eca9 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) {
@@ -113,6 +124,9 @@ func (swaggerLoader *SwaggerLoader) readURL(location *url.URL) ([]byte, error) {
 			return nil, err
 		}
 		defer resp.Body.Close()
+		if resp.StatusCode > 399 {
+			return nil, fmt.Errorf("request returned status code %d", resp.StatusCode)
+		}
 		return ioutil.ReadAll(resp.Body)
 	}
 	if location.Scheme != "" || location.Host != "" || location.RawQuery != "" {
@@ -121,28 +135,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 +234,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 +283,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 err2 = yaml.Unmarshal(data, &cursor); err2 != nil {
+			return nil, err
 		}
-		if cursor == nil {
-			return nil, failedToResolveRefFragmentPart(ref, pathPart)
+		if cursor, err2 = drill(cursor); err2 != nil || cursor == nil {
+			return nil, err
 		}
+		err = nil
 	}
 
 	switch {
@@ -388,33 +395,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 +433,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 +459,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 +468,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 +479,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 +486,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 +507,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 +536,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 +562,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 +579,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 +590,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 +597,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 +613,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 +628,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 +659,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 +666,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 +682,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 +689,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 +737,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 +762,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 +773,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 +798,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 +809,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 +845,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 +852,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 +861,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 +880,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/issue341_test.go b/openapi3/issue341_test.go
new file mode 100644
index 0000000..c026d6d
--- /dev/null
+++ b/openapi3/issue341_test.go
@@ -0,0 +1,26 @@
+package openapi3
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestIssue341(t *testing.T) {
+	sl := NewSwaggerLoader()
+	sl.IsExternalRefsAllowed = true
+	doc, err := sl.LoadSwaggerFromFile("testdata/main.yaml")
+	require.NoError(t, err)
+
+	err = doc.Validate(sl.Context)
+	require.NoError(t, err)
+
+	err = sl.ResolveRefsIn(doc, nil)
+	require.NoError(t, err)
+
+	bs, err := doc.MarshalJSON()
+	require.NoError(t, err)
+	require.Equal(t, []byte(`{"components":{},"info":{"title":"test file","version":"n/a"},"openapi":"3.0.0","paths":{"/testpath":{"get":{"responses":{"200":{"$ref":"#/components/responses/testpath_200_response"}}}}}}`), bs)
+
+	require.Equal(t, "string", doc.Paths["/testpath"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Type)
+}
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_http_error_test.go b/openapi3/swagger_loader_http_error_test.go
new file mode 100644
index 0000000..3b3d06c
--- /dev/null
+++ b/openapi3/swagger_loader_http_error_test.go
@@ -0,0 +1,55 @@
+package openapi3
+
+import (
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestLoadFromRemoteURLFailsWithHttpError(t *testing.T) {
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusBadRequest)
+		fmt.Fprint(w, "")
+	}))
+	defer ts.Close()
+
+	spec := []byte(`
+{
+    "openapi": "3.0.0",
+    "info": {
+        "title": "",
+        "version": "1"
+    },
+    "paths": {
+        "/test": {
+            "post": {
+                "responses": {
+                    "default": {
+                        "description": "test",
+                        "headers": {
+                            "X-TEST-HEADER": {
+                                "$ref": "` + ts.URL + `/components.openapi.json#/components/headers/CustomTestHeader"
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}`)
+
+	loader := NewSwaggerLoader()
+	loader.IsExternalRefsAllowed = true
+	swagger, err := loader.LoadSwaggerFromDataWithPath(spec, &url.URL{Path: "testdata/testfilename.openapi.json"})
+
+	require.Nil(t, swagger)
+	require.EqualError(t, err, fmt.Sprintf("error resolving reference \"%s/components.openapi.json#/components/headers/CustomTestHeader\": request returned status code 400", ts.URL))
+
+	swagger, err = loader.LoadSwaggerFromData(spec)
+	require.Nil(t, swagger)
+	require.EqualError(t, err, fmt.Sprintf("error resolving reference \"%s/components.openapi.json#/components/headers/CustomTestHeader\": request returned status code 400", ts.URL))
+}
diff --git a/openapi3/swagger_loader_outside_refs_test.go b/openapi3/swagger_loader_outside_refs_test.go
new file mode 100644
index 0000000..1a5cb1c
--- /dev/null
+++ b/openapi3/swagger_loader_outside_refs_test.go
@@ -0,0 +1,20 @@
+package openapi3
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestLoadOutsideRefs(t *testing.T) {
+	loader := NewSwaggerLoader()
+	loader.IsExternalRefsAllowed = true
+	doc, err := loader.LoadSwaggerFromFile("testdata/303bis/service.yaml")
+	require.NoError(t, err)
+	require.NotNil(t, doc)
+
+	err = doc.Validate(loader.Context)
+	require.NoError(t, err)
+
+	require.Equal(t, "string", doc.Paths["/service"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Items.Value.AllOf[0].Value.Properties["created_at"].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/303bis/common/properties.yaml b/openapi3/testdata/303bis/common/properties.yaml
new file mode 100644
index 0000000..e5b6cdb
--- /dev/null
+++ b/openapi3/testdata/303bis/common/properties.yaml
@@ -0,0 +1,16 @@
+timestamp:
+  type: string
+  description: Date and time in ISO 8601 format.
+  example: "2020-04-09T18:14:30Z"
+  readOnly: true
+  nullable: true
+
+timestamps:
+  type: object
+  properties:
+    created_at:
+      $ref: "#/timestamp"
+    deleted_at:
+      $ref: "#/timestamp"
+    updated_at:
+      $ref: "#/timestamp"
diff --git a/openapi3/testdata/303bis/service.yaml b/openapi3/testdata/303bis/service.yaml
new file mode 100644
index 0000000..39dd066
--- /dev/null
+++ b/openapi3/testdata/303bis/service.yaml
@@ -0,0 +1,28 @@
+openapi: 3.0.0
+info:
+  title: 'some service spec'
+  version: 1.2.3
+
+paths:
+  /service:
+    get:
+      tags:
+        - services/service
+      summary: List services
+      description: List services.
+      operationId: list-services
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: "#/components/schemas/model_service"
+
+components:
+  schemas:
+    model_service:
+      allOf:
+        - $ref: "common/properties.yaml#/timestamps"
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/main.yaml b/openapi3/testdata/main.yaml
new file mode 100644
index 0000000..e973cbe
--- /dev/null
+++ b/openapi3/testdata/main.yaml
@@ -0,0 +1,7 @@
+openapi: "3.0.0"
+info:
+  title: "test file"
+  version: "n/a"
+paths:
+  /testpath:
+    $ref: "testpath.yaml#/paths/~1testpath"
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/openapi3/testdata/testpath.yaml b/openapi3/testdata/testpath.yaml
new file mode 100644
index 0000000..de85bb4
--- /dev/null
+++ b/openapi3/testdata/testpath.yaml
@@ -0,0 +1,15 @@
+paths:
+  /testpath:
+    get:
+      responses:
+        "200":
+          $ref: "#/components/responses/testpath_200_response"
+
+components:
+  responses:
+    testpath_200_response:
+      description: a custom response
+      content:
+        application/json:
+          schema:
+            type: string
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"`
 	}