diff --git a/datamodel/low/extraction_functions.go b/datamodel/low/extraction_functions.go index 76f4cc6..3fe31ff 100644 --- a/datamodel/low/extraction_functions.go +++ b/datamodel/low/extraction_functions.go @@ -90,16 +90,15 @@ func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, error) { } // cant be found? last resort is to try a path lookup - cleaned := strings.ReplaceAll(rv, "#/paths/", "") - cleaned = strings.ReplaceAll(cleaned, "/", ".") - cleaned = strings.ReplaceAll(cleaned, "~1", "/") - yamlPath := fmt.Sprintf("$.paths.%s", cleaned) - path, err := yamlpath.NewPath(yamlPath) - if err == nil { - nodes, fErr := path.Find(idx.GetRootNode()) - if fErr == nil { - if len(nodes) > 0 { - return nodes[0], nil + _, friendly := utils.ConvertComponentIdIntoFriendlyPathSearch(rv) + if friendly != "" { + path, err := yamlpath.NewPath(friendly) + if err == nil { + nodes, fErr := path.Find(idx.GetRootNode()) + if fErr == nil { + if len(nodes) > 0 { + return nodes[0], nil + } } } } diff --git a/datamodel/low/v3/callback_test.go b/datamodel/low/v3/callback_test.go index e07ef2d..96aac8d 100644 --- a/datamodel/low/v3/callback_test.go +++ b/datamodel/low/v3/callback_test.go @@ -55,8 +55,7 @@ func TestCallback_Build_Error(t *testing.T) { idx := index.NewSpecIndex(&idxNode) yml := `'{$request.query.queryUrl}': - post: - $ref: #/does/not/exist/and/invalid` + $ref: '#/does/not/exist/and/invalid'` var rootNode yaml.Node mErr = yaml.Unmarshal([]byte(yml), &rootNode) diff --git a/index/spec_index.go b/index/spec_index.go index e85cfdd..bc8d001 100644 --- a/index/spec_index.go +++ b/index/spec_index.go @@ -1832,16 +1832,17 @@ func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference { if index.root != nil { name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId) friendlySearch = strings.ReplaceAll(friendlySearch, "~1", "/") - path, _ := yamlpath.NewPath(friendlySearch) + path, err := yamlpath.NewPath(friendlySearch) + if path == nil || err != nil { + return nil // no component found + } res, _ := path.Find(index.root) - if len(res) == 1 { ref := &Reference{ Definition: componentId, Name: name, Node: res[0], } - return ref } } diff --git a/index/spec_index_test.go b/index/spec_index_test.go index 1d93af0..c9431de 100644 --- a/index/spec_index_test.go +++ b/index/spec_index_test.go @@ -454,7 +454,54 @@ func TestSpecIndex_ExtractComponentsFromRefs(t *testing.T) { assert.Len(t, index.GetReferenceIndexErrors(), 1) } -func TestSpecIndex_FindComponent(t *testing.T) { +func TestSpecIndex_FindComponent_WithACrazyAssPath(t *testing.T) { + yml := `paths: + /crazy/ass/references: + get: + parameters: + - name: a param + schema: + type: string + description: Show information about one architecture. + responses: + "200": + content: + application/xml; charset=utf-8: + schema: + example: + name: x86_64 + description: OK. The request has succeeded. + "404": + content: + application/xml; charset=utf-8: + example: + code: unknown_architecture + summary: "Architecture does not exist: x999" + schema: + $ref: "#/paths/~1crazy~1ass~1references/get/parameters/0" + "400": + content: + application/xml; charset=utf-8: + example: + code: unknown_architecture + summary: "Architecture does not exist: x999" + schema: + $ref: "#/paths/~1crazy~1ass~1references/get/responses/404/content/application~1xml;%20charset=utf-8/schema" + description: Not Found.` + + var rootNode yaml.Node + yaml.Unmarshal([]byte(yml), &rootNode) + + index := NewSpecIndex(&rootNode) + assert.Equal(t, "#/paths/~1crazy~1ass~1references/get/parameters/0", + index.FindComponent("#/paths/~1crazy~1ass~1references/get/responses/404/content/application~1xml;%20charset=utf-8/schema", nil).Node.Content[1].Value) + + assert.Equal(t, "a param", + index.FindComponent("#/paths/~1crazy~1ass~1references/get/parameters/0", nil).Node.Content[1].Value) + +} + +func TestSpecIndex_FindComponenth(t *testing.T) { yml := `components: schemas: pizza: diff --git a/utils/utils.go b/utils/utils.go index ae593d2..a9da13d 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" "gopkg.in/yaml.v3" + "net/url" "regexp" "strconv" "strings" @@ -480,12 +481,48 @@ func IsHttpVerb(verb string) bool { func ConvertComponentIdIntoFriendlyPathSearch(id string) (string, string) { segs := strings.Split(id, "/") name := strings.ReplaceAll(segs[len(segs)-1], "~1", "/") + var cleaned []string - replaced := strings.ReplaceAll(fmt.Sprintf("%s['%s']", - strings.Join(segs[:len(segs)-1], "."), name), "#", "$") + // check for strange spaces, chars and if found, wrap them up, clean them and create a new cleaned path. + for i := range segs { + reg, _ := regexp.MatchString("[%=;~]", segs[i]) + if reg { + segs[i], _ = url.QueryUnescape(strings.ReplaceAll(segs[i], "~1", "/")) + segs[i] = fmt.Sprintf("['%s']", segs[i]) + h := i + if h-1 == len(cleaned) { + h-- + } + cleaned[h-1] = fmt.Sprintf("%s%s", segs[i-1], segs[i]) + } else { + cleaned = append(cleaned, segs[i]) + } + } - if replaced[0] != '$' { - replaced = fmt.Sprintf("$%s", replaced) + nameIntVal, err := strconv.ParseInt(name, 10, 32) + var replaced string + if err != nil { + if len(cleaned) > 2 { + replaced = strings.ReplaceAll(fmt.Sprintf("%s['%s']", + strings.Join(cleaned[:len(cleaned)-1], "."), name), "#", "$") + } else { + replaced = strings.ReplaceAll(fmt.Sprintf("%s", + strings.Join(cleaned, ".")), "#", "$") + } + } else { + if nameIntVal <= 99 { // codes start at 100 + replaced = strings.ReplaceAll(fmt.Sprintf("%s[%d]", + strings.Join(cleaned[:len(cleaned)-1], "."), nameIntVal), "#", "$") + } else { + replaced = strings.ReplaceAll(fmt.Sprintf("%s.%d", + strings.Join(cleaned[:len(cleaned)-1], "."), nameIntVal), "#", "$") + } + } + + if len(replaced) > 0 { + if replaced[0] != '$' { + replaced = fmt.Sprintf("$%s", replaced) + } } return name, replaced } diff --git a/utils/utils_test.go b/utils/utils_test.go index 5024a6e..cffa266 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -611,6 +611,35 @@ func TestConvertComponentIdIntoFriendlyPathSearch_WithRootSymbol(t *testing.T) { segment, path := ConvertComponentIdIntoFriendlyPathSearch("/chicken/chips/pizza/cake") assert.Equal(t, "$.chicken.chips.pizza['cake']", path) assert.Equal(t, "cake", segment) + + segment, path = ConvertComponentIdIntoFriendlyPathSearch("#/paths/~1crazy~1ass~1references/get/responses/404/content/application~1xml;%20charset=utf-8/schema") + assert.Equal(t, "$.paths['/crazy/ass/references'].get.responses.404.content['application/xml; charset=utf-8']['schema']", path) + assert.Equal(t, "schema", segment) + +} + +func TestConvertComponentIdIntoFriendlyPathSearch_Crazy(t *testing.T) { + segment, path := ConvertComponentIdIntoFriendlyPathSearch("#/paths/~1crazy~1ass~1references/get/responses/404/content/application~1xml;%20charset=utf-8/schema") + assert.Equal(t, "$.paths['/crazy/ass/references'].get.responses.404.content['application/xml; charset=utf-8']['schema']", path) + assert.Equal(t, "schema", segment) +} + +func TestConvertComponentIdIntoFriendlyPathSearch_CrazyShort(t *testing.T) { + segment, path := ConvertComponentIdIntoFriendlyPathSearch("#/paths/~1crazy~1ass~1references") + assert.Equal(t, "$.paths['/crazy/ass/references']", path) + assert.Equal(t, "/crazy/ass/references", segment) +} + +func TestConvertComponentIdIntoFriendlyPathSearch_Array(t *testing.T) { + segment, path := ConvertComponentIdIntoFriendlyPathSearch("#/paths/~1crazy~1ass~1references/get/parameters/0") + assert.Equal(t, "$.paths['/crazy/ass/references'].get.parameters[0]", path) + assert.Equal(t, "0", segment) +} + +func TestConvertComponentIdIntoFriendlyPathSearch_HTTPCode(t *testing.T) { + segment, path := ConvertComponentIdIntoFriendlyPathSearch("#/paths/~1crazy~1ass~1references/get/responses/404") + assert.Equal(t, "$.paths['/crazy/ass/references'].get.responses.404", path) + assert.Equal(t, "404", segment) } func TestConvertComponentIdIntoPath(t *testing.T) { @@ -628,7 +657,6 @@ func TestDetectCase(t *testing.T) { assert.Equal(t, KebabCase, DetectCase("chicken-be-be-beef-or-pork")) assert.Equal(t, RegularCase, DetectCase("kebab-TimeIn_london-TOWN")) assert.Equal(t, UnknownCase, DetectCase("")) - } func TestIsNodeRefValue(t *testing.T) {