How to parse an OpenAPI 3 specification

Using OpenAPI 3.0 and 3.1

Loading a model from a specification

There are two steps to creating a model, creating a document and then building a model from that document.

Creating a new document

First we need to create a reference to a Document that we create using NewDocument() and pass in a []byte slice that contains the specification.

For example:

// load an OpenAPI 3 specification from bytes
petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json")

// create a new document from specification bytes
document, err := libopenapi.NewDocument(petstore)

Documents with relative or remote references

If the specification has lots of relative or remote references, (just like the DigitalOcean OpenAPI Specification), then a datamodel.DocumentConfigutation is required to set a BaseURL or a BasePath and allow the library to resolve the references by making HTTP requests or loading files from the local file system.

The NewDocumentWithConfiguration() method can be used to create a document with a configuration.

// Digital Ocean needs a baseURL to be set, so we can resolve relative references.
baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification")

// create a DocumentConfiguration that allows loading file and remote references, and sets the baseURL
// to somewhere that can resolve the relative references.
config := datamodel.DocumentConfiguration{
    AllowFileReferences:   true,
    AllowRemoteReferences: true,
    BaseURL:               baseURL,
}

// create a new document from specification bytes
doc, err := NewDocumentWithConfiguration(digitalOceanBytes, &config)

Or you can use a BasePath instead of a BaseURL if all the files are local, but they are in a different working directory.

// create a DocumentConfiguration that allows loading file and remote references, and sets the BasePath
// to somewhere other than the local working directory.
config := datamodel.DocumentConfiguration{
    AllowFileReferences:   true,
    AllowRemoteReferences: true,
    BasePath:               "../../some/path/to/files",
}

// create a new document from specification bytes
doc, err := NewDocumentWithConfiguration(digitalOceanBytes, &config)

Building a model from the document

Once we have the Document pointer, we can then build a model from the document.

The reason why you have to create a Document first, is because the library supports both Swagger and OpenAPI models. The model is generated from the Document, depending on what version of specification you have read in.

Here is an example of doing that, if we know we’re looking at an OpenAPI 3 document.


import (
    "fmt"
    "github.com/pb33f/libopenapi"
    "io/ioutil"
)

func main() {
    
    // load an OpenAPI 3 specification from bytes
    petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json")

    // create a new document from specification bytes
    document, err := libopenapi.NewDocument(petstore)

    // if anything went wrong, an error is thrown
    if err != nil {
        panic(fmt.Sprintf("cannot create new document: %e", err))
    }

    // because we know this is a v3 spec, 
    // we can build a ready to go model from it.
    v3Model, errors := document.BuildV3Model()

    // if anything went wrong when building the v3 model, 
    // a slice of errors will be returned
    if len(errors) > 0 {
        for i := range errors {
            fmt.Printf("error: %e\n", errors[i])
        }
        panic(fmt.Sprintf("cannot create v3 model from " +
            "document: %d errors reported",
            len(errors)))
    }

    // get a count of the number of paths and schemas.
    paths := len(v3Model.Model.Paths.PathItems)
    schemas := len(v3Model.Model.Components.Schemas)

    // print the number of paths and schemas in the document
    fmt.Printf("There are %d paths and %d schemas " +
        "in the document", paths, schemas)
}

This will print the following to the console:

There are 13 paths and 8 schemas in the document