Bundle OpenAPI references into a single file
Compact multiple OpenAPI references into a single fileLots of OpenAPI specifications are exploded out across lots of different files. This is great for maintainability, but not so great for portability.
Lots of OpenAPI tools do not support exploded / multi-file OpenAPI specifications. Loading and locating references can be tricky, particularly when they are spread across local or remote file systems, so lots of tools just give up on trying to locate references and just ask for a single monolithic file.
Bundling raw bytes
The simplest way to bundle multiple OpenAPI references into a single file is to use the BundleBytes
function from the
bundler
package:
func BundleBytes(bytes []byte, configuration *datamodel.DocumentConfiguration) ([]byte, error)
This method takes a slice of bytes, and a DocumentConfiguration
pointer and returns a slice of bytes and an error
if something went wrong.
The returned bytes is a single, fully bundled OpenAPI specification, all external references are resolved and inlined.
Local (references inside the root document) references are not bundled, They stay in place.
Bundling a document
The BundleDocument
function from the bundler
package is a convenience function that takes a v3.Document
pointer and
returns a slice of bytes and an error if something went wrong:
func BundleDocument(model *v3.Document) ([]byte, error)
Here is an example of checking out the mother of all exploded specifications the Digital Ocean OpenAPI specification and bundling it into a single file:
import (
"bytes"
"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi/bundler"
"github.com/pb33f/libopenapi/datamodel"
"github.com/stretchr/testify/assert"
"log"
"log/slog"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
func main() {
// check out the mother of all exploded specifications
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"))
specBytes, _ := os.ReadFile(spec)
doc, err := libopenapi.NewDocumentWithConfiguration([]byte(specBytes), &datamodel.DocumentConfiguration{
BasePath: tmp + "/specification",
ExtractRefsSequentially: true,
Logger: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelWarn,
})),
})
if err != nil {
panic(err)
}
v3Doc, errs := doc.BuildV3Model()
if len(errs) > 0 {
panic(errs)
}
bytes, e := bundler.BundleDocument(&v3Doc.Model)
//... do something with the bytes
}
And now we have a single, bundled OpenAPI specification that we can use with any tool that doesn’t support exploded specs.