mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-09 20:47:44 +00:00
Every `Build()` method now requires a `context.Context`. This is so the rolodex knows where to resolve from when locating relative links. Without knowing where we are, there is no way to resolve anything. This new mechanism allows the model to recurse across as many files as required to locate references, without loosing track of where we are in the process. Signed-off-by: quobix <dave@quobix.com>
214 lines
5.5 KiB
Go
214 lines
5.5 KiB
Go
// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package v3
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/pb33f/libopenapi/datamodel"
|
|
"github.com/pb33f/libopenapi/datamodel/low"
|
|
"github.com/pb33f/libopenapi/index"
|
|
"github.com/pb33f/libopenapi/utils"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Paths represents a high-level OpenAPI 3+ Paths object, that is backed by a low-level one.
|
|
//
|
|
// Holds the relative paths to the individual endpoints and their operations. The path is appended to the URL from the
|
|
// Server Object in order to construct the full URL. The Paths MAY be empty, due to Access Control List (ACL)
|
|
// constraints.
|
|
// - https://spec.openapis.org/oas/v3.1.0#paths-object
|
|
type Paths struct {
|
|
PathItems map[low.KeyReference[string]]low.ValueReference[*PathItem]
|
|
Extensions map[low.KeyReference[string]]low.ValueReference[any]
|
|
*low.Reference
|
|
}
|
|
|
|
// FindPath will attempt to locate a PathItem using the provided path string.
|
|
func (p *Paths) FindPath(path string) *low.ValueReference[*PathItem] {
|
|
for k, j := range p.PathItems {
|
|
if k.Value == path {
|
|
return &j
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FindPathAndKey attempts to locate a PathItem instance, given a path key.
|
|
func (p *Paths) FindPathAndKey(path string) (*low.KeyReference[string], *low.ValueReference[*PathItem]) {
|
|
for k, j := range p.PathItems {
|
|
if k.Value == path {
|
|
return &k, &j
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// FindExtension will attempt to locate an extension using the specified string.
|
|
func (p *Paths) FindExtension(ext string) *low.ValueReference[any] {
|
|
return low.FindItemInMap[any](ext, p.Extensions)
|
|
}
|
|
|
|
// GetExtensions returns all Paths extensions and satisfies the low.HasExtensions interface.
|
|
func (p *Paths) GetExtensions() map[low.KeyReference[string]]low.ValueReference[any] {
|
|
return p.Extensions
|
|
}
|
|
|
|
// Build will extract extensions and all PathItems. This happens asynchronously for speed.
|
|
func (p *Paths) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
|
|
root = utils.NodeAlias(root)
|
|
utils.CheckForMergeNodes(root)
|
|
p.Reference = new(low.Reference)
|
|
p.Extensions = low.ExtractExtensions(root)
|
|
|
|
// Translate YAML nodes to pathsMap using `TranslatePipeline`.
|
|
type buildResult struct {
|
|
key low.KeyReference[string]
|
|
value low.ValueReference[*PathItem]
|
|
}
|
|
type buildInput struct {
|
|
currentNode *yaml.Node
|
|
pathNode *yaml.Node
|
|
}
|
|
pathsMap := make(map[low.KeyReference[string]]low.ValueReference[*PathItem])
|
|
in := make(chan buildInput)
|
|
out := make(chan buildResult)
|
|
done := make(chan struct{})
|
|
var wg sync.WaitGroup
|
|
wg.Add(2) // input and output goroutines.
|
|
|
|
// TranslatePipeline input.
|
|
go func() {
|
|
defer func() {
|
|
close(in)
|
|
wg.Done()
|
|
}()
|
|
skip := false
|
|
var currentNode *yaml.Node
|
|
for i, pathNode := range root.Content {
|
|
if strings.HasPrefix(strings.ToLower(pathNode.Value), "x-") {
|
|
skip = true
|
|
continue
|
|
}
|
|
if skip {
|
|
skip = false
|
|
continue
|
|
}
|
|
if i%2 == 0 {
|
|
currentNode = pathNode
|
|
continue
|
|
}
|
|
|
|
select {
|
|
case in <- buildInput{
|
|
currentNode: currentNode,
|
|
pathNode: pathNode,
|
|
}:
|
|
case <-done:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// TranslatePipeline output.
|
|
go func() {
|
|
for {
|
|
result, ok := <-out
|
|
if !ok {
|
|
break
|
|
}
|
|
pathsMap[result.key] = result.value
|
|
}
|
|
close(done)
|
|
wg.Done()
|
|
}()
|
|
|
|
err := datamodel.TranslatePipeline[buildInput, buildResult](in, out,
|
|
func(value buildInput) (buildResult, error) {
|
|
pNode := value.pathNode
|
|
cNode := value.currentNode
|
|
|
|
if ok, _, _ := utils.IsNodeRefValue(pNode); ok {
|
|
r, _, err := low.LocateRefNode(pNode, idx)
|
|
if r != nil {
|
|
pNode = r
|
|
if r.Tag == "" {
|
|
// If it's a node from file, tag is empty
|
|
// If it's a reference we need to extract actual operation node
|
|
pNode = r.Content[0]
|
|
}
|
|
|
|
if err != nil {
|
|
if !idx.AllowCircularReferenceResolving() {
|
|
return buildResult{}, fmt.Errorf("path item build failed: %s", err.Error())
|
|
}
|
|
}
|
|
} else {
|
|
return buildResult{}, fmt.Errorf("path item build failed: cannot find reference: %s at line %d, col %d",
|
|
pNode.Content[1].Value, pNode.Content[1].Line, pNode.Content[1].Column)
|
|
}
|
|
}
|
|
|
|
path := new(PathItem)
|
|
_ = low.BuildModel(pNode, path)
|
|
err := path.Build(ctx, cNode, pNode, idx)
|
|
|
|
// don't fail the pipeline if there is an error, log it instead.
|
|
if err != nil {
|
|
//return buildResult{}, err
|
|
idx.GetLogger().Error(fmt.Sprintf("error building path item '%s'", err.Error()))
|
|
}
|
|
|
|
return buildResult{
|
|
key: low.KeyReference[string]{
|
|
Value: cNode.Value,
|
|
KeyNode: cNode,
|
|
},
|
|
value: low.ValueReference[*PathItem]{
|
|
Value: path,
|
|
ValueNode: pNode,
|
|
},
|
|
}, nil
|
|
},
|
|
)
|
|
wg.Wait()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p.PathItems = pathsMap
|
|
return nil
|
|
}
|
|
|
|
// Hash will return a consistent SHA256 Hash of the PathItem object
|
|
func (p *Paths) Hash() [32]byte {
|
|
var f []string
|
|
l := make([]string, len(p.PathItems))
|
|
keys := make(map[string]low.ValueReference[*PathItem])
|
|
z := 0
|
|
for k := range p.PathItems {
|
|
keys[k.Value] = p.PathItems[k]
|
|
l[z] = k.Value
|
|
z++
|
|
}
|
|
sort.Strings(l)
|
|
for k := range l {
|
|
f = append(f, fmt.Sprintf("%s-%s", l[k], low.GenerateHashString(keys[l[k]].Value)))
|
|
}
|
|
ekeys := make([]string, len(p.Extensions))
|
|
z = 0
|
|
for k := range p.Extensions {
|
|
ekeys[z] = fmt.Sprintf("%s-%x", k.Value, sha256.Sum256([]byte(fmt.Sprint(p.Extensions[k].Value))))
|
|
z++
|
|
}
|
|
sort.Strings(ekeys)
|
|
f = append(f, ekeys...)
|
|
return sha256.Sum256([]byte(strings.Join(f, "|")))
|
|
}
|