Compare commits

..

No commits in common. "master" and "2.0.6" have entirely different histories.

88 changed files with 535 additions and 3113 deletions

View File

@ -1,17 +0,0 @@
## Describe the bug
A clear and concise description of what the bug is.
## Steps to Test or Reproduce
Please provide an example repo or the steps to reproduce the behavior.
## Expected behavior
A clear and concise description of what you expected to happen.
## Screenshots
If applicable, add screenshots to help explain your problem.
## Additional context
Add any other context about the problem here.
## Rswag Version
The version of rswag are you using.

View File

@ -1,11 +0,0 @@
## Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is.
## Describe the solution you'd like
A clear and concise description of what you want to happen.
## Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
## Additional context
Add any other context or screenshots about the feature request here.

View File

@ -1,15 +0,0 @@
## Problem
A clear and concise description of what the problem is.
## Solution
A clear and concise description of what the solution is.
### Related Issues
Links to any related issues.
### Checklist
- [ ] Added tests
- [ ] Changelog updated
### Steps to Test or Reproduce
Outline the steps to test or reproduce the PR here.

View File

@ -1,60 +0,0 @@
name: Ruby
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby: [2.6, 2.7, truffleruby-head]
rails: [5.2.4.4, 6.0.3.4]
env:
RAILS_VERSION: ${{ matrix.rails }}
steps:
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
with: { ruby-version: 2.6 }
- uses: actions/cache@v2
id: cache
with:
path: |
rswag-ui/node_modules
vendor/bundle
key: ${{ runner.os }}-ruby_${{ matrix.ruby }}-rails_${{ matrix.rails }}-${{ hashFiles('Gemfile', '**/package-lock.json') }}
- name: Install dependencies
run: |
gem update --system
bundle install
cd rswag-ui && npm install
- name: rswag-api
run: |
cd rswag-api
bundle exec rspec
- name: rswag-specs
if: success() || failure()
run: |
cd rswag-specs
bundle exec rspec
- name: rswag-ui
if: success() || failure()
run: |
cd rswag-ui
bundle exec rspec
- name: test-app
if: success() || failure()
run: |
cd test-app
bundle exec rake db:migrate db:test:prepare
bundle exec rspec

2
.gitignore vendored
View File

@ -6,5 +6,3 @@
**/*/node_modules
*.swp
Gemfile.lock
/.idea/
**/.byebug_history

View File

@ -1 +1 @@
2.7.2
2.6.4

View File

@ -1,11 +1,11 @@
language: ruby
dist: bionic
dist: xenial
services:
- xvfb
rvm:
- 2.6.3
- 2.6.4
env:
- RAILS_VERSION=6.0.0
@ -19,7 +19,7 @@ addons:
cache:
directories:
- /home/travis/.rvm/gems/ruby-2.6.3
- /home/travis/.rvm/gems/ruby-2.6.4
install: ./ci/build.sh
@ -29,7 +29,6 @@ jobs:
include:
- stage: publish components
script: 'cd rswag-api'
if: tag IS present
deploy:
gemspec: rswag-api.gemspec
provider: rubygems
@ -40,7 +39,6 @@ jobs:
- stage: publish components
script: 'cd rswag-specs'
if: tag IS present
deploy:
gemspec: rswag-specs.gemspec
provider: rubygems
@ -51,7 +49,6 @@ jobs:
- stage: publish components
script: 'cd rswag-ui'
if: tag IS present
deploy:
gemspec: rswag-ui.gemspec
provider: rubygems
@ -63,7 +60,6 @@ jobs:
- stage: publish rswag
script: 'cd rswag'
if: tag IS present
deploy:
gemspec: rswag.gemspec
provider: rubygems

View File

