Repository: getkin/kin-openapi
Pull request #307 (2021-02-25 10:32:48)

Enlarge support for JSON Path in $ref resolution
Fixes https://github.com/getkin/kin-openapi/issues/303


Patch:
diff --git a/openapi3/swagger_loader.go b/openapi3/swagger_loader.go
index 44d98ab..1005dfc 100644
--- a/openapi3/swagger_loader.go
+++ b/openapi3/swagger_loader.go
@@ -20,22 +20,33 @@ func foundUnresolvedRef(ref string) error {
 	return fmt.Errorf("found unresolved ref: %q", ref)
 }
 
-func failedToResolveRefFragment(value string) error {
-	return fmt.Errorf("failed to resolve fragment in URI: %q", value)
-}
-
-func failedToResolveRefFragmentPart(value string, what string) error {
+func failedToResolveRefFragmentPart(value, what string) error {
 	return fmt.Errorf("failed to resolve %q in fragment in URI: %q", what, value)
 }
 
+// SwaggerLoader helps deserialize a Swagger object
 type SwaggerLoader struct {
-	IsExternalRefsAllowed  bool
-	Context                context.Context
+	// IsExternalRefsAllowed enables visiting other files
+	IsExternalRefsAllowed bool
+
+	// LoadSwaggerFromURIFunc allows overriding the file/URL reading func
 	LoadSwaggerFromURIFunc func(loader *SwaggerLoader, url *url.URL) (*Swagger, error)
-	visited                map[interface{}]struct{}
-	visitedFiles           map[string]struct{}
+
+	Context context.Context
+
+	visitedFiles map[string]struct{}
+
+	visitedHeader         map[*Header]struct{}
+	visitedParameter      map[*Parameter]struct{}
+	visitedRequestBody    map[*RequestBody]struct{}
+	visitedResponse       map[*Response]struct{}
+	visitedSchema         map[*Schema]struct{}
+	visitedSecurityScheme map[*SecurityScheme]struct{}
+	visitedExample        map[*Example]struct{}
+	visitedLink           map[*Link]struct{}
 }
 
+// NewSwaggerLoader returns an empty SwaggerLoader
 func NewSwaggerLoader() *SwaggerLoader {
 	return &SwaggerLoader{}
 }
@@ -44,6 +55,7 @@ func (swaggerLoader *SwaggerLoader) reset() {
 	swaggerLoader.visitedFiles = make(map[string]struct{})
 }
 
+// LoadSwaggerFromURI loads a spec from a remote URL
 func (swaggerLoader *SwaggerLoader) LoadSwaggerFromURI(location *url.URL) (*Swagger, error) {
 	swaggerLoader.reset()
 	return swaggerLoader.loadSwaggerFromURIInternal(location)
@@ -116,6 +128,7 @@ func readURL(location *url.URL) ([]byte, error) {
 	return data, nil
 }
 
+// LoadSwaggerFromFile loads a spec from a local file path
 func (swaggerLoader *SwaggerLoader) LoadSwaggerFromFile(path string) (*Swagger, error) {
 	swaggerLoader.reset()
 	return swaggerLoader.loadSwaggerFromFileInternal(path)
@@ -135,6 +148,7 @@ func (swaggerLoader *SwaggerLoader) loadSwaggerFromFileInternal(path string) (*S
 	return swaggerLoader.loadSwaggerFromDataWithPathInternal(data, pathAsURL)
 }
 
+// LoadSwaggerFromData loads a spec from a byte array
 func (swaggerLoader *SwaggerLoader) LoadSwaggerFromData(data []byte) (*Swagger, error) {
 	swaggerLoader.reset()
 	return swaggerLoader.loadSwaggerFromDataInternal(data)
@@ -163,9 +177,17 @@ func (swaggerLoader *SwaggerLoader) loadSwaggerFromDataWithPathInternal(data []b
 	return swagger, swaggerLoader.ResolveRefsIn(swagger, path)
 }
 
+// ResolveRefsIn expands references if for instance spec was just unmarshalled
 func (swaggerLoader *SwaggerLoader) ResolveRefsIn(swagger *Swagger, path *url.URL) (err error) {
-	if swaggerLoader.visited == nil {
-		swaggerLoader.visited = make(map[interface{}]struct{})
+	if swaggerLoader.visitedHeader == nil {
+		swaggerLoader.visitedHeader = make(map[*Header]struct{})
+		swaggerLoader.visitedParameter = make(map[*Parameter]struct{})
+		swaggerLoader.visitedRequestBody = make(map[*RequestBody]struct{})
+		swaggerLoader.visitedResponse = make(map[*Response]struct{})
+		swaggerLoader.visitedSchema = make(map[*Schema]struct{})
+		swaggerLoader.visitedSecurityScheme = make(map[*SecurityScheme]struct{})
+		swaggerLoader.visitedExample = make(map[*Example]struct{})
+		swaggerLoader.visitedLink = make(map[*Link]struct{})
 	}
 	if swaggerLoader.visitedFiles == nil {
 		swaggerLoader.reset()
@@ -253,38 +275,66 @@ func isSingleRefElement(ref string) bool {
 	return !strings.Contains(ref, "#")
 }
 
-func (swaggerLoader *SwaggerLoader) resolveComponent(swagger *Swagger, ref string, path *url.URL) (
-	cursor interface{},
+func (swaggerLoader *SwaggerLoader) resolveComponent(
+	swagger *Swagger,
+	ref string,
+	path *url.URL,
+	resolved interface{},
+) (
 	componentPath *url.URL,
 	err error,
 ) {
 	if swagger, ref, componentPath, err = swaggerLoader.resolveRefSwagger(swagger, ref, path); err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 
 	parsedURL, err := url.Parse(ref)
 	if err != nil {
-		return nil, nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL)
+		return nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL)
 	}
 	fragment := parsedURL.Fragment
 	if !strings.HasPrefix(fragment, "/") {
-		err := fmt.Errorf("expected fragment prefix '#/' in URI %q", ref)
-		return nil, nil, err
+		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)
 
 		if cursor, err = drillIntoSwaggerField(cursor, pathPart); err != nil {
-			return nil, nil, fmt.Errorf("failed to resolve %q in fragment in URI: %q: %v", ref, pathPart, err.Error())
+			e := failedToResolveRefFragmentPart(ref, pathPart)
+			return nil, fmt.Errorf("%s: %s", e.Error(), err.Error())
 		}
 		if cursor == nil {
-			return nil, nil, failedToResolveRefFragmentPart(ref, pathPart)
+			return nil, failedToResolveRefFragmentPart(ref, pathPart)
 		}
 	}
 
-	return cursor, componentPath, nil
+	switch {
+	case reflect.TypeOf(cursor) == reflect.TypeOf(resolved):
+		reflect.ValueOf(resolved).Elem().Set(reflect.ValueOf(cursor).Elem())
+		return componentPath, nil
+
+	case reflect.TypeOf(cursor) == reflect.TypeOf(map[string]interface{}{}):
+		codec := func(got, expect interface{}) error {
+			enc, err := json.Marshal(got)
+			if err != nil {
+				return err
+			}
+			if err = json.Unmarshal(enc, expect); err != nil {
+				return err
+			}
+			return nil
+		}
+		if err := codec(cursor, resolved); err != nil {
+			return nil, fmt.Errorf("bad data in %q", ref)
+		}
+		return componentPath, nil
+
+	default:
+		return nil, fmt.Errorf("bad data in %q", ref)
+	}
 }
 
 func drillIntoSwaggerField(cursor interface{}, fieldName string) (interface{}, error) {
@@ -302,13 +352,15 @@ func drillIntoSwaggerField(cursor interface{}, fieldName string) (interface{}, e
 			return nil, err
 		}
 		index := int(i)
-		if index >= val.Len() {
+		if 0 > index || index >= val.Len() {
 			return nil, errors.New("slice index out of bounds")
 		}
 		return val.Index(index).Interface(), nil
 
 	case reflect.Struct:
+		hasFields := false
 		for i := 0; i < val.NumField(); i++ {
+			hasFields = true
 			field := val.Type().Field(i)
 			tagValue := field.Tag.Get("yaml")
 			yamlKey := strings.Split(tagValue, ",")[0]
@@ -316,12 +368,23 @@ func drillIntoSwaggerField(cursor interface{}, fieldName string) (interface{}, e
 				return val.Field(i).Interface(), nil
 			}
 		}
-		// if cursor if a "ref wrapper" struct (e.g. RequestBodyRef), try digging into its Value field
-		_, ok := val.Type().FieldByName("Value")
-		if ok {
-			return drillIntoSwaggerField(val.FieldByName("Value").Interface(), fieldName) // recurse into .Value
+		// if cursor is a "ref wrapper" struct (e.g. RequestBodyRef),
+		if _, ok := val.Type().FieldByName("Value"); ok {
+			// try digging into its Value field
+			return drillIntoSwaggerField(val.FieldByName("Value").Interface(), fieldName)
+		}
+		if hasFields {
+			if ff := val.Type().Field(0); ff.PkgPath == "" && ff.Name == "ExtensionProps" {
+				extensions := val.Field(0).Interface().(ExtensionProps).Extensions
+				if enc, ok := extensions[fieldName]; ok {
+					var dec interface{}
+					if err := json.Unmarshal(enc.(json.RawMessage), &dec); err != nil {
+						return nil, err
+					}
+					return dec, nil
+				}
+			}
 		}
-		// give up
 		return nil, fmt.Errorf("struct field %q not found", fieldName)
 
 	default:
@@ -356,12 +419,13 @@ func (swaggerLoader *SwaggerLoader) resolveRefSwagger(swagger *Swagger, ref stri
 	return swagger, ref, componentPath, nil
 }
 
-func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component *HeaderRef, path *url.URL) error {
-	visited := swaggerLoader.visited
-	if _, isVisited := visited[component]; isVisited {
-		return nil
+func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component *HeaderRef, documentPath *url.URL) error {
+	if component != nil && component.Value != nil {
+		if _, ok := swaggerLoader.visitedHeader[component.Value]; ok {
+			return nil
+		}
+		swaggerLoader.visitedHeader[component.Value] = struct{}{}
 	}
-	visited[component] = struct{}{}
 
 	const prefix = "#/components/headers/"
 	if component == nil {
@@ -370,21 +434,18 @@ func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
 			var header Header
-			if err := swaggerLoader.loadSingleElementFromURI(ref, path, &header); err != nil {
+			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &header); err != nil {
 				return err
 			}
 
 			component.Value = &header
 		} else {
-			untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
+			var resolved HeaderRef
+			componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
-			resolved, ok := untypedResolved.(*HeaderRef)
-			if !ok {
-				return failedToResolveRefFragment(ref)
-			}
-			if err := swaggerLoader.resolveHeaderRef(swagger, resolved, componentPath); err != nil {
+			if err := swaggerLoader.resolveHeaderRef(swagger, &resolved, componentPath); err != nil {
 				return err
 			}
 			component.Value = resolved.Value
@@ -395,7 +456,7 @@ func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component
 		return nil
 	}
 	if schema := value.Schema; schema != nil {
-		if err := swaggerLoader.resolveSchemaRef(swagger, schema, path); err != nil {
+		if err := swaggerLoader.resolveSchemaRef(swagger, schema, documentPath); err != nil {
 			return err
 		}
 	}
@@ -403,11 +464,12 @@ func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component
 }
 
 func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, component *ParameterRef, documentPath *url.URL) error {
-	visited := swaggerLoader.visited
-	if _, isVisited := visited[component]; isVisited {
-		return nil
+	if component != nil && component.Value != nil {
+		if _, ok := swaggerLoader.visitedParameter[component.Value]; ok {
+			return nil
+		}
+		swaggerLoader.visitedParameter[component.Value] = struct{}{}
 	}
-	visited[component] = struct{}{}
 
 	const prefix = "#/components/parameters/"
 	if component == nil {
@@ -422,15 +484,12 @@ func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, compon
 			}
 			component.Value = &param
 		} else {
-			untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath)
+			var resolved ParameterRef
+			componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
-			resolved, ok := untypedResolved.(*ParameterRef)
-			if !ok {
-				return failedToResolveRefFragment(ref)
-			}
-			if err := swaggerLoader.resolveParameterRef(swagger, resolved, componentPath); err != nil {
+			if err := swaggerLoader.resolveParameterRef(swagger, &resolved, componentPath); err != nil {
 				return err
 			}
 			component.Value = resolved.Value
@@ -464,12 +523,13 @@ func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, compon
 	return nil
 }
 
-func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, component *RequestBodyRef, path *url.URL) error {
-	visited := swaggerLoader.visited
-	if _, isVisited := visited[component]; isVisited {
-		return nil
+func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, component *RequestBodyRef, documentPath *url.URL) error {
+	if component != nil && component.Value != nil {
+		if _, ok := swaggerLoader.visitedRequestBody[component.Value]; ok {
+			return nil
+		}
+		swaggerLoader.visitedRequestBody[component.Value] = struct{}{}
 	}
-	visited[component] = struct{}{}
 
 	const prefix = "#/components/requestBodies/"
 	if component == nil {
@@ -478,21 +538,18 @@ func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, comp
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
 			var requestBody RequestBody
-			if err := swaggerLoader.loadSingleElementFromURI(ref, path, &requestBody); err != nil {
+			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &requestBody); err != nil {
 				return err
 			}
 
 			component.Value = &requestBody
 		} else {
-			untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
+			var resolved RequestBodyRef
+			componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
-			resolved, ok := untypedResolved.(*RequestBodyRef)
-			if !ok {
-				return failedToResolveRefFragment(ref)
-			}
-			if err = swaggerLoader.resolveRequestBodyRef(swagger, resolved, componentPath); err != nil {
+			if err = swaggerLoader.resolveRequestBodyRef(swagger, &resolved, componentPath); err != nil {
 				return err
 			}
 			component.Value = resolved.Value
@@ -504,13 +561,13 @@ func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, comp
 	}
 	for _, contentType := range value.Content {
 		for name, example := range contentType.Examples {
-			if err := swaggerLoader.resolveExampleRef(swagger, example, path); 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, path); err != nil {
+			if err := swaggerLoader.resolveSchemaRef(swagger, schema, documentPath); err != nil {
 				return err
 			}
 		}
@@ -519,11 +576,12 @@ func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, comp
 }
 
 func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, component *ResponseRef, documentPath *url.URL) error {
-	visited := swaggerLoader.visited
-	if _, isVisited := visited[component]; isVisited {
-		return nil
+	if component != nil && component.Value != nil {
+		if _, ok := swaggerLoader.visitedResponse[component.Value]; ok {
+			return nil
+		}
+		swaggerLoader.visitedResponse[component.Value] = struct{}{}
 	}
-	visited[component] = struct{}{}
 
 	const prefix = "#/components/responses/"
 	if component == nil {
@@ -531,24 +589,19 @@ func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, compone
 	}
 	ref := component.Ref
 	if ref != "" {
-
 		if isSingleRefElement(ref) {
 			var resp Response
 			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &resp); err != nil {
 				return err
 			}
-
 			component.Value = &resp
 		} else {
-			untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath)
+			var resolved ResponseRef
+			componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
-			resolved, ok := untypedResolved.(*ResponseRef)
-			if !ok {
-				return failedToResolveRefFragment(ref)
-			}
-			if err := swaggerLoader.resolveResponseRef(swagger, resolved, componentPath); err != nil {
+			if err := swaggerLoader.resolveResponseRef(swagger, &resolved, componentPath); err != nil {
 				return err
 			}
 			component.Value = resolved.Value
@@ -594,11 +647,12 @@ func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, compone
 }
 
 func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component *SchemaRef, documentPath *url.URL) error {
-	visited := swaggerLoader.visited
-	if _, isVisited := visited[component]; isVisited {
-		return nil
+	if component != nil && component.Value != nil {
+		if _, ok := swaggerLoader.visitedSchema[component.Value]; ok {
+			return nil
+		}
+		swaggerLoader.visitedSchema[component.Value] = struct{}{}
 	}
-	visited[component] = struct{}{}
 
 	const prefix = "#/components/schemas/"
 	if component == nil {
@@ -613,16 +667,12 @@ func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component
 			}
 			component.Value = &schema
 		} else {
-			untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath)
+			var resolved SchemaRef
+			componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
-
-			resolved, ok := untypedResolved.(*SchemaRef)
-			if !ok {
-				return failedToResolveRefFragment(ref)
-			}
-			if err := swaggerLoader.resolveSchemaRef(swagger, resolved, componentPath); err != nil {
+			if err := swaggerLoader.resolveSchemaRef(swagger, &resolved, componentPath); err != nil {
 				return err
 			}
 			component.Value = resolved.Value
@@ -679,12 +729,13 @@ func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component
 	return nil
 }
 
-func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, component *SecuritySchemeRef, path *url.URL) error {
-	visited := swaggerLoader.visited
-	if _, isVisited := visited[component]; isVisited {
-		return nil
+func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, component *SecuritySchemeRef, documentPath *url.URL) error {
+	if component != nil && component.Value != nil {
+		if _, ok := swaggerLoader.visitedSecurityScheme[component.Value]; ok {
+			return nil
+		}
+		swaggerLoader.visitedSecurityScheme[component.Value] = struct{}{}
 	}
-	visited[component] = struct{}{}
 
 	const prefix = "#/components/securitySchemes/"
 	if component == nil {
@@ -693,21 +744,18 @@ func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, c
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
 			var scheme SecurityScheme
-			if err := swaggerLoader.loadSingleElementFromURI(ref, path, &scheme); err != nil {
+			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &scheme); err != nil {
 				return err
 			}
 
 			component.Value = &scheme
 		} else {
-			untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
+			var resolved SecuritySchemeRef
+			componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
-			resolved, ok := untypedResolved.(*SecuritySchemeRef)
-			if !ok {
-				return failedToResolveRefFragment(ref)
-			}
-			if err := swaggerLoader.resolveSecuritySchemeRef(swagger, resolved, componentPath); err != nil {
+			if err := swaggerLoader.resolveSecuritySchemeRef(swagger, &resolved, componentPath); err != nil {
 				return err
 			}
 			component.Value = resolved.Value
@@ -716,12 +764,13 @@ func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, c
 	return nil
 }
 
-func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, component *ExampleRef, path *url.URL) error {
-	visited := swaggerLoader.visited
-	if _, isVisited := visited[component]; isVisited {
-		return nil
+func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, component *ExampleRef, documentPath *url.URL) error {
+	if component != nil && component.Value != nil {
+		if _, ok := swaggerLoader.visitedExample[component.Value]; ok {
+			return nil
+		}
+		swaggerLoader.visitedExample[component.Value] = struct{}{}
 	}
-	visited[component] = struct{}{}
 
 	const prefix = "#/components/examples/"
 	if component == nil {
@@ -730,21 +779,18 @@ func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, componen
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
 			var example Example
-			if err := swaggerLoader.loadSingleElementFromURI(ref, path, &example); err != nil {
+			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &example); err != nil {
 				return err
 			}
 
 			component.Value = &example
 		} else {
-			untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
+			var resolved ExampleRef
+			componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
-			resolved, ok := untypedResolved.(*ExampleRef)
-			if !ok {
-				return failedToResolveRefFragment(ref)
-			}
-			if err := swaggerLoader.resolveExampleRef(swagger, resolved, componentPath); err != nil {
+			if err := swaggerLoader.resolveExampleRef(swagger, &resolved, componentPath); err != nil {
 				return err
 			}
 			component.Value = resolved.Value
@@ -753,12 +799,13 @@ func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, componen
 	return nil
 }
 
-func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *LinkRef, path *url.URL) error {
-	visited := swaggerLoader.visited
-	if _, isVisited := visited[component]; isVisited {
-		return nil
+func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *LinkRef, documentPath *url.URL) error {
+	if component != nil && component.Value != nil {
+		if _, ok := swaggerLoader.visitedLink[component.Value]; ok {
+			return nil
+		}
+		swaggerLoader.visitedLink[component.Value] = struct{}{}
 	}
-	visited[component] = struct{}{}
 
 	const prefix = "#/components/links/"
 	if component == nil {
@@ -767,21 +814,18 @@ func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *
 	if ref := component.Ref; ref != "" {
 		if isSingleRefElement(ref) {
 			var link Link
-			if err := swaggerLoader.loadSingleElementFromURI(ref, path, &link); err != nil {
+			if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &link); err != nil {
 				return err
 			}
 
 			component.Value = &link
 		} else {
-			untypedResolved, componentPath, err := swaggerLoader.resolveComponent(swagger, ref, path)
+			var resolved LinkRef
+			componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved)
 			if err != nil {
 				return err
 			}
-			resolved, ok := untypedResolved.(*LinkRef)
-			if !ok {
-				return failedToResolveRefFragment(ref)
-			}
-			if err := swaggerLoader.resolveLinkRef(swagger, resolved, componentPath); err != nil {
+			if err := swaggerLoader.resolveLinkRef(swagger, &resolved, componentPath); err != nil {
 				return err
 			}
 			component.Value = resolved.Value
@@ -791,16 +835,15 @@ func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *
 }
 
 func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypoint string, pathItem *PathItem, documentPath *url.URL) (err error) {
-	visited := swaggerLoader.visitedFiles
 	key := "_"
 	if documentPath != nil {
 		key = documentPath.EscapedPath()
 	}
 	key += entrypoint
-	if _, isVisited := visited[key]; isVisited {
+	if _, ok := swaggerLoader.visitedFiles[key]; ok {
 		return nil
 	}
-	visited[key] = struct{}{}
+	swaggerLoader.visitedFiles[key] = struct{}{}
 
 	const prefix = "#/paths/"
 	if pathItem == nil {


Test patch:
diff --git a/openapi3/discriminator_test.go b/openapi3/discriminator_test.go
index 602b4fd..e3ed22c 100644
--- a/openapi3/discriminator_test.go
+++ b/openapi3/discriminator_test.go
@@ -38,5 +38,5 @@ var jsonSpecWithDiscriminator = []byte(`
 func TestParsingDiscriminator(t *testing.T) {
 	loader, err := NewSwaggerLoader().LoadSwaggerFromData(jsonSpecWithDiscriminator)
 	require.NoError(t, err)
-	require.Equal(t, 2, len(loader.Components.Schemas["MyResponseType"].Value.OneOf))
+	require.Equal(t, 2, len(loader.Components.Schemas["MyResponseType"].Value.Discriminator.Mapping))
 }
diff --git a/openapi3/swagger_loader_test.go b/openapi3/swagger_loader_test.go
index 40ae719..5789320 100644
--- a/openapi3/swagger_loader_test.go
+++ b/openapi3/swagger_loader_test.go
@@ -201,7 +201,7 @@ func TestLoadErrorOnRefMisuse(t *testing.T) {
 openapi: '3.0.0'
 servers: [{url: /}]
 info:
-  title: ''
+  title: Some API
   version: '1'
 components:
   schemas:
@@ -211,6 +211,7 @@ paths:
     put:
       description: ''
       requestBody:
+        # Uses a schema ref instead of a requestBody ref.
         $ref: '#/components/schemas/Thing'
       responses:
         '201':
@@ -314,6 +315,17 @@ func TestLoadFromRemoteURL(t *testing.T) {
 	require.Equal(t, "string", swagger.Components.Schemas["TestSchema"].Value.Type)
 }
 
+func TestLoadWithReferenceInReference(t *testing.T) {
+	loader := NewSwaggerLoader()
+	loader.IsExternalRefsAllowed = true
+	doc, err := loader.LoadSwaggerFromFile("testdata/refInRef/openapi.json")
+	require.NoError(t, err)
+	require.NotNil(t, doc)
+	err = doc.Validate(loader.Context)
+	require.NoError(t, err)
+	require.Equal(t, "string", doc.Paths["/api/test/ref/in/ref"].Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["definition_reference"].Value.Type)
+}
+
 func TestLoadFileWithExternalSchemaRef(t *testing.T) {
 	loader := NewSwaggerLoader()
 	loader.IsExternalRefsAllowed = true
diff --git a/openapi3/testdata/refInRef/messages/definitions.json b/openapi3/testdata/refInRef/messages/definitions.json
new file mode 100644
index 0000000..78b9428
--- /dev/null
+++ b/openapi3/testdata/refInRef/messages/definitions.json
@@ -0,0 +1,7 @@
+{
+  "definitions": {
+    "External": {
+      "type": "string"
+    }
+  }
+}
diff --git a/openapi3/testdata/refInRef/messages/request.json b/openapi3/testdata/refInRef/messages/request.json
new file mode 100644
index 0000000..10ff329
--- /dev/null
+++ b/openapi3/testdata/refInRef/messages/request.json
@@ -0,0 +1,11 @@
+{
+  "type": "object",
+  "required": [
+    "definition_reference"
+  ],
+  "properties": {
+    "definition_reference": {
+      "$ref": "definitions.json#/definitions/External"
+    }
+  }
+}
diff --git a/openapi3/testdata/refInRef/messages/response.json b/openapi3/testdata/refInRef/messages/response.json
new file mode 100644
index 0000000..b636f52
--- /dev/null
+++ b/openapi3/testdata/refInRef/messages/response.json
@@ -0,0 +1,9 @@
+{
+  "type": "object",
+  "properties": {
+    "id": {
+      "type": "integer",
+      "format": "int32"
+    }
+  }
+}
diff --git a/openapi3/testdata/refInRef/openapi.json b/openapi3/testdata/refInRef/openapi.json
new file mode 100644
index 0000000..0e9a5b1
--- /dev/null
+++ b/openapi3/testdata/refInRef/openapi.json
@@ -0,0 +1,34 @@
+{
+  "openapi": "3.0.3",
+  "info": {
+    "title": "Reference in reference example",
+    "version": "1.0.0"
+  },
+  "paths": {
+    "/api/test/ref/in/ref": {
+      "post": {
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "messages/request.json"
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "Successful response",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "messages/response.json"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/openapi3/testdata/testref.openapi.json b/openapi3/testdata/testref.openapi.json
index 42b651a..f25ffbd 100644
--- a/openapi3/testdata/testref.openapi.json
+++ b/openapi3/testdata/testref.openapi.json
@@ -1,7 +1,7 @@
 {
     "openapi": "3.0.0",
     "info": {
-        "title": "",
+        "title": "OAI Specification w/ refs in JSON",
         "x-my-extension": {"k": 42},
         "version": "1"
     },
diff --git a/openapi3/testdata/testref.openapi.yml b/openapi3/testdata/testref.openapi.yml
index fe75593..eace245 100644
--- a/openapi3/testdata/testref.openapi.yml
+++ b/openapi3/testdata/testref.openapi.yml
@@ -2,9 +2,10 @@
 openapi: 3.0.0
 info:
   title: 'OAI Specification w/ refs in YAML'
+  # x-my-extension: {k: 42},
   version: '1'
 paths: {}
 components:
   schemas:
     AnotherTestSchema:
-      "$ref": components.openapi.yml#/components/schemas/CustomTestSchema
+      $ref: 'components.openapi.yml#/components/schemas/CustomTestSchema'