Using Swagger to detect breaking API changes

  October 08, 2015

Author: Jeff Cousens, Sr. Staff Engineer at Civis - Analytics

A while back, we decided that all new functionality in the Civis platform would be implemented as a combination of API endpoints and front-end code. At the same time, we decided that all endpoints will be available to our customers. That is, if you can use a piece of functionality via the Civis UI, you can leverage the underlying API for programmatic access, too. This has been a huge force multiplier, as you can read about in The Civis API: Scale Up Your Data Science. It also means that our Engineering Team can’t break our API contract just because it suits the needs of the UI.

One solution is to rely upon API versions, which were built in to the Civis API from day one. But even with versions, we still need to know whether a change constitutes a breaking change. We consider it a breaking change to:

  • remove an endpoint
  • add a new required request parameter
  • remove or change the type of a request parameter or response attribute

Published guidelines are good, but they can be fallible. We prefer to leverage automated solutions that codify our best practices, and make explicit and enforce our policies. With that in mind, we wrote Swagger::Diff. Swagger::Diff compares two Swagger specifications and identifies any breaking changes. For example, to compare the Swagger Project’s expanded petstore example against the minimal petstore example, verifying the expanded example is backwards compatible with the minimal example:

$ gem install swagger-diff

$ wget https://raw.githubusercontent.com/swagger-api/swagger-spec/master/examples/v2.0/json/petstore-minimal.json

$ wget https://raw.githubusercontent.com/swagger-api/swagger-spec/master/examples/v2.0/json/petstore-expanded.json

$ swagger-diff petstore-minimal.json petstore-expanded.json

Swapping the order of the arguments, it compares the minimal example against the expanded example, flagging the endpoints, parameters, and attributes that are missing from the minimal example:

$ swagger-diff petstore-expanded.json petstore-minimal.json

- missing endpoints

- delete /pets/{}

- get /pets/{}

- post /pets

- incompatible request params

- get /pets

- missing request param: tags (type: array)

- missing request param: limit (type: integer)

- incompatible response attributes

- get /pets

- missing default response

Swagger::Diff also integrates directly with our test suite. People might forget to run a check, but continuous integration (CI) does not. Coupled with an API endpoint that returns our current Swagger specification, it allows us to simply and easily compare two versions of our API via a request spec:

it 'is backwards compatible' do

production_swagger = open('https://host.domain/endpoints').read

get '/endpoints'

test_swagger = response.body

expect(test_swagger).to be_compatible_with(production_swagger)

end

Every time we push code, our CI tests the development branch against the production specification, and fails with a message detailing backwards-incompatible changes if any were introduced. This guarantees that no backwards-incompatible changes will accidentally slip into our API.

We’ve open-sourced Swagger::Diff under the BSD 3-Clause License, published the gem to RubyGems.org, and made the source available on GitHub. We encourage you to add it to your CLI toolset, project’s CI, or test suite, and we welcome issues or pull requests.

This post was originally published in the Civis blog.