rspec-openapi
rspec-openapi copied to clipboard
Generate OpenAPI schema from RSpec request specs
rspec-openapi 
Generate OpenAPI schema from RSpec request specs.
What's this?
There are some gems which generate OpenAPI specs from RSpec request specs. However, they require a special DSL specific to these gems, and we can't reuse existing request specs as they are.
Unlike such existing gems, rspec-openapi can generate OpenAPI specs from request specs without requiring any special DSL. Furthermore, rspec-openapi keeps manual modifications when it merges automated changes to OpenAPI specs in case we can't generate everything from request specs.
Installation
Add this line to your application's Gemfile:
gem 'rspec-openapi', group: :test
Usage
Run rspec with OPENAPI=1 to generate doc/openapi.yaml
for your request specs.
$ OPENAPI=1 bundle exec rspec
Example
Let's say you have a request spec like this:
RSpec.describe 'Tables', type: :request do
describe '#index' do
it 'returns a list of tables' do
get '/tables', params: { page: '1', per: '10' }, headers: { authorization: 'k0kubun' }
expect(response.status).to eq(200)
end
it 'does not return tables if unauthorized' do
get '/tables'
expect(response.status).to eq(401)
end
end
# ...
end
If you run the spec with OPENAPI=1
,
OPENAPI=1 rspec spec/requests/tables_spec.rb
It will generate doc/openapi.yaml
file like:
openapi: 3.0.3
info:
title: rspec-openapi
paths:
"/tables":
get:
summary: index
tags:
- Table
parameters:
- name: page
in: query
schema:
type: integer
example: 1
- name: per
in: query
schema:
type: integer
example: 10
responses:
'200':
description: returns a list of tables
content:
application/json:
schema:
type: array
items:
type: object
properties:
id:
type: integer
name:
type: string
# ...
and the schema file can be used as an input of Swagger UI or Redoc.
Configuration
The following configurations are optional.
require 'rspec/openapi'
# Change the path to generate schema from `doc/openapi.yaml`
RSpec::OpenAPI.path = 'doc/schema.yaml'
# Change the output type to JSON
RSpec::OpenAPI.path = 'doc/schema.json'
# Or generate multiple partial schema files, given an RSpec example
RSpec::OpenAPI.path = -> (example) {
case example.file_path
when %r[spec/requests/api/v1/] then 'doc/openapi/v1.yaml'
when %r[spec/requests/api/v2/] then 'doc/openapi/v2.yaml'
else 'doc/openapi.yaml'
end
}
# Disable generating `example`
RSpec::OpenAPI.enable_example = false
# Change `info.version`
RSpec::OpenAPI.application_version = '1.0.0'
# Set the info header details
RSpec::OpenAPI.info = {
description: 'My beautiful API',
license: {
'name': 'Apache 2.0',
'url': 'https://www.apache.org/licenses/LICENSE-2.0.html'
}
}
# Set request `headers` - generate parameters with headers for a request
RSpec::OpenAPI.request_headers = %w[X-Authorization-Token]
# Set response `headers` - generate parameters with headers for a response
RSpec::OpenAPI.response_headers = %w[X-Cursor]
# Set `servers` - generate servers of a schema file
RSpec::OpenAPI.servers = [{ url: 'http://localhost:3000' }]
# Generate a comment on top of a schema file
RSpec::OpenAPI.comment = <<~EOS
This file is auto-generated by rspec-openapi https://github.com/k0kubun/rspec-openapi
When you write a spec in spec/requests, running the spec with `OPENAPI=1 rspec` will
update this file automatically. You can also manually edit this file.
EOS
# Generate a custom description, given an RSpec example
RSpec::OpenAPI.description_builder = -> (example) { example.description }
# Change the example type(s) that will generate schema
RSpec::OpenAPI.example_types = %i[request]
Can I use rspec-openapi with $ref
to minimize duplication of schema?
Yes, rspec-openapi v0.7.0+ supports $ref
mechanism and generates
schemas under #/components/schemas
with some manual steps.
- First, generate plain OpenAPI file.
- Then, manually replace the duplications with
$ref
.
paths:
"/users":
get:
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
"/users/{id}":
get:
responses:
'200':
content:
application/json:
schema:
$ref: "#/components/schemas/User"
# Note) #/components/schamas is not needed to be defined.
- Then, re-run rspec-openapi. It will generate
#/components/schemas
with the referenced schema (User
for example) newly-generated or updated.
paths:
"/users":
get:
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
"/users/{id}":
get:
responses:
'200':
content:
application/json:
schema:
$ref: "#/components/schemas/User"
components:
schemas:
User:
type: object
properties:
id:
type: string
name:
type: string
role:
type: array
items:
type: string
rspec-openapi also supports $ref
in properties
of schemas. Example)
paths:
"/locations":
get:
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Location"
components:
schemas:
Location:
type: object
properties:
id:
type: string
name:
type: string
Coordinate:
"$ref": "#/components/schemas/Coordinate"
Coordinate:
type: object
properties:
lat:
type: string
lon:
type: string
Note that automatic schemas
update feature is still new and may not work in complex scenario.
If you find a room for improvement, open an issue.
How can I add information which can't be generated from RSpec?
rspec-openapi tries to keep manual modifications as much as possible when generating specs.
You can directly edit doc/openapi.yaml
as you like without spoiling the automatic generation capability.
Can I exclude specific specs from OpenAPI generation?
Yes, you can specify openapi: false
to disable the automatic generation.
RSpec.describe '/resources', type: :request, openapi: false do
# ...
end
# or
RSpec.describe '/resources', type: :request do
it 'returns a resource', openapi: false do
# ...
end
end
Customizations
Some examples' attributes can be overwritten via RSpec metadata options. Example:
describe 'GET /api/v1/posts', openapi: {
summary: 'list all posts',
description: 'list all posts ordered by pub_date',
tags: %w[v1 posts],
} do
# ...
end
NOTE: description
key will override also the one provided by RSpec::OpenAPI.description_builder
method.
Links
Existing RSpec plugins which have OpenAPI integration:
Acknowledgements
This gem was heavily inspired by the following gem:
License
The gem is available as open source under the terms of the MIT License.