(patch): Some crazy references are causing panics. #48 #45

This patch bumps up support for creating valid JSON paths from references. It addresses #48 and makes invalid specs, valid! This addresses issues #48 and #45 that report references cannot be found. Now `libopenapi` can support much more funky lookups.
This commit is contained in:
Dave Shanley
2022-12-15 09:47:23 -05:00
parent ece888d201
commit 05eb4ffec9
6 changed files with 132 additions and 21 deletions

View File

@@ -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 // cant be found? last resort is to try a path lookup
cleaned := strings.ReplaceAll(rv, "#/paths/", "") _, friendly := utils.ConvertComponentIdIntoFriendlyPathSearch(rv)
cleaned = strings.ReplaceAll(cleaned, "/", ".") if friendly != "" {
cleaned = strings.ReplaceAll(cleaned, "~1", "/") path, err := yamlpath.NewPath(friendly)
yamlPath := fmt.Sprintf("$.paths.%s", cleaned) if err == nil {
path, err := yamlpath.NewPath(yamlPath) nodes, fErr := path.Find(idx.GetRootNode())
if err == nil { if fErr == nil {
nodes, fErr := path.Find(idx.GetRootNode()) if len(nodes) > 0 {
if fErr == nil { return nodes[0], nil
if len(nodes) > 0 { }
return nodes[0], nil
} }
} }
} }

View File

@@ -55,8 +55,7 @@ func TestCallback_Build_Error(t *testing.T) {
idx := index.NewSpecIndex(&idxNode) idx := index.NewSpecIndex(&idxNode)
yml := `'{$request.query.queryUrl}': yml := `'{$request.query.queryUrl}':
post: $ref: '#/does/not/exist/and/invalid'`
$ref: #/does/not/exist/and/invalid`
var rootNode yaml.Node var rootNode yaml.Node
mErr = yaml.Unmarshal([]byte(yml), &rootNode) mErr = yaml.Unmarshal([]byte(yml), &rootNode)

View File

@@ -1832,16 +1832,17 @@ func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference {
if index.root != nil { if index.root != nil {
name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId) name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId)
friendlySearch = strings.ReplaceAll(friendlySearch, "~1", "/") 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) res, _ := path.Find(index.root)
if len(res) == 1 { if len(res) == 1 {
ref := &Reference{ ref := &Reference{
Definition: componentId, Definition: componentId,
Name: name, Name: name,
Node: res[0], Node: res[0],
} }
return ref return ref
} }
} }

View File

@@ -454,7 +454,54 @@ func TestSpecIndex_ExtractComponentsFromRefs(t *testing.T) {
assert.Len(t, index.GetReferenceIndexErrors(), 1) 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: yml := `components:
schemas: schemas:
pizza: pizza:

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"net/url"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@@ -480,12 +481,48 @@ func IsHttpVerb(verb string) bool {
func ConvertComponentIdIntoFriendlyPathSearch(id string) (string, string) { func ConvertComponentIdIntoFriendlyPathSearch(id string) (string, string) {
segs := strings.Split(id, "/") segs := strings.Split(id, "/")
name := strings.ReplaceAll(segs[len(segs)-1], "~1", "/") name := strings.ReplaceAll(segs[len(segs)-1], "~1", "/")
var cleaned []string
replaced := strings.ReplaceAll(fmt.Sprintf("%s['%s']", // check for strange spaces, chars and if found, wrap them up, clean them and create a new cleaned path.
strings.Join(segs[:len(segs)-1], "."), name), "#", "$") 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] != '$' { nameIntVal, err := strconv.ParseInt(name, 10, 32)
replaced = fmt.Sprintf("$%s", replaced) 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 return name, replaced
} }

View File

@@ -611,6 +611,35 @@ func TestConvertComponentIdIntoFriendlyPathSearch_WithRootSymbol(t *testing.T) {
segment, path := ConvertComponentIdIntoFriendlyPathSearch("/chicken/chips/pizza/cake") segment, path := ConvertComponentIdIntoFriendlyPathSearch("/chicken/chips/pizza/cake")
assert.Equal(t, "$.chicken.chips.pizza['cake']", path) assert.Equal(t, "$.chicken.chips.pizza['cake']", path)
assert.Equal(t, "cake", segment) 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) { 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, KebabCase, DetectCase("chicken-be-be-beef-or-pork"))
assert.Equal(t, RegularCase, DetectCase("kebab-TimeIn_london-TOWN")) assert.Equal(t, RegularCase, DetectCase("kebab-TimeIn_london-TOWN"))
assert.Equal(t, UnknownCase, DetectCase("")) assert.Equal(t, UnknownCase, DetectCase(""))
} }
func TestIsNodeRefValue(t *testing.T) { func TestIsNodeRefValue(t *testing.T) {