@ -1,66 +1,16 @@
# rswag
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Changed
- Update swagger-ui to 3.52.5
## [2.4.0] - 2021-02-09
### Added
- Added `SWAGGER_DRY_RUN` env variable [#274](https://github.com/rswag/rswag/pull/274)
## [2.3.3] - 2021-02-07
### Changed
### Deprecated
### Removed
### Fixed
- Include response examples [#394](https://github.com/rswag/rswag/pull/394)
### Changed
- Update swagger-ui to 3.42.0
## [2.3.2] - 2021-01-27
### Added
- RequestBody now supports the `required` flag [#342](https://github.com/rswag/rswag/pull/342)
### Fixed
- Fix response example rendering [#330](https://github.com/rswag/rswag/pull/330)
- Fix empty content block [#347](https://github.com/rswag/rswag/pull/347)
## [2.3.1] - 2020-04-08
### Fixed
- Remove require for byebug [#295](https://github.com/rswag/rswag/issues/295)
## [2.3.0] - 2020-04-05
### Added
- Support for OpenAPI 3.0 ! [#286](https://github.com/rswag/rswag/pull/286)
- Custom headers in rswag-api [#187](https://github.com/rswag/rswag/pull/187)
- Allow document: false rspec metatag [#255](https://github.com/rswag/rswag/pull/255)
- Add parameterized pattern for spec files [#254](https://github.com/rswag/rswag/pull/254)
- Support Basic Auth on rswag-ui [#167](https://github.com/rswag/rswag/pull/167)
### Changed
- Update swagger-ui version to 3.23.11 [#239](https://github.com/rswag/rswag/pull/239)
- Rails constraint moved from < 6.1 to < 7 [#253](https://github.com/rswag/rswag/pull/253)
- Swaggerize now outputs base RSpec text on completion to avoid silent failures [#293](https://github.com/rswag/rswag/pull/293)
- Update swagger-ui version to 3.28.0
## [2.2.0] - 2019-11-01
### Added
- New swagger_format config option for setting YAML output [#251](https://github.com/rswag/rswag/pull/251)
### Changed
- rswag-api will serve yaml files as yaml [#251](https://github.com/rswag/rswag/pull/251)
## [2.1.1] - 2019-10-18
### Fixed
- Fix incorrect require reference for swagger_generator [#248](https://github.com/rswag/rswag/issues/248)
## [2.1.0] - 2019-10-17
### Added
- New Spec Generator [#75](https://github.com/rswag/rswag/pull/75)
- Support for Options and Trace verbs; You must use a framework that supports this, for Options Rails 6.1+ Rails 6 does not support Trace. [#237](https://github.com/rswag/rswag/pull/75)
### Changed
- Update swagger-ui to 3.18.2 [#240](https://github.com/rswag/rswag/pull/240)
### Security
## [2.0.6] - 2019-10-03
### Added
@ -68,5 +18,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Support for Windows paths [#176](https://github.com/rswag/rswag/pull/176)
### Changed
- Show response body when error code is not expected [#117](https://github.com/rswag/rswag/pull/177)
### Deprecated
### Removed
### Fixed
### Security
## [2.0.5] - 2018-07-10

View File

@ -1,11 +1,5 @@
# Contributing
🎉 Thanks for taking the time to contribute! 🎉
We put forward the philosophy put forward by the [react community](https://reactcommunity.org/) about ownership, responsibility and avoiding burnout.
We also strive to achieve [semantic versioning](https://semver.org/) for this repo.
## Fork, then clone the repo:
```
git clone git@github.com:rswag/rswag.git
@ -30,11 +24,6 @@ cd -
```
## Test
Initialize the rswag-ui repo with assets.
```
ci/build.sh
```
Make sure the tests pass:
```
./ci/test.sh
@ -55,19 +44,6 @@ Push to your fork and [submit a Pull Request][pr].
[pr]: https://github.com/rswag/rswag/compare/
## Updating Swagger UI
Find the latest versions of swagger-ui here:
https://github.com/swagger-api/swagger-ui/releases
Update the swagger-ui-dist version in the rswag-ui dependencies
```
./rswag-ui/package.json
```
Navigate to the rswag-ui folder and run npm install to update the package-lock.json
## Release
(for maintainers)

30
Gemfile
View File

@ -1,12 +1,10 @@
# frozen_string_literal: true
source 'https://rubygems.org'
source "https://rubygems.org"
# Allow the rails version to come from an ENV setting so Travis can test multiple versions.
# See http://www.schneems.com/post/50991826838/testing-against-multiple-rails-versions/
rails_version = ENV['RAILS_VERSION'] || '5.2.4.2'
rails_version = ENV['RAILS_VERSION'] || '5.1.2'
gem 'rails', rails_version.to_s
gem 'rails', "#{rails_version}"
case rails_version.split('.').first
when '3'
@ -25,26 +23,18 @@ end
gem 'rswag-api', path: './rswag-api'
gem 'rswag-ui', path: './rswag-ui'
group :development, :test do
group :test do
gem 'test-unit'
gem 'rspec-rails'
gem 'generator_spec'
gem 'capybara'
gem 'capybara-webkit'
gem 'rswag-specs', path: './rswag-specs'
end
group :test do
gem 'capybara'
gem 'geckodriver-helper'
gem 'generator_spec'
gem 'rspec-rails'
gem 'selenium-webdriver'
gem 'test-unit'
end
group :development do
gem 'rubocop'
end
group :assets do
gem 'mini_racer'
gem 'uglifier'
gem 'therubyracer'
end
gem 'byebug'

View File

@ -1,20 +0,0 @@
Copyright 2015 domaindrivendev
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

514
README.md
View File

@ -1,16 +1,10 @@
rswag
=========
[![Build Status](https://travis-ci.org/rswag/rswag.svg?branch=master)](https://travis-ci.org/rswag/rswag)
[![Maintainability](https://api.codeclimate.com/v1/badges/1175b984edc4610f82ab/maintainability)](https://codeclimate.com/github/rswag/rswag/maintainability)
OpenApi 3.0 and Swagger 2.0 compatible!
Seeking maintainers! Got a pet-bug that needs fixing? Just let us know in your issue/pr that you'd like to step up to help.
Rswag extends rspec-rails "request specs" with a Swagger-based DSL for describing and testing API operations. You describe your API operations with a succinct, intuitive syntax, and it automaticaly runs the tests. Once you have green tests, run a rake task to auto-generate corresponding Swagger files and expose them as YAML or JSON endpoints. Rswag also provides an embedded version of the awesome [swagger-ui](https://github.com/swagger-api/swagger-ui) that's powered by the exposed file. This toolchain makes it seamless to go from integration specs, which youre probably doing in some form already, to living documentation for your API consumers.
Api Rswag creates [Swagger](http://swagger.io) tooling for Rails API's. Generate beautiful API documentation, including a UI to explore and test operations, directly from your rspec integration tests.
[Swagger](http://swagger.io) tooling for Rails API's. Generate beautiful API documentation, including a UI to explore and test operations, directly from your rspec integration tests.
Rswag extends rspec-rails "request specs" with a Swagger-based DSL for describing and testing API operations. You describe your API operations with a succinct, intuitive syntax, and it automaticaly runs the tests. Once you have green tests, run a rake task to auto-generate corresponding Swagger files and expose them as JSON endpoints. Rswag also provides an embedded version of the awesome [swagger-ui](https://github.com/swagger-api/swagger-ui) that's powered by the exposed JSON. This toolchain makes it seamless to go from integration specs, which youre probably doing in some form already, to living documentation for your API consumers.
And that's not all ...
@ -20,50 +14,10 @@ Once you have an API that can describe itself in Swagger, you've opened the trea
|Rswag Version|Swagger (OpenAPI) Spec.|swagger-ui|
|----------|----------|----------|
|[master](https://github.com/rswag/rswag/tree/master)|3.0.3|3.52.5|
|[2.3.0](https://github.com/rswag/rswag/tree/2.3.0)|3.0.3|3.23.11|
|[2.2.0](https://github.com/rswag/rswag/tree/2.2.0)|2.0|3.18.2|
|[master](https://github.com/rswag/rswag/tree/master)|2.0|3.17.3|
|[2.0.5](https://github.com/rswag/rswag/tree/2.0.4)|2.0|3.17.3|
|[1.6.0](https://github.com/rswag/rswag/tree/1.6.0)|2.0|2.2.5|
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
**Table of Contents**
- [rswag](#rswag)
- [Compatibility](#compatibility)
- [Getting Started](#getting-started)
- [The rspec DSL](#the-rspec-dsl)
- [Paths, Operations and Responses](#paths-operations-and-responses)
- [Null Values](#null-values)
- [Support for oneOf, anyOf or AllOf schemas](#support-for-oneof-anyof-or-allof-schemas)
- [Global Metadata](#global-metadata)
- [Supporting multiple versions of API](#supporting-multiple-versions-of-api)
- [Formatting the description literals:](#formatting-the-description-literals)
- [Specifying/Testing API Security](#specifyingtesting-api-security)
- [Configuration & Customization](#configuration--customization)
- [Output Location for Generated Swagger Files](#output-location-for-generated-swagger-files)
- [Input Location for Rspec Tests](#input-location-for-rspec-tests)
- [Referenced Parameters and Schema Definitions](#referenced-parameters-and-schema-definitions)
- [Response headers](#response-headers)
- [Response examples](#response-examples)
- [Enable auto generation examples from responses](#enable-auto-generation-examples-from-responses)
- [Running tests without documenting](#running-tests-without-documenting)
- [rswag helper methods](#rswag-helper-methods)
- [rswag response examples](#rswag-response-examples)
- [Route Prefix for Swagger JSON Endpoints](#route-prefix-for-swagger-json-endpoints)
- [Root Location for Swagger Files](#root-location-for-swagger-files)
- [Dynamic Values for Swagger JSON](#dynamic-values-for-swagger-json)
- [Custom Headers for Swagger Files](#custom-headers-for-swagger-files)
- [Enable Swagger Endpoints for swagger-ui](#enable-swagger-endpoints-for-swagger-ui)
- [Enable Simple Basic Auth for swagger-ui](#enable-simple-basic-auth-for-swagger-ui)
- [Route Prefix for the swagger-ui](#route-prefix-for-the-swagger-ui)
- [Customizing the swagger-ui](#customizing-the-swagger-ui)
- [Serve UI Assets Directly from your Web Server](#serve-ui-assets-directly-from-your-web-server)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Getting Started ##
1. Add this line to your applications _Gemfile_:
@ -72,15 +26,14 @@ Once you have an API that can describe itself in Swagger, you've opened the trea
gem 'rswag'
```
or if you like to avoid loading rspec in other bundler groups load the rswag-specs component separately.
Note: Adding it to the :development group is not strictly necessary, but without it, generators and rake tasks must be preceded by RAILS_ENV=test.
or if you like to avoid loading rspec in other bundler groups.
```ruby
# Gemfile
gem 'rswag-api'
gem 'rswag-ui'
group :development, :test do
group :test do
gem 'rspec-rails'
gem 'rswag-specs'
end
@ -91,17 +44,15 @@ Once you have an API that can describe itself in Swagger, you've opened the trea
```ruby
rails g rswag:install
```
Or run the install generators for each package separately if you installed Rswag as separate gems, as indicated above:
```ruby
rails g rswag:api:install
rails g rswag:ui:install
rails g rswag:api:install rswag:ui:install
RAILS_ENV=test rails g rswag:specs:install
```
3. Create an integration spec to describe and test your API.
There is also a generator which can help get you started `rails generate rspec:swagger API::MyController`
```ruby
# spec/integration/blogs_spec.rb
@ -113,7 +64,7 @@ There is also a generator which can help get you started `rails generate rspec:s
post 'Creates a blog' do
tags 'Blogs'
consumes 'application/json'
consumes 'application/json', 'application/xml'
parameter name: :blog, in: :body, schema: {
type: :object,
properties: {
@ -138,9 +89,9 @@ There is also a generator which can help get you started `rails generate rspec:s
path '/blogs/{id}' do
get 'Retrieves a blog' do
tags 'Blogs', 'Another Tag'
tags 'Blogs'
produces 'application/json', 'application/xml'
parameter name: :id, in: :path, type: :string
parameter name: :id, :in => :path, :type => :string
response '200', 'blog found' do
schema type: :object,
@ -169,20 +120,12 @@ There is also a generator which can help get you started `rails generate rspec:s
end
```
4. Generate the Swagger JSON file(s)
```ruby
rake rswag:specs:swaggerize
```
This common command is also aliased as `rake rswag`.
Or if you installed your gems separately:
```
RAILS_ENV=test rails rswag
```
5. Spin up your app and check out the awesome, auto-generated docs at _/api-docs_!
## The rspec DSL ##
@ -222,7 +165,7 @@ end
### Null Values ###
This library is currently using JSON::Draft4 for validation of response models. Nullable properties can be supported with the non-standard property 'x-nullable' to a definition to allow null/nil values to pass. Or you can add the new standard ```nullable``` property to a definition.
This library is currently using JSON::Draft4 for validation of response models. It does not support null as a value. So you can add the property 'x-nullable' to a definition to allow null/nil values to pass.
```ruby
describe 'Blogs API' do
path '/blogs' do
@ -233,8 +176,8 @@ describe 'Blogs API' do
schema type: :object,
properties: {
id: { type: :integer },
title: { type: :string, nullable: true }, # preferred syntax
content: { type: :string, 'x-nullable': true } # legacy syntax, but still works
title: { type: :string },
content: { type: :string, 'x-nullable': true }
}
....
end
@ -242,45 +185,12 @@ describe 'Blogs API' do
end
end
```
### Support for oneOf, anyOf or AllOf schemas ###
Open API 3.0 now supports more flexible schema validation with the ```oneOf```, ```anyOf``` and ```allOf``` directives. rswag will handle these definitions and validate them properly.
Notice the ```schema``` inside the ```response``` section. Placing a ```schema``` method inside the response will validate (and fail the tests)
if during the integration test run the endpoint response does not match the response schema. This test validation can handle anyOf and allOf as well. See below:
```ruby
path '/blogs/flexible' do
post 'Creates a blog flexible body' do
tags 'Blogs'
description 'Creates a flexible blog from provided data'
operationId 'createFlexibleBlog'
consumes 'application/json'
produces 'application/json'
parameter name: :blog, in: :body, schema: {
oneOf: [
{ '$ref' => '#/components/schemas/blog' },
{ '$ref' => '#/components/schemas/flexible_blog' }
]
}
response '201', 'flexible blog created' do
schema oneOf: [{ '$ref' => '#/components/schemas/blog' }, { '$ref' => '#/components/schemas/flexible_blog' }]
run_test!
end
end
end
```
This automatic schema validation is a powerful feature of rswag.
*Note:* the OAI v3 may be released soon(ish?) and include a nullable property. This may have an effect on the need/use of custom extension to the draft. Do not use this property if you don't understand the implications.
<https://github.com/OAI/OpenAPI-Specification/issues/229#issuecomment-280376087>
### Global Metadata ###
In addition to paths, operations and responses, Swagger also supports global API metadata. When you install rswag, a file called _swagger_helper.rb_ is added to your spec folder. This is where you define one or more Swagger documents and provide global metadata. Again, the format is based on Swagger so most of the global fields supported by the top level ["Swagger" object](http://swagger.io/specification/#swaggerObject) can be provided with each document definition. As an example, you could define a Swagger document for each version of your API and in each case specify a title, version string. In Open API 3.0 the pathing and server definitions have changed a bit [Swagger host/basePath](https://swagger.io/docs/specification/api-host-and-base-path/):
In addition to paths, operations and responses, Swagger also supports global API metadata. When you install rswag, a file called _swagger_helper.rb_ is added to your spec folder. This is where you define one or more Swagger documents and provide global metadata. Again, the format is based on Swagger so most of the global fields supported by the top level ["Swagger" object](http://swagger.io/specification/#swaggerObject) can be provided with each document definition. As an example, you could define a Swagger document for each version of your API and in each case specify a title, version string and URL basePath:
```ruby
# spec/swagger_helper.rb
@ -289,52 +199,34 @@ RSpec.configure do |config|
config.swagger_docs = {
'v1/swagger.json' => {
openapi: '3.0.1',
swagger: '2.0',
info: {
title: 'API V1',
version: 'v1',
description: 'This is the first version of my API'
},
servers: [
{
url: 'https://{defaultHost}',
variables: {
defaultHost: {
default: 'www.example.com'
}
}
}
]
basePath: '/api/v1'
},
'v2/swagger.yaml' => {
openapi: '3.0.1',
'v2/swagger.json' => {
swagger: '2.0',
info: {
title: 'API V2',
version: 'v2',
description: 'This is the second version of my API'
},
servers: [
{
url: 'https://{defaultHost}',
variables: {
defaultHost: {
default: 'www.example.com'
}
}
}
]
basePath: '/api/v2'
}
}
end
```
#### Supporting multiple versions of API ####
#### Supporting multiple versions of API ####
By default, the paths, operations and responses defined in your spec files will be associated with the first Swagger document in _swagger_helper.rb_. If your API has multiple versions, you should be using separate documents to describe each of them. In order to assign a file with a given version of API, you'll need to add the ```swagger_doc``` tag to each spec specifying its target document name:
```ruby
# spec/integration/v2/blogs_spec.rb
describe 'Blogs API', swagger_doc: 'v2/swagger.yaml' do
describe 'Blogs API', swagger_doc: 'v2/swagger.json' do
path '/blogs' do
...
@ -344,14 +236,14 @@ describe 'Blogs API', swagger_doc: 'v2/swagger.yaml' do
end
```
#### Formatting the description literals: ####
Swagger supports the Markdown syntax to format strings. This can be especially handy if you were to provide a long description of a given API version or endpoint. Use [this guide](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) for reference.
#### Formatting the description literals: ####
Swagger supports the Markdown syntax to format strings. This can be especially handy if you were to provide a long description of a given API version or endpoint. Use [this guide](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) for reference.
__NOTE:__ There is one difference between the official Markdown syntax and Swagger interpretation, namely tables. To create a table like this:
| Column1 | Collumn2 |
| ------- | -------- |
| cell1 | cell2 |
| cell1 | cell2 |
you should use the folowing syntax, making sure there are no whitespaces at the start of any of the lines:
@ -365,9 +257,7 @@ you should use the folowing syntax, making sure there are no whitespaces at the
### Specifying/Testing API Security ###
Swagger allows for the specification of different security schemes and their applicability to operations in an API.
To leverage this in rswag, you define the schemes globally in _swagger_helper.rb_ and then use the "security" attribute at the operation level to specify which schemes, if any, are applicable to that operation.
Swagger supports :basic, :bearer, :apiKey and :oauth2 and :openIdConnect scheme types. See [the spec](https://swagger.io/docs/specification/authentication/) for more info, as this underwent major changes between Swagger 2.0 and Open API 3.0
Swagger allows for the specification of different security schemes and their applicability to operations in an API. To leverage this in rswag, you define the schemes globally in _swagger_helper.rb_ and then use the "security" attribute at the operation level to specify which schemes, if any, are applicable to that operation. Swagger supports :basic, :apiKey and :oauth2 scheme types. See [the spec](http://swagger.io/specification/#security-definitions-object-109) for more info.
```ruby
# spec/swagger_helper.rb
@ -376,18 +266,15 @@ RSpec.configure do |config|
config.swagger_docs = {
'v1/swagger.json' => {
... # note the new Open API 3.0 compliant security structure here, under "components"
components: {
securitySchemes: {
basic_auth: {
type: :http,
scheme: :basic
},
api_key: {
type: :apiKey,
name: 'api_key',
in: :query
}
...
securityDefinitions: {
basic: {
type: :basic
},
apiKey: {
type: :apiKey,
name: 'api_key',
in: :query
}
}
}
@ -401,7 +288,7 @@ describe 'Blogs API' do
post 'Creates a blog' do
tags 'Blogs'
security [ basic_auth: [] ]
security [ basic: [] ]
...
response '201', 'blog created' do
@ -416,35 +303,9 @@ describe 'Blogs API' do
end
end
end
# example of documenting an endpoint that handles basic auth and api key based security
describe 'Auth examples API' do
path '/auth-tests/basic-and-api-key' do
post 'Authenticates with basic auth and api key' do
tags 'Auth Tests'
operationId 'testBasicAndApiKey'
security [{ basic_auth: [], api_key: [] }]
response '204', 'Valid credentials' do
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
let(:api_key) { 'foobar' }
run_test!
end
response '401', 'Invalid credentials' do
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
let(:api_key) { 'barfoo' }
run_test!
end
end
end
end
```
__NOTE:__ Depending on the scheme types, you'll be required to assign a corresponding parameter value with each example.
For example, :basic auth is required above and so the :Authorization (header) parameter must be set accordingly
__NOTE:__ Depending on the scheme types, you'll be required to assign a corresponding parameter value with each example. For example, :basic auth is required above and so the :Authorization (header) parameter must be set accordingly
## Configuration & Customization ##
@ -453,7 +314,7 @@ The steps described above will get you up and running with minimal setup. Howeve
|Gem|Description|Added/Updated|
|---------|-----------|-------------|
|__rswag-specs__|Swagger-based DSL for rspec & accompanying rake task for generating Swagger files|_spec/swagger_helper.rb_|
|__rswag-api__ |Rails Engine that exposes your Swagger files as JSON endpoints|_config/initializers/rswag_api.rb, config/routes.rb_|
|__rswag-api__ |Rails Engine that exposes your Swagger files as JSON endpoints|_config/initializers/rswag-api.rb, config/routes.rb_|
|__rswag-ui__ |Rails Engine that includes [swagger-ui](https://github.com/swagger-api/swagger-ui) and powers it from your Swagger endpoints|_config/initializers/rswag-ui.rb, config/routes.rb_|
### Output Location for Generated Swagger Files ###
@ -468,65 +329,32 @@ RSpec.configure do |config|
end
```
__NOTE__: If you do change this, you'll also need to update the rswag_api.rb initializer (assuming you're using rswag-api). More on this later.
### Input Location for Rspec Tests ###
By default, rswag will search for integration tests in _spec/requests_, _spec/api_ and _spec/integration_. If you want to use tests from other locations, provide the PATTERN argument to rake:
```ruby
# search for tests in spec/swagger
rake rswag:specs:swaggerize PATTERN="spec/swagger/**/*_spec.rb"
```
__NOTE__: If you do change this, you'll also need to update the rswag-api.rb initializer (assuming you're using rswag-api). More on this later.
### Referenced Parameters and Schema Definitions ###
Swagger allows you to describe JSON structures inline with your operation descriptions OR as referenced globals.
For example, you might have a standard response structure for all failed operations.
Again, this is a structure that changed since swagger 2.0. Notice the new "schemas" section for these.
Rather than repeating the schema in every operation spec, you can define it globally and provide a reference to it in each spec:
Swagger allows you to describe JSON structures inline with your operation descriptions OR as referenced globals. For example, you might have a standard response structure for all failed operations. Rather than repeating the schema in every operation spec, you can define it globally and provide a reference to it in each spec:
```ruby
# spec/swagger_helper.rb
config.swagger_docs = {
'v1/swagger.json' => {
openapi: '3.0.0',
swagger: '2.0',
info: {
title: 'API V1'
},
components: {
schemas: {
errors_object: {
type: 'object',
properties: {
errors: { '$ref' => '#/components/schemas/errors_map' }
}
},
errors_map: {
type: 'object',
additionalProperties: {
type: 'array',
items: { type: 'string' }
}
},
blog: {
type: 'object',
properties: {
id: { type: 'integer' },
title: { type: 'string' },
content: { type: 'string', nullable: true },
thumbnail: { type: 'string', nullable: true }
},
required: %w[id title]
},
new_blog: {
type: 'object',
properties: {
title: { type: 'string' },
content: { type: 'string', nullable: true },
thumbnail: { type: 'string', format: 'binary', nullable: true }
},
required: %w[title]
definitions: {
errors_object: {
type: 'object',
properties: {
errors: { '$ref' => '#/definitions/errors_map' }
}
},
errors_map: {
type: 'object',
additionalProperties: {
type: 'array',
items: { type: 'string' }
}
}
}
@ -540,10 +368,8 @@ describe 'Blogs API' do
post 'Creates a blog' do
parameter name: :new_blog, in: :body, schema: { '$ref' => '#/components/schemas/new_blog' }
response 422, 'invalid request' do
schema '$ref' => '#/components/schemas/errors_object'
schema '$ref' => '#/definitions/errors_object'
...
end
@ -555,15 +381,14 @@ describe 'Blogs API' do
post 'Creates a comment' do
response 422, 'invalid request' do
schema '$ref' => '#/components/schemas/errors_object'
schema '$ref' => '#/definitions/errors_object'
...
end
```
### Response headers ###
In Rswag, you could use `header` method inside the response block to specify header objects for this response.
Rswag will validate your response headers with those header objects and inject them into the generated swagger file:
In Rswag, you could use `header` method inside the response block to specify header objects for this response. Rswag will validate your response headers with those header objects and inject them into the generated swagger file:
```ruby
# spec/integration/comments_spec.rb
@ -583,7 +408,7 @@ end
### Response examples ###
You can provide custom response examples to the generated swagger file by calling the method `examples` inside the response block:
However, auto generated example responses are now enabled by default in rswag. See below.
```ruby
# spec/integration/blogs_spec.rb
describe 'Blogs API' do
@ -602,192 +427,22 @@ describe 'Blogs API' do
end
```
### Enable auto generation examples from responses ###
### Enable generation examples from responses ###
To enable examples generation from responses add callback above run_test! like:
```
```ruby
after do |example|
example.metadata[:response][:content] = {
'application/json' => {
example: JSON.parse(response.body, symbolize_names: true)
}
}
example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) }
end
```
You need to disable --dry-run option for Rspec > 3
#### Dry Run Option ####
The `--dry-run` option is enabled by default for Rspec 3, but if you need to
disable it you can use the environment varible `SWAGGER_DRY_RUN=0` during the
generation command or add the following to your `config/environments/test.rb`:
Add to application.rb:
```ruby
RSpec.configure do |config|
config.swagger_dry_run = false
end
```
#### Running tests without documenting ####
If you want to use Rswag for testing without adding it to you swagger docs, you can provide the document tag:
```ruby
describe 'Blogs API' do
path '/blogs/{blog_id}' do
get 'Retrieves a blog' do
# documentation is now disabled for this response only
response 200, 'blog found', document: false do
...
```
You can also reenable documentation for specific responses only:
```ruby
# documentation is now disabled
describe 'Blogs API', document: false do
path '/blogs/{blog_id}' do
get 'Retrieves a blog' do
# documentation is reenabled for this response only
response 200, 'blog found', document: true do
...
end
response 401, 'special case' do
...
end
```
##### rswag helper methods #####
<!--
There are some helper methods to help with documenting request bodies.
```ruby
describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
let(:api_key) { 'fake_key' }
path '/blogs' do
post 'Creates a blog' do
tags 'Blogs'
description 'Creates a new blog from provided data'
operationId 'createBlog'
consumes 'application/json'
produces 'application/json'
request_body_json schema: { '$ref' => '#/components/schemas/blog' },
examples: :blog
request_body_text_plain
request_body_xml schema: { '$ref' => '#/components/schemas/blog' }
let(:blog) { { blog: { title: 'foo', content: 'bar' } } }
response '201', 'blog created' do
schema '$ref' => '#/components/schemas/blog'
run_test!
end
response '422', 'invalid request' do
schema '$ref' => '#/components/schemas/errors_object'
let(:blog) { { blog: { title: 'foo' } } }
run_test! do |response|
expect(response.body).to include("can't be blank")
end
end
end
end
end
```
In the above example, we see methods ```request_body_json``` ```request_body_plain``` ```request_body_xml```.
These methods can be used to describe json, plain text and xml body. They are just wrapper methods to setup posting JSON, plain text or xml into your endpoint.
The simplest most common usage is for json formatted body to use the schema: to specify the location of the schema for the request body
and the examples: :blog which will create a named example "blog" under the "requestBody / content / application/json / examples" section.
Again, documenting request response examples changed in Open API 3.0. The example above would generate a swagger.json snippet that looks like this:
```json
...
{"requestBody": {
"required": true,
"content": {
"application/json": {
"examples": {
"blog": { // takes the name from examples: :blog above
"value": { //this is open api 3.0 structure -> https://swagger.io/docs/specification/adding-examples/
"blog": { // here is the actual JSON payload that is submitted to the service, and shows up in swagger UI as an example
"title": "foo",
"content": "bar"
}
}
}
},
"schema": {
"$ref": "#/components/schemas/blog"
}
},
"test/plain": {
"schema": {
"type": "string"
}
},
"application/xml": {
"schema": {
"$ref": "#/components/schemas/blog"
}
}
}
},
}
```
*NOTE:* for this example request body to work in the tests properly, you need to ``let`` a variable named *blog*.
The variable with the matching name (blog in this case) is eval-ed and captured to be placed in the examples section.
This ```let``` value is used in the integration test to run the test AND captured and injected into the requestBody section.
##### rswag response examples #####
In the same way that requestBody examples can be captured and injected into the swagger output, response examples can also be captured.
Using the above example, when the integration test is run - the swagger would include the following snippet providing more useful real world examples
capturing the response from the execution of the integration test. Again 3.0 swagger changed the structure of how these are documented.
```json
... "responses": {
"201": {
"description": "blog created",
"content": {
"application/json": {
"example": {
"id": 1,
"title": "foo",
"content": "bar",
"thumbnail": null
},
"schema": {
"$ref": "#/components/schemas/blog"
}
}
}
},
"422": {
"description": "invalid request",
"content": {
"application/json": {
"example": {
"errors": {
"content": [
"can't be blank"
]
}
},
"schema": {
"$ref": "#/components/schemas/errors_object"
}
}
}
}
}
```
-->
### Route Prefix for Swagger JSON Endpoints ###
The functionality to expose Swagger files, such as those generated by rswag-specs, as JSON endpoints is implemented as a Rails Engine. As with any Engine, you can change it's mount prefix in _routes.rb_:
@ -808,7 +463,7 @@ GET http://<hostname>/your-custom-prefix/v1/swagger.json
### Root Location for Swagger Files ###
You can adjust this in the _rswag_api.rb_ initializer that's installed with __rspec-api__:
You can adjust this in the _rswag-api.rb_ initializer that's installed with __rspec-api__:
```ruby
Rswag::Api.configure do |c|
@ -833,21 +488,6 @@ end
Note how the filter is passed the rack env for the current request. This provides a lot of flexibilty. For example, you can assign the "host" property (as shown) or you could inspect session information or an Authorization header and remove operations based on user permissions.
### Custom Headers for Swagger Files ###
You can specify custom headers for serving your generated Swagger JSON. For example you may want to force a specific charset for the 'Content-Type' header. You can configure a hash of headers to be sent with the request:
```ruby
Rswag::Api.configure do |c|
...
c.swagger_headers = { 'Content-Type' => 'application/json; charset=UTF-8' }
end
```
Take care when overriding Content-Type if you serve both YAML and JSON files as it will no longer switch the Content-Type header correctly.
### Enable Swagger Endpoints for swagger-ui ###
You can update the _rswag-ui.rb_ initializer, installed with rswag-ui, to specify which Swagger endpoints should be available to power the documentation UI. If you're using rswag-api, these should correspond to the Swagger endpoints it exposes. When the UI is rendered, you'll see these listed in a drop-down to the top right of the page:
@ -859,17 +499,6 @@ Rswag::Ui.configure do |c|
end
```
### Enable Simple Basic Auth for swagger-ui
You can also update the _rswag-ui.rb_ initializer, installed with rswag-ui to specify a username and password should you want to keep your documentation private.
```ruby
Rswag::Ui.configure do |c|
c.basic_auth_enabled = true
c.basic_auth_credentials 'username', 'password'
end
```
### Route Prefix for the swagger-ui ###
Similar to rswag-api, you can customize the swagger-ui path by changing it's mount prefix in _routes.rb_:
@ -903,14 +532,3 @@ bundle exec rake rswag:ui:copy_assets[public/api-docs]
```
__NOTE:__: The provided subfolder MUST correspond to the UI mount prefix - "api-docs" by default.
Notes to test swagger output locally with swagger editor
```
docker pull swaggerapi/swagger-editor
```
```
docker run -d -p 80:8080 swaggerapi/swagger-editor
```
This will run the swagger editor in the docker daemon and can be accessed
at ```http://localhost```. From here, you can use the UI to load the generated swagger.json to validate the output.

View File

@ -5,4 +5,4 @@ Example:
rails generate rswag:api:install
This will create:
config/initializers/rswag_api.rb
config/initializers/rswag-api.rb

View File

@ -7,7 +7,7 @@ module Rswag
source_root File.expand_path('../templates', __FILE__)
def add_initializer
template('rswag_api.rb', 'config/initializers/rswag_api.rb')
template('rswag-api.rb', 'config/initializers/rswag-api.rb')
end
def add_routes

View File

@ -1,7 +1,7 @@
module Rswag
module Api
class Configuration
attr_accessor :swagger_root, :swagger_filter, :swagger_headers
attr_accessor :swagger_root, :swagger_filter
def resolve_swagger_root(env)
path_params = env['action_dispatch.request.path_parameters'] || {}

View File

@ -1,10 +1,8 @@
require 'json'
require 'yaml'
require 'rack/mime'
module Rswag
module Api
class Middleware
class Middleware
def initialize(app, config)
@app = app
@ -16,47 +14,24 @@ module Rswag
filename = "#{@config.resolve_swagger_root(env)}/#{path}"
if env['REQUEST_METHOD'] == 'GET' && File.file?(filename)
swagger = parse_file(filename)
swagger = load_json(filename)
@config.swagger_filter.call(swagger, env) unless @config.swagger_filter.nil?
mime = Rack::Mime.mime_type(::File.extname(path), 'text/plain')
headers = { 'Content-Type' => mime }.merge(@config.swagger_headers || {})
body = unload_swagger(filename, swagger)
return [
'200',
headers,
[ body ]
{ 'Content-Type' => 'application/json' },
[ JSON.dump(swagger) ]
]
end
return @app.call(env)
end
private
def parse_file(filename)
if /\.ya?ml$/ === filename
load_yaml(filename)
else
load_json(filename)
end
end
def load_yaml(filename)
YAML.safe_load(File.read(filename))
end
def load_json(filename)
JSON.parse(File.read(filename))
end
def unload_swagger(filename, swagger)
if /\.ya?ml$/ === filename
YAML.dump(swagger)
else
JSON.dump(swagger)
end
end
end
end
end

View File

@ -1,19 +1,17 @@
# frozen_string_literal: true
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
$:.push File.expand_path("../lib", __FILE__)
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = 'rswag-api'
s.name = "rswag-api"
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
s.authors = ['Richie Morris', 'Greg Myers', 'Jay Danielian']
s.email = ['domaindrivendev@gmail.com']
s.homepage = 'https://github.com/rswag/rswag'
s.summary = 'A Rails Engine that exposes OpenAPI (formerly called Swagger) files as JSON endpoints'
s.description = 'Open up your API to the phenomenal OpenAPI ecosystem by exposing OpenAPI files, that describe your service, as JSON endpoints. More about the OpenAPI initiative here: http://spec.openapis.org/'
s.license = 'MIT'
s.authors = ["Richie Morris"]
s.email = ["domaindrivendev@gmail.com"]
s.homepage = "https://github.com/domaindrivendev/rswag"
s.summary = "A Rails Engine that exposes Swagger files as JSON endpoints"
s.description = "Open up your API to the phenomenal Swagger ecosystem by exposing Swagger files, that describe your service, as JSON endpoints"
s.license = "MIT"
s.files = Dir['{lib}/**/*'] + ['MIT-LICENSE', 'Rakefile']
s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile"]
s.add_dependency 'railties', '>= 3.1', '< 7.1'
s.add_dependency 'railties', '>= 3.1', '< 6.1'
end

View File

@ -1,7 +1,6 @@
require 'generator_spec'
require 'generators/rswag/api/install/install_generator'
module Rswag
module Api
@ -18,7 +17,7 @@ module Rswag
end
it 'installs the Rails initializer' do
assert_file('config/initializers/rswag_api.rb')
assert_file('config/initializers/rswag-api.rb')
end
# Don't know how to test this
@ -26,4 +25,3 @@ module Rswag
end
end
end

View File

@ -1,5 +1,5 @@
{
"openapi": "3.0.1",
"swagger": "2.0",
"info": {
"title": "API V1",
"version": "v1"

View File

@ -1,5 +0,0 @@
swagger: '2.0'
info:
title: API V1
version: v1
paths: {}

View File

@ -37,42 +37,6 @@ module Rswag
end
end
context 'when swagger_headers is configured' do
let(:env) { env_defaults.merge('PATH_INFO' => 'v1/swagger.json') }
context 'replacing the default content type header' do
before do
config.swagger_headers = { 'Content-Type' => 'application/json; charset=UTF-8' }
end
it 'returns a 200 status' do
expect(response.length).to eql(3)
expect(response.first).to eql('200')
end
it 'applies the headers to the response' do
expect(response[1]).to include( 'Content-Type' => 'application/json; charset=UTF-8')
end
end
context 'adding an additional header' do
before do
config.swagger_headers = { 'Access-Control-Allow-Origin' => '*' }
end
it 'returns a 200 status' do
expect(response.length).to eql(3)
expect(response.first).to eql('200')
end
it 'applies the headers to the response' do
expect(response[1]).to include( 'Access-Control-Allow-Origin' => '*')
end
it 'keeps the default header' do
expect(response[1]).to include( 'Content-Type' => 'application/json')
end
end
end
context "given a path that doesn't map to any swagger file" do
let(:env) { env_defaults.merge('PATH_INFO' => 'foobar.json') }
before do
@ -97,7 +61,7 @@ module Rswag
it 'locates files at the provided swagger_root' do
expect(response.length).to eql(3)
expect(response[1]).to include( 'Content-Type' => 'application/json')
expect(response[2].join).to include('"openapi":"3.0.1"')
expect(response[2].join).to include('"swagger":"2.0"')
end
end
@ -112,21 +76,6 @@ module Rswag
expect(response[2].join).to include('"host":"tempuri.org"')
end
end
context 'when a path maps to a yaml swagger file' do
let(:env) { env_defaults.merge('PATH_INFO' => 'v1/swagger.yml') }
it 'returns a 200 status' do
expect(response.length).to eql(3)
expect(response.first).to eql('200')
end
it 'returns contents of the swagger file' do
expect(response.length).to eql(3)
expect(response[1]).to include( 'Content-Type' => 'text/yaml')
expect(response[2].join).to include('title: API V1')
end
end
end
end
end

View File

@ -20,4 +20,8 @@ RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_files.include('lib/**/*.rb')
end
Bundler::GemHelper.install_tasks

View File

@ -1,9 +0,0 @@
Description:
This creates an RSpec request spec to define Swagger documentation for a
controller. It will create a test for each of the controller's methods.
Example:
rails generate rspec:swagger V3::AccountsController
This will create:
spec/requests/v3/accounts_spec.rb

View File

@ -1,24 +0,0 @@
# frozen_string_literal: true
require 'rswag/route_parser'
require 'rails/generators'
module Rspec
class SwaggerGenerator < ::Rails::Generators::NamedBase
source_root File.expand_path('templates', __dir__)
def setup
@routes = Rswag::RouteParser.new(controller_path).routes
end
def create_spec_file
template 'spec.rb', File.join('spec', 'requests', "#{controller_path}_spec.rb")
end
private
def controller_path
file_path.chomp('_controller')
end
end
end

View File

@ -1,34 +0,0 @@
require 'swagger_helper'
RSpec.describe '<%= controller_path %>', type: :request do
<% @routes.each do | template, path_item | %>
path '<%= template %>' do
<% unless path_item[:params].empty? -%>
# You'll want to customize the parameter types...
<% path_item[:params].each do |param| -%>
parameter name: '<%= param %>', in: :path, type: :string, description: '<%= param %>'
<% end -%>
<% end -%>
<% path_item[:actions].each do | action, details | %>
<%= action %>('<%= details[:summary] %>') do
response(200, 'successful') do
<% unless path_item[:params].empty? -%>
<% path_item[:params].each do |param| -%>
let(:<%= param %>) { '123' }
<% end -%>
<% end -%>
after do |example|
example.metadata[:response][:content] = {
'application/json' => {
example: JSON.parse(response.body, symbolize_names: true)
}
}
end
run_test!
end
end
<% end -%>
end
<% end -%>
end

View File

@ -1,11 +1,10 @@
# frozen_string_literal: true
require 'rails/generators'
module Rswag
module Specs
class InstallGenerator < Rails::Generators::Base
source_root File.expand_path('templates', __dir__)
source_root File.expand_path('../templates', __FILE__)
def add_swagger_helper
template('swagger_helper.rb', 'spec/swagger_helper.rb')

View File

@ -1,5 +1,3 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.configure do |config|
@ -15,29 +13,13 @@ RSpec.configure do |config|
# document below. You can override this behavior by adding a swagger_doc tag to the
# the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
config.swagger_docs = {
'v1/swagger.yaml' => {
openapi: '3.0.1',
'v1/swagger.json' => {
swagger: '2.0',
info: {
title: 'API V1',
version: 'v1'
},
paths: {},
servers: [
{
url: 'https://{defaultHost}',
variables: {
defaultHost: {
default: 'www.example.com'
}
}
}
]
paths: {}
}
}
# Specify the format of the output Swagger file when running 'rswag:specs:swaggerize'.
# The swagger_docs configuration option has the filename including format in
# the key, this may want to be changed to avoid putting yaml in json files.
# Defaults to json. Accepts ':json' and ':yaml'.
config.swagger_format = :yaml
end

View File

@ -1,60 +0,0 @@
# frozen_string_literal: true
module Rswag
class RouteParser
attr_reader :controller
def initialize(controller)
@controller = controller
end
def routes
::Rails.application.routes.routes.select do |route|
route.defaults[:controller] == controller
end.each_with_object({}) do |route, tree|
path = path_from(route)
verb = verb_from(route)
tree[path] ||= { params: params_from(route), actions: {} }
tree[path][:actions][verb] = { summary: summary_from(route) }
tree
end
end
private
def path_from(route)
route.path.spec.to_s
.chomp('(.:format)') # Ignore any format suffix
.gsub(/:([^\/.?]+)/, '{\1}') # Convert :id to {id}
end
def verb_from(route)
verb = route.verb
if verb.is_a? String
verb.downcase
else
verb.source.gsub(/[$^]/, '').downcase
end
end
def summary_from(route)
verb = route.requirements[:action]
noun = route.requirements[:controller].split('/').last.singularize
# Apply a few customizations to make things more readable
case verb
when 'index'
verb = 'list'
noun = noun.pluralize
when 'destroy'
verb = 'delete'
end
"#{verb} #{noun}"
end
def params_from(route)
route.segments - ['format']
end
end
end

View File

@ -1,5 +1,3 @@
# frozen_string_literal: true
require 'rspec/core'
require 'rswag/specs/example_group_helpers'
require 'rswag/specs/example_helpers'
@ -8,12 +6,12 @@ require 'rswag/specs/railtie' if defined?(Rails::Railtie)
module Rswag
module Specs
# Extend RSpec with a swagger-based DSL
::RSpec.configure do |c|
c.add_setting :swagger_root
c.add_setting :swagger_docs
c.add_setting :swagger_dry_run
c.add_setting :swagger_format
c.extend ExampleGroupHelpers, type: :request
c.include ExampleHelpers, type: :request
end

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true
module Rswag
module Specs
class Configuration
def initialize(rspec_config)
@rspec_config = rspec_config
end
@ -12,7 +12,6 @@ module Rswag
if @rspec_config.swagger_root.nil?
raise ConfigurationError, 'No swagger_root provided. See swagger_helper.rb'
end
@rspec_config.swagger_root
end
end
@ -22,39 +21,21 @@ module Rswag
if @rspec_config.swagger_docs.nil? || @rspec_config.swagger_docs.empty?
raise ConfigurationError, 'No swagger_docs defined. See swagger_helper.rb'
end
@rspec_config.swagger_docs
end
end
def swagger_dry_run
return @swagger_dry_run if defined? @swagger_dry_run
if ENV.key?('SWAGGER_DRY_RUN')
@rspec_config.swagger_dry_run = ENV['SWAGGER_DRY_RUN'] == '1'
end
@swagger_dry_run = @rspec_config.swagger_dry_run.nil? || @rspec_config.swagger_dry_run
end
def swagger_format
@swagger_format ||= begin
@rspec_config.swagger_format = :json if @rspec_config.swagger_format.nil? || @rspec_config.swagger_format.empty?
raise ConfigurationError, "Unknown swagger_format '#{@rspec_config.swagger_format}'" unless [:json, :yaml].include?(@rspec_config.swagger_format)
@rspec_config.swagger_format
@swagger_dry_run ||= begin
@rspec_config.swagger_dry_run.nil? || @rspec_config.swagger_dry_run
end
end
def get_swagger_doc(name)
return swagger_docs.values.first if name.nil?
raise ConfigurationError, "Unknown swagger_doc '#{name}'" unless swagger_docs[name]
swagger_docs[name]
end
def get_swagger_doc_version(name)
doc = get_swagger_doc(name)
doc[:openapi] || doc[:swagger]
end
end
class ConfigurationError < StandardError; end

View File

@ -1,21 +1,20 @@
# frozen_string_literal: true
module Rswag
module Specs
module ExampleGroupHelpers
def path(template, metadata = {}, &block)
def path(template, metadata={}, &block)
metadata[:path_item] = { template: template }
describe(template, metadata, &block)
end
[:get, :post, :patch, :put, :delete, :head, :options, :trace].each do |verb|
[ :get, :post, :patch, :put, :delete, :head ].each do |verb|
define_method(verb) do |summary, &block|
api_metadata = { operation: { verb: verb, summary: summary } }
describe(verb, api_metadata, &block)
end
end
[:operationId, :deprecated, :security].each do |attr_name|
[ :operationId, :deprecated, :security ].each do |attr_name|
define_method(attr_name) do |value|
metadata[:operation][attr_name] = value
end
@ -24,14 +23,13 @@ module Rswag
# NOTE: 'description' requires special treatment because ExampleGroup already
# defines a method with that name. Provide an override that supports the existing
# functionality while also setting the appropriate metadata if applicable
def description(value = nil)
def description(value=nil)
return super() if value.nil?
metadata[:operation][:description] = value
end
# These are array properties - note the splat operator
[:tags, :consumes, :produces, :schemes].each do |attr_name|
[ :tags, :consumes, :produces, :schemes ].each do |attr_name|
define_method(attr_name) do |*value|
metadata[:operation][attr_name] = value
end
@ -42,7 +40,7 @@ module Rswag
attributes[:required] = true
end
if metadata.key?(:operation)
if metadata.has_key?(:operation)
metadata[:operation][:parameters] ||= []
metadata[:operation][:parameters] << attributes
else
@ -51,7 +49,7 @@ module Rswag
end
end
def response(code, description, metadata = {}, &block)
def response(code, description, metadata={}, &block)
metadata[:response] = { code: code, description: description }
context(description, metadata, &block)
end
@ -62,7 +60,6 @@ module Rswag
def header(name, attributes)
metadata[:response][:headers] ||= {}
metadata[:response][:headers][name] = attributes
end
@ -71,11 +68,7 @@ module Rswag
# rspec-core ExampleGroup
def examples(example = nil)
return super() if example.nil?
metadata[:response][:content] =
example.each_with_object({}) do |(mime, example_object), memo|
memo[mime] = { example: example_object }
end
metadata[:response][:examples] = example
end
def run_test!(&block)

View File

@ -1,11 +1,10 @@
# frozen_string_literal: true
require 'rswag/specs/request_factory'
require 'rswag/specs/response_validator'
module Rswag
module Specs
module ExampleHelpers
def submit_request(metadata)
request = RequestFactory.new.build_request(metadata, self)
@ -20,8 +19,10 @@ module Rswag
send(
request[:verb],
request[:path],
params: request[:payload],
headers: request[:headers]
{
params: request[:payload],
headers: request[:headers]
}
)
end
end

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true
require 'json-schema'
module Rswag
module Specs
class ExtendedSchema < JSON::Schema::Draft4
def initialize
super
@attributes['type'] = ExtendedTypeAttribute
@ -14,9 +13,9 @@ module Rswag
end
class ExtendedTypeAttribute < JSON::Schema::TypeV4Attribute
def self.validate(current_schema, data, fragments, processor, validator, options = {})
return if data.nil? && (current_schema.schema['nullable'] == true || current_schema.schema['x-nullable'] == true)
def self.validate(current_schema, data, fragments, processor, validator, options={})
return if data.nil? && current_schema.schema['x-nullable'] == true
super
end
end

View File

@ -1,14 +1,9 @@
# frozen_string_literal: true
module Rswag
module Specs
class Railtie < ::Rails::Railtie
rake_tasks do
load File.expand_path('../../tasks/rswag-specs_tasks.rake', __dir__)
end
generators do
require 'generators/rspec/swagger_generator.rb'
rake_tasks do
load File.expand_path('../../../tasks/rswag-specs_tasks.rake', __FILE__)
end
end
end

View File

@ -1,5 +1,3 @@
# frozen_string_literal: true
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/conversions'
require 'json'
@ -7,6 +5,7 @@ require 'json'
module Rswag
module Specs
class RequestFactory
def initialize(config = ::Rswag::Specs.config)
@config = config
end
@ -39,8 +38,8 @@ module Rswag
def derive_security_params(metadata, swagger_doc)
requirements = metadata[:operation][:security] || swagger_doc[:security] || []
scheme_names = requirements.flat_map(&:keys)
schemes = security_version(scheme_names, swagger_doc)
scheme_names = requirements.flat_map { |r| r.keys }
schemes = (swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values
schemes.map do |scheme|
param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }
@ -48,78 +47,35 @@ module Rswag
end
end
def security_version(scheme_names, swagger_doc)
if doc_version(swagger_doc).start_with?('2')
(swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values
else # Openapi3
if swagger_doc.key?(:securityDefinitions)
ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: securityDefinitions is replaced in OpenAPI3! Rename to components/securitySchemes (in swagger_helper.rb)')
swagger_doc[:components] ||= { securitySchemes: swagger_doc[:securityDefinitions] }
swagger_doc.delete(:securityDefinitions)
end
components = swagger_doc[:components] || {}
(components[:securitySchemes] || {}).slice(*scheme_names).values
end
end
def resolve_parameter(ref, swagger_doc)
key = key_version(ref, swagger_doc)
definitions = definition_version(swagger_doc)
key = ref.sub('#/parameters/', '').to_sym
definitions = swagger_doc[:parameters]
raise "Referenced parameter '#{ref}' must be defined" unless definitions && definitions[key]
definitions[key]
end
def key_version(ref, swagger_doc)
if doc_version(swagger_doc).start_with?('2')
ref.sub('#/parameters/', '').to_sym
else # Openapi3
if ref.start_with?('#/parameters/')
ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: #/parameters/ refs are replaced in OpenAPI3! Rename to #/components/parameters/')
ref.sub('#/parameters/', '').to_sym
else
ref.sub('#/components/parameters/', '').to_sym
end
end
end
def definition_version(swagger_doc)
if doc_version(swagger_doc).start_with?('2')
swagger_doc[:parameters]
else # Openapi3
if swagger_doc.key?(:parameters)
ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: parameters is replaced in OpenAPI3! Rename to components/parameters (in swagger_helper.rb)')
swagger_doc[:parameters]
else
components = swagger_doc[:components] || {}
components[:parameters]
end
end
end
def add_verb(request, metadata)
def add_verb(request, metadata)
request[:verb] = metadata[:operation][:verb]
end
def add_path(request, metadata, swagger_doc, parameters, example)
template = (swagger_doc[:basePath] || '') + metadata[:path_item][:template]
request[:path] = template.tap do |path_template|
request[:path] = template.tap do |template|
parameters.select { |p| p[:in] == :path }.each do |p|
path_template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
end
parameters.select { |p| p[:in] == :query }.each_with_index do |p, i|
path_template.concat(i.zero? ? '?' : '&')
path_template.concat(build_query_string_part(p, example.send(p[:name])))
template.concat(i == 0 ? '?' : '&')
template.concat(build_query_string_part(p, example.send(p[:name])))
end
end
end
def build_query_string_part(param, value)
name = param[:name]
type = param[:type] || param.dig(:schema, :type)
return "#{name}=#{value}" unless type&.to_sym == :array
return "#{name}=#{value.to_s}" unless param[:type].to_sym == :array
case param[:collectionFormat]
when :ssv
@ -138,43 +94,43 @@ module Rswag
def add_headers(request, metadata, swagger_doc, parameters, example)
tuples = parameters
.select { |p| p[:in] == :header }
.map { |p| [p[:name], example.send(p[:name]).to_s] }
.map { |p| [ p[:name], example.send(p[:name]).to_s ] }
# Accept header
produces = metadata[:operation][:produces] || swagger_doc[:produces]
if produces
accept = example.respond_to?(:Accept) ? example.send(:Accept) : produces.first
tuples << ['Accept', accept]
accept = example.respond_to?(:'Accept') ? example.send(:'Accept') : produces.first
tuples << [ 'Accept', accept ]
end
# Content-Type header
consumes = metadata[:operation][:consumes] || swagger_doc[:consumes]
consumes = metadata[:operation][:consumes] || swagger_doc[:consumes]
if consumes
content_type = example.respond_to?(:'Content-Type') ? example.send(:'Content-Type') : consumes.first
tuples << ['Content-Type', content_type]
tuples << [ 'Content-Type', content_type ]
end
# Rails test infrastructure requires rackified headers
rackified_tuples = tuples.map do |pair|
[
case pair[0]
when 'Accept' then 'HTTP_ACCEPT'
when 'Content-Type' then 'CONTENT_TYPE'
when 'Authorization' then 'HTTP_AUTHORIZATION'
else pair[0]
when 'Accept' then 'HTTP_ACCEPT'
when 'Content-Type' then 'CONTENT_TYPE'
when 'Authorization' then 'HTTP_AUTHORIZATION'
else pair[0]
end,
pair[1]
]
end
request[:headers] = Hash[rackified_tuples]
request[:headers] = Hash[ rackified_tuples ]
end
def add_payload(request, parameters, example)
content_type = request[:headers]['CONTENT_TYPE']
return if content_type.nil?
if ['application/x-www-form-urlencoded', 'multipart/form-data'].include?(content_type)
if [ 'application/x-www-form-urlencoded', 'multipart/form-data' ].include?(content_type)
request[:payload] = build_form_payload(parameters, example)
else
request[:payload] = build_json_payload(parameters, example)
@ -188,41 +144,13 @@ module Rswag
# PROS: simple to implement, CONS: serialization/deserialization is bypassed in test
tuples = parameters
.select { |p| p[:in] == :formData }
.map { |p| [p[:name], example.send(p[:name])] }
Hash[tuples]
.map { |p| [ p[:name], example.send(p[:name]) ] }
Hash[ tuples ]
end
def build_json_payload(parameters, example)
body_param = parameters.select { |p| p[:in] == :body }.first
return nil unless body_param
raise(MissingParameterError, body_param[:name]) unless example.respond_to?(body_param[:name])
example.send(body_param[:name]).to_json
end
def doc_version(doc)
doc[:openapi] || doc[:swagger] || '3'
end
end
class MissingParameterError < StandardError
attr_reader :body_param
def initialize(body_param)
@body_param = body_param
end
def message
<<~MSG
Missing parameter '#{body_param}'
Please check your spec. It looks like you defined a body parameter,
but did not declare usage via let. Try adding:
let(:#{body_param}) {}
MSG
body_param ? example.send(body_param[:name]).to_json : nil
end
end
end

View File

@ -1,5 +1,3 @@
# frozen_string_literal: true
require 'active_support/core_ext/hash/slice'
require 'json-schema'
require 'json'
@ -8,6 +6,7 @@ require 'rswag/specs/extended_schema'
module Rswag
module Specs
class ResponseValidator
def initialize(config = ::Rswag::Specs.config)
@config = config
end
@ -26,8 +25,8 @@ module Rswag
expected = metadata[:response][:code].to_s
if response.code != expected
raise UnexpectedResponse,
"Expected response code '#{response.code}' to match '#{expected}'\n" \
"Response body: #{response.body}"
"Expected response code '#{response.code}' to match '#{expected}'\n" \
"Response body: #{response.body}"
end
end
@ -42,33 +41,11 @@ module Rswag
response_schema = metadata[:response][:schema]
return if response_schema.nil?
version = @config.get_swagger_doc_version(metadata[:swagger_doc])
schemas = definitions_or_component_schemas(swagger_doc, version)
validation_schema = response_schema
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
.merge(schemas)
.merge(swagger_doc.slice(:definitions))
errors = JSON::Validator.fully_validate(validation_schema, body)
return unless errors.any?
raise UnexpectedResponse,
"Expected response body to match schema: #{errors[0]}\n" \
"Response body: #{JSON.pretty_generate(JSON.parse(body))}"
end
def definitions_or_component_schemas(swagger_doc, version)
if version.start_with?('2')
swagger_doc.slice(:definitions)
else # Openapi3
if swagger_doc.key?(:definitions)
ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: definitions is replaced in OpenAPI3! Rename to components/schemas (in swagger_helper.rb)')
swagger_doc.slice(:definitions)
else
components = swagger_doc[:components] || {}
{ components: components }
end
end
raise UnexpectedResponse, "Expected response body to match schema: #{errors[0]}" if errors.any?
end
end

View File

@ -1,12 +1,9 @@
# frozen_string_literal: true
require 'active_support/core_ext/hash/deep_merge'
require 'rspec/core/formatters/base_text_formatter'
require 'swagger_helper'
module Rswag
module Specs
class SwaggerFormatter < ::RSpec::Core::Formatters::BaseTextFormatter
class SwaggerFormatter
# NOTE: rspec 2.x support
if RSPEC_VERSION > 2
@ -22,62 +19,25 @@ module Rswag
def example_group_finished(notification)
# NOTE: rspec 2.x support
metadata = if RSPEC_VERSION > 2
notification.group.metadata
if RSPEC_VERSION > 2
metadata = notification.group.metadata
else
notification.metadata
metadata = notification.metadata
end
# !metadata[:document] won't work, since nil means we should generate
# docs.
return if metadata[:document] == false
return unless metadata.key?(:response)
return unless metadata.has_key?(:response)
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
unless doc_version(swagger_doc).start_with?('2')
# This is called multiple times per file!
# metadata[:operation] is also re-used between examples within file
# therefore be careful NOT to modify its content here.
upgrade_request_type!(metadata)
upgrade_servers!(swagger_doc)
upgrade_oauth!(swagger_doc)
upgrade_response_produces!(swagger_doc, metadata)
end
swagger_doc.deep_merge!(metadata_to_swagger(metadata))
end
def stop(_notification = nil)
def stop(notification=nil)
@config.swagger_docs.each do |url_path, doc|
unless doc_version(doc).start_with?('2')
doc[:paths]&.each_pair do |_k, v|
v.each_pair do |_verb, value|
is_hash = value.is_a?(Hash)
if is_hash && value.dig(:parameters)
schema_param = value.dig(:parameters)&.find { |p| (p[:in] == :body || p[:in] == :formData) && p[:schema] }
mime_list = value.dig(:consumes) || doc[:consumes]
if value && schema_param && mime_list
value[:requestBody] = { content: {} } unless value.dig(:requestBody, :content)
value[:requestBody][:required] = true if schema_param[:required]
mime_list.each do |mime|
value[:requestBody][:content][mime] = { schema: schema_param[:schema] }
end
end
value[:parameters].reject! { |p| p[:in] == :body || p[:in] == :formData }
end
remove_invalid_operation_keys!(value)
end
end
end
file_path = File.join(@config.swagger_root, url_path)
dirname = File.dirname(file_path)
FileUtils.mkdir_p dirname unless File.exist?(dirname)
FileUtils.mkdir_p dirname unless File.exists?(dirname)
File.open(file_path, 'w') do |file|
file.write(pretty_generate(doc))
file.write(JSON.pretty_generate(doc))
end
@output.puts "Swagger doc generated at #{file_path}"
@ -86,119 +46,22 @@ module Rswag
private
def pretty_generate(doc)
if @config.swagger_format == :yaml
clean_doc = yaml_prepare(doc)
YAML.dump(clean_doc)
else # config errors are thrown in 'def swagger_format', no throw needed here
JSON.pretty_generate(doc)
end
end
def yaml_prepare(doc)
json_doc = JSON.pretty_generate(doc)
JSON.parse(json_doc)
end
def metadata_to_swagger(metadata)
response_code = metadata[:response][:code]
response = metadata[:response].reject { |k, _v| k == :code }
response = metadata[:response].reject { |k,v| k == :code }
verb = metadata[:operation][:verb]
operation = metadata[:operation]
.reject { |k, _v| k == :verb }
.reject { |k,v| k == :verb }
.merge(responses: { response_code => response })
path_template = metadata[:path_item][:template]
path_item = metadata[:path_item]
.reject { |k, _v| k == :template }
.reject { |k,v| k == :template }
.merge(verb => operation)
{ paths: { path_template => path_item } }
end
def doc_version(doc)
doc[:openapi] || doc[:swagger] || '3'
end
def upgrade_response_produces!(swagger_doc, metadata)
# Accept header
mime_list = Array(metadata[:operation][:produces] || swagger_doc[:produces])
target_node = metadata[:response]
upgrade_content!(mime_list, target_node)
metadata[:response].delete(:schema)
end
def upgrade_content!(mime_list, target_node)
schema = target_node[:schema]
return if mime_list.empty? || schema.nil?
target_node[:content] ||= {}
mime_list.each do |mime_type|
# TODO upgrade to have content-type specific schema
(target_node[:content][mime_type] ||= {}).merge!(schema: schema)
end
end
def upgrade_request_type!(metadata)
# No deprecation here as it seems valid to allow type as a shorthand
operation_nodes = metadata[:operation][:parameters] || []
path_nodes = metadata[:path_item][:parameters] || []
header_node = metadata[:response][:headers] || {}
(operation_nodes + path_nodes + [header_node]).each do |node|
if node && node[:type] && node[:schema].nil?
node[:schema] = { type: node[:type] }
node.delete(:type)
end
end
end
def upgrade_servers!(swagger_doc)
return unless swagger_doc[:servers].nil? && swagger_doc.key?(:schemes)
ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: schemes, host, and basePath are replaced in OpenAPI3! Rename to array of servers[{url}] (in swagger_helper.rb)')
swagger_doc[:servers] = { urls: [] }
swagger_doc[:schemes].each do |scheme|
swagger_doc[:servers][:urls] << scheme + '://' + swagger_doc[:host] + swagger_doc[:basePath]
end
swagger_doc.delete(:schemes)
swagger_doc.delete(:host)
swagger_doc.delete(:basePath)
end
def upgrade_oauth!(swagger_doc)
# find flow in securitySchemes (securityDefinitions will have been re-written)
schemes = swagger_doc.dig(:components, :securitySchemes)
return unless schemes&.any? { |_k, v| v.key?(:flow) }
schemes.each do |name, v|
next unless v.key?(:flow)
ActiveSupport::Deprecation.warn("Rswag::Specs: WARNING: securityDefinitions flow is replaced in OpenAPI3! Rename to components/securitySchemes/#{name}/flows[] (in swagger_helper.rb)")
flow = swagger_doc[:components][:securitySchemes][name].delete(:flow).to_s
if flow == 'accessCode'
ActiveSupport::Deprecation.warn("Rswag::Specs: WARNING: securityDefinitions accessCode is replaced in OpenAPI3! Rename to clientCredentials (in swagger_helper.rb)")
flow = 'authorizationCode'
end
if flow == 'application'
ActiveSupport::Deprecation.warn("Rswag::Specs: WARNING: securityDefinitions application is replaced in OpenAPI3! Rename to authorizationCode (in swagger_helper.rb)")
flow = 'clientCredentials'
end
flow_elements = swagger_doc[:components][:securitySchemes][name].except(:type).each_with_object({}) do |(k, _v), a|
a[k] = swagger_doc[:components][:securitySchemes][name].delete(k)
end
swagger_doc[:components][:securitySchemes][name].merge!(flows: { flow => flow_elements })
end
end
def remove_invalid_operation_keys!(value)
is_hash = value.is_a?(Hash)
value.delete(:consumes) if is_hash && value.dig(:consumes)
value.delete(:produces) if is_hash && value.dig(:produces)
end
end
end
end

View File

@ -1,24 +1,18 @@
# frozen_string_literal: true
require 'rspec/core/rake_task'
namespace :rswag do
namespace :specs do
desc 'Generate Swagger JSON files from integration specs'
RSpec::Core::RakeTask.new('swaggerize') do |t|
t.pattern = ENV.fetch(
'PATTERN',
'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
)
t.pattern = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
# NOTE: rspec 2.x support
if Rswag::Specs::RSPEC_VERSION > 2 && Rswag::Specs.config.swagger_dry_run
t.rspec_opts = ['--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined']
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined' ]
else
t.rspec_opts = ['--format Rswag::Specs::SwaggerFormatter', '--order defined']
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--order defined' ]
end
end
end
end
task rswag: ['rswag:specs:swaggerize']

View File

@ -1,21 +1,19 @@
# frozen_string_literal: true
$LOAD_PATH.push File.expand_path('lib', __dir__)
$:.push File.expand_path("../lib", __FILE__)
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = 'rswag-specs'
s.name = "rswag-specs"
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
s.authors = ['Richie Morris', 'Greg Myers', 'Jay Danielian']
s.email = ['domaindrivendev@gmail.com']
s.homepage = 'https://github.com/rswag/rswag'
s.summary = 'An OpenAPI-based (formerly called Swagger) DSL for rspec-rails & accompanying rake task for generating OpenAPI specification files'
s.description = 'Simplify API integration testing with a succinct rspec DSL and generate OpenAPI specification files directly from your rspecs. More about the OpenAPI initiative here: http://spec.openapis.org/'
s.license = 'MIT'
s.authors = ["Richie Morris"]
s.email = ["domaindrivendev@gmail.com"]
s.homepage = "https://github.com/domaindrivendev/rswag"
s.summary = "A Swagger-based DSL for rspec-rails & accompanying rake task for generating Swagger files"
s.description = "Simplify API integration testing with a succinct rspec DSL and generate Swagger files directly from your rspecs"
s.license = "MIT"
s.files = Dir['{lib}/**/*'] + ['MIT-LICENSE', 'Rakefile']
s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
s.add_dependency 'activesupport', '>= 3.1', '< 7.1'
s.add_dependency 'railties', '>= 3.1', '< 7.1'
s.add_dependency 'activesupport', '>= 3.1', '< 6.1'
s.add_dependency 'railties', '>= 3.1', '< 6.1'
s.add_dependency 'json-schema', '~> 2.2'
end

View File

@ -1,45 +0,0 @@
# frozen_string_literal: true
require 'generator_spec'
require 'generators/rspec/swagger_generator'
require 'tmpdir'
module Rspec
describe SwaggerGenerator do
include GeneratorSpec::TestCase
destination Dir.mktmpdir
before(:all) do
prepare_destination
fixtures_dir = File.expand_path('fixtures', __dir__)
FileUtils.cp_r("#{fixtures_dir}/spec", destination_root)
end
after(:all) do
end
it 'installs the swagger_helper for rspec' do
allow_any_instance_of(Rswag::RouteParser).to receive(:routes).and_return(fake_routes)
run_generator ['Posts::CommentsController']
assert_file('spec/requests/posts/comments_spec.rb') do |content|
assert_match(/parameter name: 'post_id', in: :path, type: :string/, content)
assert_match(/patch\('update_comments comment'\)/, content)
end
end
private
def fake_routes
{
'/posts/{post_id}/comments/{id}' => {
params: ['post_id', 'id'],
actions: {
'get' => { summary: 'show comment' },
'patch' => { summary: 'update_comments comment' }
}
}
}
end
end
end

View File

@ -1,17 +1,16 @@
# frozen_string_literal: true
require 'generator_spec'
require 'generators/rswag/specs/install/install_generator'
module Rswag
module Specs
RSpec.describe InstallGenerator do
describe InstallGenerator do
include GeneratorSpec::TestCase
destination File.expand_path('tmp', __dir__)
destination File.expand_path('../tmp', __FILE__)
before(:all) do
prepare_destination
fixtures_dir = File.expand_path('fixtures', __dir__)
fixtures_dir = File.expand_path('../fixtures', __FILE__)
FileUtils.cp_r("#{fixtures_dir}/spec", destination_root)
run_generator

View File

@ -1,50 +0,0 @@
# frozen_string_literal: true
RSpec.describe Rswag::RouteParser do
describe "#routes" do
let(:controller) { "api/v1/posts" }
subject { described_class.new(controller) }
let(:routes) do
[
double(
defaults: {
controller: controller
},
path: double(
spec: double(
to_s: "/api/v1/posts/:id(.:format)"
)
),
verb: "GET",
requirements: {
action: "show",
controller: "api/v1/posts"
},
segments: ["id", "format"]
)
]
end
let(:expectation) do
{
"/api/v1/posts/{id}" => {
params: ["id"],
actions: {
"get" => {
summary: "show post"
}
}
}
}
end
before do
allow(::Rails).to receive_message_chain("application.routes.routes") { routes }
end
it "returns correct routes" do
expect(subject.routes).to eq(expectation)
end
end
end

View File

@ -1,15 +1,12 @@
# frozen_string_literal: true
require 'rswag/specs/configuration'
module Rswag
module Specs
RSpec.describe Configuration do
describe Configuration do
subject { described_class.new(rspec_config) }
let(:rspec_config) do
OpenStruct.new(swagger_root: swagger_root, swagger_docs: swagger_docs, swagger_format: swagger_format)
end
let(:rspec_config) { OpenStruct.new(swagger_root: swagger_root, swagger_docs: swagger_docs) }
let(:swagger_root) { 'foobar' }
let(:swagger_docs) do
{
@ -17,7 +14,6 @@ module Rswag
'v2/swagger.json' => { info: { title: 'v2' } }
}
end
let(:swagger_format) { :yaml }
describe '#swagger_root' do
let(:response) { subject.swagger_root }
@ -50,26 +46,6 @@ module Rswag
end
end
describe '#swagger_format' do
let(:response) { subject.swagger_format }
context 'provided in rspec config' do
it { expect(response).to be_an_instance_of(Symbol) }
end
context 'unsupported format provided' do
let(:swagger_format) { :xml }
it { expect { response }.to raise_error ConfigurationError }
end
context 'not provided' do
let(:swagger_format) { nil }
it { expect(response).to eq(:json) }
end
end
describe '#get_swagger_doc(tag=nil)' do
let(:swagger_doc) { subject.get_swagger_doc(tag) }

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true
require 'rswag/specs/example_group_helpers'
module Rswag
module Specs
RSpec.describe ExampleGroupHelpers do
describe ExampleGroupHelpers do
subject { double('example_group') }
before do
@ -25,7 +24,7 @@ module Rswag
end
end
describe '#get|post|patch|put|delete|head|options|trace(verb, summary)' do
describe '#get|post|patch|put|delete|head(verb, summary)' do
before { subject.post('Creates a blog') }
it "delegates to 'describe' with 'operation' metadata" do
@ -35,6 +34,31 @@ module Rswag
end
end
describe '#tags|description|operationId|consumes|produces|schemes|deprecated(value)' do
before do
subject.tags('Blogs', 'Admin')
subject.description('Some description')
subject.operationId('createBlog')
subject.consumes('application/json', 'application/xml')
subject.produces('application/json', 'application/xml')
subject.schemes('http', 'https')
subject.deprecated(true)
end
let(:api_metadata) { { operation: {} } }
it "adds to the 'operation' metadata" do
expect(api_metadata[:operation]).to match(
tags: [ 'Blogs', 'Admin' ],
description: 'Some description',
operationId: 'createBlog',
consumes: [ 'application/json', 'application/xml' ],
produces: [ 'application/json', 'application/xml' ],
schemes: [ 'http', 'https' ],
deprecated: true
)
end
end
describe '#tags|description|operationId|consumes|produces|schemes|deprecated|security(value)' do
before do
subject.tags('Blogs', 'Admin')
@ -50,12 +74,12 @@ module Rswag
it "adds to the 'operation' metadata" do
expect(api_metadata[:operation]).to match(
tags: ['Blogs', 'Admin'],
tags: [ 'Blogs', 'Admin' ],
description: 'Some description',
operationId: 'createBlog',
consumes: ['application/json', 'application/xml'],
produces: ['application/json', 'application/xml'],
schemes: ['http', 'https'],
consumes: [ 'application/json', 'application/xml' ],
produces: [ 'application/json', 'application/xml' ],
schemes: [ 'http', 'https' ],
deprecated: true,
security: { api_key: [] }
)
@ -63,13 +87,14 @@ module Rswag
end
describe '#parameter(attributes)' do
context "when called at the 'path' level" do
before { subject.parameter(name: :blog, in: :body, schema: { type: 'object' }) }
let(:api_metadata) { { path_item: {} } } # i.e. operation not defined yet
it "adds to the 'path_item parameters' metadata" do
expect(api_metadata[:path_item][:parameters]).to match(
[name: :blog, in: :body, schema: { type: 'object' }]
[ name: :blog, in: :body, schema: { type: 'object' } ]
)
end
end
@ -80,7 +105,7 @@ module Rswag
it "adds to the 'operation parameters' metadata" do
expect(api_metadata[:operation][:parameters]).to match(
[name: :blog, in: :body, schema: { type: 'object' }]
[ name: :blog, in: :body, schema: { type: 'object' } ]
)
end
end
@ -91,7 +116,7 @@ module Rswag
it "automatically sets the 'required' flag" do
expect(api_metadata[:operation][:parameters]).to match(
[name: :id, in: :path, required: true]
[ name: :id, in: :path, required: true ]
)
end
end
@ -101,7 +126,7 @@ module Rswag
let(:api_metadata) { { operation: {} } }
it "does not require the 'in' parameter key" do
expect(api_metadata[:operation][:parameters]).to match([name: :id])
expect(api_metadata[:operation][:parameters]).to match([ name: :id ])
end
end
end
@ -137,10 +162,9 @@ module Rswag
end
describe '#examples(example)' do
let(:mime) { 'application/json' }
let(:json_example) do
{
mime => {
'application/json' => {
foo: 'bar'
}
}
@ -152,11 +176,7 @@ module Rswag
end
it "adds to the 'response examples' metadata" do
expect(api_metadata[:response][:content]).to match(
mime => {
example: json_example[mime]
}
)
expect(api_metadata[:response][:examples]).to eq(json_example)
end
end
end

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true
require 'rswag/specs/example_helpers'
module Rswag
module Specs
RSpec.describe ExampleHelpers do
describe ExampleHelpers do
subject { double('example') }
before do
@ -13,10 +12,9 @@ module Rswag
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
stub_const('Rswag::Specs::RAILS_VERSION', 3)
end
let(:config) { double('config') }
let(:config) { double('config') }
let(:swagger_doc) do
{
swagger: '2.0',
securityDefinitions: {
api_key: {
type: :apiKey,
@ -26,14 +24,13 @@ module Rswag
}
}
end
let(:metadata) do
{
path_item: { template: '/blogs/{blog_id}/comments/{id}' },
operation: {
verb: :put,
summary: 'Updates a blog',
consumes: ['application/json'],
consumes: [ 'application/json' ],
parameters: [
{ name: :blog_id, in: :path, type: 'integer' },
{ name: 'id', in: :path, type: 'integer' },
@ -61,7 +58,7 @@ module Rswag
it "submits a request built from metadata and 'let' values" do
expect(subject).to have_received(:put).with(
'/blogs/1/comments/2?q1=foo&api_key=fookey',
'{"text":"Some comment"}',
"{\"text\":\"Some comment\"}",
{ 'CONTENT_TYPE' => 'application/json' }
)
end

View File

@ -1,17 +1,16 @@
# frozen_string_literal: true
require 'rswag/specs/request_factory'
module Rswag
module Specs
RSpec.describe RequestFactory do
describe RequestFactory do
subject { RequestFactory.new(config) }
before do
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
end
let(:config) { double('config') }
let(:swagger_doc) { { swagger: '2.0' } }
let(:config) { double('config') }
let(:swagger_doc) { {} }
let(:example) { double('example') }
let(:metadata) do
{
@ -54,7 +53,7 @@ module Rswag
allow(example).to receive(:q2).and_return('bar')
end
it 'builds the query string from example values' do
it "builds the query string from example values" do
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
end
end
@ -64,40 +63,40 @@ module Rswag
metadata[:operation][:parameters] = [
{ name: 'things', in: :query, type: :array, collectionFormat: collection_format }
]
allow(example).to receive(:things).and_return(['foo', 'bar'])
allow(example).to receive(:things).and_return([ 'foo', 'bar' ])
end
context 'collectionFormat = csv' do
let(:collection_format) { :csv }
it 'formats as comma separated values' do
it "formats as comma separated values" do
expect(request[:path]).to eq('/blogs?things=foo,bar')
end
end
context 'collectionFormat = ssv' do
let(:collection_format) { :ssv }
it 'formats as space separated values' do
it "formats as space separated values" do
expect(request[:path]).to eq('/blogs?things=foo bar')
end
end
context 'collectionFormat = tsv' do
let(:collection_format) { :tsv }
it 'formats as tab separated values' do
it "formats as tab separated values" do
expect(request[:path]).to eq('/blogs?things=foo\tbar')
end
end
context 'collectionFormat = pipes' do
let(:collection_format) { :pipes }
it 'formats as pipe separated values' do
it "formats as pipe separated values" do
expect(request[:path]).to eq('/blogs?things=foo|bar')
end
end
context 'collectionFormat = multi' do
let(:collection_format) { :multi }
it 'formats as multiple parameter instances' do
it "formats as multiple parameter instances" do
expect(request[:path]).to eq('/blogs?things=foo&things=bar')
end
end
@ -105,7 +104,7 @@ module Rswag
context "'header' parameters" do
before do
metadata[:operation][:parameters] = [{ name: 'Api-Key', in: :header, type: :string }]
metadata[:operation][:parameters] = [ { name: 'Api-Key', in: :header, type: :string } ]
allow(example).to receive(:'Api-Key').and_return('foobar')
end
@ -128,9 +127,9 @@ module Rswag
end
end
context 'consumes content' do
context "consumes content" do
before do
metadata[:operation][:consumes] = ['application/json', 'application/xml']
metadata[:operation][:consumes] = [ 'application/json', 'application/xml' ]
end
context "no 'Content-Type' provided" do
@ -151,33 +150,18 @@ module Rswag
context 'JSON payload' do
before do
metadata[:operation][:parameters] = [{ name: 'comment', in: :body, schema: { type: 'object' } }]
metadata[:operation][:parameters] = [ { name: 'comment', in: :body, schema: { type: 'object' } } ]
allow(example).to receive(:comment).and_return(text: 'Some comment')
end
it "serializes first 'body' parameter to JSON string" do
expect(request[:payload]).to eq('{"text":"Some comment"}')
end
end
context 'missing body parameter' do
before do
metadata[:operation][:parameters] = [{ name: 'comment', in: :body, schema: { type: 'object' } }]
allow(example).to receive(:comment).and_raise(NoMethodError, "undefined method 'comment'")
allow(example).to receive(:respond_to?).with(:'Content-Type')
allow(example).to receive(:respond_to?).with('comment').and_return(false)
end
it 'uses the referenced metadata to build the request' do
expect do
request[:payload]
end.to raise_error(Rswag::Specs::MissingParameterError, /Missing parameter 'comment'/)
expect(request[:payload]).to eq("{\"text\":\"Some comment\"}")
end
end
context 'form payload' do
before do
metadata[:operation][:consumes] = ['multipart/form-data']
metadata[:operation][:consumes] = [ 'multipart/form-data' ]
metadata[:operation][:parameters] = [
{ name: 'f1', in: :formData, type: :string },
{ name: 'f2', in: :formData, type: :string }
@ -197,7 +181,7 @@ module Rswag
context 'produces content' do
before do
metadata[:operation][:produces] = ['application/json', 'application/xml']
metadata[:operation][:produces] = [ 'application/json', 'application/xml' ]
end
context "no 'Accept' value provided" do
@ -208,7 +192,7 @@ module Rswag
context "explicit 'Accept' value provided" do
before do
allow(example).to receive(:Accept).and_return('application/xml')
allow(example).to receive(:'Accept').and_return('application/xml')
end
it "sets 'HTTP_ACCEPT' header to example value" do
@ -218,53 +202,21 @@ module Rswag
end
context 'basic auth' do
context 'swagger 2.0' do
before do
swagger_doc[:securityDefinitions] = { basic: { type: :basic } }
metadata[:operation][:security] = [basic: []]
allow(example).to receive(:Authorization).and_return('Basic foobar')
end
it "sets 'HTTP_AUTHORIZATION' header to example value" do
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
end
before do
swagger_doc[:securityDefinitions] = { basic: { type: :basic } }
metadata[:operation][:security] = [ basic: [] ]
allow(example).to receive(:Authorization).and_return('Basic foobar')
end
context 'openapi 3.0.1' do
let(:swagger_doc) { { openapi: '3.0.1' } }
before do
swagger_doc[:components] = { securitySchemes: { basic: { type: :basic } } }
metadata[:operation][:security] = [basic: []]
allow(example).to receive(:Authorization).and_return('Basic foobar')
end
it "sets 'HTTP_AUTHORIZATION' header to example value" do
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
end
end
context 'openapi 3.0.1 upgrade notice' do
let(:swagger_doc) { { openapi: '3.0.1' } }
before do
allow(ActiveSupport::Deprecation).to receive(:warn)
swagger_doc[:securityDefinitions] = { basic: { type: :basic } }
metadata[:operation][:security] = [basic: []]
allow(example).to receive(:Authorization).and_return('Basic foobar')
end
it 'warns the user to upgrade' do
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
expect(ActiveSupport::Deprecation).to have_received(:warn)
.with('Rswag::Specs: WARNING: securityDefinitions is replaced in OpenAPI3! Rename to components/securitySchemes (in swagger_helper.rb)')
expect(swagger_doc[:components]).to have_key(:securitySchemes)
end
it "sets 'HTTP_AUTHORIZATION' header to example value" do
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
end
end
context 'apiKey' do
before do
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: key_location } }
metadata[:operation][:security] = [apiKey: []]
metadata[:operation][:security] = [ apiKey: [] ]
allow(example).to receive(:api_key).and_return('foobar')
end
@ -304,8 +256,8 @@ module Rswag
context 'oauth2' do
before do
swagger_doc[:securityDefinitions] = { oauth2: { type: :oauth2, scopes: ['read:blogs'] } }
metadata[:operation][:security] = [oauth2: ['read:blogs']]
swagger_doc[:securityDefinitions] = { oauth2: { type: :oauth2, scopes: [ 'read:blogs' ] } }
metadata[:operation][:security] = [ oauth2: [ 'read:blogs' ] ]
allow(example).to receive(:Authorization).and_return('Bearer foobar')
end
@ -320,72 +272,39 @@ module Rswag
basic: { type: :basic },
api_key: { type: :apiKey, name: 'api_key', in: :query }
}
metadata[:operation][:security] = [{ basic: [], api_key: [] }]
metadata[:operation][:security] = [ { basic: [], api_key: [] } ]
allow(example).to receive(:Authorization).and_return('Basic foobar')
allow(example).to receive(:api_key).and_return('foobar')
end
it 'sets both params to example values' do
it "sets both params to example values" do
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
expect(request[:path]).to eq('/blogs?api_key=foobar')
end
end
context 'path-level parameters' do
context "path-level parameters" do
before do
metadata[:operation][:parameters] = [{ name: 'q1', in: :query, type: :string }]
metadata[:path_item][:parameters] = [{ name: 'q2', in: :query, type: :string }]
metadata[:operation][:parameters] = [ { name: 'q1', in: :query, type: :string } ]
metadata[:path_item][:parameters] = [ { name: 'q2', in: :query, type: :string } ]
allow(example).to receive(:q1).and_return('foo')
allow(example).to receive(:q2).and_return('bar')
end
it 'populates operation and path level parameters' do
it "populates operation and path level parameters " do
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
end
end
context 'referenced parameters' do
context 'swagger 2.0' do
before do
swagger_doc[:parameters] = { q1: { name: 'q1', in: :query, type: :string } }
metadata[:operation][:parameters] = [{ '$ref' => '#/parameters/q1' }]
allow(example).to receive(:q1).and_return('foo')
end
it 'uses the referenced metadata to build the request' do
expect(request[:path]).to eq('/blogs?q1=foo')
end
before do
swagger_doc[:parameters] = { q1: { name: 'q1', in: :query, type: :string } }
metadata[:operation][:parameters] = [ { '$ref' => '#/parameters/q1' } ]
allow(example).to receive(:q1).and_return('foo')
end
context 'openapi 3.0.1' do
let(:swagger_doc) { { openapi: '3.0.1' } }
before do
swagger_doc[:components] = { parameters: { q1: { name: 'q1', in: :query, type: :string } } }
metadata[:operation][:parameters] = [{ '$ref' => '#/components/parameters/q1' }]
allow(example).to receive(:q1).and_return('foo')
end
it 'uses the referenced metadata to build the request' do
expect(request[:path]).to eq('/blogs?q1=foo')
end
end
context 'openapi 3.0.1 upgrade notice' do
let(:swagger_doc) { { openapi: '3.0.1' } }
before do
allow(ActiveSupport::Deprecation).to receive(:warn)
swagger_doc[:parameters] = { q1: { name: 'q1', in: :query, type: :string } }
metadata[:operation][:parameters] = [{ '$ref' => '#/parameters/q1' }]
allow(example).to receive(:q1).and_return('foo')
end
it 'warns the user to upgrade' do
expect(request[:path]).to eq('/blogs?q1=foo')
expect(ActiveSupport::Deprecation).to have_received(:warn)
.with('Rswag::Specs: WARNING: #/parameters/ refs are replaced in OpenAPI3! Rename to #/components/parameters/')
expect(ActiveSupport::Deprecation).to have_received(:warn)
.with('Rswag::Specs: WARNING: parameters is replaced in OpenAPI3! Rename to components/parameters (in swagger_helper.rb)')
end
it 'uses the referenced metadata to build the request' do
expect(request[:path]).to eq('/blogs?q1=foo')
end
end
@ -397,18 +316,18 @@ module Rswag
end
end
context 'global consumes' do
before { swagger_doc[:consumes] = ['application/xml'] }
context "global consumes" do
before { swagger_doc[:consumes] = [ 'application/xml' ] }
it "defaults 'CONTENT_TYPE' to global value(s)" do
expect(request[:headers]).to eq('CONTENT_TYPE' => 'application/xml')
end
end
context 'global security requirements' do
context "global security requirements" do
before do
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: :query } }
swagger_doc[:security] = [apiKey: []]
swagger_doc[:security] = [ apiKey: [] ]
allow(example).to receive(:api_key).and_return('foobar')
end

View File

@ -1,17 +1,15 @@
# frozen_string_literal: true
require 'rswag/specs/response_validator'
module Rswag
module Specs
RSpec.describe ResponseValidator do
describe ResponseValidator do
subject { ResponseValidator.new(config) }
before do
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
allow(config).to receive(:get_swagger_doc_version).and_return('2.0')
end
let(:config) { double('config') }
let(:config) { double('config') }
let(:swagger_doc) { {} }
let(:example) { double('example') }
let(:metadata) do
@ -22,7 +20,7 @@ module Rswag
schema: {
type: :object,
properties: { text: { type: :string } },
required: ['text']
required: [ 'text' ]
}
}
}
@ -34,89 +32,43 @@ module Rswag
OpenStruct.new(
code: '200',
headers: { 'X-Rate-Limit-Limit' => '10' },
body: '{"text":"Some comment"}'
body: "{\"text\":\"Some comment\"}"
)
end
context 'response matches metadata' do
context "response matches metadata" do
it { expect { call }.to_not raise_error }
end
context 'response code differs from metadata' do
context "response code differs from metadata" do
before { response.code = '400' }
it { expect { call }.to raise_error(/Expected response code/) }
it { expect { call }.to raise_error /Expected response code/ }
end
context 'response headers differ from metadata' do
context "response headers differ from metadata" do
before { response.headers = {} }
it { expect { call }.to raise_error(/Expected response header/) }
it { expect { call }.to raise_error /Expected response header/ }
end
context 'response body differs from metadata' do
before { response.body = '{"foo":"Some comment"}' }
it { expect { call }.to raise_error(/Expected response body/) }
context "response body differs from metadata" do
before { response.body = "{\"foo\":\"Some comment\"}" }
it { expect { call }.to raise_error /Expected response body/ }
end
context 'referenced schemas' do
context 'swagger 2.0' do
before do
swagger_doc[:definitions] = {
'blog' => {
type: :object,
properties: { foo: { type: :string } },
required: ['foo']
}
before do
swagger_doc[:definitions] = {
'blog' => {
type: :object,
properties: { foo: { type: :string } },
required: [ 'foo' ]
}
metadata[:response][:schema] = { '$ref' => '#/definitions/blog' }
end
it 'uses the referenced schema to validate the response body' do
expect { call }.to raise_error(/Expected response body/)
end
}
metadata[:response][:schema] = { '$ref' => '#/definitions/blog' }
end
context 'openapi 3.0.1' do
context 'components/schemas' do
before do
allow(ActiveSupport::Deprecation).to receive(:warn)
allow(config).to receive(:get_swagger_doc_version).and_return('3.0.1')
swagger_doc[:components] = {
schemas: {
'blog' => {
type: :object,
properties: { foo: { type: :string } },
required: ['foo']
}
}
}
metadata[:response][:schema] = { '$ref' => '#/components/schemas/blog' }
end
it 'uses the referenced schema to validate the response body' do
expect { call }.to raise_error(/Expected response body/)
end
end
context 'deprecated definitions' do
before do
allow(ActiveSupport::Deprecation).to receive(:warn)
allow(config).to receive(:get_swagger_doc_version).and_return('3.0.1')
swagger_doc[:definitions] = {
'blog' => {
type: :object,
properties: { foo: { type: :string } },
required: ['foo']
}
}
metadata[:response][:schema] = { '$ref' => '#/definitions/blog' }
end
it 'warns the user to upgrade' do
expect { call }.to raise_error(/Expected response body/)
expect(ActiveSupport::Deprecation).to have_received(:warn)
.with('Rswag::Specs: WARNING: definitions is replaced in OpenAPI3! Rename to components/schemas (in swagger_helper.rb)')
end
end
it 'uses the referenced schema to validate the response body' do
expect { call }.to raise_error /Expected response body/
end
end
end

View File

@ -1,375 +1,70 @@
# frozen_string_literal: true
require 'rswag/specs/swagger_formatter'
require 'ostruct'
module Rswag
module Specs
RSpec.describe SwaggerFormatter do
describe SwaggerFormatter do
subject { described_class.new(output, config) }
# Mock out some infrastructure
before do
allow(config).to receive(:swagger_root).and_return(swagger_root)
allow(ActiveSupport::Deprecation).to receive(:warn) # Silence deprecation output from specs
end
let(:config) { double('config') }
let(:output) { double('output').as_null_object }
let(:swagger_root) { File.expand_path('tmp/swagger', __dir__) }
let(:swagger_root) { File.expand_path('../tmp/swagger', __FILE__) }
describe '#example_group_finished(notification)' do
before do
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
subject.example_group_finished(notification)
end
let(:swagger_doc) { {} }
let(:notification) { OpenStruct.new(group: OpenStruct.new(metadata: api_metadata)) }
let(:api_metadata) do
{
path_item: { template: '/blogs', parameters: [{ type: :string }] },
operation: { verb: :post, summary: 'Creates a blog', parameters: [{ type: :string }] },
response: response_metadata,
document: document
path_item: { template: '/blogs' },
operation: { verb: :post, summary: 'Creates a blog' },
response: { code: '201', description: 'blog created' }
}
end
let(:response_metadata) { { code: '201', description: 'blog created', headers: { type: :string }, schema: { '$ref' => '#/definitions/blog' } } }
context 'with the document tag set to false' do
let(:swagger_doc) { { swagger: '2.0' } }
let(:document) { false }
it 'does not update the swagger doc' do
expect(swagger_doc).to match({ swagger: '2.0' })
end
end
context 'with the document tag set to anything but false' do
let(:swagger_doc) { { swagger: '2.0' } }
# anything works, including its absence when specifying responses.
let(:document) { nil }
it 'converts to swagger and merges into the corresponding swagger doc' do
expect(swagger_doc).to match(
swagger: '2.0',
paths: {
'/blogs' => {
parameters: [{ type: :string }],
post: {
parameters: [{ type: :string }],
summary: 'Creates a blog',
responses: {
'201' => {
description: 'blog created',
headers: { type: :string },
schema: { '$ref' => '#/definitions/blog' }
}
}
}
}
}
)
end
end
context 'with metadata upgrades for 3.0' do
let(:swagger_doc) do
{
openapi: '3.0.1',
basePath: '/foo',
schemes: ['http', 'https'],
host: 'api.example.com',
produces: ['application/vnd.my_mime', 'application/json'],
components: {
securitySchemes: {
myClientCredentials: {
type: :oauth2,
flow: :application,
token_url: :somewhere
},
myAuthorizationCode: {
type: :oauth2,
flow: :accessCode,
token_url: :somewhere
},
myImplicit: {
type: :oauth2,
flow: :implicit,
token_url: :somewhere
it 'converts to swagger and merges into the corresponding swagger doc' do
expect(swagger_doc).to match(
paths: {
'/blogs' => {
post: {
summary: 'Creates a blog',
responses: {
'201' => { description: 'blog created' }
}
}
}
}
end
let(:document) { nil }
it 'converts query and path params, type: to schema: { type: }' do
expect(swagger_doc.slice(:paths)).to match(
paths: {
'/blogs' => {
parameters: [{ schema: { type: :string } }],
post: {
parameters: [{ schema: { type: :string } }],
summary: 'Creates a blog',
responses: {
'201' => {
content: {
'application/vnd.my_mime' => {
schema: { '$ref' => '#/definitions/blog' }
},
'application/json' => {
schema: { '$ref' => '#/definitions/blog' }
}
},
description: 'blog created',
headers: { schema: { type: :string } }
}
}
}
}
}
)
end
context 'with response example' do
let(:response_metadata) do
{
code: '201',
description: 'blog created',
headers: { type: :string },
content: { 'application/json' => { example: { foo: :bar } } },
schema: { '$ref' => '#/definitions/blog' }
}
end
it 'adds example to definition' do
expect(swagger_doc.slice(:paths)).to match(
paths: {
'/blogs' => {
parameters: [{ schema: { type: :string } }],
post: {
parameters: [{ schema: { type: :string } }],
summary: 'Creates a blog',
responses: {
'201' => {
content: {
'application/vnd.my_mime' => {
schema: { '$ref' => '#/definitions/blog' }
},
'application/json' => {
schema: { '$ref' => '#/definitions/blog' },
example: { foo: :bar }
}
},
description: 'blog created',
headers: { schema: { type: :string } }
}
}
}
}
}
)
end
end
context 'with empty content' do
let(:swagger_doc) do
{
openapi: '3.0.1',
basePath: '/foo',
schemes: ['http', 'https'],
host: 'api.example.com',
components: {
securitySchemes: {
myClientCredentials: {
type: :oauth2,
flow: :application,
token_url: :somewhere
},
myAuthorizationCode: {
type: :oauth2,
flow: :accessCode,
token_url: :somewhere
},
myImplicit: {
type: :oauth2,
flow: :implicit,
token_url: :somewhere
}
}
}
}
end
it 'converts query and path params, type: to schema: { type: }' do
expect(swagger_doc.slice(:paths)).to match(
paths: {
'/blogs' => {
parameters: [{ schema: { type: :string } }],
post: {
parameters: [{ schema: { type: :string } }],
summary: 'Creates a blog',
responses: {
'201' => {
description: 'blog created',
headers: { schema: { type: :string } }
}
}
}
}
}
)
end
end
it 'converts basePath, schemas and host to urls' do
expect(swagger_doc.slice(:servers)).to match(
servers: {
urls: ['http://api.example.com/foo', 'https://api.example.com/foo']
}
)
end
it 'upgrades oauth flow to flows' do
expect(swagger_doc.slice(:components)).to match(
components: {
securitySchemes: {
myClientCredentials: {
type: :oauth2,
flows: {
'clientCredentials' => {
token_url: :somewhere
}
}
},
myAuthorizationCode: {
type: :oauth2,
flows: {
'authorizationCode' => {
token_url: :somewhere
}
}
},
myImplicit: {
type: :oauth2,
flows: {
'implicit' => {
token_url: :somewhere
}
}
}
}
}
)
end
)
end
end
describe '#stop' do
before do
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root)
before do
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
allow(config).to receive(:swagger_docs).and_return(
'v1/swagger.json' => doc_1,
'v2/swagger.json' => doc_2
'v1/swagger.json' => { info: { version: 'v1' } },
'v2/swagger.json' => { info: { version: 'v2' } }
)
allow(config).to receive(:swagger_format).and_return(swagger_format)
subject.stop(notification)
end
let(:doc_1) { { info: { version: 'v1' } } }
let(:doc_2) { { info: { version: 'v2' } } }
let(:swagger_format) { :json }
let(:notification) { double('notification') }
context 'with default format' do
it 'writes the swagger_doc(s) to file' do
expect(File).to exist("#{swagger_root}/v1/swagger.json")
expect(File).to exist("#{swagger_root}/v2/swagger.json")
expect { JSON.parse(File.read("#{swagger_root}/v2/swagger.json")) }.not_to raise_error
end
end
context 'with yaml format' do
let(:swagger_format) { :yaml }
it 'writes the swagger_doc(s) as yaml' do
expect(File).to exist("#{swagger_root}/v1/swagger.json")
expect { JSON.parse(File.read("#{swagger_root}/v1/swagger.json")) }.to raise_error(JSON::ParserError)
# Psych::DisallowedClass would be raised if we do not pre-process ruby symbols
expect { YAML.safe_load(File.read("#{swagger_root}/v1/swagger.json")) }.not_to raise_error
end
end
context 'with oauth3 upgrades' do
let(:doc_2) do
{
paths: {
'/path/' => {
get: {
summary: 'Retrieve Nested Paths',
tags: ['nested Paths'],
produces: ['application/json'],
consumes: ['application/xml', 'application/json'],
parameters: [{
in: :body,
schema: { foo: :bar }
}, {
in: :headers
}]
}
}
}
}
end
it 'removes remaining consumes/produces' do
expect(doc_2[:paths]['/path/'][:get].keys).to eql([:summary, :tags, :parameters, :requestBody])
end
it 'duplicates params in: :body to requestBody from consumes list' do
expect(doc_2[:paths]['/path/'][:get][:parameters]).to eql([{ in: :headers }])
expect(doc_2[:paths]['/path/'][:get][:requestBody]).to eql(content: {
'application/xml' => { schema: { foo: :bar } },
'application/json' => { schema: { foo: :bar } }
})
end
end
context 'with oauth3 formData' do
let(:doc_2) do
{
paths: {
'/path/' => {
post: {
summary: 'Retrieve Nested Paths',
tags: ['nested Paths'],
produces: ['application/json'],
consumes: ['multipart/form-data'],
parameters: [{
in: :formData,
schema: { type: :file }
},{
in: :headers
}]
}
}
}
}
end
it 'removes remaining consumes/produces' do
expect(doc_2[:paths]['/path/'][:post].keys).to eql([:summary, :tags, :parameters, :requestBody])
end
it 'duplicates params in: :formData to requestBody from consumes list' do
expect(doc_2[:paths]['/path/'][:post][:parameters]).to eql([{ in: :headers }])
expect(doc_2[:paths]['/path/'][:post][:requestBody]).to eql(content: {
'multipart/form-data' => { schema: { type: :file } }
})
end
it 'writes the swagger_doc(s) to file' do
expect(File).to exist("#{swagger_root}/v1/swagger.json")
expect(File).to exist("#{swagger_root}/v2/swagger.json")
end
after do
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root)
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
end
end
end

View File

@ -1,5 +1,3 @@
# frozen_string_literal: true
module Rails
module VERSION
MAJOR = 3

View File

@ -1,4 +1,2 @@
# frozen_string_literal: true
# NOTE: For the specs in this gem, all configuration is completely mocked out
# The file just needs to be present because it gets required by the swagger_formatter

View File

@ -2,13 +2,9 @@ Rswag::Ui.configure do |c|
# List the Swagger endpoints that you want to be documented through the swagger-ui
# The first parameter is the path (absolute or relative to the UI host) to the corresponding
# endpoint and the second is a title that will be displayed in the document selector
# NOTE: If you're using rspec-api to expose Swagger files (under swagger_root) as JSON or YAML endpoints,
# JSON endpoint and the second is a title that will be displayed in the document selector
# NOTE: If you're using rspec-api to expose Swagger files (under swagger_root) as JSON endpoints,
# then the list below should correspond to the relative paths for those endpoints
c.swagger_endpoint '/api-docs/v1/swagger.yaml', 'API V1 Docs'
# Add Basic Auth in case your API is private
# c.basic_auth_enabled = true
# c.basic_auth_credentials 'username', 'password'
c.swagger_endpoint '/api-docs/v1/swagger.json', 'API V1 Docs'
end

View File

@ -1,31 +0,0 @@
# frozen_string_literal: true
require 'rack/auth/basic'
module Rswag
module Ui
# Extend Rack HTTP Basic Authentication, as per RFC 2617.
# @api private
#
class BasicAuth < ::Rack::Auth::Basic
def call(env)
return @app.call(env) unless env_matching_path(env)
super(env)
end
private
def env_matching_path(env)
path = base_path(env['PATH_INFO'])
Rswag::Ui.config.config_object[:urls].find do |endpoint|
base_path(endpoint[:url]) == path
end
end
def base_path(url)
url.downcase.split('/')[1]
end
end
end
end

View File

@ -1,11 +1,9 @@
require 'ostruct'
require 'rack'
module Rswag
module Ui
class Configuration
attr_reader :template_locations
attr_accessor :basic_auth_enabled
attr_accessor :config_object
attr_accessor :oauth_config_object
attr_reader :assets_root
@ -22,7 +20,6 @@ module Rswag
@assets_root = File.expand_path('../../../../node_modules/swagger-ui-dist', __FILE__)
@config_object = {}
@oauth_config_object = {}
@basic_auth_enabled = false
end
def swagger_endpoint(url, name)
@ -30,15 +27,9 @@ module Rswag
@config_object[:urls] << { url: url, name: name }
end
def basic_auth_credentials(username, password)
@config_object[:basic_auth] = { username: username, password: password }
end
# rubocop:disable Naming/AccessorMethodName
def get_binding
binding
end
# rubocop:enable Naming/AccessorMethodName
end
end
end

View File

@ -1,5 +1,4 @@
require 'rswag/ui/middleware'
require 'rswag/ui/basic_auth'
module Rswag
module Ui
@ -8,13 +7,6 @@ module Rswag
initializer 'rswag-ui.initialize' do |app|
middleware.use Rswag::Ui::Middleware, Rswag::Ui.config
if Rswag::Ui.config.basic_auth_enabled
c = Rswag::Ui.config
app.middleware.use Rswag::Ui::BasicAuth do |username, password|
c.config_object[:basic_auth].values == [username, password]
end
end
end
rake_tasks do

View File

@ -8,7 +8,7 @@ module Rswag
end
def call(env)
if base_path?(env)
if base_path?(env)
redirect_uri = env['SCRIPT_NAME'].chomp('/') + '/index.html'
return [ 301, { 'Location' => redirect_uri }, [ ] ]
end

View File

@ -5,9 +5,9 @@
"requires": true,
"dependencies": {
"swagger-ui-dist": {
"version": "3.52.5",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.52.5.tgz",
"integrity": "sha512-8z18eX8G/jbTXYzyNIaobrnD7PSN7yU/YkSasMmajrXtw0FGS64XjrKn5v37d36qmU3o1xLeuYnktshRr7uIFw=="
"version": "3.17.3",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.17.3.tgz",
"integrity": "sha1-37lkCMzEZ3UVX3NpGQxdSyAW/lw="
}
}
}

View File

@ -3,6 +3,6 @@
"version": "1.0.0",
"private": true,
"dependencies": {
"swagger-ui-dist": "3.52.5"
"swagger-ui-dist": "3.17.3"
}
}

View File

@ -1,20 +1,18 @@
# frozen_string_literal: true
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
$:.push File.expand_path("../lib", __FILE__)
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = 'rswag-ui'
s.name = "rswag-ui"
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
s.authors = ['Richie Morris', 'Greg Myers', 'Jay Danielian']
s.email = ['domaindrivendev@gmail.com']
s.homepage = 'https://github.com/rswag/rswag'
s.summary = 'A Rails Engine that includes swagger-ui and powers it from configured OpenAPI (formerly named Swagger) endpoints'
s.description = 'Expose beautiful API documentation, powered by Swagger JSON endpoints, including a UI to explore and test operations. More about the OpenAPI initiative here: http://spec.openapis.org/'
s.license = 'MIT'
s.authors = ["Richie Morris"]
s.email = ["domaindrivendev@gmail.com"]
s.homepage = "https://github.com/domaindrivendev/rswag"
s.summary = "A Rails Engine that includes swagger-ui and powers it from configured Swagger endpoints"
s.description = "Expose beautiful API documentation, that's powered by Swagger JSON endpoints, including a UI to explore and test operations"
s.license = "MIT"
s.files = Dir.glob('{lib,node_modules}/**/*') + ['MIT-LICENSE', 'Rakefile' ]
s.files = Dir.glob("{lib,node_modules}/**/*") + ["MIT-LICENSE", "Rakefile" ]
s.add_dependency 'actionpack', '>=3.1', '< 7.1'
s.add_dependency 'railties', '>= 3.1', '< 7.1'
s.add_dependency 'actionpack', '>=3.1', '< 6.1'
s.add_dependency 'railties', '>= 3.1', '< 6.1'
end

View File

@ -1,52 +0,0 @@
require 'rswag/ui/configuration'
require_relative '../../spec_helper'
RSpec.describe Rswag::Ui::Configuration do
describe '#swagger_endpoints'
describe '#basic_auth_enabled' do
context 'when unspecified' do
it 'defaults to false' do
configuration = described_class.new
basic_auth_enabled = configuration.basic_auth_enabled
expect(basic_auth_enabled).to be(false)
end
end
context 'when specified' do
context 'when set to true' do
it 'returns true' do
configuration = described_class.new
configuration.basic_auth_enabled = true
basic_auth_enabled = configuration.basic_auth_enabled
expect(basic_auth_enabled).to be(true)
end
end
context 'when set to false' do
it 'returns false' do
configuration = described_class.new
configuration.basic_auth_enabled = false
basic_auth_enabled = configuration.basic_auth_enabled
expect(basic_auth_enabled).to be(false)
end
end
end
end
describe '#basic_auth_credentials' do
it 'sets the username and password' do
configuration = described_class.new
configuration.basic_auth_credentials 'foo', 'bar'
credentials = configuration.config_object[:basic_auth]
expect(credentials).to eq(username: 'foo', password: 'bar')
end
end
describe '#get_binding'
end

View File

@ -1,100 +0,0 @@
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause
# this file to always be loaded, without a need to explicitly require it in any
# files.
#
# Given that it is always loaded, you are encouraged to keep this file as
# light-weight as possible. Requiring heavyweight dependencies from this file
# will add to the boot time of your test suite on EVERY test run, even for an
# individual file that may not need all of that loaded. Instead, consider making
# a separate helper file that requires the additional dependencies and performs
# the additional setup, and require it from the spec files that actually need
# it.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
# This option will default to `true` in RSpec 4. It makes the `description`
# and `failure_message` of custom matchers include text for helper methods
# defined using `chain`, e.g.:
# be_bigger_than(2).and_smaller_than(4).description
# # => "be bigger than 2 and smaller than 4"
# ...rather than:
# # => "be bigger than 2"
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
# rspec-mocks config goes here. You can use an alternate test double
# library (such as bogus or mocha) by changing the `mock_with` option here.
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to
# `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
# have no way to turn it off -- the option exists only for backwards
# compatibility in RSpec 3). It causes shared context metadata to be
# inherited by the metadata hash of host groups and examples, rather than
# triggering implicit auto-inclusion in groups with matching metadata.
config.shared_context_metadata_behavior = :apply_to_host_groups
# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
=begin
# This allows you to limit a spec run to individual examples or groups
# you care about by tagging them with `:focus` metadata. When nothing
# is tagged with `:focus`, all examples get run. RSpec also provides
# aliases for `it`, `describe`, and `context` that include `:focus`
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
config.filter_run_when_matching :focus
# Allows RSpec to persist some state between runs in order to support
# the `--only-failures` and `--next-failure` CLI options. We recommend
# you configure your source control system to ignore this file.
config.example_status_persistence_file_path = "spec/examples.txt"
# Limits the available syntax to the non-monkey patched syntax that is
# recommended. For more details, see:
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
config.disable_monkey_patching!
# This setting enables warnings. It's recommended, but in some cases may
# be too noisy due to issues in dependencies.
config.warnings = true
# Many RSpec users commonly either run the entire suite or an individual
# file, and it's useful to allow more verbose output when running an
# individual spec file.
if config.files_to_run.one?
# Use the documentation formatter for detailed output,
# unless a formatter has already been configured
# (e.g. via a command-line flag).
config.default_formatter = "doc"
end
# Print the 10 slowest examples and example groups at the
# end of the spec run, to help surface which specs are running
# particularly slow.
config.profile_examples = 10
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = :random
# Seed global randomization in this process using the `--seed` CLI option.
# Setting this allows you to use `--seed` to deterministically reproduce
# test failures related to randomization by passing the same `--seed` value
# as the one that triggered the failure.
Kernel.srand config.seed
=end
end

View File

@ -1,19 +1,17 @@
# frozen_string_literal: true
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
$:.push File.expand_path("../lib", __FILE__)
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = 'rswag'
s.name = "rswag"
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
s.authors = ['Richie Morris', 'Greg Myers', 'Jay Danielian']
s.email = ['domaindrivendev@gmail.com']
s.homepage = 'https://github.com/rswag/rswag'
s.summary = 'OpenAPI (formerly named Swagger) tooling for Rails APIs'
s.description = 'Generate beautiful API documentation, including a UI to explore and test operations, directly from your rspec integration tests. OpenAPI 2 and 3 supported. More about the OpenAPI initiative here: http://spec.openapis.org/'
s.license = 'MIT'
s.authors = ["Richie Morris"]
s.email = ["domaindrivendev@gmail.com"]
s.homepage = "https://github.com/domaindrivendev/rswag"
s.summary = "Swagger tooling for Rails API's"
s.description = "Generate beautiful API documentation, including a UI to explore and test operations, directly from your rspec integration tests"
s.license = "MIT"
s.files = Dir['{lib}/**/*'] + [ 'MIT-LICENSE' ]
s.files = Dir["{lib}/**/*"] + [ "MIT-LICENSE" ]
s.add_dependency 'rswag-specs', ENV['TRAVIS_TAG'] || '0.0.0'
s.add_dependency 'rswag-api', ENV['TRAVIS_TAG'] || '0.0.0'

View File

@ -22,7 +22,7 @@ module Rswag
end
it 'installs initializer for rswag-api' do
assert_file('config/rswag_api.rb')
assert_file('config/rswag-api.rb')
end
it 'installs initializer for rswag-ui' do

4
test-app/.byebug_history Normal file
View File

@ -0,0 +1,4 @@
exit
env['PATH_INFO']
env['SCRIPT_NAME']
env

View File

@ -5,8 +5,3 @@
require File.expand_path('../config/application', __FILE__)
TestApp::Application.load_tasks
RSpec::Core::RakeTask.new('swaggerize') do |t|
t.pattern = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--order defined' ]
end

View File

@ -1,2 +0,0 @@
//= link_tree ../images
//= link_directory ../stylesheets .css

View File

@ -1,2 +0,0 @@
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.

View File

@ -1,4 +0,0 @@
/*
Place all the styles related to the matching controller here.
They will automatically be included in application.css.
*/

View File

@ -8,31 +8,12 @@ class BlogsController < ApplicationController
respond_with @blog
end
# POST /blogs/flexible
def flexible_create
# contrived example to play around with new anyOf and oneOf
# request body definition for 3.0
blog_params = params.require(:blog).permit(:title, :content, :headline, :text)
@blog = Blog.create(blog_params)
respond_with @blog
end
# POST /blogs/alternate
def alternate_create
# contrived example to show different :examples in the requestBody section
@blog = Blog.create(params.require(:blog).permit(:title, :content))
respond_with @blog
end
# Put /blogs/1
def upload
@blog = Blog.find_by_id(params[:id])
return head :not_found if @blog.nil?
@blog.thumbnail = save_uploaded_file params[:file]
head @blog.save ? :ok : :unprocessable_entity
head @blog.save ? :ok : :unprocsessible_entity
end
# GET /blogs

View File

@ -1,13 +0,0 @@
class StubsController < ApplicationController
def index
render plain: 'OK'
end
def create
render plain: 'OK'
end
def show
render plain: 'OK'
end
end

View File

@ -1,2 +0,0 @@
module StubsHelper
end

View File

@ -1,16 +1,11 @@
# frozen_string_literal: true
class Blog < ActiveRecord::Base
validates :content, presence: true
alias_attribute :headline, :title
alias_attribute :text, :content
def as_json(_options)
def as_json(options)
{
id: id,
title: title,
content: content,
content: nil,
thumbnail: thumbnail
}
end

View File

@ -11,7 +11,6 @@ Bundler.require(*Rails.groups)
module TestApp
class Application < Rails::Application
config.load_defaults 5.2
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.

View File

@ -1,12 +1,10 @@
# Be sure to restart your server when you modify this file.
if Rails.version.first.to_i < 5
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
TestApp::Application.config.secret_token = '60f36cd33756d73f362053f1d45256ae50d75440b634ae73b070a6e35a2df38692f59e28e5ecbd1f9f2e850255f6d29a468bc59ac4484c2b7f0548ddbfc1b870'
else
# See http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#config-secrets-yml
TestApp::Application.config.secret_key_base = 'f6a820cc8aa76094583cd68ef46a735e25e3278648086355f8bd24721f036959c728c06a28dcecfe695f17ae2db44dfa1424f22b81377f2a1496d4e19f6f7faa'
end
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
TestApp::Application.config.secret_token = '60f36cd33756d73f362053f1d45256ae50d75440b634ae73b070a6e35a2df38692f59e28e5ecbd1f9f2e850255f6d29a468bc59ac4484c2b7f0548ddbfc1b870'
# See http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#config-secrets-yml
TestApp::Application.config.secret_key_base = 'f6a820cc8aa76094583cd68ef46a735e25e3278648086355f8bd24721f036959c728c06a28dcecfe695f17ae2db44dfa1424f22b81377f2a1496d4e19f6f7faa'

View File

@ -1,7 +1,4 @@
TestApp::Application.routes.draw do
post '/blogs/flexible', to: 'blogs#flexible_create'
post '/blogs/alternate', to: 'blogs#alternate_create'
resources :blogs
put '/blogs/:id/upload', to: 'blogs#upload'
@ -9,8 +6,6 @@ TestApp::Application.routes.draw do
post 'auth-tests/api-key', to: 'auth_tests#api_key'
post 'auth-tests/basic-and-api-key', to: 'auth_tests#basic_and_api_key'
resources :stubs
mount Rswag::Api::Engine => 'api-docs'
mount Rswag::Ui::Engine => 'api-docs'
mount Rswag::Api::Engine => 'api-docs'
mount Rswag::Ui::Engine => 'api-docs'
end

View File

@ -2,11 +2,11 @@
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
# This file is the source Rails uses to define your schema when running `rails
# db:schema:load`. When creating a new database, `rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.

View File

@ -1,13 +1,12 @@
require 'rails_helper'
RSpec.feature 'swagger-ui', js: true do
feature 'swagger-ui', js: true do
scenario 'browsing api-docs' do
skip "Needs work to run on others' machines"
visit '/api-docs'
expect(page).to have_content('GET /blogs Searches blogs', normalize_ws: true)
expect(page).to have_content('POST /blogs Creates a blog', normalize_ws: true)
expect(page).to have_content('GET /blogs/{id} Retrieves a blog', normalize_ws: true)
expect(page).to have_content('GET /blogs Searches blogs')
expect(page).to have_content('POST /blogs Creates a blog')
expect(page).to have_content('GET /blogs/{id} Retrieves a blog')
end
end

View File

@ -1,17 +1,12 @@
# frozen_string_literal: true
require 'swagger_helper'
RSpec.describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
before do
allow(ActiveSupport::Deprecation).to receive(:warn) # Silence deprecation output from specs
end
describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
path '/auth-tests/basic' do
post 'Authenticates with basic auth' do
tags 'Auth Tests'
operationId 'testBasicAuth'
security [basic_auth: []]
security [ basic_auth: [] ]
response '204', 'Valid credentials' do
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
@ -29,7 +24,7 @@ RSpec.describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json'
post 'Authenticates with an api key' do
tags 'Auth Tests'
operationId 'testApiKey'
security [api_key: []]
security [ api_key: [] ]
response '204', 'Valid credentials' do
let(:api_key) { 'foobar' }
@ -47,7 +42,7 @@ RSpec.describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json'
post 'Authenticates with basic auth and api key' do
tags 'Auth Tests'
operationId 'testBasicAndApiKey'
security [{ basic_auth: [], api_key: [] }]
security [ { basic_auth: [], api_key: [] } ]
response '204', 'Valid credentials' do
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }

View File

@ -1,12 +1,8 @@
require 'swagger_helper'
RSpec.describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
let(:api_key) { 'fake_key' }
before do
allow(ActiveSupport::Deprecation).to receive(:warn) # Silence deprecation output from specs
end
path '/blogs' do
post 'Creates a blog' do
tags 'Blogs'
@ -19,7 +15,6 @@ RSpec.describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
let(:blog) { { title: 'foo', content: 'bar' } }
response '201', 'blog created' do
# schema '$ref' => '#/definitions/blog'
run_test!
end
@ -53,30 +48,6 @@ RSpec.describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
end
end
path '/blogs/flexible' do
post 'Creates a blog flexible body' do
tags 'Blogs'
description 'Creates a flexible blog from provided data'
operationId 'createFlexibleBlog'
consumes 'application/json'
produces 'application/json'
parameter name: :flexible_blog, in: :body, schema: {
oneOf: [
{ '$ref' => '#/definitions/blog' },
{ '$ref' => '#/definitions/flexible_blog' }
]
}
let(:flexible_blog) { { blog: { headline: 'my headline', text: 'my text' } } }
response '201', 'flexible blog created' do
schema oneOf: [{ '$ref' => '#/definitions/blog' }, { '$ref' => '#/definitions/flexible_blog' }]
run_test!
end
end
end
path '/blogs/{id}' do
parameter name: :id, in: :path, type: :string
@ -97,11 +68,11 @@ RSpec.describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
schema '$ref' => '#/definitions/blog'
examples 'application/json' => {
id: 1,
title: 'Hello world!',
content: 'Hello world and hello universe. Thank you all very much!!!',
thumbnail: 'thumbnail.png'
}
id: 1,
title: 'Hello world!',
content: 'Hello world and hello universe. Thank you all very much!!!',
thumbnail: "thumbnail.png"
}
let(:id) { blog.id }
run_test!

View File

@ -1,349 +0,0 @@
require 'swagger_helper'
require 'rswag/specs/swagger_formatter'
# This spec file validates OpenAPI output generated by spec metadata.
# Specifically here, we look at OpenApi 3 as documented at
# https://swagger.io/docs/specification/about/
RSpec.describe 'Generated OpenApi', type: :request, swagger_doc: 'v3/openapi.json' do
before do |example|
output = double('output').as_null_object
swagger_root = File.expand_path('tmp/swagger', __dir__)
config = double('config', swagger_root: swagger_root, get_swagger_doc: swagger_doc )
formatter = Rswag::Specs::SwaggerFormatter.new(output, config)
example_group = OpenStruct.new(group: OpenStruct.new(metadata: example.metadata))
formatter.example_group_finished(example_group)
end
# Framework definition, to be overridden for contexts
let(:swagger_doc) do
{ # That which would be defined in swagger_helper.rb
openapi: api_openapi,
info: {},
servers: api_servers,
produces: api_produces,
components: api_components
}
end
let(:api_openapi) { '3.0.3' }
let(:api_servers) {[{ url: "https://api.example.com/foo" }]}
let(:api_produces) { ['application/json'] }
let(:api_components) { {} }
describe 'Basic Structure'
describe 'API Server and Base Path' do
path '/stubs' do
get 'a summary' do
tags 'Server and Path'
response '200', 'OK' do
run_test!
it 'lists server' do
tree = swagger_doc.dig(:servers)
expect(tree).to eq([
{ url: "https://api.example.com/foo" }
])
end
context "multiple" do
let(:api_servers) {[
{ url: "https://api.example.com/foo" },
{ url: "http://api.example.com/foo" },
]}
it 'lists servers' do
tree = swagger_doc.dig(:servers)
expect(tree).to eq([
{ url: "https://api.example.com/foo" },
{ url: "http://api.example.com/foo" }
])
end
end
context "with variables" do
let(:api_servers) {[{
url: "https://{defaultHost}/foo",
variables: {
defaultHost: {
default: "api.example.com"
}
}
}]}
it 'lists server and variables' do
tree = swagger_doc.dig(:servers)
expect(tree).to eq([{
url: "https://{defaultHost}/foo",
variables: {
defaultHost: {
default: "api.example.com"
}
}
}])
end
end
# TODO: Enum variables, defaults, override at path/operation
end
end
end
end
describe 'Media Types' do
path '/stubs' do
get 'a summary' do
tags 'Media Types'
response '200', 'OK' do
run_test!
it 'declares output as application/json' do
pending "Not yet implemented?"
tree = swagger_doc.dig(:paths, "/stubs", :get, :responses, '200', :content)
expect(tree).to have_key('application/json')
end
end
end
end
end
describe 'Paths and Operations'
describe 'Parameter Serialization' do
describe 'Path Parameters' do
path '/stubs/{a_param}' do
get 'a summary' do
tags 'Parameter Serialization: Query String'
produces 'application/json'
parameter(
name: 'a_param',
in: :path,
)
let(:a_param) { "42" }
response '200', 'OK' do
run_test!
it 'declares parameter in path' do
tree = swagger_doc.dig(:paths, "/stubs/{a_param}", :get, :parameters)
expect(tree.first[:name]).to eq('a_param')
expect(tree.first[:in]).to eq(:path)
end
it 'declares path parameters as required' do
tree = swagger_doc.dig(:paths, "/stubs/{a_param}", :get, :parameters)
expect(tree.first[:required]).to eq(true)
end
end
end
end
end
describe 'Query Parameters' do
path '/stubs' do
get 'a summary' do
tags 'Parameter Serialization: Query String'
produces 'application/json'
parameter(
name: 'a_param',
in: :query,
)
let(:a_param) { "a foo" }
response '200', 'OK' do
run_test!
it 'declares parameter in query string' do
tree = swagger_doc.dig(:paths, "/stubs", :get, :parameters)
expect(tree.first[:name]).to eq('a_param')
expect(tree.first[:in]).to eq(:query)
end
end
# TODO: Serialization (form/spaceDelimited/pipeDelimited/deepObject)
end
end
end
# TODO: Header
# TODO: Cookie
# TODO: Default values
# TODO: Enum
# TODO: Constant
# TODO: Empty/Nullable
# TODO: Examples
# TODO: Deprecated
# TODO: Common Parameters
end
describe 'Request Body' do
path '/stubs' do
post 'body is required' do
tags 'Media Types'
consumes 'multipart/form-data'
parameter name: :file, :in => :formData, :type => :file, required: true
let(:file) { Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) }
response '200', 'OK' do
run_test!
it 'declares requestBody is required' do
pending "This output is massaged in SwaggerFormatter#stop, and isn't quite ready here to assert"
tree = swagger_doc.dig(:paths, "/stubs", :post, :requestBody)
expect(tree[:required]).to eq(true)
end
end
end
end
end
describe 'Responses'
describe 'Data Models (Schemas)'
describe 'Examples'
describe 'Authentication'
describe 'Links'
describe 'Callbacks'
describe 'Components Section'
describe 'Using $ref'
describe 'Grouping Operations with Tags'
# path '/blogs' do
# post 'Creates a blog' do
# tags 'Blogs'
# description 'Creates a new blog from provided data'
# operationId 'createBlog'
# consumes 'application/json'
# produces 'application/json'
# parameter name: :blog, in: :body, schema: { '$ref' => '#/definitions/blog' }
# let(:blog) { { title: 'foo', content: 'bar' } }
# response '201', 'blog created' do
# # schema '$ref' => '#/definitions/blog'
# run_test!
# end
# response '422', 'invalid request' do
# schema '$ref' => '#/definitions/errors_object'
# let(:blog) { { title: 'foo' } }
# run_test! do |response|
# expect(response.body).to include("can't be blank")
# end
# it 'outputs parameters' do
# pp swagger_doc
# params = swagger_doc.dig(:paths, "/blogs", :post, :parameters)
# expect(params[0][:name]).to eq(:blog)
# end
# end
# end
# get 'Searches blogs' do
# tags 'Blogs'
# description 'Searches blogs by keywords'
# operationId 'searchBlogs'
# produces 'application/json'
# parameter name: :keywords, in: :query, type: 'string'
# let(:keywords) { 'foo bar' }
# response '200', 'success' do
# schema type: 'array', items: { '$ref' => '#/definitions/blog' }
# end
# response '406', 'unsupported accept header' do
# let(:'Accept') { 'application/foo' }
# run_test!
# end
# end
# end
# path '/blogs/flexible' do
# post 'Creates a blog flexible body' do
# tags 'Blogs'
# description 'Creates a flexible blog from provided data'
# operationId 'createFlexibleBlog'
# consumes 'application/json'
# produces 'application/json'
# parameter name: :flexible_blog, in: :body, schema: {
# oneOf: [
# { '$ref' => '#/definitions/blog' },
# { '$ref' => '#/definitions/flexible_blog' }
# ]
# }
# let(:flexible_blog) { { blog: { headline: 'my headline', text: 'my text' } } }
# response '201', 'flexible blog created' do
# schema oneOf: [{ '$ref' => '#/definitions/blog' }, { '$ref' => '#/definitions/flexible_blog' }]
# run_test!
# end
# end
# end
# path '/blogs/{id}' do
# parameter name: :id, in: :path, type: :string
# let(:id) { blog.id }
# let(:blog) { Blog.create(title: 'foo', content: 'bar', thumbnail: 'thumbnail.png') }
# get 'Retrieves a blog' do
# tags 'Blogs'
# description 'Retrieves a specific blog by id'
# operationId 'getBlog'
# produces 'application/json'
# response '200', 'blog found' do
# header 'ETag', type: :string
# header 'Last-Modified', type: :string
# header 'Cache-Control', type: :string
# schema '$ref' => '#/definitions/blog'
# examples 'application/json' => {
# id: 1,
# title: 'Hello world!',
# content: 'Hello world and hello universe. Thank you all very much!!!',
# thumbnail: 'thumbnail.png'
# }
# let(:id) { blog.id }
# run_test!
# end
# response '404', 'blog not found' do
# let(:id) { 'invalid' }
# run_test!
# end
# end
# end
# path '/blogs/{id}/upload' do
# parameter name: :id, in: :path, type: :string
# let(:id) { blog.id }
# let(:blog) { Blog.create(title: 'foo', content: 'bar') }
# put 'Uploads a blog thumbnail' do
# tags 'Blogs'
# description 'Upload a thumbnail for specific blog by id'
# operationId 'uploadThumbnailBlog'
# consumes 'multipart/form-data'
# parameter name: :file, :in => :formData, :type => :file, required: true
# response '200', 'blog updated' do
# let(:file) { Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) }
# run_test!
# end
# end
# end
end

View File

@ -1,10 +1,8 @@
# frozen_string_literal: true
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production
abort('The Rails environment is running in production mode!') if Rails.env.production?
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
@ -53,12 +51,9 @@ RSpec.configure do |config|
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
Capybara.register_driver :firefox_headless do |app|
options = ::Selenium::WebDriver::Firefox::Options.new
options.args << '--headless'
Capybara::Selenium::Driver.new(app, browser: :firefox, options: options)
end
Capybara.javascript_driver = :firefox_headless
Capybara.javascript_driver = :webkit
end
Capybara::Webkit.configure do |config|
config.block_unknown_urls
end

View File

@ -1,17 +1,15 @@
# frozen_string_literal: true
require 'spec_helper'
require 'rake'
RSpec.describe 'rswag:specs:swaggerize' do
describe 'rswag:specs:swaggerize' do
let(:swagger_root) { Rails.root.to_s + '/swagger' }
before do
before do
TestApp::Application.load_tasks
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root)
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
end
it 'generates Swagger JSON files from integration specs' do
Rake::Task['rswag:specs:swaggerize'].invoke
expect { Rake::Task['rswag:specs:swaggerize'].invoke }.not_to raise_exception
expect(File).to exist("#{swagger_root}/v1/swagger.json")
end
end

View File

@ -1,5 +1,3 @@
# frozen_string_literal: true
# This file was generated by the `rails generate rspec:install` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause
@ -52,4 +50,54 @@ RSpec.configure do |config|
config.after(:suite) do
File.delete("#{Rails.root}/tmp/thumbnail.png") if File.file?("#{Rails.root}/tmp/thumbnail.png")
end
# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
=begin
# This allows you to limit a spec run to individual examples or groups
# you care about by tagging them with `:focus` metadata. When nothing
# is tagged with `:focus`, all examples get run. RSpec also provides
# aliases for `it`, `describe`, and `context` that include `:focus`
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
config.filter_run_when_matching :focus
# Allows RSpec to persist some state between runs in order to support
# the `--only-failures` and `--next-failure` CLI options. We recommend
# you configure your source control system to ignore this file.
config.example_status_persistence_file_path = "spec/examples.txt"
# Limits the available syntax to the non-monkey patched syntax that is
# recommended. For more details, see:
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
config.disable_monkey_patching!
# Many RSpec users commonly either run the entire suite or an individual
# file, and it's useful to allow more verbose output when running an
# individual spec file.
if config.files_to_run.one?
# Use the documentation formatter for detailed output,
# unless a formatter has already been configured
# (e.g. via a command-line flag).
config.default_formatter = 'doc'
end
# Print the 10 slowest examples and example groups at the
# end of the spec run, to help surface which specs are running
# particularly slow.
config.profile_examples = 10
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = :random
# Seed global randomization in this process using the `--seed` CLI option.
# Setting this allows you to use `--seed` to deterministically reproduce
# test failures related to randomization by passing the same `--seed` value
# as the one that triggered the failure.
Kernel.srand config.seed
=end
end

View File

@ -1,5 +1,3 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.configure do |config|
@ -7,7 +5,7 @@ RSpec.configure do |config|
# NOTE: If you're using the rswag-api to serve API descriptions, you'll need
# to ensure that it's configured to serve Swagger from the same folder
config.swagger_root = Rails.root.to_s + '/swagger'
config.swagger_dry_run = false
# Define one or more Swagger documents and provide global metadata for each one
# When you run the 'rswag:specs:to_swagger' rake task, the complete Swagger will
# be generated at the provided relative path under swagger_root
@ -16,22 +14,12 @@ RSpec.configure do |config|
# the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
config.swagger_docs = {
'v1/swagger.json' => {
openapi: '3.0.0',
swagger: '2.0',
info: {
title: 'API V1',
version: 'v1'
},
paths: {},
servers: [
{
url: 'https://{defaultHost}',
variables: {
defaultHost: {
default: 'www.example.com'
}
}
}
],
definitions: {
errors_object: {
type: 'object',
@ -52,98 +40,19 @@ RSpec.configure do |config|
id: { type: 'integer' },
title: { type: 'string' },
content: { type: 'string', 'x-nullable': true },
thumbnail: { type: 'string', 'x-nullable': true}
thumbnail: { type: 'string'}
},
required: [ 'id', 'title' ]
},
flexible_blog: {
type: 'object',
properties: {
id: { type: 'integer' },
headline: { type: 'string' },
text: { type: 'string', nullable: true },
thumbnail: { type: 'string', nullable: true }
},
required: ['id', 'headline']
required: [ 'id', 'title', 'content', 'thumbnail' ]
}
},
components: {
securitySchemes: {
basic_auth: {
type: :http,
scheme: :basic
},
api_key: {
type: :apiKey,
name: 'api_key',
in: :query
}
}
}
},
'v3/openapi.json' => {
openapi: '3.0.0',
info: {
title: 'API V1',
version: 'v1'
},
paths: {},
servers: [
{
url: 'https://{defaultHost}',
variables: {
defaultHost: {
default: 'www.example.com'
}
}
}
],
definitions: {
errors_object: {
type: 'object',
properties: {
errors: { '$ref' => '#/definitions/errors_map' }
}
securityDefinitions: {
basic_auth: {
type: :basic
},
errors_map: {
type: 'object',
additionalProperties: {
type: 'array',
items: { type: 'string' }
}
},
blog: {
type: 'object',
properties: {
id: { type: 'integer' },
title: { type: 'string' },
content: { type: 'string', 'x-nullable': true },
thumbnail: { type: 'string', 'x-nullable': true}
},
required: [ 'id', 'title' ]
},
flexible_blog: {
type: 'object',
properties: {
id: { type: 'integer' },
headline: { type: 'string' },
text: { type: 'string', nullable: true },
thumbnail: { type: 'string', nullable: true }
},
required: ['id', 'headline']
}
},
components: {
securitySchemes: {
basic_auth: {
type: :http,
scheme: :basic
},
api_key: {
type: :apiKey,
name: 'api_key',
in: :query
}
api_key: {
type: :apiKey,
name: 'api_key',
in: :query
}
}
}

View File

@ -1,5 +1,5 @@
{
"openapi": "3.0.0",
"swagger": "2.0",
"info": {
"title": "API V1",
"version": "v1"
@ -88,8 +88,20 @@
],
"description": "Creates a new blog from provided data",
"operationId": "createBlog",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"name": "blog",
"in": "body",
"schema": {
"$ref": "#/definitions/blog"
}
}
],
"responses": {
"201": {
@ -97,21 +109,8 @@
},
"422": {
"description": "invalid request",
"content": {
"application/json": {
"schema": {
"$ref": "#/definitions/errors_object"
}
}
}
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/definitions/blog"
}
"schema": {
"$ref": "#/definitions/errors_object"
}
}
}
@ -123,13 +122,14 @@
],
"description": "Searches blogs by keywords",
"operationId": "searchBlogs",
"produces": [
"application/json"
],
"parameters": [
{
"name": "keywords",
"in": "query",
"schema": {
"type": "string"
}
"type": "string"
}
],
"responses": {
@ -139,63 +139,13 @@
}
}
},
"/blogs/flexible": {
"post": {
"summary": "Creates a blog flexible body",
"tags": [
"Blogs"
],
"description": "Creates a flexible blog from provided data",
"operationId": "createFlexibleBlog",
"parameters": [
],
"responses": {
"201": {
"description": "flexible blog created",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/definitions/blog"
},
{
"$ref": "#/definitions/flexible_blog"
}
]
}
}
}
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/definitions/blog"
},
{
"$ref": "#/definitions/flexible_blog"
}
]
}
}
}
}
}
},
"/blogs/{id}": {
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
"type": "string",
"required": true
}
],
"get": {
@ -205,6 +155,9 @@
],
"description": "Retrieves a specific blog by id",
"operationId": "getBlog",
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "blog found",
@ -219,17 +172,15 @@
"type": "string"
}
},
"content": {
"schema": {
"$ref": "#/definitions/blog"
},
"examples": {
"application/json": {
"example": {
"id": 1,
"title": "Hello world!",
"content": "Hello world and hello universe. Thank you all very much!!!",
"thumbnail": "thumbnail.png"
},
"schema": {
"$ref": "#/definitions/blog"
}
"id": 1,
"title": "Hello world!",
"content": "Hello world and hello universe. Thank you all very much!!!",
"thumbnail": "thumbnail.png"
}
}
},
@ -244,10 +195,8 @@
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
"type": "string",
"required": true
}
],
"put": {
@ -257,37 +206,25 @@
],
"description": "Upload a thumbnail for specific blog by id",
"operationId": "uploadThumbnailBlog",
"consumes": [
"multipart/form-data"
],
"parameters": [
{
"name": "file",
"in": "formData",
"type": "file",
"required": true
}
],
"responses": {
"200": {
"description": "blog updated"
}
},
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "file"
}
}
},
"required": true
}
}
}
},
"servers": [
{
"url": "https://{defaultHost}",
"variables": {
"defaultHost": {
"default": "www.example.com"
}
}
}
],
"definitions": {
"errors_object": {
"type": "object",
@ -320,50 +257,25 @@
"x-nullable": true
},
"thumbnail": {
"type": "string",
"x-nullable": true
}
},
"required": [
"id",
"title"
]
},
"flexible_blog": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"headline": {
"type": "string"
},
"text": {
"type": "string",
"nullable": true
},
"thumbnail": {
"type": "string",
"nullable": true
}
},
"required": [
"id",
"headline"
"title",
"content",
"thumbnail"
]
}
},
"components": {
"securitySchemes": {
"basic_auth": {
"type": "http",
"scheme": "basic"
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "query"
}
"securityDefinitions": {
"basic_auth": {
"type": "basic"
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "query"
}
}
}

View File

@ -1,160 +0,0 @@
{
"openapi": "3.0.0",
"info": {
"title": "API V1",
"version": "v1"
},
"paths": {
"/stubs": {
"get": {
"summary": "a summary",
"tags": [
"Parameter Serialization: Query String"
],
"responses": {
"200": {
"description": "OK"
}
},
"parameters": [
{
"name": "a_param",
"in": "query"
}
]
},
"post": {
"summary": "body is required",
"tags": [
"Media Types"
],
"parameters": [
],
"responses": {
"200": {
"description": "OK"
}
},
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "file"
}
}
},
"required": true
}
}
},
"/stubs/{a_param}": {
"get": {
"summary": "a summary",
"tags": [
"Parameter Serialization: Query String"
],
"parameters": [
{
"name": "a_param",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
}
},
"servers": [
{
"url": "https://{defaultHost}",
"variables": {
"defaultHost": {
"default": "www.example.com"
}
}
}
],
"definitions": {
"errors_object": {
"type": "object",
"properties": {
"errors": {
"$ref": "#/definitions/errors_map"
}
}
},
"errors_map": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"blog": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"title": {
"type": "string"
},
"content": {
"type": "string",
"x-nullable": true
},
"thumbnail": {
"type": "string",
"x-nullable": true
}
},
"required": [
"id",
"title"
]
},
"flexible_blog": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"headline": {
"type": "string"
},
"text": {
"type": "string",
"nullable": true
},
"thumbnail": {
"type": "string",
"nullable": true
}
},
"required": [
"id",
"headline"
]
}
},
"components": {
"securitySchemes": {
"basic_auth": {
"type": "http",
"scheme": "basic"
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "query"
}
}
}
}