diff --git a/index/spec_index_test.go b/index/spec_index_test.go index fc61e33f..9549640d 100644 --- a/index/spec_index_test.go +++ b/index/spec_index_test.go @@ -3068,3 +3068,49 @@ paths: assert.Equal(t, 1, len(errors), "should have 1 error for duplicate at path level") assert.Contains(t, errors[0].Error(), "index 1 has a duplicate name `id` and `in` type") } + +func TestSpecIndex_GetAllParametersFromOperations_RefWithSiblingBeforeIt(t *testing.T) { + // OpenAPI 3.1 allows `summary` and `description` next to a `$ref`, and YAML + // key order carries no meaning, so a `$ref` that is not the first key must + // still be treated as a reference. + yml := `openapi: 3.1.0 +info: + title: Test API + version: 1.0.0 +paths: + /things/{id}: + parameters: + - description: Thing ID + $ref: '#/components/parameters/ID' + get: + parameters: + - description: Page number + $ref: '#/components/parameters/Page' + responses: + '200': + description: OK +components: + parameters: + ID: + name: id + in: path + required: true + schema: + type: string + Page: + name: page + in: query + schema: + type: integer` + + var rootNode yaml.Node + _ = yaml.Unmarshal([]byte(yml), &rootNode) + + index := NewSpecIndexWithConfig(&rootNode, CreateOpenAPIIndexConfig()) + + assert.Empty(t, index.GetOperationParametersIndexErrors()) + + refs := index.GetOperationParameterReferences()["/things/{id}"] + assert.Len(t, refs["top"]["#/components/parameters/ID"], 1) + assert.Len(t, refs["get"]["#/components/parameters/Page"], 1) +} diff --git a/index/utility_methods.go b/index/utility_methods.go index addca04d..b602772d 100644 --- a/index/utility_methods.go +++ b/index/utility_methods.go @@ -424,14 +424,14 @@ func formatParameterPath(pathValue, method string, index int) string { func (index *SpecIndex) scanOperationParams(params []*yaml.Node, keyNode, pathItemNode *yaml.Node, method string) { for i, param := range params { - // param is ref - if len(param.Content) > 0 && param.Content[0].Value == "$ref" { + // param is ref ($ref may have siblings in OA 3.1, so it is not always the first key) + if refValueNode := utils.GetRefValueNode(param); refValueNode != nil { - paramRefName := param.Content[1].Value + paramRefName := refValueNode.Value paramRef := index.allMappedRefs[paramRefName] if paramRef == nil { // could be in the rolodex - searchInIndex := findIndex(index, param.Content[1]) + searchInIndex := findIndex(index, refValueNode) ctx := context.WithValue(context.Background(), CurrentPathKey, searchInIndex.specAbsolutePath) ctx = context.WithValue(ctx, RootIndexKey, searchInIndex) ref := seekRefEnd(ctx, searchInIndex, paramRefName)