mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-10 04:20:24 +00:00
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:
@@ -90,11 +90,9 @@ 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)
|
|
||||||
path, err := yamlpath.NewPath(yamlPath)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
nodes, fErr := path.Find(idx.GetRootNode())
|
nodes, fErr := path.Find(idx.GetRootNode())
|
||||||
if fErr == nil {
|
if fErr == nil {
|
||||||
@@ -103,6 +101,7 @@ func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("reference '%s' at line %d, column %d was not found",
|
return nil, fmt.Errorf("reference '%s' at line %d, column %d was not found",
|
||||||
rv, root.Line, root.Column)
|
rv, root.Line, root.Column)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,13 +481,49 @@ 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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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] != '$' {
|
if replaced[0] != '$' {
|
||||||
replaced = fmt.Sprintf("$%s", replaced)
|
replaced = fmt.Sprintf("$%s", replaced)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return name, replaced
|
return name, replaced
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user