Allow to override basePath for file lookup

This commit is contained in:
Dmitry
2023-03-16 06:17:59 +01:00
committed by Dave Shanley
parent 9004d57197
commit 634d675eef
2 changed files with 194 additions and 184 deletions

View File

@@ -15,6 +15,10 @@ type DocumentConfiguration struct {
// Schema must be set to "http/https". // Schema must be set to "http/https".
BaseURL *url.URL BaseURL *url.URL
// If resolving locally, the BasePath will be the root from which relative references will be resolved from.
// It usually location of the root specification.
BasePath string // set the Base Path for resolving relative references if the spec is exploded.
// AllowFileReferences will allow the index to locate relative file references. This is disabled by default. // AllowFileReferences will allow the index to locate relative file references. This is disabled by default.
AllowFileReferences bool AllowFileReferences bool

View File

@@ -1,15 +1,16 @@
package v3 package v3
import ( import (
"errors" "errors"
"github.com/pb33f/libopenapi/datamodel" "os"
"github.com/pb33f/libopenapi/datamodel/low" "sync"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/resolver" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/datamodel/low/base"
"os" "github.com/pb33f/libopenapi/index"
"sync" "github.com/pb33f/libopenapi/resolver"
"github.com/pb33f/libopenapi/utils"
) )
// CreateDocument will create a new Document instance from the provided SpecInfo. // CreateDocument will create a new Document instance from the provided SpecInfo.
@@ -17,221 +18,226 @@ import (
// Deprecated: Use CreateDocumentFromConfig instead. This function will be removed in a later version, it // Deprecated: Use CreateDocumentFromConfig instead. This function will be removed in a later version, it
// defaults to allowing file and remote references, and does not support relative file references. // defaults to allowing file and remote references, and does not support relative file references.
func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) { func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
config := datamodel.DocumentConfiguration{ config := datamodel.DocumentConfiguration{
AllowFileReferences: true, AllowFileReferences: true,
AllowRemoteReferences: true, AllowRemoteReferences: true,
} }
return createDocument(info, &config) return createDocument(info, &config)
} }
// CreateDocumentFromConfig Create a new document from the provided SpecInfo and DocumentConfiguration pointer. // CreateDocumentFromConfig Create a new document from the provided SpecInfo and DocumentConfiguration pointer.
func CreateDocumentFromConfig(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, []error) { func CreateDocumentFromConfig(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, []error) {
return createDocument(info, config) return createDocument(info, config)
} }
func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, []error) { func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, []error) {
_, labelNode, versionNode := utils.FindKeyNodeFull(OpenAPILabel, info.RootNode.Content) _, labelNode, versionNode := utils.FindKeyNodeFull(OpenAPILabel, info.RootNode.Content)
var version low.NodeReference[string] var version low.NodeReference[string]
if versionNode == nil { if versionNode == nil {
return nil, []error{errors.New("no openapi version/tag found, cannot create document")} return nil, []error{errors.New("no openapi version/tag found, cannot create document")}
} }
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 // get current working directory as a basePath
cwd, _ := os.Getwd() cwd, _ := os.Getwd()
// build an index // If basePath is provided override it
idx := index.NewSpecIndexWithConfig(info.RootNode, &index.SpecIndexConfig{ if config.BasePath != "" {
BaseURL: config.BaseURL, cwd = config.BasePath
BasePath: cwd, }
AllowFileLookup: config.AllowFileReferences, // build an index
AllowRemoteLookup: config.AllowRemoteReferences, idx := index.NewSpecIndexWithConfig(info.RootNode, &index.SpecIndexConfig{
}) BaseURL: config.BaseURL,
doc.Index = idx BasePath: cwd,
AllowFileLookup: config.AllowFileReferences,
AllowRemoteLookup: config.AllowRemoteReferences,
})
doc.Index = idx
var errs []error var errs []error
errs = idx.GetReferenceIndexErrors() errs = idx.GetReferenceIndexErrors()
// create resolver and check for circular references. // create resolver and check for circular references.
resolve := resolver.NewResolver(idx) resolve := resolver.NewResolver(idx)
resolvingErrors := resolve.CheckForCircularReferences() resolvingErrors := resolve.CheckForCircularReferences()
if len(resolvingErrors) > 0 { if len(resolvingErrors) > 0 {
for r := range resolvingErrors { for r := range resolvingErrors {
errs = append(errs, resolvingErrors[r]) errs = append(errs, resolvingErrors[r])
} }
} }
var wg sync.WaitGroup var wg sync.WaitGroup
doc.Extensions = low.ExtractExtensions(info.RootNode.Content[0]) doc.Extensions = low.ExtractExtensions(info.RootNode.Content[0])
// if set, extract jsonSchemaDialect (3.1) // if set, extract jsonSchemaDialect (3.1)
_, dialectLabel, dialectNode := utils.FindKeyNodeFull(JSONSchemaDialectLabel, info.RootNode.Content) _, dialectLabel, dialectNode := utils.FindKeyNodeFull(JSONSchemaDialectLabel, info.RootNode.Content)
if dialectNode != nil { if dialectNode != nil {
doc.JsonSchemaDialect = low.NodeReference[string]{ doc.JsonSchemaDialect = low.NodeReference[string]{
Value: dialectNode.Value, KeyNode: dialectLabel, ValueNode: dialectNode} Value: dialectNode.Value, KeyNode: dialectLabel, ValueNode: dialectNode,
} }
}
var runExtraction = func(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex, runExtraction := func(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex,
runFunc func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error, runFunc func(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 {
*ers = append(*ers, er)
}
wg.Done()
}
extractionFuncs := []func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{
extractInfo,
extractServers,
extractTags,
extractComponents,
extractSecurity,
extractExternalDocs,
extractPaths,
extractWebhooks,
}
if er := runFunc(info, doc, idx); er != nil { wg.Add(len(extractionFuncs))
*ers = append(*ers, er) for _, f := range extractionFuncs {
} go runExtraction(info, &doc, idx, f, &errs, &wg)
wg.Done() }
} wg.Wait()
extractionFuncs := []func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{ return &doc, errs
extractInfo,
extractServers,
extractTags,
extractComponents,
extractSecurity,
extractExternalDocs,
extractPaths,
extractWebhooks,
}
wg.Add(len(extractionFuncs))
for _, f := range extractionFuncs {
go runExtraction(info, &doc, idx, f, &errs, &wg)
}
wg.Wait()
return &doc, errs
} }
func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractInfo(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(vn, idx) _ = ir.Build(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(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](SecurityLabel, info.RootNode.Content[0], idx)
if err != nil { if err != nil {
return err return err
} }
doc.Security = low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]{ doc.Security = low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]{
Value: sec, Value: sec,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
return nil return nil
} }
func extractExternalDocs(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractExternalDocs(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](base.ExternalDocsLabel, info.RootNode.Content[0], idx)
if dErr != nil { if dErr != nil {
return dErr return dErr
} }
doc.ExternalDocs = extDocs doc.ExternalDocs = extDocs
return nil return nil
} }
func extractComponents(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractComponents(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(vn, idx)
if err != nil { if err != nil {
return err return err
} }
nr := low.NodeReference[*Components]{Value: &ir, ValueNode: vn, KeyNode: ln} nr := low.NodeReference[*Components]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Components = nr doc.Components = nr
} }
return nil return nil
} }
func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractServers(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) {
var servers []low.ValueReference[*Server] var servers []low.ValueReference[*Server]
for _, srvN := range vn.Content { for _, srvN := range vn.Content {
if utils.IsNodeMap(srvN) { if utils.IsNodeMap(srvN) {
srvr := Server{} srvr := Server{}
_ = low.BuildModel(srvN, &srvr) _ = low.BuildModel(srvN, &srvr)
_ = srvr.Build(srvN, idx) _ = srvr.Build(srvN, idx)
servers = append(servers, low.ValueReference[*Server]{ servers = append(servers, low.ValueReference[*Server]{
Value: &srvr, Value: &srvr,
ValueNode: srvN, ValueNode: srvN,
}) })
} }
} }
doc.Servers = low.NodeReference[[]low.ValueReference[*Server]]{ doc.Servers = low.NodeReference[[]low.ValueReference[*Server]]{
Value: servers, Value: servers,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
} }
} }
return nil return nil
} }
func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractTags(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) {
var tags []low.ValueReference[*base.Tag] var tags []low.ValueReference[*base.Tag]
for _, tagN := range vn.Content { for _, tagN := range vn.Content {
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(tagN, idx); err != nil { if err := tag.Build(tagN, idx); err != nil {
return err return err
} }
tags = append(tags, low.ValueReference[*base.Tag]{ tags = append(tags, low.ValueReference[*base.Tag]{
Value: &tag, Value: &tag,
ValueNode: tagN, ValueNode: tagN,
}) })
} }
} }
doc.Tags = low.NodeReference[[]low.ValueReference[*base.Tag]]{ doc.Tags = low.NodeReference[[]low.ValueReference[*base.Tag]]{
Value: tags, Value: tags,
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
} }
} }
return nil return nil
} }
func extractPaths(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractPaths(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(vn, idx) err := ir.Build(vn, idx)
if err != nil { if err != nil {
return err return err
} }
nr := low.NodeReference[*Paths]{Value: &ir, ValueNode: vn, KeyNode: ln} nr := low.NodeReference[*Paths]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Paths = nr doc.Paths = nr
} }
return nil return nil
} }
func extractWebhooks(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractWebhooks(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](WebhooksLabel, info.RootNode, idx)
if eErr != nil { if eErr != nil {
return eErr return eErr
} }
if hooks != nil { if hooks != nil {
doc.Webhooks = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*PathItem]]{ doc.Webhooks = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*PathItem]]{
Value: hooks, Value: hooks,
KeyNode: hooksL, KeyNode: hooksL,
ValueNode: hooksN, ValueNode: hooksN,
} }
} }
return nil return nil
} }