Files
libopenapi/index/search_index.go
2024-09-09 23:56:54 +01:00

234 lines
6.3 KiB
Go

// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package index
import (
"context"
"fmt"
"net/url"
"path/filepath"
"strings"
)
type ContextKey string
const (
CurrentPathKey ContextKey = "currentPath"
FoundIndexKey ContextKey = "foundIndex"
)
func (index *SpecIndex) SearchIndexForReferenceByReference(fullRef *Reference) (*Reference, *SpecIndex) {
r, idx, _ := index.SearchIndexForReferenceByReferenceWithContext(context.Background(), fullRef)
return r, idx
}
// SearchIndexForReference searches the index for a reference, first looking through the mapped references
// and then externalSpecIndex for a match. If no match is found, it will recursively search the child indexes
// extracted when parsing the OpenAPI Spec.
func (index *SpecIndex) SearchIndexForReference(ref string) (*Reference, *SpecIndex) {
return index.SearchIndexForReferenceByReference(&Reference{FullDefinition: ref})
}
func (index *SpecIndex) SearchIndexForReferenceWithContext(ctx context.Context, ref string) (*Reference, *SpecIndex, context.Context) {
return index.SearchIndexForReferenceByReferenceWithContext(ctx, &Reference{FullDefinition: ref})
}
func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx context.Context, searchRef *Reference) (*Reference, *SpecIndex, context.Context) {
if index.cache != nil {
if v, ok := index.cache.Load(searchRef.FullDefinition); ok {
idx := index.extractIndex(v.(*Reference))
return v.(*Reference), idx, context.WithValue(ctx, CurrentPathKey, v.(*Reference).RemoteLocation)
}
}
ref := searchRef.FullDefinition
refAlt := ref
absPath := index.specAbsolutePath
if searchRef.RemoteLocation != "" {
absPath = searchRef.RemoteLocation
}
if absPath == "" {
absPath = index.config.BasePath
}
var roloLookup string
uri := strings.Split(ref, "#/")
if len(uri) == 2 {
if uri[0] != "" {
if strings.HasPrefix(uri[0], "http") {
roloLookup = searchRef.FullDefinition
} else {
if filepath.IsAbs(uri[0]) {
roloLookup = uri[0]
} else {
if filepath.Ext(absPath) != "" {
absPath = filepath.Dir(absPath)
}
roloLookup, _ = filepath.Abs(filepath.Join(absPath, uri[0]))
}
}
} else {
if filepath.Ext(uri[1]) != "" {
roloLookup = absPath
} else {
roloLookup = ""
}
ref = fmt.Sprintf("#/%s", uri[1])
refAlt = fmt.Sprintf("%s#/%s", absPath, uri[1])
}
} else {
if filepath.IsAbs(uri[0]) {
roloLookup = uri[0]
} else {
if strings.HasPrefix(uri[0], "http") {
roloLookup = ref
} else {
if filepath.Ext(absPath) != "" {
absPath = filepath.Dir(absPath)
}
roloLookup, _ = filepath.Abs(filepath.Join(absPath, uri[0]))
}
}
ref = uri[0]
}
if strings.Contains(ref, "%") {
// decode the url.
ref, _ = url.QueryUnescape(ref)
refAlt, _ = url.QueryUnescape(refAlt)
}
if r, ok := index.allMappedRefs[ref]; ok {
idx := index.extractIndex(r)
index.cache.Store(ref, r)
return r, idx, context.WithValue(ctx, CurrentPathKey, r.RemoteLocation)
}
if r, ok := index.allMappedRefs[refAlt]; ok {
idx := index.extractIndex(r)
idx.cache.Store(refAlt, r)
return r, idx, context.WithValue(ctx, CurrentPathKey, r.RemoteLocation)
}
if r, ok := index.allComponentSchemaDefinitions.Load(refAlt); ok {
rf := r.(*Reference)
idx := index.extractIndex(rf)
index.cache.Store(refAlt, r)
return rf, idx, context.WithValue(ctx, CurrentPathKey, rf.RemoteLocation)
}
// check the rolodex for the reference.
if roloLookup != "" {
if strings.Contains(roloLookup, "#") {
roloLookup = strings.Split(roloLookup, "#")[0]
}
if filepath.Base(roloLookup) == index.GetSpecFileName() {
return nil, index, ctx
}
rFile, err := index.rolodex.Open(roloLookup)
if err != nil {
return nil, index, ctx
}
// extract the index from the rolodex file.
if rFile != nil {
n := rFile.GetFullPath()
refParsed := ref
// do we have a relative reference and an exact match on the suffix?
if strings.HasPrefix(ref, "./") {
refParsed = strings.ReplaceAll(ref, "./", "")
}
if strings.HasSuffix(refParsed, n) {
node, _ := rFile.GetContentAsYAMLNode()
if node != nil {
r := &Reference{
FullDefinition: n,
Definition: n,
IsRemote: true,
RemoteLocation: n,
Index: rFile.GetIndex(),
Node: node.Content[0],
ParentNode: node,
}
index.cache.Store(ref, r)
return r, rFile.GetIndex(), context.WithValue(ctx, CurrentPathKey, rFile.GetFullPath())
}
}
idx := rFile.GetIndex()
if index.resolver != nil {
index.resolver.indexesVisited++
}
if idx != nil {
// check mapped refs.
if r, ok := idx.allMappedRefs[ref]; ok {
i := index.extractIndex(r)
return r, i, context.WithValue(ctx, CurrentPathKey, r.RemoteLocation)
}
// build a collection of all the inline schemas and search them
// for the reference.
var d []*Reference
d = append(d, idx.allInlineSchemaDefinitions...)
d = append(d, idx.allRefSchemaDefinitions...)
d = append(d, idx.allInlineSchemaObjectDefinitions...)
for _, s := range d {
if s.FullDefinition == ref {
i := index.extractIndex(s)
idx.cache.Store(ref, s)
index.cache.Store(ref, s)
return s, i, context.WithValue(ctx, CurrentPathKey, s.RemoteLocation)
}
}
// does component exist in the root?
node, _ := rFile.GetContentAsYAMLNode()
if node != nil {
var found *Reference
exp := strings.Split(ref, "#/")
compId := ref
if len(exp) == 2 {
compId = fmt.Sprintf("#/%s", exp[1])
found = FindComponent(node, compId, exp[0], idx)
}
if found == nil {
found = idx.FindComponent(ref)
}
if found != nil {
i := index.extractIndex(found)
i.cache.Store(ref, found)
return found, i, context.WithValue(ctx, CurrentPathKey, found.RemoteLocation)
}
}
}
}
}
if index.logger != nil {
index.logger.Error("unable to locate reference anywhere in the rolodex", "reference", ref)
}
return nil, index, ctx
}
func (index *SpecIndex) extractIndex(r *Reference) *SpecIndex {
idx := r.Index
if idx != nil && r.Index.GetSpecAbsolutePath() != r.RemoteLocation {
for i := range r.Index.rolodex.indexes {
if r.Index.rolodex.indexes[i].GetSpecAbsolutePath() == r.RemoteLocation {
idx = r.Index.rolodex.indexes[i]
break
}
}
}
return idx
}