An enormous amount of surgery on the low level model.

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>
This commit is contained in:
quobix
2023-10-23 15:04:34 -04:00
parent 3bf830c2b3
commit 8717b3cd33
68 changed files with 1343 additions and 1002 deletions

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
@@ -65,7 +66,7 @@ func TestCallback_MarshalYAML(t *testing.T) {
var n v3.Callback var n v3.Callback
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewCallback(&n) r := NewCallback(&n)

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
@@ -46,7 +47,7 @@ func TestComponents_MarshalYAML(t *testing.T) {
var n v3.Components var n v3.Components
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(idxNode.Content[0], idx) _ = n.Build(context.Background(), idxNode.Content[0], idx)
r := NewComponents(&n) r := NewComponents(&n)

View File

@@ -5,16 +5,22 @@ package v3
import ( import (
"fmt" "fmt"
"net/url"
"os"
"strings"
"testing"
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
v2 "github.com/pb33f/libopenapi/datamodel/high/v2" v2 "github.com/pb33f/libopenapi/datamodel/high/v2"
lowv2 "github.com/pb33f/libopenapi/datamodel/low/v2" lowv2 "github.com/pb33f/libopenapi/datamodel/low/v2"
lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3" lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
) )
var lowDoc *lowv3.Document var lowDoc *lowv3.Document
@@ -22,7 +28,7 @@ var lowDoc *lowv3.Document
func initTest() { func initTest() {
data, _ := os.ReadFile("../../../test_specs/burgershop.openapi.yaml") data, _ := os.ReadFile("../../../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: true, AllowFileReferences: true,
AllowRemoteReferences: true, AllowRemoteReferences: true,
@@ -382,9 +388,9 @@ func testBurgerShop(t *testing.T, h *Document, checkLines bool) {
func TestStripeAsDoc(t *testing.T) { func TestStripeAsDoc(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/stripe.yaml") data, _ := os.ReadFile("../../../test_specs/stripe.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration()) lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
assert.Len(t, err, 3) assert.Len(t, utils.UnwrapErrors(err), 3)
d := NewDocument(lowDoc) d := NewDocument(lowDoc)
assert.NotNil(t, d) assert.NotNil(t, d)
} }
@@ -402,7 +408,7 @@ func TestK8sAsDoc(t *testing.T) {
func TestAsanaAsDoc(t *testing.T) { func TestAsanaAsDoc(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/asana.yaml") data, _ := os.ReadFile("../../../test_specs/asana.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration()) lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
if err != nil { if err != nil {
panic("broken something") panic("broken something")
@@ -412,10 +418,51 @@ func TestAsanaAsDoc(t *testing.T) {
assert.Equal(t, 118, len(d.Paths.PathItems)) assert.Equal(t, 118, len(d.Paths.PathItems))
} }
func TestDigitalOceanAsDocViaCheckout(t *testing.T) {
// this is a full checkout of the digitalocean API repo.
tmp, _ := os.MkdirTemp("", "openapi")
cmd := exec.Command("git", "clone", "https://github.com/digitalocean/openapi", tmp)
defer os.RemoveAll(filepath.Join(tmp, "openapi"))
err := cmd.Run()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
spec, _ := filepath.Abs(filepath.Join(tmp, "specification", "DigitalOcean-public.v2.yaml"))
doLocal, _ := os.ReadFile(spec)
var rootNode yaml.Node
_ = yaml.Unmarshal(doLocal, &rootNode)
basePath := filepath.Join(tmp, "specification")
data, _ := os.ReadFile("../../../test_specs/digitalocean.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
config := datamodel.DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: true,
BasePath: basePath,
}
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &config)
if err != nil {
er := utils.UnwrapErrors(err)
for e := range er {
fmt.Println(er[e])
}
}
d := NewDocument(lowDoc)
assert.NotNil(t, d)
assert.Equal(t, 183, len(d.Paths.PathItems))
}
func TestDigitalOceanAsDocFromSHA(t *testing.T) { func TestDigitalOceanAsDocFromSHA(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/digitalocean.yaml") data, _ := os.ReadFile("../../../test_specs/digitalocean.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err error
baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/82e1d558e15a59edc1d47d2c5544e7138f5b3cbf/specification") baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/82e1d558e15a59edc1d47d2c5544e7138f5b3cbf/specification")
config := datamodel.DocumentConfiguration{ config := datamodel.DocumentConfiguration{
@@ -424,12 +471,53 @@ func TestDigitalOceanAsDocFromSHA(t *testing.T) {
BaseURL: baseURL, BaseURL: baseURL,
} }
if os.Getenv("GITHUB_TOKEN") != "" {
client := &http.Client{
Timeout: time.Second * 60,
}
config.RemoteURLHandler = func(url string) (*http.Response, error) {
request, _ := http.NewRequest(http.MethodGet, url, nil)
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", os.Getenv("GITHUB_TOKEN")))
return client.Do(request)
}
}
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &config)
assert.Len(t, utils.UnwrapErrors(err), 3) // there are 3 404's in this release of the API.
d := NewDocument(lowDoc)
assert.NotNil(t, d)
assert.Equal(t, 183, len(d.Paths.PathItems))
}
func TestDigitalOceanAsDocFromMain(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/digitalocean.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
var err error
baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification")
config := datamodel.DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: true,
BaseURL: baseURL,
}
if os.Getenv("GITHUB_TOKEN") != "" {
client := &http.Client{
Timeout: time.Second * 60,
}
config.RemoteURLHandler = func(url string) (*http.Response, error) {
request, _ := http.NewRequest(http.MethodGet, url, nil)
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", os.Getenv("GITHUB_TOKEN")))
return client.Do(request)
}
}
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &config) lowDoc, err = lowv3.CreateDocumentFromConfig(info, &config)
if err != nil { if err != nil {
for e := range err { er := utils.UnwrapErrors(err)
fmt.Println(err[e]) for e := range er {
fmt.Printf("Reported Error: %s\n", er[e])
} }
panic("broken something")
} }
d := NewDocument(lowDoc) d := NewDocument(lowDoc)
assert.NotNil(t, d) assert.NotNil(t, d)
@@ -439,7 +527,7 @@ func TestDigitalOceanAsDocFromSHA(t *testing.T) {
func TestPetstoreAsDoc(t *testing.T) { func TestPetstoreAsDoc(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/petstorev3.json") data, _ := os.ReadFile("../../../test_specs/petstorev3.json")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration()) lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
if err != nil { if err != nil {
panic("broken something") panic("broken something")
@@ -452,10 +540,10 @@ func TestPetstoreAsDoc(t *testing.T) {
func TestCircularReferencesDoc(t *testing.T) { func TestCircularReferencesDoc(t *testing.T) {
data, _ := os.ReadFile("../../../test_specs/circular-tests.yaml") data, _ := os.ReadFile("../../../test_specs/circular-tests.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration()) lDoc, err := lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
assert.Len(t, err, 3) assert.Len(t, utils.UnwrapErrors(err), 3)
d := NewDocument(lowDoc) d := NewDocument(lDoc)
assert.Len(t, d.Components.Schemas, 9) assert.Len(t, d.Components.Schemas, 9)
assert.Len(t, d.Index.GetCircularReferences(), 3) assert.Len(t, d.Index.GetCircularReferences(), 3)
} }
@@ -604,7 +692,7 @@ components:
numPatties: 1` numPatties: 1`
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: true, AllowFileReferences: true,
AllowRemoteReferences: true, AllowRemoteReferences: true,
@@ -657,7 +745,7 @@ components:
required: true` required: true`
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: true, AllowFileReferences: true,
AllowRemoteReferences: true, AllowRemoteReferences: true,
@@ -685,7 +773,7 @@ components:
` `
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
var err []error var err error
lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ lowDoc, err = lowv3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: true, AllowFileReferences: true,
AllowRemoteReferences: true, AllowRemoteReferences: true,

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"os" "os"
"strings" "strings"
"testing" "testing"
@@ -20,7 +21,7 @@ func TestMediaType_MarshalYAMLInline(t *testing.T) {
// load the petstore spec // load the petstore spec
data, _ := os.ReadFile("../../../test_specs/petstorev3.json") data, _ := os.ReadFile("../../../test_specs/petstorev3.json")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err error
lowDoc, err = v3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{}) lowDoc, err = v3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
if err != nil { if err != nil {
panic("broken something") panic("broken something")
@@ -110,7 +111,7 @@ func TestMediaType_MarshalYAML(t *testing.T) {
// load the petstore spec // load the petstore spec
data, _ := os.ReadFile("../../../test_specs/petstorev3.json") data, _ := os.ReadFile("../../../test_specs/petstorev3.json")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err error
lowDoc, err = v3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{}) lowDoc, err = v3.CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{})
if err != nil { if err != nil {
panic("broken something") panic("broken something")
@@ -161,7 +162,7 @@ func TestMediaType_Examples(t *testing.T) {
var n v3.MediaType var n v3.MediaType
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewMediaType(&n) r := NewMediaType(&n)

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
@@ -43,7 +44,7 @@ clientCredentials:
var n v3.OAuthFlows var n v3.OAuthFlows
_ = low.BuildModel(&idxNode, &n) _ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewOAuthFlows(&n) r := NewOAuthFlows(&n)

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"strings" "strings"
"testing" "testing"
@@ -43,7 +44,7 @@ callbacks:
var n v3.Operation var n v3.Operation
_ = low.BuildModel(&idxNode, &n) _ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewOperation(&n) r := NewOperation(&n)
@@ -140,7 +141,7 @@ security: []`
var n v3.Operation var n v3.Operation
_ = low.BuildModel(&idxNode, &n) _ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewOperation(&n) r := NewOperation(&n)
@@ -158,7 +159,7 @@ func TestOperation_NoSecurity(t *testing.T) {
var n v3.Operation var n v3.Operation
_ = low.BuildModel(&idxNode, &n) _ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewOperation(&n) r := NewOperation(&n)

View File

@@ -5,6 +5,7 @@ package v3
import ( import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/utils"
"os" "os"
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
@@ -19,17 +20,14 @@ func Example_createHighLevelOpenAPIDocument() {
// Create a new *datamodel.SpecInfo from bytes. // Create a new *datamodel.SpecInfo from bytes.
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err error
// Create a new low-level Document, capture any errors thrown during creation. // Create a new low-level Document, capture any errors thrown during creation.
lowDoc, err = lowv3.CreateDocument(info) lowDoc, err = lowv3.CreateDocumentFromConfig(info, datamodel.NewOpenDocumentConfiguration())
// Get upset if any errors were thrown. // Get upset if any errors were thrown.
if len(err) > 0 { for i := range utils.UnwrapErrors(err) {
for i := range err { fmt.Printf("error: %v", i)
fmt.Printf("error: %e", err[i])
}
panic("something went wrong")
} }
// Create a high-level Document from the low-level one. // Create a high-level Document from the low-level one.

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"strings" "strings"
"testing" "testing"
@@ -28,7 +29,7 @@ func TestPathItem(t *testing.T) {
var n v3.PathItem var n v3.PathItem
_ = low.BuildModel(&idxNode, &n) _ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewPathItem(&n) r := NewPathItem(&n)
@@ -62,7 +63,7 @@ trace:
var n v3.PathItem var n v3.PathItem
_ = low.BuildModel(&idxNode, &n) _ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewPathItem(&n) r := NewPathItem(&n)

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"strings" "strings"
"testing" "testing"
@@ -37,7 +38,7 @@ func TestPaths_MarshalYAML(t *testing.T) {
err := low.BuildModel(&idxNode, &n) err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err) assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx) err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
high := NewPaths(&n) high := NewPaths(&n)
@@ -89,7 +90,7 @@ func TestPaths_MarshalYAMLInline(t *testing.T) {
err := low.BuildModel(&idxNode, &n) err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err) assert.NoError(t, err)
err = n.Build(nil, idxNode.Content[0], idx) err = n.Build(context.Background(), nil, idxNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
high := NewPaths(&n) high := NewPaths(&n)

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"strings" "strings"
"testing" "testing"
@@ -38,7 +39,7 @@ links:
var n v3.Response var n v3.Response
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewResponse(&n) r := NewResponse(&n)
@@ -69,7 +70,7 @@ links:
var n v3.Response var n v3.Response
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewResponse(&n) r := NewResponse(&n)
@@ -97,7 +98,7 @@ links:
var n v3.Response var n v3.Response
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewResponse(&n) r := NewResponse(&n)

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"strings" "strings"
"testing" "testing"
@@ -30,7 +31,7 @@ func TestNewResponses(t *testing.T) {
var n v3.Responses var n v3.Responses
_ = low.BuildModel(&idxNode, &n) _ = low.BuildModel(&idxNode, &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewResponses(&n) r := NewResponses(&n)
@@ -60,7 +61,7 @@ func TestResponses_MarshalYAML(t *testing.T) {
var n v3.Responses var n v3.Responses
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewResponses(&n) r := NewResponses(&n)
@@ -90,7 +91,7 @@ func TestResponses_MarshalYAMLInline(t *testing.T) {
var n v3.Responses var n v3.Responses
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewResponses(&n) r := NewResponses(&n)

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
v3 "github.com/pb33f/libopenapi/datamodel/low/v3" v3 "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
@@ -31,7 +32,7 @@ func TestSecurityScheme_MarshalYAML(t *testing.T) {
var n v3.SecurityScheme var n v3.SecurityScheme
_ = low.BuildModel(idxNode.Content[0], &n) _ = low.BuildModel(idxNode.Content[0], &n)
_ = n.Build(nil, idxNode.Content[0], idx) _ = n.Build(context.Background(), nil, idxNode.Content[0], idx)
r := NewSecurityScheme(&n) r := NewSecurityScheme(&n)

View File

@@ -4,6 +4,7 @@
package base package base
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
@@ -23,7 +24,7 @@ type Contact struct {
} }
// Build is not implemented for Contact (there is nothing to build). // Build is not implemented for Contact (there is nothing to build).
func (c *Contact) Build(_, _ *yaml.Node, _ *index.SpecIndex) error { func (c *Contact) Build(_ context.Context, _, _ *yaml.Node, _ *index.SpecIndex) error {
c.Reference = new(low.Reference) c.Reference = new(low.Reference)
// not implemented. // not implemented.
return nil return nil

View File

@@ -4,6 +4,7 @@
package base package base
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -60,7 +61,7 @@ func (ex *Example) Hash() [32]byte {
} }
// Build extracts extensions and example value // Build extracts extensions and example value
func (ex *Example) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (ex *Example) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
ex.Reference = new(low.Reference) ex.Reference = new(low.Reference)

View File

@@ -4,6 +4,7 @@
package base package base
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -33,7 +34,7 @@ func (ex *ExternalDoc) FindExtension(ext string) *low.ValueReference[any] {
} }
// Build will extract extensions from the ExternalDoc instance. // Build will extract extensions from the ExternalDoc instance.
func (ex *ExternalDoc) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (ex *ExternalDoc) Build(_ context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
ex.Reference = new(low.Reference) ex.Reference = new(low.Reference)

View File

@@ -4,6 +4,7 @@
package base package base
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
@@ -45,18 +46,18 @@ func (i *Info) GetExtensions() map[low.KeyReference[string]]low.ValueReference[a
} }
// Build will extract out the Contact and Info objects from the supplied root node. // Build will extract out the Contact and Info objects from the supplied root node.
func (i *Info) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (i *Info) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
i.Reference = new(low.Reference) i.Reference = new(low.Reference)
i.Extensions = low.ExtractExtensions(root) i.Extensions = low.ExtractExtensions(root)
// extract contact // extract contact
contact, _ := low.ExtractObject[*Contact](ContactLabel, root, idx) contact, _ := low.ExtractObject[*Contact](ctx, ContactLabel, root, idx)
i.Contact = contact i.Contact = contact
// extract license // extract license
lic, _ := low.ExtractObject[*License](LicenseLabel, root, idx) lic, _ := low.ExtractObject[*License](ctx, LicenseLabel, root, idx)
i.License = lic i.License = lic
return nil return nil
} }

View File

@@ -4,6 +4,7 @@
package base package base
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -25,7 +26,7 @@ type License struct {
} }
// Build out a license, complain if both a URL and identifier are present as they are mutually exclusive // Build out a license, complain if both a URL and identifier are present as they are mutually exclusive
func (l *License) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (l *License) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
l.Reference = new(low.Reference) l.Reference = new(low.Reference)

View File

@@ -1,6 +1,7 @@
package base package base
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"sort" "sort"
@@ -490,13 +491,13 @@ func (s *Schema) GetExtensions() map[low.KeyReference[string]]low.ValueReference
// - UnevaluatedItems // - UnevaluatedItems
// - UnevaluatedProperties // - UnevaluatedProperties
// - Anchor // - Anchor
func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
s.Reference = new(low.Reference) s.Reference = new(low.Reference)
s.Index = idx s.Index = idx
if h, _, _ := utils.IsNodeRefValue(root); h { if h, _, _ := utils.IsNodeRefValue(root); h {
ref, err := low.LocateRefNode(root, idx) ref, _, err := low.LocateRefNode(root, idx)
if ref != nil { if ref != nil {
root = ref root = ref
if err != nil { if err != nil {
@@ -704,7 +705,7 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
if extDocNode != nil { if extDocNode != nil {
var exDoc ExternalDoc var exDoc ExternalDoc
_ = low.BuildModel(extDocNode, &exDoc) _ = low.BuildModel(extDocNode, &exDoc)
_ = exDoc.Build(extDocLabel, extDocNode, idx) // throws no errors, can't check for one. _ = exDoc.Build(ctx, extDocLabel, extDocNode, idx) // throws no errors, can't check for one.
s.ExternalDocs = low.NodeReference[*ExternalDoc]{Value: &exDoc, KeyNode: extDocLabel, ValueNode: extDocNode} s.ExternalDocs = low.NodeReference[*ExternalDoc]{Value: &exDoc, KeyNode: extDocLabel, ValueNode: extDocNode}
} }
@@ -1069,7 +1070,7 @@ func buildPropertyMap(root *yaml.Node, idx *index.SpecIndex, label string) (*low
isRef := false isRef := false
refString := "" refString := ""
if h, _, l := utils.IsNodeRefValue(prop); h { if h, _, l := utils.IsNodeRefValue(prop); h {
ref, _ := low.LocateRefNode(prop, idx) ref, _, _ := low.LocateRefNode(prop, idx)
if ref != nil { if ref != nil {
isRef = true isRef = true
prop = ref prop = ref
@@ -1165,7 +1166,7 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
h := false h := false
if h, _, refLocation = utils.IsNodeRefValue(valueNode); h { if h, _, refLocation = utils.IsNodeRefValue(valueNode); h {
isRef = true isRef = true
ref, _ := low.LocateRefNode(valueNode, idx) ref, _, _ := low.LocateRefNode(valueNode, idx)
if ref != nil { if ref != nil {
valueNode = ref valueNode = ref
} else { } else {
@@ -1196,7 +1197,7 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
h := false h := false
if h, _, refLocation = utils.IsNodeRefValue(vn); h { if h, _, refLocation = utils.IsNodeRefValue(vn); h {
isRef = true isRef = true
ref, _ := low.LocateRefNode(vn, idx) ref, _, _ := low.LocateRefNode(vn, idx)
if ref != nil { if ref != nil {
vn = ref vn = ref
} else { } else {
@@ -1237,16 +1238,17 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
// ExtractSchema will return a pointer to a NodeReference that contains a *SchemaProxy if successful. The function // ExtractSchema will return a pointer to a NodeReference that contains a *SchemaProxy if successful. The function
// will specifically look for a key node named 'schema' and extract the value mapped to that key. If the operation // will specifically look for a key node named 'schema' and extract the value mapped to that key. If the operation
// fails then no NodeReference is returned and an error is returned instead. // fails then no NodeReference is returned and an error is returned instead.
func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*SchemaProxy], error) { func ExtractSchema(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*SchemaProxy], error) {
var schLabel, schNode *yaml.Node var schLabel, schNode *yaml.Node
errStr := "schema build failed: reference '%s' cannot be found at line %d, col %d" errStr := "schema build failed: reference '%s' cannot be found at line %d, col %d"
isRef := false isRef := false
refLocation := "" refLocation := ""
if rf, rl, _ := utils.IsNodeRefValue(root); rf { if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index. // locate reference in index.
isRef = true isRef = true
ref, _ := low.LocateRefNode(root, idx) ref, _, _ := low.LocateRefNode(root, idx)
if ref != nil { if ref != nil {
schNode = ref schNode = ref
schLabel = rl schLabel = rl
@@ -1260,9 +1262,13 @@ func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*S
h := false h := false
if h, _, refLocation = utils.IsNodeRefValue(schNode); h { if h, _, refLocation = utils.IsNodeRefValue(schNode); h {
isRef = true isRef = true
ref, _ := low.LocateRefNode(schNode, idx) ref, foundIdx, _, nCtx := low.LocateRefNodeWithContext(ctx, schNode, idx)
if ref != nil { if ref != nil {
schNode = ref schNode = ref
if foundIdx != nil {
//idx = foundIdx
}
ctx = nCtx
} else { } else {
return nil, fmt.Errorf(errStr, return nil, fmt.Errorf(errStr,
schNode.Content[1].Value, schNode.Content[1].Line, schNode.Content[1].Column) schNode.Content[1].Value, schNode.Content[1].Line, schNode.Content[1].Column)
@@ -1273,7 +1279,7 @@ func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*S
if schNode != nil { if schNode != nil {
// check if schema has already been built. // check if schema has already been built.
schema := &SchemaProxy{kn: schLabel, vn: schNode, idx: idx, isReference: isRef, referenceLookup: refLocation} schema := &SchemaProxy{kn: schLabel, vn: schNode, idx: idx, ctx: ctx, isReference: isRef, referenceLookup: refLocation}
return &low.NodeReference[*SchemaProxy]{ return &low.NodeReference[*SchemaProxy]{
Value: schema, KeyNode: schLabel, ValueNode: schNode, ReferenceNode: isRef, Value: schema, KeyNode: schLabel, ValueNode: schNode, ReferenceNode: isRef,
Reference: refLocation, Reference: refLocation,

View File

@@ -4,6 +4,7 @@
package base package base
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
@@ -51,14 +52,16 @@ type SchemaProxy struct {
buildError error buildError error
isReference bool // Is the schema underneath originally a $ref? isReference bool // Is the schema underneath originally a $ref?
referenceLookup string // If the schema is a $ref, what's its name? referenceLookup string // If the schema is a $ref, what's its name?
ctx context.Context
} }
// Build will prepare the SchemaProxy for rendering, it does not build the Schema, only sets up internal state. // Build will prepare the SchemaProxy for rendering, it does not build the Schema, only sets up internal state.
// Key maybe nil if absent. // Key maybe nil if absent.
func (sp *SchemaProxy) Build(key, value *yaml.Node, idx *index.SpecIndex) error { func (sp *SchemaProxy) Build(ctx context.Context, key, value *yaml.Node, idx *index.SpecIndex) error {
sp.kn = key sp.kn = key
sp.vn = value sp.vn = value
sp.idx = idx sp.idx = idx
sp.ctx = ctx
if rf, _, r := utils.IsNodeRefValue(value); rf { if rf, _, r := utils.IsNodeRefValue(value); rf {
sp.isReference = true sp.isReference = true
sp.referenceLookup = r sp.referenceLookup = r
@@ -83,7 +86,7 @@ func (sp *SchemaProxy) Schema() *Schema {
} }
schema := new(Schema) schema := new(Schema)
utils.CheckForMergeNodes(sp.vn) utils.CheckForMergeNodes(sp.vn)
err := schema.Build(sp.vn, sp.idx) err := schema.Build(sp.ctx, sp.vn, sp.idx)
if err != nil { if err != nil {
sp.buildError = err sp.buildError = err
return nil return nil

View File

@@ -4,6 +4,7 @@
package base package base
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -28,7 +29,7 @@ type SecurityRequirement struct {
} }
// Build will extract security requirements from the node (the structure is odd, to be honest) // Build will extract security requirements from the node (the structure is odd, to be honest)
func (s *SecurityRequirement) Build(_, root *yaml.Node, _ *index.SpecIndex) error { func (s *SecurityRequirement) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
s.Reference = new(low.Reference) s.Reference = new(low.Reference)

View File

@@ -4,6 +4,7 @@
package base package base
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -34,14 +35,14 @@ func (t *Tag) FindExtension(ext string) *low.ValueReference[any] {
} }
// Build will extract extensions and external docs for the Tag. // Build will extract extensions and external docs for the Tag.
func (t *Tag) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (t *Tag) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
t.Reference = new(low.Reference) t.Reference = new(low.Reference)
t.Extensions = low.ExtractExtensions(root) t.Extensions = low.ExtractExtensions(root)
// extract externalDocs // extract externalDocs
extDocs, err := low.ExtractObject[*ExternalDoc](ExternalDocsLabel, root, idx) extDocs, err := low.ExtractObject[*ExternalDoc](ctx, ExternalDocsLabel, root, idx)
t.ExternalDocs = extDocs t.ExternalDocs = extDocs
return err return err
} }

File diff suppressed because it is too large Load Diff

View File

@@ -4,15 +4,14 @@
package low package low
import ( import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"os" "strings"
"strings" "testing"
"testing"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func TestFindItemInMap(t *testing.T) { func TestFindItemInMap(t *testing.T) {
@@ -1570,38 +1569,6 @@ func TestExtractMapFlat_Ref_Bad(t *testing.T) {
assert.Len(t, things, 0) assert.Len(t, things, 0)
} }
func TestLocateRefNode_RemoteFile(t *testing.T) {
ymlFile := fmt.Sprintf(`components:
schemas:
hey:
$ref: '%s#/components/schemas/hey'`, "remote.yaml")
ymlRemote := `components:
schemas:
hey:
AlmostWork: 999`
_ = os.WriteFile("remote.yaml", []byte(ymlRemote), 0665)
defer os.Remove("remote.yaml")
ymlLocal := `$ref: '#/components/schemas/hey'`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(ymlFile), &idxNode) // an empty index.
assert.NoError(t, mErr)
idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateOpenAPIIndexConfig())
var cNode yaml.Node
e := yaml.Unmarshal([]byte(ymlLocal), &cNode)
assert.NoError(t, e)
things, _, _, err := ExtractMap[*test_Good]("one", cNode.Content[0], idx)
assert.NoError(t, err)
assert.Len(t, things, 1)
}
func TestExtractExtensions(t *testing.T) { func TestExtractExtensions(t *testing.T) {
yml := `x-bing: ding yml := `x-bing: ding

View File

@@ -1,6 +1,7 @@
package low package low
import ( import (
"context"
"fmt" "fmt"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
@@ -38,7 +39,7 @@ type IsReferenced interface {
// //
// Used by generic functions when automatically building out structs based on yaml.Node inputs. // Used by generic functions when automatically building out structs based on yaml.Node inputs.
type Buildable[T any] interface { type Buildable[T any] interface {
Build(key, value *yaml.Node, idx *index.SpecIndex) error Build(ctx context.Context, key, value *yaml.Node, idx *index.SpecIndex) error
*T *T
} }
@@ -112,6 +113,8 @@ type NodeReference[T any] struct {
// If HasReference is true, then Reference contains the original $ref value. // If HasReference is true, then Reference contains the original $ref value.
Reference string Reference string
Context context.Context
} }
// KeyReference is a low-level container for key nodes holding a Value of type T. A KeyNode is a pointer to the // KeyReference is a low-level container for key nodes holding a Value of type T. A KeyNode is a pointer to the

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
@@ -71,7 +72,7 @@ func (s *SecurityDefinitions) FindSecurityDefinition(securityDef string) *low.Va
} }
// Build will extract all definitions into SchemaProxy instances. // Build will extract all definitions into SchemaProxy instances.
func (d *Definitions) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (d *Definitions) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
errorChan := make(chan error) errorChan := make(chan error)
@@ -81,7 +82,7 @@ func (d *Definitions) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*base.SchemaProxy], e chan error) { r chan definitionResult[*base.SchemaProxy], e chan error) {
obj, err, _, rv := low.ExtractObjectRaw[*base.SchemaProxy](label, value, idx) obj, err, _, rv := low.ExtractObjectRaw[*base.SchemaProxy](ctx, label, value, idx)
if err != nil { if err != nil {
e <- err e <- err
} }
@@ -133,7 +134,7 @@ func (d *Definitions) Hash() [32]byte {
} }
// Build will extract all ParameterDefinitions into Parameter instances. // Build will extract all ParameterDefinitions into Parameter instances.
func (pd *ParameterDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (pd *ParameterDefinitions) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
errorChan := make(chan error) errorChan := make(chan error)
resultChan := make(chan definitionResult[*Parameter]) resultChan := make(chan definitionResult[*Parameter])
var defLabel *yaml.Node var defLabel *yaml.Node
@@ -141,7 +142,7 @@ func (pd *ParameterDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex)
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*Parameter], e chan error) { r chan definitionResult[*Parameter], e chan error) {
obj, err, _, rv := low.ExtractObjectRaw[*Parameter](label, value, idx) obj, err, _, rv := low.ExtractObjectRaw[*Parameter](ctx, label, value, idx)
if err != nil { if err != nil {
e <- err e <- err
} }
@@ -182,7 +183,7 @@ type definitionResult[T any] struct {
} }
// Build will extract all ResponsesDefinitions into Response instances. // Build will extract all ResponsesDefinitions into Response instances.
func (r *ResponsesDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (r *ResponsesDefinitions) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
errorChan := make(chan error) errorChan := make(chan error)
resultChan := make(chan definitionResult[*Response]) resultChan := make(chan definitionResult[*Response])
var defLabel *yaml.Node var defLabel *yaml.Node
@@ -190,7 +191,7 @@ func (r *ResponsesDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) e
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*Response], e chan error) { r chan definitionResult[*Response], e chan error) {
obj, err, _, rv := low.ExtractObjectRaw[*Response](label, value, idx) obj, err, _, rv := low.ExtractObjectRaw[*Response](ctx, label, value, idx)
if err != nil { if err != nil {
e <- err e <- err
} }
@@ -225,7 +226,7 @@ func (r *ResponsesDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) e
} }
// Build will extract all SecurityDefinitions into SecurityScheme instances. // Build will extract all SecurityDefinitions into SecurityScheme instances.
func (s *SecurityDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (s *SecurityDefinitions) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
errorChan := make(chan error) errorChan := make(chan error)
resultChan := make(chan definitionResult[*SecurityScheme]) resultChan := make(chan definitionResult[*SecurityScheme])
var defLabel *yaml.Node var defLabel *yaml.Node
@@ -234,7 +235,7 @@ func (s *SecurityDefinitions) Build(_, root *yaml.Node, idx *index.SpecIndex) er
var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex, var buildFunc = func(label *yaml.Node, value *yaml.Node, idx *index.SpecIndex,
r chan definitionResult[*SecurityScheme], e chan error) { r chan definitionResult[*SecurityScheme], e chan error) {
obj, err, _, rv := low.ExtractObjectRaw[*SecurityScheme](label, value, idx) obj, err, _, rv := low.ExtractObjectRaw[*SecurityScheme](ctx, label, value, idx)
if err != nil { if err != nil {
e <- err e <- err
} }

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -27,7 +28,7 @@ func (e *Examples) FindExample(name string) *low.ValueReference[any] {
} }
// Build will extract all examples and will attempt to unmarshal content into a map or slice based on type. // Build will extract all examples and will attempt to unmarshal content into a map or slice based on type.
func (e *Examples) Build(_, root *yaml.Node, _ *index.SpecIndex) error { func (e *Examples) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
var keyNode, currNode *yaml.Node var keyNode, currNode *yaml.Node

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -51,11 +52,11 @@ func (h *Header) GetExtensions() map[low.KeyReference[string]]low.ValueReference
} }
// Build will build out items, extensions and default value from the supplied node. // Build will build out items, extensions and default value from the supplied node.
func (h *Header) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (h *Header) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
h.Extensions = low.ExtractExtensions(root) h.Extensions = low.ExtractExtensions(root)
items, err := low.ExtractObject[*Items](ItemsLabel, root, idx) items, err := low.ExtractObject[*Items](ctx, ItemsLabel, root, idx)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -102,11 +103,11 @@ func (i *Items) Hash() [32]byte {
} }
// Build will build out items and default value. // Build will build out items and default value.
func (i *Items) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (i *Items) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
i.Extensions = low.ExtractExtensions(root) i.Extensions = low.ExtractExtensions(root)
items, iErr := low.ExtractObject[*Items](ItemsLabel, root, idx) items, iErr := low.ExtractObject[*Items](ctx, ItemsLabel, root, idx)
if iErr != nil { if iErr != nil {
return iErr return iErr
} }

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -36,20 +37,20 @@ type Operation struct {
} }
// Build will extract external docs, extensions, parameters, responses and security requirements. // Build will extract external docs, extensions, parameters, responses and security requirements.
func (o *Operation) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (o *Operation) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
o.Extensions = low.ExtractExtensions(root) o.Extensions = low.ExtractExtensions(root)
// extract externalDocs // extract externalDocs
extDocs, dErr := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, root, idx) extDocs, dErr := low.ExtractObject[*base.ExternalDoc](ctx, base.ExternalDocsLabel, root, idx)
if dErr != nil { if dErr != nil {
return dErr return dErr
} }
o.ExternalDocs = extDocs o.ExternalDocs = extDocs
// extract parameters // extract parameters
params, ln, vn, pErr := low.ExtractArray[*Parameter](ParametersLabel, root, idx) params, ln, vn, pErr := low.ExtractArray[*Parameter](ctx, ParametersLabel, root, idx)
if pErr != nil { if pErr != nil {
return pErr return pErr
} }
@@ -62,14 +63,14 @@ func (o *Operation) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// extract responses // extract responses
respBody, respErr := low.ExtractObject[*Responses](ResponsesLabel, root, idx) respBody, respErr := low.ExtractObject[*Responses](ctx, ResponsesLabel, root, idx)
if respErr != nil { if respErr != nil {
return respErr return respErr
} }
o.Responses = respBody o.Responses = respBody
// extract security // extract security
sec, sln, svn, sErr := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, root, idx) sec, sln, svn, sErr := low.ExtractArray[*base.SecurityRequirement](ctx, SecurityLabel, root, idx)
if sErr != nil { if sErr != nil {
return sErr return sErr
} }

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -94,18 +95,18 @@ func (p *Parameter) GetExtensions() map[low.KeyReference[string]]low.ValueRefere
} }
// Build will extract out extensions, schema, items and default value // Build will extract out extensions, schema, items and default value
func (p *Parameter) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (p *Parameter) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
p.Extensions = low.ExtractExtensions(root) p.Extensions = low.ExtractExtensions(root)
sch, sErr := base.ExtractSchema(root, idx) sch, sErr := base.ExtractSchema(ctx, root, idx)
if sErr != nil { if sErr != nil {
return sErr return sErr
} }
if sch != nil { if sch != nil {
p.Schema = *sch p.Schema = *sch
} }
items, iErr := low.ExtractObject[*Items](ItemsLabel, root, idx) items, iErr := low.ExtractObject[*Items](ctx, ItemsLabel, root, idx)
if iErr != nil { if iErr != nil {
return iErr return iErr
} }

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"sort" "sort"
@@ -48,7 +49,7 @@ func (p *PathItem) GetExtensions() map[low.KeyReference[string]]low.ValueReferen
// Build will extract extensions, parameters and operations for all methods. Every method is handled // Build will extract extensions, parameters and operations for all methods. Every method is handled
// asynchronously, in order to keep things moving quickly for complex operations. // asynchronously, in order to keep things moving quickly for complex operations.
func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (p *PathItem) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
p.Extensions = low.ExtractExtensions(root) p.Extensions = low.ExtractExtensions(root)
@@ -61,7 +62,7 @@ func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
var ops []low.NodeReference[*Operation] var ops []low.NodeReference[*Operation]
// extract parameters // extract parameters
params, ln, vn, pErr := low.ExtractArray[*Parameter](ParametersLabel, root, idx) params, ln, vn, pErr := low.ExtractArray[*Parameter](ctx, ParametersLabel, root, idx)
if pErr != nil { if pErr != nil {
return pErr return pErr
} }
@@ -158,7 +159,7 @@ func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
opErrorChan := make(chan error) opErrorChan := make(chan error)
var buildOpFunc = func(op low.NodeReference[*Operation], ch chan<- bool, errCh chan<- error) { var buildOpFunc = func(op low.NodeReference[*Operation], ch chan<- bool, errCh chan<- error) {
er := op.Value.Build(op.KeyNode, op.ValueNode, idx) er := op.Value.Build(ctx, op.KeyNode, op.ValueNode, idx)
if er != nil { if er != nil {
errCh <- er errCh <- er
} }

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"sort" "sort"
@@ -54,7 +55,7 @@ func (p *Paths) FindExtension(ext string) *low.ValueReference[any] {
} }
// Build will extract extensions and paths from node. // Build will extract extensions and paths from node.
func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (p *Paths) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
p.Extensions = low.ExtractExtensions(root) p.Extensions = low.ExtractExtensions(root)
@@ -126,7 +127,7 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
cNode := value.currentNode cNode := value.currentNode
path := new(PathItem) path := new(PathItem)
_ = low.BuildModel(pNode, path) _ = low.BuildModel(pNode, path)
err := path.Build(cNode, pNode, idx) err := path.Build(ctx, cNode, pNode, idx)
if err != nil { if err != nil {
return pathBuildResult{}, err return pathBuildResult{}, err
} }

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -43,11 +44,11 @@ func (r *Response) FindHeader(hType string) *low.ValueReference[*Header] {
} }
// Build will extract schema, extensions, examples and headers from node // Build will extract schema, extensions, examples and headers from node
func (r *Response) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (r *Response) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)
s, err := base.ExtractSchema(root, idx) s, err := base.ExtractSchema(ctx, root, idx)
if err != nil { if err != nil {
return err return err
} }
@@ -56,14 +57,14 @@ func (r *Response) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// extract examples // extract examples
examples, expErr := low.ExtractObject[*Examples](ExamplesLabel, root, idx) examples, expErr := low.ExtractObject[*Examples](ctx, ExamplesLabel, root, idx)
if expErr != nil { if expErr != nil {
return expErr return expErr
} }
r.Examples = examples r.Examples = examples
//extract headers //extract headers
headers, lN, kN, err := low.ExtractMap[*Header](HeadersLabel, root, idx) headers, lN, kN, err := low.ExtractMap[*Header](ctx, HeadersLabel, root, idx)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -27,13 +28,13 @@ func (r *Responses) GetExtensions() map[low.KeyReference[string]]low.ValueRefere
} }
// Build will extract default value and extensions from node. // Build will extract default value and extensions from node.
func (r *Responses) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (r *Responses) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)
if utils.IsNodeMap(root) { if utils.IsNodeMap(root) {
codes, err := low.ExtractMapNoLookup[*Response](root, idx) codes, err := low.ExtractMapNoLookup[*Response](ctx, root, idx)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -34,7 +35,7 @@ func (s *Scopes) FindScope(scope string) *low.ValueReference[string] {
} }
// Build will extract scope values and extensions from node. // Build will extract scope values and extensions from node.
func (s *Scopes) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (s *Scopes) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
s.Extensions = low.ExtractExtensions(root) s.Extensions = low.ExtractExtensions(root)

View File

@@ -4,6 +4,7 @@
package v2 package v2
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -38,12 +39,12 @@ func (ss *SecurityScheme) GetExtensions() map[low.KeyReference[string]]low.Value
} }
// Build will extract extensions and scopes from the node. // Build will extract extensions and scopes from the node.
func (ss *SecurityScheme) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (ss *SecurityScheme) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
ss.Extensions = low.ExtractExtensions(root) ss.Extensions = low.ExtractExtensions(root)
scopes, sErr := low.ExtractObject[*Scopes](ScopesLabel, root, idx) scopes, sErr := low.ExtractObject[*Scopes](ctx, ScopesLabel, root, idx)
if sErr != nil { if sErr != nil {
return sErr return sErr
} }

View File

@@ -12,6 +12,7 @@
package v2 package v2
import ( import (
"context"
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
@@ -20,7 +21,7 @@ import (
) )
// processes a property of a Swagger document asynchronously using bool and error channels for signals. // processes a property of a Swagger document asynchronously using bool and error channels for signals.
type documentFunction func(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) type documentFunction func(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error)
// Swagger represents a high-level Swagger / OpenAPI 2 document. An instance of Swagger is the root of the specification. // Swagger represents a high-level Swagger / OpenAPI 2 document. An instance of Swagger is the root of the specification.
type Swagger struct { type Swagger struct {
@@ -129,6 +130,9 @@ func CreateDocumentFromConfig(info *datamodel.SpecInfo,
// CreateDocument will create a new Swagger document from the provided SpecInfo. // CreateDocument will create a new Swagger document from the provided SpecInfo.
// //
// Deprecated: Use CreateDocumentFromConfig instead. // Deprecated: Use CreateDocumentFromConfig instead.
// TODO; DELETE ME
func CreateDocument(info *datamodel.SpecInfo) (*Swagger, []error) { func CreateDocument(info *datamodel.SpecInfo) (*Swagger, []error) {
return createDocument(info, &datamodel.DocumentConfiguration{ return createDocument(info, &datamodel.DocumentConfiguration{
AllowRemoteReferences: true, AllowRemoteReferences: true,
@@ -155,8 +159,10 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
// build out swagger scalar variables. // build out swagger scalar variables.
_ = low.BuildModel(info.RootNode.Content[0], &doc) _ = low.BuildModel(info.RootNode.Content[0], &doc)
ctx := context.Background()
// extract externalDocs // extract externalDocs
extDocs, err := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, info.RootNode, idx) extDocs, err := low.ExtractObject[*base.ExternalDoc](ctx, base.ExternalDocsLabel, info.RootNode, idx)
if err != nil { if err != nil {
errors = append(errors, err) errors = append(errors, err)
} }
@@ -186,7 +192,7 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
doneChan := make(chan bool) doneChan := make(chan bool)
errChan := make(chan error) errChan := make(chan error)
for i := range extractionFuncs { for i := range extractionFuncs {
go extractionFuncs[i](info.RootNode.Content[0], &doc, idx, doneChan, errChan) go extractionFuncs[i](ctx, info.RootNode.Content[0], &doc, idx, doneChan, errChan)
} }
completedExtractions := 0 completedExtractions := 0
for completedExtractions < len(extractionFuncs) { for completedExtractions < len(extractionFuncs) {
@@ -210,8 +216,8 @@ func (s *Swagger) GetExternalDocs() *low.NodeReference[any] {
} }
} }
func extractInfo(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractInfo(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
info, err := low.ExtractObject[*base.Info](base.InfoLabel, root, idx) info, err := low.ExtractObject[*base.Info](ctx, base.InfoLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
@@ -220,8 +226,8 @@ func extractInfo(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- b
c <- true c <- true
} }
func extractPaths(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractPaths(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
paths, err := low.ExtractObject[*Paths](PathsLabel, root, idx) paths, err := low.ExtractObject[*Paths](ctx, PathsLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
@@ -229,8 +235,8 @@ func extractPaths(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<-
doc.Paths = paths doc.Paths = paths
c <- true c <- true
} }
func extractDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractDefinitions(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
def, err := low.ExtractObject[*Definitions](DefinitionsLabel, root, idx) def, err := low.ExtractObject[*Definitions](ctx, DefinitionsLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
@@ -238,8 +244,8 @@ func extractDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c c
doc.Definitions = def doc.Definitions = def
c <- true c <- true
} }
func extractParamDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractParamDefinitions(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
param, err := low.ExtractObject[*ParameterDefinitions](ParametersLabel, root, idx) param, err := low.ExtractObject[*ParameterDefinitions](ctx, ParametersLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
@@ -248,8 +254,8 @@ func extractParamDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex
c <- true c <- true
} }
func extractResponsesDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractResponsesDefinitions(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
resp, err := low.ExtractObject[*ResponsesDefinitions](ResponsesLabel, root, idx) resp, err := low.ExtractObject[*ResponsesDefinitions](ctx, ResponsesLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
@@ -258,8 +264,8 @@ func extractResponsesDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecI
c <- true c <- true
} }
func extractSecurityDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractSecurityDefinitions(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
sec, err := low.ExtractObject[*SecurityDefinitions](SecurityDefinitionsLabel, root, idx) sec, err := low.ExtractObject[*SecurityDefinitions](ctx, SecurityDefinitionsLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
@@ -268,8 +274,8 @@ func extractSecurityDefinitions(root *yaml.Node, doc *Swagger, idx *index.SpecIn
c <- true c <- true
} }
func extractTags(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractTags(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
tags, ln, vn, err := low.ExtractArray[*base.Tag](base.TagsLabel, root, idx) tags, ln, vn, err := low.ExtractArray[*base.Tag](ctx, base.TagsLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return
@@ -282,8 +288,8 @@ func extractTags(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- b
c <- true c <- true
} }
func extractSecurity(root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) { func extractSecurity(ctx context.Context, root *yaml.Node, doc *Swagger, idx *index.SpecIndex, c chan<- bool, e chan<- error) {
sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, root, idx) sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](ctx, SecurityLabel, root, idx)
if err != nil { if err != nil {
e <- err e <- err
return return

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
@@ -39,7 +40,7 @@ func (cb *Callback) FindExpression(exp string) *low.ValueReference[*PathItem] {
} }
// Build will extract extensions, expressions and PathItem objects for Callback // Build will extract extensions, expressions and PathItem objects for Callback
func (cb *Callback) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (cb *Callback) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
cb.Reference = new(low.Reference) cb.Reference = new(low.Reference)
@@ -57,7 +58,7 @@ func (cb *Callback) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
if strings.HasPrefix(currentCB.Value, "x-") { if strings.HasPrefix(currentCB.Value, "x-") {
continue // ignore extension. continue // ignore extension.
} }
callback, eErr, _, rv := low.ExtractObjectRaw[*PathItem](currentCB, callbackNode, idx) callback, eErr, _, rv := low.ExtractObjectRaw[*PathItem](ctx, currentCB, callbackNode, idx)
if eErr != nil { if eErr != nil {
return eErr return eErr
} }

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"sort" "sort"
@@ -141,7 +142,7 @@ func (co *Components) FindCallback(callback string) *low.ValueReference[*Callbac
// Build converts root YAML node containing components to low level model. // Build converts root YAML node containing components to low level model.
// Process each component in parallel. // Process each component in parallel.
func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error { func (co *Components) Build(ctx context.Context, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
co.Reference = new(low.Reference) co.Reference = new(low.Reference)
@@ -161,55 +162,55 @@ func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
go func() { go func() {
schemas, err := extractComponentValues[*base.SchemaProxy](SchemasLabel, root, idx) schemas, err := extractComponentValues[*base.SchemaProxy](ctx, SchemasLabel, root, idx)
captureError(err) captureError(err)
co.Schemas = schemas co.Schemas = schemas
wg.Done() wg.Done()
}() }()
go func() { go func() {
parameters, err := extractComponentValues[*Parameter](ParametersLabel, root, idx) parameters, err := extractComponentValues[*Parameter](ctx, ParametersLabel, root, idx)
captureError(err) captureError(err)
co.Parameters = parameters co.Parameters = parameters
wg.Done() wg.Done()
}() }()
go func() { go func() {
responses, err := extractComponentValues[*Response](ResponsesLabel, root, idx) responses, err := extractComponentValues[*Response](ctx, ResponsesLabel, root, idx)
captureError(err) captureError(err)
co.Responses = responses co.Responses = responses
wg.Done() wg.Done()
}() }()
go func() { go func() {
examples, err := extractComponentValues[*base.Example](base.ExamplesLabel, root, idx) examples, err := extractComponentValues[*base.Example](ctx, base.ExamplesLabel, root, idx)
captureError(err) captureError(err)
co.Examples = examples co.Examples = examples
wg.Done() wg.Done()
}() }()
go func() { go func() {
requestBodies, err := extractComponentValues[*RequestBody](RequestBodiesLabel, root, idx) requestBodies, err := extractComponentValues[*RequestBody](ctx, RequestBodiesLabel, root, idx)
captureError(err) captureError(err)
co.RequestBodies = requestBodies co.RequestBodies = requestBodies
wg.Done() wg.Done()
}() }()
go func() { go func() {
headers, err := extractComponentValues[*Header](HeadersLabel, root, idx) headers, err := extractComponentValues[*Header](ctx, HeadersLabel, root, idx)
captureError(err) captureError(err)
co.Headers = headers co.Headers = headers
wg.Done() wg.Done()
}() }()
go func() { go func() {
securitySchemes, err := extractComponentValues[*SecurityScheme](SecuritySchemesLabel, root, idx) securitySchemes, err := extractComponentValues[*SecurityScheme](ctx, SecuritySchemesLabel, root, idx)
captureError(err) captureError(err)
co.SecuritySchemes = securitySchemes co.SecuritySchemes = securitySchemes
wg.Done() wg.Done()
}() }()
go func() { go func() {
links, err := extractComponentValues[*Link](LinksLabel, root, idx) links, err := extractComponentValues[*Link](ctx, LinksLabel, root, idx)
captureError(err) captureError(err)
co.Links = links co.Links = links
wg.Done() wg.Done()
}() }()
go func() { go func() {
callbacks, err := extractComponentValues[*Callback](CallbacksLabel, root, idx) callbacks, err := extractComponentValues[*Callback](ctx, CallbacksLabel, root, idx)
captureError(err) captureError(err)
co.Callbacks = callbacks co.Callbacks = callbacks
wg.Done() wg.Done()
@@ -222,7 +223,7 @@ func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error {
// extractComponentValues converts all the YAML nodes of a component type to // extractComponentValues converts all the YAML nodes of a component type to
// low level model. // low level model.
// Process each node in parallel. // Process each node in parallel.
func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]], error) { func extractComponentValues[T low.Buildable[N], N any](ctx context.Context, label string, root *yaml.Node, idx *index.SpecIndex) (low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]], error) {
var emptyResult low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]] var emptyResult low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]]
_, nodeLabel, nodeValue := utils.FindKeyNodeFullTop(label, root.Content) _, nodeLabel, nodeValue := utils.FindKeyNodeFullTop(label, root.Content)
if nodeValue == nil { if nodeValue == nil {
@@ -288,7 +289,7 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.
// TODO: check circular crazy on this. It may explode // TODO: check circular crazy on this. It may explode
var err error var err error
if h, _, _ := utils.IsNodeRefValue(node); h && label != SchemasLabel { if h, _, _ := utils.IsNodeRefValue(node); h && label != SchemasLabel {
node, err = low.LocateRefNode(node, idx) node, _, err = low.LocateRefNode(node, idx)
} }
if err != nil { if err != nil {
return componentBuildResult[T]{}, err return componentBuildResult[T]{}, err
@@ -296,7 +297,7 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.
// build. // build.
_ = low.BuildModel(node, n) _ = low.BuildModel(node, n)
err = n.Build(currentLabel, node, idx) err = n.Build(ctx, currentLabel, node, idx)
if err != nil { if err != nil {
return componentBuildResult[T]{}, err return componentBuildResult[T]{}, err
} }

View File

@@ -1,6 +1,7 @@
package v3 package v3
import ( import (
"context"
"errors" "errors"
"os" "os"
"path/filepath" "path/filepath"
@@ -39,28 +40,24 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
version = low.NodeReference[string]{Value: versionNode.Value, KeyNode: labelNode, ValueNode: versionNode} version = low.NodeReference[string]{Value: versionNode.Value, KeyNode: labelNode, ValueNode: versionNode}
doc := Document{Version: version} doc := Document{Version: version}
// get current working directory as a basePath
cwd, _ := os.Getwd()
if config.BasePath != "" {
cwd = config.BasePath
}
// TODO: configure allowFileReferences and allowRemoteReferences stuff // TODO: configure allowFileReferences and allowRemoteReferences stuff
// create an index config and shadow the document configuration. // create an index config and shadow the document configuration.
idxConfig := index.CreateOpenAPIIndexConfig() idxConfig := index.CreateOpenAPIIndexConfig()
idxConfig.SpecInfo = info idxConfig.SpecInfo = info
idxConfig.BasePath = cwd
idxConfig.IgnoreArrayCircularReferences = config.IgnoreArrayCircularReferences idxConfig.IgnoreArrayCircularReferences = config.IgnoreArrayCircularReferences
idxConfig.IgnorePolymorphicCircularReferences = config.IgnorePolymorphicCircularReferences idxConfig.IgnorePolymorphicCircularReferences = config.IgnorePolymorphicCircularReferences
idxConfig.AvoidCircularReferenceCheck = config.SkipCircularReferenceCheck idxConfig.AvoidCircularReferenceCheck = true
idxConfig.BaseURL = config.BaseURL
idxConfig.BasePath = config.BasePath
rolodex := index.NewRolodex(idxConfig) rolodex := index.NewRolodex(idxConfig)
rolodex.SetRootNode(info.RootNode)
doc.Rolodex = rolodex doc.Rolodex = rolodex
// If basePath is provided override it // If basePath is provided, add a local filesystem to the rolodex.
if config.BasePath != "" { if idxConfig.BasePath != "" {
var absError error var absError error
var cwd string
cwd, absError = filepath.Abs(config.BasePath) cwd, absError = filepath.Abs(config.BasePath)
if absError != nil { if absError != nil {
return nil, absError return nil, absError
@@ -77,13 +74,34 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
} }
// TODO: Remote filesystem // if base url is provided, add a remote filesystem to the rolodex.
if idxConfig.BaseURL != nil {
// create a remote filesystem
remoteFS, fsErr := index.NewRemoteFSWithConfig(idxConfig)
if fsErr != nil {
return nil, fsErr
}
if config.RemoteURLHandler != nil {
remoteFS.RemoteHandlerFunc = config.RemoteURLHandler
}
// add to the rolodex
rolodex.AddRemoteFS(config.BaseURL.String(), remoteFS)
}
// index the rolodex // index the rolodex
err := rolodex.IndexTheRolodex()
var errs []error var errs []error
if err != nil {
errs = append(errs, rolodex.GetCaughtErrors()...) _ = rolodex.IndexTheRolodex()
if !config.SkipCircularReferenceCheck {
rolodex.CheckForCircularReferences()
}
roloErrs := rolodex.GetCaughtErrors()
if roloErrs != nil {
errs = append(errs, roloErrs...)
} }
doc.Index = rolodex.GetRootIndex() doc.Index = rolodex.GetRootIndex()
@@ -125,17 +143,17 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
} }
} }
runExtraction := func(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex, runExtraction := func(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex,
runFunc func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error, runFunc func(ctx context.Context, i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error,
ers *[]error, ers *[]error,
wg *sync.WaitGroup, wg *sync.WaitGroup,
) { ) {
if er := runFunc(info, doc, idx); er != nil { if er := runFunc(ctx, info, doc, idx); er != nil {
*ers = append(*ers, er) *ers = append(*ers, er)
} }
wg.Done() wg.Done()
} }
extractionFuncs := []func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{ extractionFuncs := []func(ctx context.Context, i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{
extractInfo, extractInfo,
extractServers, extractServers,
extractTags, extractTags,
@@ -146,28 +164,30 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
extractWebhooks, extractWebhooks,
} }
ctx := context.Background()
wg.Add(len(extractionFuncs)) wg.Add(len(extractionFuncs))
for _, f := range extractionFuncs { for _, f := range extractionFuncs {
go runExtraction(info, &doc, rolodex.GetRootIndex(), f, &errs, &wg) go runExtraction(ctx, info, &doc, rolodex.GetRootIndex(), f, &errs, &wg)
} }
wg.Wait() wg.Wait()
return &doc, errors.Join(errs...) return &doc, errors.Join(errs...)
} }
func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractInfo(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFullTop(base.InfoLabel, info.RootNode.Content[0].Content) _, ln, vn := utils.FindKeyNodeFullTop(base.InfoLabel, info.RootNode.Content[0].Content)
if vn != nil { if vn != nil {
ir := base.Info{} ir := base.Info{}
_ = low.BuildModel(vn, &ir) _ = low.BuildModel(vn, &ir)
_ = ir.Build(ln, vn, idx) _ = ir.Build(ctx, ln, vn, idx)
nr := low.NodeReference[*base.Info]{Value: &ir, ValueNode: vn, KeyNode: ln} nr := low.NodeReference[*base.Info]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Info = nr doc.Info = nr
} }
return nil return nil
} }
func extractSecurity(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractSecurity(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, info.RootNode.Content[0], idx) sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](ctx, SecurityLabel, info.RootNode.Content[0], idx)
if err != nil { if err != nil {
return err return err
} }
@@ -181,8 +201,8 @@ func extractSecurity(info *datamodel.SpecInfo, doc *Document, idx *index.SpecInd
return nil return nil
} }
func extractExternalDocs(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractExternalDocs(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
extDocs, dErr := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, info.RootNode.Content[0], idx) extDocs, dErr := low.ExtractObject[*base.ExternalDoc](ctx, base.ExternalDocsLabel, info.RootNode.Content[0], idx)
if dErr != nil { if dErr != nil {
return dErr return dErr
} }
@@ -190,12 +210,12 @@ func extractExternalDocs(info *datamodel.SpecInfo, doc *Document, idx *index.Spe
return nil return nil
} }
func extractComponents(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractComponents(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFullTop(ComponentsLabel, info.RootNode.Content[0].Content) _, ln, vn := utils.FindKeyNodeFullTop(ComponentsLabel, info.RootNode.Content[0].Content)
if vn != nil { if vn != nil {
ir := Components{} ir := Components{}
_ = low.BuildModel(vn, &ir) _ = low.BuildModel(vn, &ir)
err := ir.Build(vn, idx) err := ir.Build(ctx, vn, idx)
if err != nil { if err != nil {
return err return err
} }
@@ -205,7 +225,7 @@ func extractComponents(info *datamodel.SpecInfo, doc *Document, idx *index.SpecI
return nil return nil
} }
func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractServers(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(ServersLabel, info.RootNode.Content[0].Content) _, ln, vn := utils.FindKeyNodeFull(ServersLabel, info.RootNode.Content[0].Content)
if vn != nil { if vn != nil {
if utils.IsNodeArray(vn) { if utils.IsNodeArray(vn) {
@@ -214,7 +234,7 @@ func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecInde
if utils.IsNodeMap(srvN) { if utils.IsNodeMap(srvN) {
srvr := Server{} srvr := Server{}
_ = low.BuildModel(srvN, &srvr) _ = low.BuildModel(srvN, &srvr)
_ = srvr.Build(ln, srvN, idx) _ = srvr.Build(ctx, ln, srvN, idx)
servers = append(servers, low.ValueReference[*Server]{ servers = append(servers, low.ValueReference[*Server]{
Value: &srvr, Value: &srvr,
ValueNode: srvN, ValueNode: srvN,
@@ -231,7 +251,7 @@ func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecInde
return nil return nil
} }
func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractTags(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(base.TagsLabel, info.RootNode.Content[0].Content) _, ln, vn := utils.FindKeyNodeFull(base.TagsLabel, info.RootNode.Content[0].Content)
if vn != nil { if vn != nil {
if utils.IsNodeArray(vn) { if utils.IsNodeArray(vn) {
@@ -240,7 +260,7 @@ func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex)
if utils.IsNodeMap(tagN) { if utils.IsNodeMap(tagN) {
tag := base.Tag{} tag := base.Tag{}
_ = low.BuildModel(tagN, &tag) _ = low.BuildModel(tagN, &tag)
if err := tag.Build(ln, tagN, idx); err != nil { if err := tag.Build(ctx, ln, tagN, idx); err != nil {
return err return err
} }
tags = append(tags, low.ValueReference[*base.Tag]{ tags = append(tags, low.ValueReference[*base.Tag]{
@@ -259,11 +279,11 @@ func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex)
return nil return nil
} }
func extractPaths(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractPaths(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(PathsLabel, info.RootNode.Content[0].Content) _, ln, vn := utils.FindKeyNodeFull(PathsLabel, info.RootNode.Content[0].Content)
if vn != nil { if vn != nil {
ir := Paths{} ir := Paths{}
err := ir.Build(ln, vn, idx) err := ir.Build(ctx, ln, vn, idx)
if err != nil { if err != nil {
return err return err
} }
@@ -273,8 +293,8 @@ func extractPaths(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex)
return nil return nil
} }
func extractWebhooks(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractWebhooks(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
hooks, hooksL, hooksN, eErr := low.ExtractMap[*PathItem](WebhooksLabel, info.RootNode, idx) hooks, hooksL, hooksN, eErr := low.ExtractMap[*PathItem](ctx, WebhooksLabel, info.RootNode, idx)
if eErr != nil { if eErr != nil {
return eErr return eErr
} }

View File

@@ -147,9 +147,8 @@ func TestCreateDocumentStripe(t *testing.T) {
d, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{ d, err := CreateDocumentFromConfig(info, &datamodel.DocumentConfiguration{
AllowFileReferences: false, AllowFileReferences: false,
AllowRemoteReferences: false, AllowRemoteReferences: false,
BasePath: "/here",
}) })
assert.Len(t, err, 3) assert.Len(t, utils.UnwrapErrors(err), 3)
assert.Equal(t, "3.0.0", d.Version.Value) assert.Equal(t, "3.0.0", d.Version.Value)
assert.Equal(t, "Stripe API", d.Info.Value.Title.Value) assert.Equal(t, "Stripe API", d.Info.Value.Title.Value)
@@ -206,7 +205,8 @@ func TestCreateDocument_WebHooks(t *testing.T) {
} }
func TestCreateDocument_WebHooks_Error(t *testing.T) { func TestCreateDocument_WebHooks_Error(t *testing.T) {
yml := `webhooks: yml := `openapi: 3.0
webhooks:
$ref: #bork` $ref: #bork`
info, _ := datamodel.ExtractSpecInfo([]byte(yml)) info, _ := datamodel.ExtractSpecInfo([]byte(yml))
@@ -215,7 +215,7 @@ func TestCreateDocument_WebHooks_Error(t *testing.T) {
AllowFileReferences: false, AllowFileReferences: false,
AllowRemoteReferences: false, AllowRemoteReferences: false,
}) })
assert.Len(t, err, 1) assert.Len(t, utils.UnwrapErrors(err), 1)
} }
func TestCreateDocument_Servers(t *testing.T) { func TestCreateDocument_Servers(t *testing.T) {
@@ -613,7 +613,7 @@ webhooks:
AllowFileReferences: false, AllowFileReferences: false,
AllowRemoteReferences: false, AllowRemoteReferences: false,
}) })
assert.Equal(t, "flat map build failed: reference cannot be found: reference '' at line 4, column 5 was not found", assert.Equal(t, "flat map build failed: reference cannot be found: reference at line 4, column 5 is empty, it cannot be resolved",
err.Error()) err.Error())
} }
@@ -630,7 +630,7 @@ components:
AllowFileReferences: false, AllowFileReferences: false,
AllowRemoteReferences: false, AllowRemoteReferences: false,
}) })
assert.Equal(t, "reference '' at line 5, column 7 was not found", err.Error()) assert.Equal(t, "reference at line 5, column 7 is empty, it cannot be resolved", err.Error())
} }
func TestCreateDocument_Paths_Errors(t *testing.T) { func TestCreateDocument_Paths_Errors(t *testing.T) {
@@ -661,7 +661,7 @@ tags:
AllowRemoteReferences: false, AllowRemoteReferences: false,
}) })
assert.Equal(t, assert.Equal(t,
"object extraction failed: reference '' at line 3, column 5 was not found", err.Error()) "object extraction failed: reference at line 3, column 5 is empty, it cannot be resolved", err.Error())
} }
func TestCreateDocument_Security_Error(t *testing.T) { func TestCreateDocument_Security_Error(t *testing.T) {
@@ -676,7 +676,7 @@ security:
AllowRemoteReferences: false, AllowRemoteReferences: false,
}) })
assert.Equal(t, assert.Equal(t,
"array build failed: reference cannot be found: reference '' at line 3, column 3 was not found", "array build failed: reference cannot be found: reference at line 3, column 3 is empty, it cannot be resolved",
err.Error()) err.Error())
} }
@@ -691,7 +691,7 @@ externalDocs:
AllowFileReferences: false, AllowFileReferences: false,
AllowRemoteReferences: false, AllowRemoteReferences: false,
}) })
assert.Equal(t, "object extraction failed: reference '' at line 3, column 3 was not found", err.Error()) assert.Equal(t, "object extraction failed: reference at line 3, column 3 is empty, it cannot be resolved", err.Error())
} }
func TestCreateDocument_YamlAnchor(t *testing.T) { func TestCreateDocument_YamlAnchor(t *testing.T) {

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -58,11 +59,11 @@ func (en *Encoding) Hash() [32]byte {
} }
// Build will extract all Header objects from supplied node. // Build will extract all Header objects from supplied node.
func (en *Encoding) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (en *Encoding) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
en.Reference = new(low.Reference) en.Reference = new(low.Reference)
headers, hL, hN, err := low.ExtractMap[*Header](HeadersLabel, root, idx) headers, hL, hN, err := low.ExtractMap[*Header](ctx, HeadersLabel, root, idx)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -95,7 +96,7 @@ func (h *Header) Hash() [32]byte {
} }
// Build will extract extensions, examples, schema and content/media types from node. // Build will extract extensions, examples, schema and content/media types from node.
func (h *Header) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (h *Header) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
h.Reference = new(low.Reference) h.Reference = new(low.Reference)
@@ -108,7 +109,7 @@ func (h *Header) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle examples if set. // handle examples if set.
exps, expsL, expsN, eErr := low.ExtractMap[*base.Example](base.ExamplesLabel, root, idx) exps, expsL, expsN, eErr := low.ExtractMap[*base.Example](ctx, base.ExamplesLabel, root, idx)
if eErr != nil { if eErr != nil {
return eErr return eErr
} }
@@ -121,7 +122,7 @@ func (h *Header) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle schema // handle schema
sch, sErr := base.ExtractSchema(root, idx) sch, sErr := base.ExtractSchema(ctx, root, idx)
if sErr != nil { if sErr != nil {
return sErr return sErr
} }
@@ -130,7 +131,7 @@ func (h *Header) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle content, if set. // handle content, if set.
con, cL, cN, cErr := low.ExtractMap[*MediaType](ContentLabel, root, idx) con, cL, cN, cErr := low.ExtractMap[*MediaType](ctx, ContentLabel, root, idx)
if cErr != nil { if cErr != nil {
return cErr return cErr
} }

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -53,13 +54,13 @@ func (l *Link) FindExtension(ext string) *low.ValueReference[any] {
} }
// Build will extract extensions and servers from the node. // Build will extract extensions and servers from the node.
func (l *Link) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (l *Link) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
l.Reference = new(low.Reference) l.Reference = new(low.Reference)
l.Extensions = low.ExtractExtensions(root) l.Extensions = low.ExtractExtensions(root)
// extract server. // extract server.
ser, sErr := low.ExtractObject[*Server](ServerLabel, root, idx) ser, sErr := low.ExtractObject[*Server](ctx, ServerLabel, root, idx)
if sErr != nil { if sErr != nil {
return sErr return sErr
} }

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -54,7 +55,7 @@ func (mt *MediaType) GetAllExamples() map[low.KeyReference[string]]low.ValueRefe
} }
// Build will extract examples, extensions, schema and encoding from node. // Build will extract examples, extensions, schema and encoding from node.
func (mt *MediaType) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (mt *MediaType) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
mt.Reference = new(low.Reference) mt.Reference = new(low.Reference)
@@ -83,7 +84,7 @@ func (mt *MediaType) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
//handle schema //handle schema
sch, sErr := base.ExtractSchema(root, idx) sch, sErr := base.ExtractSchema(ctx, root, idx)
if sErr != nil { if sErr != nil {
return sErr return sErr
} }
@@ -92,7 +93,7 @@ func (mt *MediaType) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle examples if set. // handle examples if set.
exps, expsL, expsN, eErr := low.ExtractMap[*base.Example](base.ExamplesLabel, root, idx) exps, expsL, expsN, eErr := low.ExtractMap[*base.Example](ctx, base.ExamplesLabel, root, idx)
if eErr != nil { if eErr != nil {
return eErr return eErr
} }
@@ -105,7 +106,7 @@ func (mt *MediaType) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle encoding // handle encoding
encs, encsL, encsN, encErr := low.ExtractMap[*Encoding](EncodingLabel, root, idx) encs, encsL, encsN, encErr := low.ExtractMap[*Encoding](ctx, EncodingLabel, root, idx)
if encErr != nil { if encErr != nil {
return encErr return encErr
} }

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -36,31 +37,31 @@ func (o *OAuthFlows) FindExtension(ext string) *low.ValueReference[any] {
} }
// Build will extract extensions and all OAuthFlow types from the supplied node. // Build will extract extensions and all OAuthFlow types from the supplied node.
func (o *OAuthFlows) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (o *OAuthFlows) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
o.Reference = new(low.Reference) o.Reference = new(low.Reference)
o.Extensions = low.ExtractExtensions(root) o.Extensions = low.ExtractExtensions(root)
v, vErr := low.ExtractObject[*OAuthFlow](ImplicitLabel, root, idx) v, vErr := low.ExtractObject[*OAuthFlow](ctx, ImplicitLabel, root, idx)
if vErr != nil { if vErr != nil {
return vErr return vErr
} }
o.Implicit = v o.Implicit = v
v, vErr = low.ExtractObject[*OAuthFlow](PasswordLabel, root, idx) v, vErr = low.ExtractObject[*OAuthFlow](ctx, PasswordLabel, root, idx)
if vErr != nil { if vErr != nil {
return vErr return vErr
} }
o.Password = v o.Password = v
v, vErr = low.ExtractObject[*OAuthFlow](ClientCredentialsLabel, root, idx) v, vErr = low.ExtractObject[*OAuthFlow](ctx, ClientCredentialsLabel, root, idx)
if vErr != nil { if vErr != nil {
return vErr return vErr
} }
o.ClientCredentials = v o.ClientCredentials = v
v, vErr = low.ExtractObject[*OAuthFlow](AuthorizationCodeLabel, root, idx) v, vErr = low.ExtractObject[*OAuthFlow](ctx, AuthorizationCodeLabel, root, idx)
if vErr != nil { if vErr != nil {
return vErr return vErr
} }
@@ -116,7 +117,7 @@ func (o *OAuthFlow) FindExtension(ext string) *low.ValueReference[any] {
} }
// Build will extract extensions from the node. // Build will extract extensions from the node.
func (o *OAuthFlow) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (o *OAuthFlow) Build(_ context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
o.Reference = new(low.Reference) o.Reference = new(low.Reference)
o.Extensions = low.ExtractExtensions(root) o.Extensions = low.ExtractExtensions(root)
return nil return nil

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -55,21 +56,21 @@ func (o *Operation) FindSecurityRequirement(name string) []low.ValueReference[st
} }
// Build will extract external docs, parameters, request body, responses, callbacks, security and servers. // Build will extract external docs, parameters, request body, responses, callbacks, security and servers.
func (o *Operation) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (o *Operation) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
o.Reference = new(low.Reference) o.Reference = new(low.Reference)
o.Extensions = low.ExtractExtensions(root) o.Extensions = low.ExtractExtensions(root)
// extract externalDocs // extract externalDocs
extDocs, dErr := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, root, idx) extDocs, dErr := low.ExtractObject[*base.ExternalDoc](ctx, base.ExternalDocsLabel, root, idx)
if dErr != nil { if dErr != nil {
return dErr return dErr
} }
o.ExternalDocs = extDocs o.ExternalDocs = extDocs
// extract parameters // extract parameters
params, ln, vn, pErr := low.ExtractArray[*Parameter](ParametersLabel, root, idx) params, ln, vn, pErr := low.ExtractArray[*Parameter](ctx, ParametersLabel, root, idx)
if pErr != nil { if pErr != nil {
return pErr return pErr
} }
@@ -82,21 +83,21 @@ func (o *Operation) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// extract request body // extract request body
rBody, rErr := low.ExtractObject[*RequestBody](RequestBodyLabel, root, idx) rBody, rErr := low.ExtractObject[*RequestBody](ctx, RequestBodyLabel, root, idx)
if rErr != nil { if rErr != nil {
return rErr return rErr
} }
o.RequestBody = rBody o.RequestBody = rBody
// extract responses // extract responses
respBody, respErr := low.ExtractObject[*Responses](ResponsesLabel, root, idx) respBody, respErr := low.ExtractObject[*Responses](ctx, ResponsesLabel, root, idx)
if respErr != nil { if respErr != nil {
return respErr return respErr
} }
o.Responses = respBody o.Responses = respBody
// extract callbacks // extract callbacks
callbacks, cbL, cbN, cbErr := low.ExtractMap[*Callback](CallbacksLabel, root, idx) callbacks, cbL, cbN, cbErr := low.ExtractMap[*Callback](ctx, CallbacksLabel, root, idx)
if cbErr != nil { if cbErr != nil {
return cbErr return cbErr
} }
@@ -109,7 +110,7 @@ func (o *Operation) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// extract security // extract security
sec, sln, svn, sErr := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, root, idx) sec, sln, svn, sErr := low.ExtractArray[*base.SecurityRequirement](ctx, SecurityLabel, root, idx)
if sErr != nil { if sErr != nil {
return sErr return sErr
} }
@@ -134,7 +135,7 @@ func (o *Operation) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// extract servers // extract servers
servers, sl, sn, serErr := low.ExtractArray[*Server](ServersLabel, root, idx) servers, sl, sn, serErr := low.ExtractArray[*Server](ctx, ServersLabel, root, idx)
if serErr != nil { if serErr != nil {
return serErr return serErr
} }

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -58,7 +59,7 @@ func (p *Parameter) GetExtensions() map[low.KeyReference[string]]low.ValueRefere
} }
// Build will extract examples, extensions and content/media types. // Build will extract examples, extensions and content/media types.
func (p *Parameter) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (p *Parameter) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
p.Reference = new(low.Reference) p.Reference = new(low.Reference)
@@ -71,7 +72,7 @@ func (p *Parameter) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle schema // handle schema
sch, sErr := base.ExtractSchema(root, idx) sch, sErr := base.ExtractSchema(ctx, root, idx)
if sErr != nil { if sErr != nil {
return sErr return sErr
} }
@@ -80,7 +81,7 @@ func (p *Parameter) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle examples if set. // handle examples if set.
exps, expsL, expsN, eErr := low.ExtractMap[*base.Example](base.ExamplesLabel, root, idx) exps, expsL, expsN, eErr := low.ExtractMap[*base.Example](ctx, base.ExamplesLabel, root, idx)
if eErr != nil { if eErr != nil {
return eErr return eErr
} }
@@ -93,7 +94,7 @@ func (p *Parameter) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle content, if set. // handle content, if set.
con, cL, cN, cErr := low.ExtractMap[*MediaType](ContentLabel, root, idx) con, cL, cN, cErr := low.ExtractMap[*MediaType](ctx, ContentLabel, root, idx)
if cErr != nil { if cErr != nil {
return cErr return cErr
} }

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"sort" "sort"
@@ -109,7 +110,7 @@ func (p *PathItem) GetExtensions() map[low.KeyReference[string]]low.ValueReferen
// Build extracts extensions, parameters, servers and each http method defined. // Build extracts extensions, parameters, servers and each http method defined.
// everything is extracted asynchronously for speed. // everything is extracted asynchronously for speed.
func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (p *PathItem) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
p.Reference = new(low.Reference) p.Reference = new(low.Reference)
@@ -123,7 +124,7 @@ func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
var ops []low.NodeReference[*Operation] var ops []low.NodeReference[*Operation]
// extract parameters // extract parameters
params, ln, vn, pErr := low.ExtractArray[*Parameter](ParametersLabel, root, idx) params, ln, vn, pErr := low.ExtractArray[*Parameter](ctx, ParametersLabel, root, idx)
if pErr != nil { if pErr != nil {
return pErr return pErr
} }
@@ -143,7 +144,7 @@ func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
if utils.IsNodeMap(srvN) { if utils.IsNodeMap(srvN) {
srvr := new(Server) srvr := new(Server)
_ = low.BuildModel(srvN, srvr) _ = low.BuildModel(srvN, srvr)
srvr.Build(ln, srvN, idx) srvr.Build(ctx, ln, srvN, idx)
servers = append(servers, low.ValueReference[*Server]{ servers = append(servers, low.ValueReference[*Server]{
Value: srvr, Value: srvr,
ValueNode: srvN, ValueNode: srvN,
@@ -198,6 +199,7 @@ func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
continue // ignore everything else. continue // ignore everything else.
} }
foundContext := ctx
var op Operation var op Operation
opIsRef := false opIsRef := false
var opRefVal string var opRefVal string
@@ -213,12 +215,15 @@ func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
opIsRef = true opIsRef = true
opRefVal = ref opRefVal = ref
r, err := low.LocateRefNode(pathNode, idx) r, newIdx, err, nCtx := low.LocateRefNodeWithContext(ctx, pathNode, idx)
if r != nil { if r != nil {
if r.Kind == yaml.DocumentNode { if r.Kind == yaml.DocumentNode {
r = r.Content[0] r = r.Content[0]
} }
pathNode = r pathNode = r
foundContext = nCtx
foundContext = context.WithValue(foundContext, "foundIndex", newIdx)
if r.Tag == "" { if r.Tag == "" {
// If it's a node from file, tag is empty // If it's a node from file, tag is empty
pathNode = r.Content[0] pathNode = r.Content[0]
@@ -233,6 +238,8 @@ func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
return fmt.Errorf("path item build failed: cannot find reference: %s at line %d, col %d", return fmt.Errorf("path item build failed: cannot find reference: %s at line %d, col %d",
pathNode.Content[1].Value, pathNode.Content[1].Line, pathNode.Content[1].Column) pathNode.Content[1].Value, pathNode.Content[1].Line, pathNode.Content[1].Column)
} }
} else {
foundContext = context.WithValue(foundContext, "foundIndex", idx)
} }
wg.Add(1) wg.Add(1)
low.BuildModelAsync(pathNode, &op, &wg, &errors) low.BuildModelAsync(pathNode, &op, &wg, &errors)
@@ -241,6 +248,7 @@ func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
Value: &op, Value: &op,
KeyNode: currentNode, KeyNode: currentNode,
ValueNode: pathNode, ValueNode: pathNode,
Context: foundContext,
} }
if opIsRef { if opIsRef {
opRef.Reference = opRefVal opRef.Reference = opRefVal
@@ -277,7 +285,7 @@ func (p *PathItem) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
ref = op.Reference ref = op.Reference
} }
err := op.Value.Build(op.KeyNode, op.ValueNode, idx) err := op.Value.Build(op.Context, op.KeyNode, op.ValueNode, op.Context.Value("foundIndex").(*index.SpecIndex))
if ref != "" { if ref != "" {
op.Value.Reference.Reference = ref op.Value.Reference.Reference = ref
} }

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"sort" "sort"
@@ -60,7 +61,7 @@ func (p *Paths) GetExtensions() map[low.KeyReference[string]]low.ValueReference[
} }
// Build will extract extensions and all PathItems. This happens asynchronously for speed. // Build will extract extensions and all PathItems. This happens asynchronously for speed.
func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (p *Paths) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
p.Reference = new(low.Reference) p.Reference = new(low.Reference)
@@ -134,7 +135,7 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
cNode := value.currentNode cNode := value.currentNode
if ok, _, _ := utils.IsNodeRefValue(pNode); ok { if ok, _, _ := utils.IsNodeRefValue(pNode); ok {
r, err := low.LocateRefNode(pNode, idx) r, _, err := low.LocateRefNode(pNode, idx)
if r != nil { if r != nil {
pNode = r pNode = r
if r.Tag == "" { if r.Tag == "" {
@@ -156,9 +157,12 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
path := new(PathItem) path := new(PathItem)
_ = low.BuildModel(pNode, path) _ = low.BuildModel(pNode, path)
err := path.Build(cNode, pNode, idx) err := path.Build(ctx, cNode, pNode, idx)
// don't fail the pipeline if there is an error, log it instead.
if err != nil { if err != nil {
return buildResult{}, err //return buildResult{}, err
idx.GetLogger().Error(fmt.Sprintf("error building path item '%s'", err.Error()))
} }
return buildResult{ return buildResult{

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -40,14 +41,14 @@ func (rb *RequestBody) FindContent(cType string) *low.ValueReference[*MediaType]
} }
// Build will extract extensions and MediaType objects from the node. // Build will extract extensions and MediaType objects from the node.
func (rb *RequestBody) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (rb *RequestBody) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
rb.Reference = new(low.Reference) rb.Reference = new(low.Reference)
rb.Extensions = low.ExtractExtensions(root) rb.Extensions = low.ExtractExtensions(root)
// handle content, if set. // handle content, if set.
con, cL, cN, cErr := low.ExtractMap[*MediaType](ContentLabel, root, idx) con, cL, cN, cErr := low.ExtractMap[*MediaType](ctx, ContentLabel, root, idx)
if cErr != nil { if cErr != nil {
return cErr return cErr
} }

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -54,14 +55,14 @@ func (r *Response) FindLink(hType string) *low.ValueReference[*Link] {
} }
// Build will extract headers, extensions, content and links from node. // Build will extract headers, extensions, content and links from node.
func (r *Response) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (r *Response) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
r.Reference = new(low.Reference) r.Reference = new(low.Reference)
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)
//extract headers //extract headers
headers, lN, kN, err := low.ExtractMapExtensions[*Header](HeadersLabel, root, idx, true) headers, lN, kN, err := low.ExtractMapExtensions[*Header](ctx, HeadersLabel, root, idx, true)
if err != nil { if err != nil {
return err return err
} }
@@ -73,7 +74,7 @@ func (r *Response) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
} }
con, clN, cN, cErr := low.ExtractMap[*MediaType](ContentLabel, root, idx) con, clN, cN, cErr := low.ExtractMap[*MediaType](ctx, ContentLabel, root, idx)
if cErr != nil { if cErr != nil {
return cErr return cErr
} }
@@ -86,7 +87,7 @@ func (r *Response) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
} }
// handle links if set // handle links if set
links, linkLabel, linkValue, lErr := low.ExtractMap[*Link](LinksLabel, root, idx) links, linkLabel, linkValue, lErr := low.ExtractMap[*Link](ctx, LinksLabel, root, idx)
if lErr != nil { if lErr != nil {
return lErr return lErr
} }

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -45,13 +46,13 @@ func (r *Responses) GetExtensions() map[low.KeyReference[string]]low.ValueRefere
} }
// Build will extract default response and all Response objects for each code // Build will extract default response and all Response objects for each code
func (r *Responses) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (r *Responses) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
r.Reference = new(low.Reference) r.Reference = new(low.Reference)
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
if utils.IsNodeMap(root) { if utils.IsNodeMap(root) {
codes, err := low.ExtractMapNoLookup[*Response](root, idx) codes, err := low.ExtractMapNoLookup[*Response](ctx, root, idx)
if err != nil { if err != nil {
return err return err

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -48,13 +49,13 @@ func (ss *SecurityScheme) GetExtensions() map[low.KeyReference[string]]low.Value
} }
// Build will extract OAuthFlows and extensions from the node. // Build will extract OAuthFlows and extensions from the node.
func (ss *SecurityScheme) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (ss *SecurityScheme) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
ss.Reference = new(low.Reference) ss.Reference = new(low.Reference)
ss.Extensions = low.ExtractExtensions(root) ss.Extensions = low.ExtractExtensions(root)
oa, oaErr := low.ExtractObject[*OAuthFlows](OAuthFlowsLabel, root, idx) oa, oaErr := low.ExtractObject[*OAuthFlows](ctx, OAuthFlowsLabel, root, idx)
if oaErr != nil { if oaErr != nil {
return oaErr return oaErr
} }

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
@@ -34,7 +35,7 @@ func (s *Server) FindVariable(serverVar string) *low.ValueReference[*ServerVaria
} }
// Build will extract server variables from the supplied node. // Build will extract server variables from the supplied node.
func (s *Server) Build(_, root *yaml.Node, idx *index.SpecIndex) error { func (s *Server) Build(_ context.Context, _, root *yaml.Node, _ *index.SpecIndex) error {
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
s.Reference = new(low.Reference) s.Reference = new(low.Reference)

View File

@@ -64,6 +64,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
Definition: definitionPath, Definition: definitionPath,
Node: node.Content[i+1], Node: node.Content[i+1],
Path: jsonPath, Path: jsonPath,
Index: index,
} }
isRef, _, _ := utils.IsNodeRefValue(node.Content[i+1]) isRef, _, _ := utils.IsNodeRefValue(node.Content[i+1])
@@ -120,6 +121,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
Definition: definitionPath, Definition: definitionPath,
Node: prop, Node: prop,
Path: jsonPath, Path: jsonPath,
Index: index,
} }
isRef, _, _ := utils.IsNodeRefValue(prop) isRef, _, _ := utils.IsNodeRefValue(prop)
@@ -165,6 +167,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
Definition: definitionPath, Definition: definitionPath,
Node: element, Node: element,
Path: jsonPath, Path: jsonPath,
Index: index,
} }
isRef, _, _ := utils.IsNodeRefValue(element) isRef, _, _ := utils.IsNodeRefValue(element)
@@ -341,6 +344,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
Name: name, Name: name,
Node: node, Node: node,
Path: p, Path: p,
Index: index,
} }
// add to raw sequenced refs // add to raw sequenced refs
@@ -367,6 +371,7 @@ func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string,
Name: ref.Name, Name: ref.Name,
Node: &copiedNode, Node: &copiedNode,
Path: p, Path: p,
Index: index,
} }
// protect this data using a copy, prevent the resolver from destroying things. // protect this data using a copy, prevent the resolver from destroying things.
index.refsWithSiblings[value] = copied index.refsWithSiblings[value] = copied
@@ -592,6 +597,11 @@ func (index *SpecIndex) ExtractComponentsFromRefs(refs []*Reference) []*Referenc
locate := func(ref *Reference, refIndex int, sequence []*ReferenceMapped) { locate := func(ref *Reference, refIndex int, sequence []*ReferenceMapped) {
located := index.FindComponent(ref.FullDefinition, ref.Node) located := index.FindComponent(ref.FullDefinition, ref.Node)
if located != nil { if located != nil {
if located.Index == nil {
index.logger.Warn("located component has no index", "component", located.FullDefinition)
}
index.refLock.Lock() index.refLock.Lock()
// have we already mapped this? // have we already mapped this?
if index.allMappedRefs[ref.FullDefinition] == nil { if index.allMappedRefs[ref.FullDefinition] == nil {

View File

@@ -50,7 +50,7 @@ func (index *SpecIndex) FindComponent(componentId string, parent *yaml.Node) *Re
} }
} }
func FindComponent(root *yaml.Node, componentId, absoluteFilePath string) *Reference { func FindComponent(root *yaml.Node, componentId, absoluteFilePath string, index *SpecIndex) *Reference {
// check component for url encoding. // check component for url encoding.
if strings.Contains(componentId, "%") { if strings.Contains(componentId, "%") {
// decode the url. // decode the url.
@@ -72,6 +72,12 @@ func FindComponent(root *yaml.Node, componentId, absoluteFilePath string) *Refer
fullDef := fmt.Sprintf("%s%s", absoluteFilePath, componentId) fullDef := fmt.Sprintf("%s%s", absoluteFilePath, componentId)
// TODO: clean this shit up
newIndexWithUpdatedPath := *index
newIndexWithUpdatedPath.specAbsolutePath = absoluteFilePath
newIndexWithUpdatedPath.AbsoluteFile = absoluteFilePath
// extract properties // extract properties
ref := &Reference{ ref := &Reference{
FullDefinition: fullDef, FullDefinition: fullDef,
@@ -79,6 +85,8 @@ func FindComponent(root *yaml.Node, componentId, absoluteFilePath string) *Refer
Name: name, Name: name,
Node: resNode, Node: resNode,
Path: friendlySearch, Path: friendlySearch,
RemoteLocation: absoluteFilePath,
Index: &newIndexWithUpdatedPath,
RequiredRefProperties: extractDefinitionRequiredRefProperties(resNode, map[string][]string{}, fullDef), RequiredRefProperties: extractDefinitionRequiredRefProperties(resNode, map[string][]string{}, fullDef),
} }
@@ -89,7 +97,7 @@ func FindComponent(root *yaml.Node, componentId, absoluteFilePath string) *Refer
func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference { func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference {
if index.root != nil { if index.root != nil {
return FindComponent(index.root, componentId, index.specAbsolutePath) return FindComponent(index.root, componentId, index.specAbsolutePath, index)
} }
return nil return nil
} }
@@ -139,6 +147,9 @@ func (index *SpecIndex) lookupRolodex(uri []string) *Reference {
var parsedDocument *yaml.Node var parsedDocument *yaml.Node
var err error var err error
idx := index
if ext != "" { if ext != "" {
// extract the document from the rolodex. // extract the document from the rolodex.
@@ -153,6 +164,9 @@ func (index *SpecIndex) lookupRolodex(uri []string) *Reference {
index.logger.Error("rolodex file is empty!", "file", absoluteFileLocation) index.logger.Error("rolodex file is empty!", "file", absoluteFileLocation)
return nil return nil
} }
if rFile.GetIndex() != nil {
idx = rFile.GetIndex()
}
parsedDocument, err = rFile.GetContentAsYAMLNode() parsedDocument, err = rFile.GetContentAsYAMLNode()
if err != nil { if err != nil {
@@ -184,6 +198,7 @@ func (index *SpecIndex) lookupRolodex(uri []string) *Reference {
FullDefinition: absoluteFileLocation, FullDefinition: absoluteFileLocation,
Definition: fileName, Definition: fileName,
Name: fileName, Name: fileName,
Index: idx,
Node: parsedDocument, Node: parsedDocument,
IsRemote: true, IsRemote: true,
RemoteLocation: absoluteFileLocation, RemoteLocation: absoluteFileLocation,
@@ -192,7 +207,7 @@ func (index *SpecIndex) lookupRolodex(uri []string) *Reference {
} }
return foundRef return foundRef
} else { } else {
foundRef = FindComponent(parsedDocument, query, absoluteFileLocation) foundRef = FindComponent(parsedDocument, query, absoluteFileLocation, index)
if foundRef != nil { if foundRef != nil {
foundRef.IsRemote = true foundRef.IsRemote = true
foundRef.RemoteLocation = absoluteFileLocation foundRef.RemoteLocation = absoluteFileLocation

View File

@@ -61,9 +61,14 @@ func TestSpecIndex_CheckCircularIndex(t *testing.T) {
index := rolo.GetRootIndex() index := rolo.GetRootIndex()
assert.Nil(t, index.uri) assert.Nil(t, index.uri)
assert.NotNil(t, index.SearchIndexForReference("second.yaml#/properties/property2"))
assert.NotNil(t, index.SearchIndexForReference("second.yaml")) a, _ := index.SearchIndexForReference("second.yaml#/properties/property2")
assert.Nil(t, index.SearchIndexForReference("fourth.yaml")) b, _ := index.SearchIndexForReference("second.yaml")
c, _ := index.SearchIndexForReference("fourth.yaml")
assert.NotNil(t, a)
assert.NotNil(t, b)
assert.Nil(t, c)
} }
func TestSpecIndex_performExternalLookup_invalidURL(t *testing.T) { func TestSpecIndex_performExternalLookup_invalidURL(t *testing.T) {

View File

@@ -35,6 +35,7 @@ type Reference struct {
Circular bool Circular bool
Seen bool Seen bool
IsRemote bool IsRemote bool
Index *SpecIndex // index that contains this reference.
RemoteLocation string RemoteLocation string
Path string // this won't always be available. Path string // this won't always be available.
RequiredRefProperties map[string][]string // definition names (eg, #/definitions/One) to a list of required properties on this definition which reference that definition RequiredRefProperties map[string][]string // definition names (eg, #/definitions/One) to a list of required properties on this definition which reference that definition
@@ -166,6 +167,8 @@ func CreateClosedAPIIndexConfig() *SpecIndexConfig {
// quick direct access to paths, operations, tags are all available. No need to walk the entire node tree in rules, // quick direct access to paths, operations, tags are all available. No need to walk the entire node tree in rules,
// everything is pre-walked if you need it. // everything is pre-walked if you need it.
type SpecIndex struct { type SpecIndex struct {
specAbsolutePath string
AbsoluteFile string
rolodex *Rolodex // the rolodex is used to fetch remote and file based documents. rolodex *Rolodex // the rolodex is used to fetch remote and file based documents.
allRefs map[string]*Reference // all (deduplicated) refs allRefs map[string]*Reference // all (deduplicated) refs
rawSequencedRefs []*Reference // all raw references in sequence as they are scanned, not deduped. rawSequencedRefs []*Reference // all raw references in sequence as they are scanned, not deduped.
@@ -261,7 +264,6 @@ type SpecIndex struct {
httpClient *http.Client httpClient *http.Client
componentIndexChan chan bool componentIndexChan chan bool
polyComponentIndexChan chan bool polyComponentIndexChan chan bool
specAbsolutePath string
resolver *Resolver resolver *Resolver
cache syncmap.Map cache syncmap.Map
built bool built bool

View File

@@ -267,7 +267,7 @@ func (resolver *Resolver) VisitReference(ref *Reference, seen map[string]bool, j
if j.FullDefinition == r.FullDefinition { if j.FullDefinition == r.FullDefinition {
var foundDup *Reference var foundDup *Reference
foundRef := resolver.specIndex.SearchIndexForReferenceByReference(r) foundRef, _ := resolver.specIndex.SearchIndexForReferenceByReference(r)
if foundRef != nil { if foundRef != nil {
foundDup = foundRef foundDup = foundRef
} }
@@ -307,7 +307,7 @@ func (resolver *Resolver) VisitReference(ref *Reference, seen map[string]bool, j
if !skip { if !skip {
var original *Reference var original *Reference
foundRef := resolver.specIndex.SearchIndexForReferenceByReference(r) foundRef, _ := resolver.specIndex.SearchIndexForReferenceByReference(r)
if foundRef != nil { if foundRef != nil {
original = foundRef original = foundRef
} }
@@ -335,7 +335,7 @@ func (resolver *Resolver) isInfiniteCircularDependency(ref *Reference, visitedDe
} }
for refDefinition := range ref.RequiredRefProperties { for refDefinition := range ref.RequiredRefProperties {
r := resolver.specIndex.SearchIndexForReference(refDefinition) r, _ := resolver.specIndex.SearchIndexForReference(refDefinition)
if initialRef != nil && initialRef.Definition == r.Definition { if initialRef != nil && initialRef.Definition == r.Definition {
return true, visitedDefinitions return true, visitedDefinitions
} }
@@ -497,7 +497,7 @@ func (resolver *Resolver) extractRelatives(ref *Reference, node, parent *yaml.No
IsRemote: true, IsRemote: true,
} }
locatedRef = resolver.specIndex.SearchIndexForReferenceByReference(searchRef) locatedRef, _ = resolver.specIndex.SearchIndexForReferenceByReference(searchRef)
if locatedRef == nil { if locatedRef == nil {
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(value) _, path := utils.ConvertComponentIdIntoFriendlyPathSearch(value)

View File

@@ -596,7 +596,7 @@ func TestResolver_ResolveComponents_MixedRef(t *testing.T) {
resolver := index().GetResolver() resolver := index().GetResolver()
assert.Len(t, resolver.GetCircularErrors(), 0) assert.Len(t, resolver.GetCircularErrors(), 0)
assert.Equal(t, 3, resolver.GetIndexesVisited()) assert.Equal(t, 2, resolver.GetIndexesVisited())
// in v0.8.2 a new check was added when indexing, to prevent re-indexing the same file multiple times. // in v0.8.2 a new check was added when indexing, to prevent re-indexing the same file multiple times.
assert.Equal(t, 6, resolver.GetRelativesSeen()) assert.Equal(t, 6, resolver.GetRelativesSeen())

View File

@@ -87,6 +87,7 @@ func (l *LocalFile) Index(config *SpecIndexConfig) (*SpecIndex, error) {
index := NewSpecIndexWithConfig(info.RootNode, config) index := NewSpecIndexWithConfig(info.RootNode, config)
index.specAbsolutePath = l.fullPath index.specAbsolutePath = l.fullPath
l.index = index l.index = index
return index, nil return index, nil

View File

@@ -176,13 +176,10 @@ const (
func NewRemoteFSWithConfig(specIndexConfig *SpecIndexConfig) (*RemoteFS, error) { func NewRemoteFSWithConfig(specIndexConfig *SpecIndexConfig) (*RemoteFS, error) {
remoteRootURL := specIndexConfig.BaseURL remoteRootURL := specIndexConfig.BaseURL
// TODO: handle logging
log := specIndexConfig.Logger log := specIndexConfig.Logger
if log == nil { if log == nil {
log = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ log = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug, Level: slog.LevelError,
})) }))
} }
@@ -324,7 +321,7 @@ func (i *RemoteFS) Open(remoteURL string) (fs.File, error) {
// remove from processing // remove from processing
i.ProcessingFiles.Delete(remoteParsedURL.Path) i.ProcessingFiles.Delete(remoteParsedURL.Path)
i.logger.Error("Unable to fetch remote document", i.logger.Error("unable to fetch remote document",
"file", remoteParsedURL.Path, "status", response.StatusCode, "resp", string(responseBytes)) "file", remoteParsedURL.Path, "status", response.StatusCode, "resp", string(responseBytes))
return nil, fmt.Errorf("unable to fetch remote document: %s", string(responseBytes)) return nil, fmt.Errorf("unable to fetch remote document: %s", string(responseBytes))
} }
@@ -371,8 +368,6 @@ func (i *RemoteFS) Open(remoteURL string) (fs.File, error) {
copiedCfg.SpecAbsolutePath = remoteParsedURL.String() copiedCfg.SpecAbsolutePath = remoteParsedURL.String()
idx, idxError := remoteFile.Index(&copiedCfg) idx, idxError := remoteFile.Index(&copiedCfg)
i.Files.Store(absolutePath, remoteFile)
if len(remoteFile.data) > 0 { if len(remoteFile.data) > 0 {
i.logger.Debug("successfully loaded file", "file", absolutePath) i.logger.Debug("successfully loaded file", "file", absolutePath)
} }
@@ -390,6 +385,7 @@ func (i *RemoteFS) Open(remoteURL string) (fs.File, error) {
// remove from processing // remove from processing
i.ProcessingFiles.Delete(remoteParsedURL.Path) i.ProcessingFiles.Delete(remoteParsedURL.Path)
i.Files.Store(absolutePath, remoteFile)
//if !i.remoteRunning { //if !i.remoteRunning {
return remoteFile, errors.Join(i.remoteErrors...) return remoteFile, errors.Join(i.remoteErrors...)

View File

@@ -4,130 +4,151 @@
package index package index
import ( import (
"fmt" "context"
"path/filepath" "fmt"
"strings" "path/filepath"
"strings"
) )
func (index *SpecIndex) SearchIndexForReferenceByReference(fullRef *Reference) *Reference { type ContextKey string
if v, ok := index.cache.Load(fullRef); ok { const CurrentPathKey ContextKey = "currentPath"
return v.(*Reference)
}
ref := fullRef.FullDefinition func (index *SpecIndex) SearchIndexForReferenceByReference(fullRef *Reference) (*Reference, *SpecIndex) {
refAlt := ref r, idx, _ := index.SearchIndexForReferenceByReferenceWithContext(context.Background(), fullRef)
absPath := index.specAbsolutePath return r, idx
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 = fullRef.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 r, ok := index.allMappedRefs[ref]; ok {
index.cache.Store(ref, r)
return r
}
if r, ok := index.allMappedRefs[refAlt]; ok {
index.cache.Store(refAlt, r)
return r
}
// check the rolodex for the reference.
if roloLookup != "" {
rFile, err := index.rolodex.Open(roloLookup)
if err != nil {
return nil
}
// extract the index from the rolodex file.
idx := rFile.GetIndex()
if index.resolver != nil {
index.resolver.indexesVisited++
}
if idx != nil {
// check mapped refs.
if r, ok := idx.allMappedRefs[ref]; ok {
return r
}
// 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.Definition == ref {
index.cache.Store(ref, s)
return s
}
}
// does component exist in the root?
node, _ := rFile.GetContentAsYAMLNode()
if node != nil {
found := idx.FindComponent(ref, node)
if found != nil {
index.cache.Store(ref, found)
return found
}
}
}
}
fmt.Printf("unable to locate reference: %s, within index: %s\n", ref, index.specAbsolutePath)
return nil
} }
// SearchIndexForReference searches the index for a reference, first looking through the mapped references // 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 // and then externalSpecIndex for a match. If no match is found, it will recursively search the child indexes
// extracted when parsing the OpenAPI Spec. // extracted when parsing the OpenAPI Spec.
func (index *SpecIndex) SearchIndexForReference(ref string) *Reference { func (index *SpecIndex) SearchIndexForReference(ref string) (*Reference, *SpecIndex) {
return index.SearchIndexForReferenceByReference(&Reference{FullDefinition: ref}) 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 v, ok := index.cache.Load(searchRef.FullDefinition); ok {
return v.(*Reference), index, context.WithValue(ctx, CurrentPathKey, v.(*Reference).RemoteLocation)
}
ref := searchRef.FullDefinition
refAlt := ref
absPath := index.specAbsolutePath
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 r, ok := index.allMappedRefs[ref]; ok {
index.cache.Store(ref, r)
return r, r.Index, context.WithValue(ctx, CurrentPathKey, r.RemoteLocation)
}
if r, ok := index.allMappedRefs[refAlt]; ok {
index.cache.Store(refAlt, r)
return r, r.Index, context.WithValue(ctx, CurrentPathKey, r.RemoteLocation)
}
// check the rolodex for the reference.
if roloLookup != "" {
rFile, err := index.rolodex.Open(roloLookup)
if err != nil {
return nil, index, ctx
}
// extract the index from the rolodex file.
if rFile != nil {
idx := rFile.GetIndex()
if index.resolver != nil {
index.resolver.indexesVisited++
}
if idx != nil {
// check mapped refs.
if r, ok := idx.allMappedRefs[ref]; ok {
index.cache.Store(ref, r)
idx.cache.Store(ref, r)
return r, r.Index, 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 {
idx.cache.Store(ref, s)
index.cache.Store(ref, s)
return s, s.Index, context.WithValue(ctx, CurrentPathKey, s.RemoteLocation)
}
}
// does component exist in the root?
node, _ := rFile.GetContentAsYAMLNode()
if node != nil {
found := idx.FindComponent(ref, node)
if found != nil {
idx.cache.Store(ref, found)
index.cache.Store(ref, found)
return found, found.Index, context.WithValue(ctx, CurrentPathKey, found.RemoteLocation)
}
}
}
}
}
index.logger.Error("unable to locate reference anywhere in the rolodex", "reference", ref)
return nil, index, ctx
} }

View File

@@ -18,6 +18,6 @@ func TestSpecIndex_SearchIndexForReference(t *testing.T) {
c := CreateOpenAPIIndexConfig() c := CreateOpenAPIIndexConfig()
idx := NewSpecIndexWithConfig(&rootNode, c) idx := NewSpecIndexWithConfig(&rootNode, c)
ref := idx.SearchIndexForReference("#/components/schemas/Pet") ref, _ := idx.SearchIndexForReference("#/components/schemas/Pet")
assert.NotNil(t, ref) assert.NotNil(t, ref)
} }

View File

@@ -149,6 +149,14 @@ func (index *SpecIndex) BuildIndex() {
index.built = true index.built = true
} }
func (index *SpecIndex) GetSpecAbsolutePath() string {
return index.specAbsolutePath
}
func (index *SpecIndex) GetLogger() *slog.Logger {
return index.logger
}
// GetRootNode returns document root node. // GetRootNode returns document root node.
func (index *SpecIndex) GetRootNode() *yaml.Node { func (index *SpecIndex) GetRootNode() *yaml.Node {
return index.root return index.root

View File

@@ -949,7 +949,7 @@ func TestSpecIndex_lookupFileReference_MultiRes(t *testing.T) {
index := rolo.GetRootIndex() index := rolo.GetRootIndex()
//index.seenRemoteSources = make(map[string]*yaml.Node) //index.seenRemoteSources = make(map[string]*yaml.Node)
absoluteRef, _ := filepath.Abs("embie.yaml#/naughty") absoluteRef, _ := filepath.Abs("embie.yaml#/naughty")
fRef := index.SearchIndexForReference(absoluteRef) fRef, _ := index.SearchIndexForReference(absoluteRef)
assert.NotNil(t, fRef) assert.NotNil(t, fRef)
} }

View File

@@ -347,7 +347,7 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *y
paramRef := index.allMappedRefs[paramRefName] paramRef := index.allMappedRefs[paramRefName]
if paramRef == nil { if paramRef == nil {
// could be in the rolodex // could be in the rolodex
ref := index.SearchIndexForReference(paramRefName) ref, _ := index.SearchIndexForReference(paramRefName)
if ref != nil { if ref != nil {
paramRef = ref paramRef = ref
} }