OpenapiContracts
Use OpenAPI documentation as an API contract.
Currently supports OpenAPI documentation in the structure as used by Redocly, but should also work for single file schemas.
Adds RSpec matchers to easily verify that your requests and responses match the OpenAPI documentation.
Usage
First, parse your API documentation:
# This must point to the folder where the OAS file is stored
$doc = OpenapiContracts::Doc.parse(Rails.root.join('spec/fixtures/openapi/api-docs'), '<filename>')
In case the filename argument is not set, parser will by default search for the file named openapi.yaml.
Ideally you do this once in an RSpec before(:suite) hook. Then you can use these matchers in your request specs:
subject { make_request and response }
let(:make_request) { get '/some/path' }
it { is_expected.to match_openapi_doc($doc) }
You can assert a specific http status to make sure the response is of the right status:
it { is_expected.to match_openapi_doc($doc).with_http_status(:ok) }
# This is equal to
it 'responds with 200 and matches the doc' do
expect(subject).to have_http_status(:ok)
expect(subject).to match_openapi_doc($doc)
end
Options
The match_openapi_doc($doc) method allows passing options as a 2nd argument.
pathallows overriding the defaultrequest.pathlookup in case it does not find the correct response definition in your schema. This is especially important when there are dynamic parameters in the path and the matcher fails to resolve the request path to an endpoint in the OAS file.
it { is_expected.to match_openapi_doc($doc, path: '/messages/{id}').with_http_status(:ok) }
request_bodycan be set totruein case the validation of the request body against the OpenAPI requestBody schema is required.
it { is_expected.to match_openapi_doc($doc, request_body: true).with_http_status(:created) }
Both options can as well be used simultaneously.
Without RSpec
You can also use the Validator directly:
# Let's raise an error if the response does not match
result = OpenapiContracts.match($doc, response, = {})
raise result.errors.merge("/n") unless result.valid?
How it works
It uses the request.path, request.method, status and headers on the test subject
(which must be the response) to find the request and response schemas in the OpenAPI document.
Then it does the following checks:
- The response is documented
- Required headers are present
- Documented headers match the schema (via json_schemer)
- The response body matches the schema (via json_schemer)
- The request body matches the schema (via json_schemer) - if
request_body: true
Known Issues
OpenApi 3.0
For openapi schemas < 3.1, data is validated using JSON Schema Draft 04, even tho OpenApi 3.0 is a super+subset of Draft 05. This is due to the fact that we validate the data using json-schemer which does not support 05 and even then would not be fully compatible. However compatibility issues should be fairly rare and there might be workarounds by describing the data slightly different.
OpenAPi 3.1
Here exists a similar problem. OpenApi 3.1 is finally fully compatible with JSON Draft 2020-12, but there is no support yet in json-schemer, so we use the closest draft which is 07.
Future plans
- Validate Webmock stubs against the OpenAPI doc
- Generate example payloads from the OpenAPI doc