Files
libopenapi/datamodel/low/v3/create_document.go
2023-11-27 12:06:39 +00:00

288 lines
8.7 KiB
Go

package v3
import (
"context"
"errors"
"path/filepath"
"sync"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi/utils"
)
// CreateDocument will create a new Document instance from the provided SpecInfo.
//
// 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.
func CreateDocument(info *datamodel.SpecInfo) (*Document, error) {
return createDocument(info, datamodel.NewDocumentConfiguration())
}
// CreateDocumentFromConfig Create a new document from the provided SpecInfo and DocumentConfiguration pointer.
func CreateDocumentFromConfig(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, error) {
return createDocument(info, config)
}
func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfiguration) (*Document, error) {
_, labelNode, versionNode := utils.FindKeyNodeFull(OpenAPILabel, info.RootNode.Content)
var version low.NodeReference[string]
if versionNode == nil {
return nil, errors.New("no openapi version/tag found, cannot create document")
}
version = low.NodeReference[string]{Value: versionNode.Value, KeyNode: labelNode, ValueNode: versionNode}
doc := Document{Version: version}
// create an index config and shadow the document configuration.
idxConfig := index.CreateClosedAPIIndexConfig()
idxConfig.SpecInfo = info
idxConfig.IgnoreArrayCircularReferences = config.IgnoreArrayCircularReferences
idxConfig.IgnorePolymorphicCircularReferences = config.IgnorePolymorphicCircularReferences
idxConfig.AvoidCircularReferenceCheck = true
idxConfig.BaseURL = config.BaseURL
idxConfig.BasePath = config.BasePath
idxConfig.Logger = config.Logger
rolodex := index.NewRolodex(idxConfig)
rolodex.SetRootNode(info.RootNode)
doc.Rolodex = rolodex
// If basePath is provided, add a local filesystem to the rolodex.
if idxConfig.BasePath != "" || config.AllowFileReferences {
var cwd string
cwd, _ = filepath.Abs(config.BasePath)
// if a supplied local filesystem is provided, add it to the rolodex.
if config.LocalFS != nil {
rolodex.AddLocalFS(cwd, config.LocalFS)
} else {
// create a local filesystem
localFSConf := index.LocalFSConfig{
BaseDirectory: cwd,
IndexConfig: idxConfig,
FileFilters: config.FileFilter,
}
fileFS, _ := index.NewLocalFSWithConfig(&localFSConf)
idxConfig.AllowFileLookup = true
// add the filesystem to the rolodex
rolodex.AddLocalFS(cwd, fileFS)
}
}
// if base url is provided, add a remote filesystem to the rolodex.
if idxConfig.BaseURL != nil || config.AllowRemoteReferences {
// create a remote filesystem
remoteFS, _ := index.NewRemoteFSWithConfig(idxConfig)
if config.RemoteURLHandler != nil {
remoteFS.RemoteHandlerFunc = config.RemoteURLHandler
}
idxConfig.AllowRemoteLookup = true
// add to the rolodex
u := "default"
if config.BaseURL != nil {
u = config.BaseURL.String()
}
rolodex.AddRemoteFS(u, remoteFS)
}
// index the rolodex
var errs []error
// index all the things.
_ = rolodex.IndexTheRolodex()
// check for circular references
if !config.SkipCircularReferenceCheck {
rolodex.CheckForCircularReferences()
}
// extract errors
roloErrs := rolodex.GetCaughtErrors()
if roloErrs != nil {
errs = append(errs, roloErrs...)
}
// set root index.
doc.Index = rolodex.GetRootIndex()
var wg sync.WaitGroup
doc.Extensions = low.ExtractExtensions(info.RootNode.Content[0])
// if set, extract jsonSchemaDialect (3.1)
_, dialectLabel, dialectNode := utils.FindKeyNodeFull(JSONSchemaDialectLabel, info.RootNode.Content)
if dialectNode != nil {
doc.JsonSchemaDialect = low.NodeReference[string]{
Value: dialectNode.Value, KeyNode: dialectLabel, ValueNode: dialectNode,
}
}
runExtraction := func(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex,
runFunc func(ctx context.Context, i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error,
ers *[]error,
wg *sync.WaitGroup,
) {
if er := runFunc(ctx, info, doc, idx); er != nil {
*ers = append(*ers, er)
}
wg.Done()
}
extractionFuncs := []func(ctx context.Context, i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{
extractInfo,
extractServers,
extractTags,
extractComponents,
extractSecurity,
extractExternalDocs,
extractPaths,
extractWebhooks,
}
ctx := context.Background()
wg.Add(len(extractionFuncs))
for _, f := range extractionFuncs {
go runExtraction(ctx, info, &doc, rolodex.GetRootIndex(), f, &errs, &wg)
}
wg.Wait()
return &doc, errors.Join(errs...)
}
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)
if vn != nil {
ir := base.Info{}
_ = low.BuildModel(vn, &ir)
_ = ir.Build(ctx, ln, vn, idx)
nr := low.NodeReference[*base.Info]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Info = nr
}
return nil
}
func extractSecurity(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](ctx, SecurityLabel, info.RootNode.Content[0], idx)
if err != nil {
return err
}
if vn != nil && ln != nil {
doc.Security = low.NodeReference[[]low.ValueReference[*base.SecurityRequirement]]{
Value: sec,
KeyNode: ln,
ValueNode: vn,
}
}
return nil
}
func extractExternalDocs(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
extDocs, dErr := low.ExtractObject[*base.ExternalDoc](ctx, base.ExternalDocsLabel, info.RootNode.Content[0], idx)
if dErr != nil {
return dErr
}
doc.ExternalDocs = extDocs
return nil
}
func extractComponents(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFullTop(ComponentsLabel, info.RootNode.Content[0].Content)
if vn != nil {
ir := Components{}
_ = low.BuildModel(vn, &ir)
err := ir.Build(ctx, vn, idx)
if err != nil {
return err
}
nr := low.NodeReference[*Components]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Components = nr
}
return nil
}
func extractServers(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(ServersLabel, info.RootNode.Content[0].Content)
if vn != nil {
if utils.IsNodeArray(vn) {
var servers []low.ValueReference[*Server]
for _, srvN := range vn.Content {
if utils.IsNodeMap(srvN) {
srvr := Server{}
_ = low.BuildModel(srvN, &srvr)
_ = srvr.Build(ctx, ln, srvN, idx)
servers = append(servers, low.ValueReference[*Server]{
Value: &srvr,
ValueNode: srvN,
})
}
}
doc.Servers = low.NodeReference[[]low.ValueReference[*Server]]{
Value: servers,
KeyNode: ln,
ValueNode: vn,
}
}
}
return nil
}
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)
if vn != nil {
if utils.IsNodeArray(vn) {
var tags []low.ValueReference[*base.Tag]
for _, tagN := range vn.Content {
if utils.IsNodeMap(tagN) {
tag := base.Tag{}
_ = low.BuildModel(tagN, &tag)
if err := tag.Build(ctx, ln, tagN, idx); err != nil {
return err
}
tags = append(tags, low.ValueReference[*base.Tag]{
Value: &tag,
ValueNode: tagN,
})
}
}
doc.Tags = low.NodeReference[[]low.ValueReference[*base.Tag]]{
Value: tags,
KeyNode: ln,
ValueNode: vn,
}
}
}
return nil
}
func extractPaths(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
_, ln, vn := utils.FindKeyNodeFull(PathsLabel, info.RootNode.Content[0].Content)
if vn != nil {
ir := Paths{}
err := ir.Build(ctx, ln, vn, idx)
if err != nil {
return err
}
nr := low.NodeReference[*Paths]{Value: &ir, ValueNode: vn, KeyNode: ln}
doc.Paths = nr
}
return nil
}
func extractWebhooks(ctx context.Context, info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
hooks, hooksL, hooksN, eErr := low.ExtractMap[*PathItem](ctx, WebhooksLabel, info.RootNode, idx)
if eErr != nil {
return eErr
}
if hooks != nil {
doc.Webhooks = low.NodeReference[orderedmap.Map[low.KeyReference[string], low.ValueReference[*PathItem]]]{
Value: hooks,
KeyNode: hooksL,
ValueNode: hooksN,
}
}
return nil
}