OpenAPI change detection
A diff engine and changelog detector for OpenAPIOpenAPI change detection is a feature of libopenapi
that allows you to detect changes between two
OpenAPI models. It’s the core engine that powers our openapi-changes
tool.
What is a change?
A change in libopenapi
is defined as…
A property or object state was changed in one of the following ways:
- Modified
- Added
- Removed
A change has a binary breaking state as well. It can either be a non-breaking or a breaking change.
What is a breaking change?
A breaking change is any change to the OpenAPI contract that would cause a consuming client or user of that API to break.
A break means that client using the OpenAPI specification as a contract to generate language bindings, or build automation against, will fail to compile or will now run incorrectly because of the change.
It can also mean anyone expecting the same results after the change, will need to change their code.
Breaking examples
- Modifying the
operationId
of an operation - Deleting a
Path
- Changing a
Schema
type - Deleting an
Operation
from aPath
- Adding/removing a
Parameter
from anOperation
orPath
There are many more examples than this however.
Comparing documents
The libopenapi.CompareDocuments
function is the main entry point for comparing two OpenAPI documents. It takes two
arguments, a left (original) and right (updated) document, and returns a libopenapi.DocumentChanges
object.
import(
"fmt"
"io/ioutil"
"github.com/pb33f/libopenapi"
func main() {
// How to compare two different OpenAPI specifications.
// load an original OpenAPI 3 specification from bytes
burgerShopOriginal, _ := ioutil.ReadFile("test_specs/burgershop.openapi.yaml")
// load an **updated** OpenAPI 3 specification from bytes
burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml")
// create a new document from original specification bytes
originalDoc, err := libopenapi.NewDocument(burgerShopOriginal)
// if anything went wrong, an error is thrown
if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err))
}
// create a new document from updated specification bytes
updatedDoc, err := libopenapi.NewDocument(burgerShopUpdated)
// if anything went wrong, an error is thrown
if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err))
}
// Compare documents for all changes made
documentChanges, errs := libopenapi.CompareDocuments(originalDoc, updatedDoc)
// If anything went wrong when building models for documents.
if len(errs) > 0 {
for i := range errs {
fmt.Printf("error: %e\n", errs[i])
}
panic(fmt.Sprintf("cannot compare documents: %d errors reported", len(errs)))
}
// Extract SchemaChanges from components changes.
schemaChanges := documentChanges.ComponentsChanges.SchemaChanges
// Print out some interesting stats about the OpenAPI document changes.
fmt.Printf("There are %d changes, of which %d are breaking. %v schemas have changes.",
documentChanges.TotalChanges(), documentChanges.TotalBreakingChanges(), len(schemaChanges))
This would print out something like:
The DocumentChanges struct
All models for what-changed
are in the what-changed/model
package/directory.
The model.DocumentChanges
struct is the main object returned by the libopenapi.CompareDocuments
function. It represents all the changes
made to the OpenAPI document and is structured in the same way as the OpenAPI document itself.
Every OpenAPI object type is broken down into its own struct, and each struct has a Changes
property that is a slice of
Change
objects.
The Change
object is a generic object that can represent any change made to an OpenAPI object.
The OpenAPI change structs are really just there to help structure the changes in a way that makes sense
programmatically. The real data is encapsulated in the Change
object.
All OpenAPI change structs contain the following two methods
TotalChanges()
TotalBreakingChanges()
Both methods calculate a sum of the total number of changes and breaking changes in the object AND all its children.
Change object
Property | Type | Description |
---|---|---|
Context |
ChangeContext |
Represents the lines and column numbers of the original and new values. |
ChangeType |
ChangeType |
Represents the type of change that occurred. stored as an integer. |
Property |
string |
The property name key being changed. |
Original |
string |
The original value represented as a string. |
New |
string |
The new value represented as a string. |
Breaking |
bool |
Determines if the change is a breaking one or not. |
OriginalObject |
any |
OriginalObject Represents the original object that was changed. |
NewObject |
any |
NewObject Represents the new object that has been modified. |