mirror of
https://github.com/ditkrg/rswag.git
synced 2026-01-22 22:06:43 +00:00
Compare commits
224 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16e40bfcf3 | ||
|
|
8c6aed4a9e | ||
|
|
8e59d2b2f2 | ||
|
|
a813b63bcc | ||
|
|
076f71388c | ||
|
|
9cfb5bc557 | ||
|
|
6cafc0c0e5 | ||
|
|
86c512f639 | ||
|
|
32638062d7 | ||
|
|
dd6530b718 | ||
|
|
095906da58 | ||
|
|
a40a06646f | ||
|
|
42c8e123a8 | ||
|
|
c85fa3b37a | ||
|
|
985f444834 | ||
|
|
1d2a25e231 | ||
|
|
a0680506e2 | ||
|
|
4a32108f78 | ||
|
|
05e22c3bd7 | ||
|
|
989aab656f | ||
|
|
f7036b7e5a | ||
|
|
84ab7a9e4c | ||
|
|
0169fbab66 | ||
|
|
4c42ad5f97 | ||
|
|
b91b6e5f1e | ||
|
|
a34c931bb6 | ||
|
|
4b7ab9d381 | ||
|
|
3d3d93f3ab | ||
|
|
0aca50c66c | ||
|
|
9644a16bce | ||
|
|
52939874d6 | ||
|
|
7ef900ec1d | ||
|
|
c0142093d4 | ||
|
|
1f4ecb3c10 | ||
|
|
aa4e6f2070 | ||
|
|
eadaf34ef6 | ||
|
|
cbaf6cd3e4 | ||
|
|
670c94cc44 | ||
|
|
b86d3063a8 | ||
|
|
7f88c5d1f3 | ||
|
|
d645df207e | ||
|
|
b63dd343af | ||
|
|
bd48e40529 | ||
|
|
465950a65c | ||
|
|
be160c5552 | ||
|
|
ad95f1098a | ||
|
|
83567e0ee2 | ||
|
|
206c088d74 | ||
|
|
5a60dee838 | ||
|
|
13a3977c2c | ||
|
|
0d1a742f6f | ||
|
|
c62bfda91d | ||
|
|
c161de3899 | ||
|
|
ab457743a8 | ||
|
|
3e10b09f23 | ||
|
|
d090516f48 | ||
|
|
91a27852f2 | ||
|
|
68e64dba2c | ||
|
|
f6630cc7a6 | ||
|
|
7e1a79220c | ||
|
|
b37c7905cd | ||
|
|
0a9487b328 | ||
|
|
b3fa5ba54e | ||
|
|
7e2c52b242 | ||
|
|
8d673068af | ||
|
|
d558bd4774 | ||
|
|
c990348f0b | ||
|
|
38c861f5e0 | ||
|
|
1cd7df89f1 | ||
|
|
04059808d5 | ||
|
|
4c06f95a68 | ||
|
|
08b1678e53 | ||
|
|
27b015b3d2 | ||
|
|
eb14eba7eb | ||
|
|
e419635209 | ||
|
|
ea89b0b0d5 | ||
|
|
423e86c463 | ||
|
|
89f95694a4 | ||
|
|
6133179aa5 | ||
|
|
89e69915f8 | ||
|
|
4e300b2255 | ||
|
|
3cb3f4a651 | ||
|
|
267007ef1b | ||
|
|
347f9da32e | ||
|
|
30002e5b98 | ||
|
|
a7944d48d6 | ||
|
|
015b1c8b56 | ||
|
|
36e79da0fd | ||
|
|
3aff6bca1f | ||
|
|
f0fba9c093 | ||
|
|
bec4f16676 | ||
|
|
7c3c357291 | ||
|
|
81c110022e | ||
|
|
1ff396fb56 | ||
|
|
9c297317b2 | ||
|
|
3b85f09acf | ||
|
|
9727ec34b7 | ||
|
|
f8dbd98bbc | ||
|
|
7ceedab4cb | ||
|
|
e7fb44fcf5 | ||
|
|
2797866224 | ||
|
|
4de6661f4c | ||
|
|
42fdf6d482 | ||
|
|
24a1483929 | ||
|
|
4ab432080b | ||
|
|
a96a466205 | ||
|
|
965f14406f | ||
|
|
d644a91da5 | ||
|
|
b158f1e164 | ||
|
|
e53f2ca257 | ||
|
|
2af7c13e59 | ||
|
|
cbc7a33ac3 | ||
|
|
f1f8b0ed18 | ||
|
|
96fc5276c4 | ||
|
|
d66be41d04 | ||
|
|
bd038949b4 | ||
|
|
74ea37e048 | ||
|
|
d32f098e0b | ||
|
|
1996f5130f | ||
|
|
8ae6653cf7 | ||
|
|
05dfd08f15 | ||
|
|
e0ed6bb646 | ||
|
|
5e2663eb6a | ||
|
|
bd06ebe526 | ||
|
|
b000c94ea0 | ||
|
|
02cf2e668b | ||
|
|
1f745003ff | ||
|
|
cddb7ae614 | ||
|
|
405ccca494 | ||
|
|
c739228c89 | ||
|
|
3393263df7 | ||
|
|
9916d3f0b0 | ||
|
|
56eec5948e | ||
|
|
6b4f49aacb | ||
|
|
5060697761 | ||
|
|
231a2d135c | ||
|
|
b70e3a0794 | ||
|
|
3046ea9f33 | ||
|
|
e9aebe6221 | ||
|
|
eb58fe687a | ||
|
|
da230a4f3e | ||
|
|
a25307dc69 | ||
|
|
9414ca16b6 | ||
|
|
70eb277e04 | ||
|
|
23a1074d07 | ||
|
|
095067792f | ||
|
|
2b239ef0f3 | ||
|
|
0020d71c90 | ||
|
|
b5e210cd96 | ||
|
|
333a0b535a | ||
|
|
e21f786926 | ||
|
|
f331e064fd | ||
|
|
79304a1bc1 | ||
|
|
b9ac008a9f | ||
|
|
7b01ae1aa1 | ||
|
|
a43e6f6546 | ||
|
|
6e706c04d7 | ||
|
|
19e985a828 | ||
|
|
63f01962ad | ||
|
|
e6bfba23d7 | ||
|
|
4c613af2ba | ||
|
|
3cd9d38e19 | ||
|
|
02a5bc988f | ||
|
|
c904a32e51 | ||
|
|
9cbd85a22a | ||
|
|
ef78b0c98f | ||
|
|
354208ac03 | ||
|
|
b29373c0c4 | ||
|
|
2e9015dd12 | ||
|
|
db45cafea2 | ||
|
|
d01dff199c | ||
|
|
ca8a2b6da2 | ||
|
|
ac1d61b08f | ||
|
|
2c3c23e460 | ||
|
|
90df5bfa30 | ||
|
|
bff44ee06e | ||
|
|
72becffefe | ||
|
|
6a4cc8de8d | ||
|
|
5fe1637407 | ||
|
|
275c3b1994 | ||
|
|
3da9eda063 | ||
|
|
8f2378eb6f | ||
|
|
6467ce6543 | ||
|
|
4670dcd4b0 | ||
|
|
91ae7732c2 | ||
|
|
90af919af3 | ||
|
|
02f72a4b1a | ||
|
|
5555edc59f | ||
|
|
ce8237110f | ||
|
|
5e48a2cae3 | ||
|
|
9ba484dc37 | ||
|
|
5a8d1ce359 | ||
|
|
9bc6e2e16a | ||
|
|
04298d655b | ||
|
|
157175c90f | ||
|
|
6d285d2c4f | ||
|
|
c889e407ec | ||
|
|
032ad5dc54 | ||
|
|
475929e9aa | ||
|
|
13f7007b2f | ||
|
|
db3f321b45 | ||
|
|
27a7481b48 | ||
|
|
b8dcc8fe30 | ||
|
|
4c2097e017 | ||
|
|
cd348b53f8 | ||
|
|
eb4e6045c5 | ||
|
|
4baf5efd11 | ||
|
|
04564d933f | ||
|
|
aa59c5ff91 | ||
|
|
659b328eda | ||
|
|
5e71651d6d | ||
|
|
28bcc121ba | ||
|
|
aa133b90fc | ||
|
|
c820bb75e0 | ||
|
|
23349b2678 | ||
|
|
fd061a2a7f | ||
|
|
0093efd4bf | ||
|
|
297cc447c8 | ||
|
|
5d7fc44af4 | ||
|
|
768a1a1d43 | ||
|
|
d00bb06e19 | ||
|
|
529cfae73e | ||
|
|
875bbfa04b | ||
|
|
b0712418a3 |
60
.github/workflows/ruby.yml
vendored
Normal file
60
.github/workflows/ruby.yml
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
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
2
.gitignore
vendored
@ -6,3 +6,5 @@
|
||||
**/*/node_modules
|
||||
*.swp
|
||||
Gemfile.lock
|
||||
/.idea/
|
||||
**/.byebug_history
|
||||
|
||||
@ -1 +1 @@
|
||||
2.6.3
|
||||
2.7.2
|
||||
|
||||
45
CHANGELOG.md
45
CHANGELOG.md
@ -1,16 +1,49 @@
|
||||
# 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]
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
### Deprecated
|
||||
### Removed
|
||||
- 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
|
||||
|
||||
### Fixed
|
||||
### Security
|
||||
- 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
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
# 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
|
||||
@ -24,6 +30,11 @@ cd -
|
||||
```
|
||||
|
||||
## Test
|
||||
Initialize the rswag-ui repo with assets.
|
||||
```
|
||||
ci/build.sh
|
||||
```
|
||||
|
||||
Make sure the tests pass:
|
||||
```
|
||||
./ci/test.sh
|
||||
@ -44,6 +55,19 @@ 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
30
Gemfile
@ -1,10 +1,12 @@
|
||||
source "https://rubygems.org"
|
||||
# frozen_string_literal: true
|
||||
|
||||
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.1.2'
|
||||
rails_version = ENV['RAILS_VERSION'] || '5.2.4.2'
|
||||
|
||||
gem 'rails', "#{rails_version}"
|
||||
gem 'rails', rails_version.to_s
|
||||
|
||||
case rails_version.split('.').first
|
||||
when '3'
|
||||
@ -23,18 +25,26 @@ end
|
||||
gem 'rswag-api', path: './rswag-api'
|
||||
gem 'rswag-ui', path: './rswag-ui'
|
||||
|
||||
group :test do
|
||||
gem 'test-unit'
|
||||
gem 'rspec-rails'
|
||||
gem 'generator_spec'
|
||||
gem 'capybara'
|
||||
gem 'capybara-webkit'
|
||||
group :development, :test do
|
||||
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'
|
||||
|
||||
20
MIT-LICENCE
Normal file
20
MIT-LICENCE
Normal file
@ -0,0 +1,20 @@
|
||||
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.
|
||||
483
README.md
483
README.md
@ -3,10 +3,15 @@ rswag
|
||||
[](https://travis-ci.org/rswag/rswag)
|
||||
[](https://codeclimate.com/github/rswag/rswag/maintainability)
|
||||
|
||||
[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.
|
||||
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.
|
||||
|
||||
|
||||
And that's not all ...
|
||||
|
||||
Once you have an API that can describe itself in Swagger, you've opened the treasure chest of Swagger-based tools including a client generator that can be targeted to a wide range of popular platforms. See [swagger-codegen](https://github.com/swagger-api/swagger-codegen) for more details.
|
||||
@ -15,10 +20,50 @@ 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)|2.0|3.18.2|
|
||||
|[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|
|
||||
|[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_:
|
||||
@ -56,6 +101,7 @@ Once you have an API that can describe itself in Swagger, you've opened the trea
|
||||
```
|
||||
|
||||
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
|
||||
@ -67,7 +113,7 @@ Once you have an API that can describe itself in Swagger, you've opened the trea
|
||||
|
||||
post 'Creates a blog' do
|
||||
tags 'Blogs'
|
||||
consumes 'application/json', 'application/xml'
|
||||
consumes 'application/json'
|
||||
parameter name: :blog, in: :body, schema: {
|
||||
type: :object,
|
||||
properties: {
|
||||
@ -92,9 +138,9 @@ Once you have an API that can describe itself in Swagger, you've opened the trea
|
||||
path '/blogs/{id}' do
|
||||
|
||||
get 'Retrieves a blog' do
|
||||
tags 'Blogs'
|
||||
tags 'Blogs', 'Another Tag'
|
||||
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,
|
||||
@ -123,8 +169,6 @@ Once you have an API that can describe itself in Swagger, you've opened the trea
|
||||
end
|
||||
```
|
||||
|
||||
There is also a generator which can help get you started `rails generate rspec:swagger API::MyController`
|
||||
|
||||
|
||||
4. Generate the Swagger JSON file(s)
|
||||
|
||||
@ -134,6 +178,11 @@ Once you have an API that can describe itself in Swagger, you've opened the trea
|
||||
|
||||
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 ##
|
||||
@ -173,7 +222,7 @@ end
|
||||
|
||||
### Null Values ###
|
||||
|
||||
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.
|
||||
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.
|
||||
```ruby
|
||||
describe 'Blogs API' do
|
||||
path '/blogs' do
|
||||
@ -184,8 +233,8 @@ describe 'Blogs API' do
|
||||
schema type: :object,
|
||||
properties: {
|
||||
id: { type: :integer },
|
||||
title: { type: :string },
|
||||
content: { type: :string, 'x-nullable': true }
|
||||
title: { type: :string, nullable: true }, # preferred syntax
|
||||
content: { type: :string, 'x-nullable': true } # legacy syntax, but still works
|
||||
}
|
||||
....
|
||||
end
|
||||
@ -193,12 +242,45 @@ describe 'Blogs API' do
|
||||
end
|
||||
end
|
||||
```
|
||||
*Note:* OAI v3 has a nullable property. Rswag will work to support this soon. 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>
|
||||
|
||||
### 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.
|
||||
|
||||
### 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 and URL basePath:
|
||||
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/):
|
||||
|
||||
```ruby
|
||||
# spec/swagger_helper.rb
|
||||
@ -207,23 +289,41 @@ RSpec.configure do |config|
|
||||
|
||||
config.swagger_docs = {
|
||||
'v1/swagger.json' => {
|
||||
swagger: '2.0',
|
||||
openapi: '3.0.1',
|
||||
info: {
|
||||
title: 'API V1',
|
||||
version: 'v1',
|
||||
description: 'This is the first version of my API'
|
||||
},
|
||||
basePath: '/api/v1'
|
||||
servers: [
|
||||
{
|
||||
url: 'https://{defaultHost}',
|
||||
variables: {
|
||||
defaultHost: {
|
||||
default: 'www.example.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
'v2/swagger.yaml' => {
|
||||
openapi: '3.0.0',
|
||||
openapi: '3.0.1',
|
||||
info: {
|
||||
title: 'API V2',
|
||||
version: 'v2',
|
||||
description: 'This is the second version of my API'
|
||||
},
|
||||
basePath: '/api/v2'
|
||||
servers: [
|
||||
{
|
||||
url: 'https://{defaultHost}',
|
||||
variables: {
|
||||
defaultHost: {
|
||||
default: 'www.example.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
end
|
||||
@ -265,7 +365,9 @@ 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, :apiKey and :oauth2 scheme types. See [the spec](http://swagger.io/specification/#security-definitions-object-109) for more info.
|
||||
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
|
||||
|
||||
```ruby
|
||||
# spec/swagger_helper.rb
|
||||
@ -274,15 +376,18 @@ RSpec.configure do |config|
|
||||
|
||||
config.swagger_docs = {
|
||||
'v1/swagger.json' => {
|
||||
...
|
||||
securityDefinitions: {
|
||||
basic: {
|
||||
type: :basic
|
||||
},
|
||||
apiKey: {
|
||||
type: :apiKey,
|
||||
name: 'api_key',
|
||||
in: :query
|
||||
... # 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -296,7 +401,7 @@ describe 'Blogs API' do
|
||||
|
||||
post 'Creates a blog' do
|
||||
tags 'Blogs'
|
||||
security [ basic: [] ]
|
||||
security [ basic_auth: [] ]
|
||||
...
|
||||
|
||||
response '201', 'blog created' do
|
||||
@ -311,9 +416,35 @@ 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 ##
|
||||
|
||||
@ -322,7 +453,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 ###
|
||||
@ -337,32 +468,65 @@ 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.
|
||||
__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"
|
||||
```
|
||||
|
||||
### 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. 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.
|
||||
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:
|
||||
|
||||
```ruby
|
||||
# spec/swagger_helper.rb
|
||||
config.swagger_docs = {
|
||||
'v1/swagger.json' => {
|
||||
swagger: '2.0',
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'API V1'
|
||||
},
|
||||
definitions: {
|
||||
errors_object: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
errors: { '$ref' => '#/definitions/errors_map' }
|
||||
}
|
||||
},
|
||||
errors_map: {
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: 'array',
|
||||
items: { type: 'string' }
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -376,8 +540,10 @@ 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' => '#/definitions/errors_object'
|
||||
schema '$ref' => '#/components/schemas/errors_object'
|
||||
...
|
||||
end
|
||||
|
||||
@ -389,14 +555,15 @@ describe 'Blogs API' do
|
||||
post 'Creates a comment' do
|
||||
|
||||
response 422, 'invalid request' do
|
||||
schema '$ref' => '#/definitions/errors_object'
|
||||
schema '$ref' => '#/components/schemas/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
|
||||
@ -416,7 +583,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
|
||||
@ -435,33 +602,192 @@ describe 'Blogs API' do
|
||||
end
|
||||
```
|
||||
|
||||
### Enable generation examples from responses ###
|
||||
|
||||
### Enable auto generation examples from responses ###
|
||||
|
||||
|
||||
To enable examples generation from responses add callback above run_test! like:
|
||||
```ruby
|
||||
|
||||
```
|
||||
after do |example|
|
||||
example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) }
|
||||
example.metadata[:response][:content] = {
|
||||
'application/json' => {
|
||||
example: JSON.parse(response.body, symbolize_names: true)
|
||||
}
|
||||
}
|
||||
end
|
||||
```
|
||||
You need to disable --dry-run option for Rspec > 3
|
||||
|
||||
Add to application.rb:
|
||||
#### 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`:
|
||||
|
||||
```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 simply nullify the response metadata after the test run.
|
||||
#### 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
|
||||
after do |example|
|
||||
example.metadata[:response] = null
|
||||
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_:
|
||||
@ -482,7 +808,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|
|
||||
@ -507,6 +833,21 @@ 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:
|
||||
@ -518,6 +859,17 @@ 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_:
|
||||
@ -551,3 +903,14 @@ 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.
|
||||
|
||||
@ -5,4 +5,4 @@ Example:
|
||||
rails generate rswag:api:install
|
||||
|
||||
This will create:
|
||||
config/initializers/rswag-api.rb
|
||||
config/initializers/rswag_api.rb
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
module Rswag
|
||||
module Api
|
||||
class Configuration
|
||||
attr_accessor :swagger_root, :swagger_filter
|
||||
attr_accessor :swagger_root, :swagger_filter, :swagger_headers
|
||||
|
||||
def resolve_swagger_root(env)
|
||||
path_params = env['action_dispatch.request.path_parameters'] || {}
|
||||
|
||||
@ -19,11 +19,12 @@ module Rswag
|
||||
swagger = parse_file(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',
|
||||
{ 'Content-Type' => mime },
|
||||
headers,
|
||||
[ body ]
|
||||
]
|
||||
end
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
# frozen_string_literal: true
|
||||
|
||||
$LOAD_PATH.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"]
|
||||
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.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.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile"]
|
||||
s.files = Dir['{lib}/**/*'] + ['MIT-LICENSE', 'Rakefile']
|
||||
|
||||
s.add_dependency 'railties', '>= 3.1', '< 6.1'
|
||||
s.add_dependency 'railties', '>= 3.1', '< 7.1'
|
||||
end
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
require 'generator_spec'
|
||||
require 'generators/rswag/api/install/install_generator'
|
||||
|
||||
|
||||
module Rswag
|
||||
module Api
|
||||
|
||||
@ -17,7 +18,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
|
||||
@ -25,3 +26,4 @@ module Rswag
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"openapi": "3.0.1",
|
||||
"info": {
|
||||
"title": "API V1",
|
||||
"version": "v1"
|
||||
|
||||
@ -37,6 +37,42 @@ 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
|
||||
@ -61,7 +97,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('"swagger":"2.0"')
|
||||
expect(response[2].join).to include('"openapi":"3.0.1"')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -20,8 +20,4 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
Bundler::GemHelper.install_tasks
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/route_parser'
|
||||
require 'rails/generators'
|
||||
|
||||
module Rspec
|
||||
class SwaggerGenerator < ::Rails::Generators::NamedBase
|
||||
source_root File.expand_path('../templates', __FILE__)
|
||||
source_root File.expand_path('templates', __dir__)
|
||||
|
||||
def setup
|
||||
@routes = Rswag::RouteParser.new(controller_path).routes
|
||||
|
||||
@ -19,7 +19,11 @@ RSpec.describe '<%= controller_path %>', type: :request do
|
||||
<% end -%>
|
||||
|
||||
after do |example|
|
||||
example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) }
|
||||
example.metadata[:response][:content] = {
|
||||
'application/json' => {
|
||||
example: JSON.parse(response.body, symbolize_names: true)
|
||||
}
|
||||
}
|
||||
end
|
||||
run_test!
|
||||
end
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails/generators'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
class InstallGenerator < Rails::Generators::Base
|
||||
source_root File.expand_path('../templates', __FILE__)
|
||||
source_root File.expand_path('templates', __dir__)
|
||||
|
||||
def add_swagger_helper
|
||||
template('swagger_helper.rb', 'spec/swagger_helper.rb')
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.configure do |config|
|
||||
@ -19,7 +21,17 @@ RSpec.configure do |config|
|
||||
title: 'API V1',
|
||||
version: 'v1'
|
||||
},
|
||||
paths: {}
|
||||
paths: {},
|
||||
servers: [
|
||||
{
|
||||
url: 'https://{defaultHost}',
|
||||
variables: {
|
||||
defaultHost: {
|
||||
default: 'www.example.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rswag
|
||||
class RouteParser
|
||||
attr_reader :controller
|
||||
@ -9,7 +11,7 @@ module Rswag
|
||||
def routes
|
||||
::Rails.application.routes.routes.select do |route|
|
||||
route.defaults[:controller] == controller
|
||||
end.reduce({}) do |tree, route|
|
||||
end.each_with_object({}) do |route, tree|
|
||||
path = path_from(route)
|
||||
verb = verb_from(route)
|
||||
tree[path] ||= { params: params_from(route), actions: {} }
|
||||
@ -28,7 +30,7 @@ module Rswag
|
||||
|
||||
def verb_from(route)
|
||||
verb = route.verb
|
||||
if verb.kind_of? String
|
||||
if verb.is_a? String
|
||||
verb.downcase
|
||||
else
|
||||
verb.source.gsub(/[$^]/, '').downcase
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rspec/core'
|
||||
require 'rswag/specs/example_group_helpers'
|
||||
require 'rswag/specs/example_helpers'
|
||||
@ -6,7 +8,6 @@ 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
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
class Configuration
|
||||
|
||||
def initialize(rspec_config)
|
||||
@rspec_config = rspec_config
|
||||
end
|
||||
@ -12,6 +12,7 @@ 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
|
||||
@ -21,20 +22,24 @@ 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
|
||||
@swagger_dry_run ||= begin
|
||||
@rspec_config.swagger_dry_run.nil? || @rspec_config.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
|
||||
end
|
||||
end
|
||||
@ -42,8 +47,14 @@ module Rswag
|
||||
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
|
||||
|
||||
@ -1,20 +1,21 @@
|
||||
# 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, :options, :trace].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
|
||||
@ -23,13 +24,14 @@ 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
|
||||
@ -40,7 +42,7 @@ module Rswag
|
||||
attributes[:required] = true
|
||||
end
|
||||
|
||||
if metadata.has_key?(:operation)
|
||||
if metadata.key?(:operation)
|
||||
metadata[:operation][:parameters] ||= []
|
||||
metadata[:operation][:parameters] << attributes
|
||||
else
|
||||
@ -49,7 +51,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
|
||||
@ -60,6 +62,7 @@ module Rswag
|
||||
|
||||
def header(name, attributes)
|
||||
metadata[:response][:headers] ||= {}
|
||||
|
||||
metadata[:response][:headers][name] = attributes
|
||||
end
|
||||
|
||||
@ -68,7 +71,11 @@ module Rswag
|
||||
# rspec-core ExampleGroup
|
||||
def examples(example = nil)
|
||||
return super() if example.nil?
|
||||
metadata[:response][:examples] = example
|
||||
|
||||
metadata[:response][:content] =
|
||||
example.each_with_object({}) do |(mime, example_object), memo|
|
||||
memo[mime] = { example: example_object }
|
||||
end
|
||||
end
|
||||
|
||||
def run_test!(&block)
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
# 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)
|
||||
|
||||
@ -19,10 +20,8 @@ module Rswag
|
||||
send(
|
||||
request[:verb],
|
||||
request[:path],
|
||||
{
|
||||
params: request[:payload],
|
||||
headers: request[:headers]
|
||||
}
|
||||
params: request[:payload],
|
||||
headers: request[:headers]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'json-schema'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
class ExtendedSchema < JSON::Schema::Draft4
|
||||
|
||||
def initialize
|
||||
super
|
||||
@attributes['type'] = ExtendedTypeAttribute
|
||||
@ -13,9 +14,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
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
class Railtie < ::Rails::Railtie
|
||||
|
||||
rake_tasks do
|
||||
load File.expand_path('../../../tasks/rswag-specs_tasks.rake', __FILE__)
|
||||
load File.expand_path('../../tasks/rswag-specs_tasks.rake', __dir__)
|
||||
end
|
||||
|
||||
generators do
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
require 'active_support/core_ext/hash/conversions'
|
||||
require 'json'
|
||||
@ -5,7 +7,6 @@ require 'json'
|
||||
module Rswag
|
||||
module Specs
|
||||
class RequestFactory
|
||||
|
||||
def initialize(config = ::Rswag::Specs.config)
|
||||
@config = config
|
||||
end
|
||||
@ -38,8 +39,8 @@ module Rswag
|
||||
|
||||
def derive_security_params(metadata, swagger_doc)
|
||||
requirements = metadata[:operation][:security] || swagger_doc[:security] || []
|
||||
scheme_names = requirements.flat_map { |r| r.keys }
|
||||
schemes = (swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values
|
||||
scheme_names = requirements.flat_map(&:keys)
|
||||
schemes = security_version(scheme_names, swagger_doc)
|
||||
|
||||
schemes.map do |scheme|
|
||||
param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }
|
||||
@ -47,13 +48,55 @@ 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 = ref.sub('#/parameters/', '').to_sym
|
||||
definitions = swagger_doc[:parameters]
|
||||
key = key_version(ref, swagger_doc)
|
||||
definitions = definition_version(swagger_doc)
|
||||
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)
|
||||
request[:verb] = metadata[:operation][:verb]
|
||||
end
|
||||
@ -61,21 +104,22 @@ module Rswag
|
||||
def add_path(request, metadata, swagger_doc, parameters, example)
|
||||
template = (swagger_doc[:basePath] || '') + metadata[:path_item][:template]
|
||||
|
||||
request[:path] = template.tap do |template|
|
||||
request[:path] = template.tap do |path_template|
|
||||
parameters.select { |p| p[:in] == :path }.each do |p|
|
||||
template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
|
||||
path_template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
|
||||
end
|
||||
|
||||
parameters.select { |p| p[:in] == :query }.each_with_index do |p, i|
|
||||
template.concat(i == 0 ? '?' : '&')
|
||||
template.concat(build_query_string_part(p, example.send(p[:name])))
|
||||
path_template.concat(i.zero? ? '?' : '&')
|
||||
path_template.concat(build_query_string_part(p, example.send(p[:name])))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_query_string_part(param, value)
|
||||
name = param[:name]
|
||||
return "#{name}=#{value.to_s}" unless param[:type].to_sym == :array
|
||||
type = param[:type] || param.dig(:schema, :type)
|
||||
return "#{name}=#{value}" unless type&.to_sym == :array
|
||||
|
||||
case param[:collectionFormat]
|
||||
when :ssv
|
||||
@ -94,43 +138,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]
|
||||
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)
|
||||
@ -144,13 +188,41 @@ 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
|
||||
body_param ? example.send(body_param[:name]).to_json : nil
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
require 'json-schema'
|
||||
require 'json'
|
||||
@ -6,7 +8,6 @@ require 'rswag/specs/extended_schema'
|
||||
module Rswag
|
||||
module Specs
|
||||
class ResponseValidator
|
||||
|
||||
def initialize(config = ::Rswag::Specs.config)
|
||||
@config = config
|
||||
end
|
||||
@ -25,8 +26,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
|
||||
|
||||
@ -41,11 +42,33 @@ 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(swagger_doc.slice(:definitions))
|
||||
.merge(schemas)
|
||||
|
||||
errors = JSON::Validator.fully_validate(validation_schema, body)
|
||||
raise UnexpectedResponse, "Expected response body to match schema: #{errors[0]}" if errors.any?
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
# 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
|
||||
class SwaggerFormatter < ::RSpec::Core::Formatters::BaseTextFormatter
|
||||
|
||||
# NOTE: rspec 2.x support
|
||||
if RSPEC_VERSION > 2
|
||||
@ -19,22 +22,59 @@ module Rswag
|
||||
|
||||
def example_group_finished(notification)
|
||||
# NOTE: rspec 2.x support
|
||||
if RSPEC_VERSION > 2
|
||||
metadata = notification.group.metadata
|
||||
metadata = if RSPEC_VERSION > 2
|
||||
notification.group.metadata
|
||||
else
|
||||
metadata = notification.metadata
|
||||
notification.metadata
|
||||
end
|
||||
|
||||
return unless metadata.has_key?(:response)
|
||||
# !metadata[:document] won't work, since nil means we should generate
|
||||
# docs.
|
||||
return if metadata[:document] == false
|
||||
return unless metadata.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.exists?(dirname)
|
||||
FileUtils.mkdir_p dirname unless File.exist?(dirname)
|
||||
|
||||
File.open(file_path, 'w') do |file|
|
||||
file.write(pretty_generate(doc))
|
||||
@ -57,25 +97,108 @@ module Rswag
|
||||
|
||||
def yaml_prepare(doc)
|
||||
json_doc = JSON.pretty_generate(doc)
|
||||
clean_doc = JSON.parse(json_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
|
||||
|
||||
@ -1,21 +1,24 @@
|
||||
# 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 = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
|
||||
t.pattern = ENV.fetch(
|
||||
'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']
|
||||
|
||||
task rswag: ['rswag:specs:swaggerize']
|
||||
|
||||
@ -1,19 +1,21 @@
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
# frozen_string_literal: true
|
||||
|
||||
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
||||
|
||||
# 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"]
|
||||
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.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.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
|
||||
s.files = Dir['{lib}/**/*'] + ['MIT-LICENSE', 'Rakefile']
|
||||
|
||||
s.add_dependency 'activesupport', '>= 3.1', '< 6.1'
|
||||
s.add_dependency 'railties', '>= 3.1', '< 6.1'
|
||||
s.add_dependency 'activesupport', '>= 3.1', '< 7.1'
|
||||
s.add_dependency 'railties', '>= 3.1', '< 7.1'
|
||||
s.add_dependency 'json-schema', '~> 2.2'
|
||||
end
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'generator_spec'
|
||||
require 'generators/rspec/swagger_generator'
|
||||
require 'tmpdir'
|
||||
@ -9,12 +11,11 @@ module Rspec
|
||||
|
||||
before(:all) do
|
||||
prepare_destination
|
||||
fixtures_dir = File.expand_path('../fixtures', __FILE__)
|
||||
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
|
||||
@ -31,11 +32,11 @@ module Rspec
|
||||
|
||||
def fake_routes
|
||||
{
|
||||
"/posts/{post_id}/comments/{id}" => {
|
||||
:params => ["post_id", "id"],
|
||||
:actions => {
|
||||
"get" => { :summary=>"show comment" },
|
||||
"patch" => { :summary=>"update_comments comment" }
|
||||
'/posts/{post_id}/comments/{id}' => {
|
||||
params: ['post_id', 'id'],
|
||||
actions: {
|
||||
'get' => { summary: 'show comment' },
|
||||
'patch' => { summary: 'update_comments comment' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'generator_spec'
|
||||
require 'generators/rswag/specs/install/install_generator'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
RSpec.describe InstallGenerator do
|
||||
include GeneratorSpec::TestCase
|
||||
destination File.expand_path('../tmp', __FILE__)
|
||||
destination File.expand_path('tmp', __dir__)
|
||||
|
||||
before(:all) do
|
||||
prepare_destination
|
||||
fixtures_dir = File.expand_path('../fixtures', __FILE__)
|
||||
fixtures_dir = File.expand_path('fixtures', __dir__)
|
||||
FileUtils.cp_r("#{fixtures_dir}/spec", destination_root)
|
||||
|
||||
run_generator
|
||||
|
||||
50
rswag-specs/spec/rswag/route_parser_spec.rb
Normal file
50
rswag-specs/spec/rswag/route_parser_spec.rb
Normal file
@ -0,0 +1,50 @@
|
||||
# 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
|
||||
@ -1,8 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/specs/configuration'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
RSpec.describe Configuration do
|
||||
subject { described_class.new(rspec_config) }
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/specs/example_group_helpers'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
RSpec.describe ExampleGroupHelpers do
|
||||
subject { double('example_group') }
|
||||
|
||||
@ -34,31 +35,6 @@ 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')
|
||||
@ -74,12 +50,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: [] }
|
||||
)
|
||||
@ -87,14 +63,13 @@ 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
|
||||
@ -105,7 +80,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
|
||||
@ -116,7 +91,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
|
||||
@ -126,7 +101,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
|
||||
@ -162,9 +137,10 @@ module Rswag
|
||||
end
|
||||
|
||||
describe '#examples(example)' do
|
||||
let(:mime) { 'application/json' }
|
||||
let(:json_example) do
|
||||
{
|
||||
'application/json' => {
|
||||
mime => {
|
||||
foo: 'bar'
|
||||
}
|
||||
}
|
||||
@ -176,7 +152,11 @@ module Rswag
|
||||
end
|
||||
|
||||
it "adds to the 'response examples' metadata" do
|
||||
expect(api_metadata[:response][:examples]).to eq(json_example)
|
||||
expect(api_metadata[:response][:content]).to match(
|
||||
mime => {
|
||||
example: json_example[mime]
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/specs/example_helpers'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
RSpec.describe ExampleHelpers do
|
||||
subject { double('example') }
|
||||
|
||||
@ -15,6 +16,7 @@ module Rswag
|
||||
let(:config) { double('config') }
|
||||
let(:swagger_doc) do
|
||||
{
|
||||
swagger: '2.0',
|
||||
securityDefinitions: {
|
||||
api_key: {
|
||||
type: :apiKey,
|
||||
@ -24,13 +26,14 @@ 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' },
|
||||
@ -58,7 +61,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
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/specs/request_factory'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
RSpec.describe RequestFactory do
|
||||
subject { RequestFactory.new(config) }
|
||||
|
||||
@ -10,7 +11,7 @@ module Rswag
|
||||
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||
end
|
||||
let(:config) { double('config') }
|
||||
let(:swagger_doc) { {} }
|
||||
let(:swagger_doc) { { swagger: '2.0' } }
|
||||
let(:example) { double('example') }
|
||||
let(:metadata) do
|
||||
{
|
||||
@ -53,7 +54,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
|
||||
@ -63,40 +64,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
|
||||
@ -104,7 +105,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
|
||||
|
||||
@ -127,9 +128,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
|
||||
@ -150,18 +151,33 @@ 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\"}")
|
||||
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'/)
|
||||
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 }
|
||||
@ -181,7 +197,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
|
||||
@ -192,7 +208,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
|
||||
@ -202,21 +218,53 @@ module Rswag
|
||||
end
|
||||
|
||||
context 'basic auth' do
|
||||
before do
|
||||
swagger_doc[:securityDefinitions] = { basic: { type: :basic } }
|
||||
metadata[:operation][:security] = [ basic: [] ]
|
||||
allow(example).to receive(:Authorization).and_return('Basic foobar')
|
||||
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
|
||||
end
|
||||
|
||||
it "sets 'HTTP_AUTHORIZATION' header to example value" do
|
||||
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
|
||||
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
|
||||
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
|
||||
|
||||
@ -256,8 +304,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
|
||||
|
||||
@ -272,39 +320,72 @@ 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
|
||||
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')
|
||||
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
|
||||
end
|
||||
|
||||
it 'uses the referenced metadata to build the request' do
|
||||
expect(request[:path]).to eq('/blogs?q1=foo')
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
@ -316,18 +397,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
|
||||
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/specs/response_validator'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
RSpec.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(:swagger_doc) { {} }
|
||||
@ -20,7 +22,7 @@ module Rswag
|
||||
schema: {
|
||||
type: :object,
|
||||
properties: { text: { type: :string } },
|
||||
required: [ 'text' ]
|
||||
required: ['text']
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -32,43 +34,89 @@ 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
|
||||
before do
|
||||
swagger_doc[:definitions] = {
|
||||
'blog' => {
|
||||
type: :object,
|
||||
properties: { foo: { type: :string } },
|
||||
required: [ 'foo' ]
|
||||
context 'swagger 2.0' do
|
||||
before do
|
||||
swagger_doc[:definitions] = {
|
||||
'blog' => {
|
||||
type: :object,
|
||||
properties: { foo: { type: :string } },
|
||||
required: ['foo']
|
||||
}
|
||||
}
|
||||
}
|
||||
metadata[:response][:schema] = { '$ref' => '#/definitions/blog' }
|
||||
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
|
||||
end
|
||||
|
||||
it 'uses the referenced schema to validate the response body' do
|
||||
expect { call }.to raise_error /Expected response body/
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,66 +1,286 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/specs/swagger_formatter'
|
||||
require 'ostruct'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
RSpec.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', __FILE__) }
|
||||
let(:swagger_root) { File.expand_path('tmp/swagger', __dir__) }
|
||||
|
||||
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' },
|
||||
operation: { verb: :post, summary: 'Creates a blog' },
|
||||
response: { code: '201', description: 'blog created' }
|
||||
path_item: { template: '/blogs', parameters: [{ type: :string }] },
|
||||
operation: { verb: :post, summary: 'Creates a blog', parameters: [{ type: :string }] },
|
||||
response: response_metadata,
|
||||
document: document
|
||||
}
|
||||
end
|
||||
let(:response_metadata) { { code: '201', description: 'blog created', headers: { type: :string }, schema: { '$ref' => '#/definitions/blog' } } }
|
||||
|
||||
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' }
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
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.exists?(swagger_root)
|
||||
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root)
|
||||
allow(config).to receive(:swagger_docs).and_return(
|
||||
'v1/swagger.json' => { info: { version: 'v1' } },
|
||||
'v2/swagger.json' => { info: { version: 'v2' } }
|
||||
'v1/swagger.json' => doc_1,
|
||||
'v2/swagger.json' => doc_2
|
||||
)
|
||||
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
|
||||
let(:swagger_format) { :json }
|
||||
|
||||
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")
|
||||
@ -79,8 +299,77 @@ module Rswag
|
||||
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
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
|
||||
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rails
|
||||
module VERSION
|
||||
MAJOR = 3
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
# 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
|
||||
|
||||
@ -7,4 +7,8 @@ Rswag::Ui.configure do |c|
|
||||
# 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'
|
||||
end
|
||||
|
||||
31
rswag-ui/lib/rswag/ui/basic_auth.rb
Normal file
31
rswag-ui/lib/rswag/ui/basic_auth.rb
Normal file
@ -0,0 +1,31 @@
|
||||
# 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
|
||||
@ -1,9 +1,11 @@
|
||||
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
|
||||
@ -20,6 +22,7 @@ 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)
|
||||
@ -27,9 +30,15 @@ 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
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
require 'rswag/ui/middleware'
|
||||
require 'rswag/ui/basic_auth'
|
||||
|
||||
module Rswag
|
||||
module Ui
|
||||
@ -7,6 +8,13 @@ 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
|
||||
|
||||
@ -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
|
||||
|
||||
6
rswag-ui/package-lock.json
generated
6
rswag-ui/package-lock.json
generated
@ -5,9 +5,9 @@
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"swagger-ui-dist": {
|
||||
"version": "3.18.2",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.18.2.tgz",
|
||||
"integrity": "sha512-pWAEiKkgWUJvjmLW9AojudnutJ+NTn5g6OdNLj1iIJWwCkoy40K3Upwa24DqFbmIE4vLX4XplND61hp2L+s5vg=="
|
||||
"version": "3.52.5",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.52.5.tgz",
|
||||
"integrity": "sha512-8z18eX8G/jbTXYzyNIaobrnD7PSN7yU/YkSasMmajrXtw0FGS64XjrKn5v37d36qmU3o1xLeuYnktshRr7uIFw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,6 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"swagger-ui-dist": "3.18.2"
|
||||
"swagger-ui-dist": "3.52.5"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
# frozen_string_literal: true
|
||||
|
||||
$LOAD_PATH.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"]
|
||||
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.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.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', '< 6.1'
|
||||
s.add_dependency 'railties', '>= 3.1', '< 6.1'
|
||||
s.add_dependency 'actionpack', '>=3.1', '< 7.1'
|
||||
s.add_dependency 'railties', '>= 3.1', '< 7.1'
|
||||
end
|
||||
|
||||
52
rswag-ui/spec/rswag/ui/configuration_spec.rb
Normal file
52
rswag-ui/spec/rswag/ui/configuration_spec.rb
Normal file
@ -0,0 +1,52 @@
|
||||
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
|
||||
@ -0,0 +1,100 @@
|
||||
# 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
|
||||
@ -1,17 +1,19 @@
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
# frozen_string_literal: true
|
||||
|
||||
$LOAD_PATH.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"]
|
||||
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.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.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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
exit
|
||||
env['PATH_INFO']
|
||||
env['SCRIPT_NAME']
|
||||
env
|
||||
@ -5,3 +5,8 @@
|
||||
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
|
||||
|
||||
2
test-app/app/assets/javascripts/stubs.js
Normal file
2
test-app/app/assets/javascripts/stubs.js
Normal file
@ -0,0 +1,2 @@
|
||||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
||||
4
test-app/app/assets/stylesheets/stubs.css
Normal file
4
test-app/app/assets/stylesheets/stubs.css
Normal file
@ -0,0 +1,4 @@
|
||||
/*
|
||||
Place all the styles related to the matching controller here.
|
||||
They will automatically be included in application.css.
|
||||
*/
|
||||
@ -8,12 +8,31 @@ 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 : :unprocsessible_entity
|
||||
head @blog.save ? :ok : :unprocessable_entity
|
||||
end
|
||||
|
||||
# GET /blogs
|
||||
|
||||
13
test-app/app/controllers/stubs_controller.rb
Normal file
13
test-app/app/controllers/stubs_controller.rb
Normal file
@ -0,0 +1,13 @@
|
||||
class StubsController < ApplicationController
|
||||
def index
|
||||
render plain: 'OK'
|
||||
end
|
||||
|
||||
def create
|
||||
render plain: 'OK'
|
||||
end
|
||||
|
||||
def show
|
||||
render plain: 'OK'
|
||||
end
|
||||
end
|
||||
2
test-app/app/helpers/stubs_helper.rb
Normal file
2
test-app/app/helpers/stubs_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module StubsHelper
|
||||
end
|
||||
@ -1,11 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Blog < ActiveRecord::Base
|
||||
validates :content, presence: true
|
||||
|
||||
def as_json(options)
|
||||
alias_attribute :headline, :title
|
||||
alias_attribute :text, :content
|
||||
|
||||
def as_json(_options)
|
||||
{
|
||||
id: id,
|
||||
title: title,
|
||||
content: nil,
|
||||
content: content,
|
||||
thumbnail: thumbnail
|
||||
}
|
||||
end
|
||||
|
||||
@ -11,6 +11,7 @@ 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.
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# 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'
|
||||
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
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
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'
|
||||
|
||||
@ -6,6 +9,8 @@ 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'
|
||||
|
||||
mount Rswag::Api::Engine => 'api-docs'
|
||||
mount Rswag::Ui::Engine => 'api-docs'
|
||||
resources :stubs
|
||||
|
||||
mount Rswag::Api::Engine => 'api-docs'
|
||||
mount Rswag::Ui::Engine => 'api-docs'
|
||||
end
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20160218212104) do
|
||||
ActiveRecord::Schema.define(version: 2016_02_18_212104) do
|
||||
|
||||
create_table "blogs", force: :cascade do |t|
|
||||
t.string "title"
|
||||
|
||||
@ -3,10 +3,11 @@ require 'rails_helper'
|
||||
RSpec.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')
|
||||
expect(page).to have_content('POST /blogs Creates a blog')
|
||||
expect(page).to have_content('GET /blogs/{id} Retrieves a blog')
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,12 +1,17 @@
|
||||
# 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
|
||||
|
||||
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')}" }
|
||||
@ -24,7 +29,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' }
|
||||
@ -42,7 +47,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')}" }
|
||||
|
||||
@ -3,6 +3,10 @@ require 'swagger_helper'
|
||||
RSpec.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'
|
||||
@ -15,6 +19,7 @@ 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
|
||||
|
||||
@ -48,6 +53,30 @@ 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
|
||||
|
||||
@ -68,11 +97,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!
|
||||
|
||||
349
test-app/spec/integration/openapi3_spec.rb
Normal file
349
test-app/spec/integration/openapi3_spec.rb
Normal file
@ -0,0 +1,349 @@
|
||||
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
|
||||
@ -1,8 +1,10 @@
|
||||
# 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', __FILE__)
|
||||
require File.expand_path('../config/environment', __dir__)
|
||||
# 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!
|
||||
@ -51,9 +53,12 @@ RSpec.configure do |config|
|
||||
# arbitrary gems may also be filtered via:
|
||||
# config.filter_gems_from_backtrace("gem name")
|
||||
|
||||
Capybara.javascript_driver = :webkit
|
||||
end
|
||||
Capybara.register_driver :firefox_headless do |app|
|
||||
options = ::Selenium::WebDriver::Firefox::Options.new
|
||||
options.args << '--headless'
|
||||
|
||||
Capybara::Webkit.configure do |config|
|
||||
config.block_unknown_urls
|
||||
Capybara::Selenium::Driver.new(app, browser: :firefox, options: options)
|
||||
end
|
||||
|
||||
Capybara.javascript_driver = :firefox_headless
|
||||
end
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require 'rake'
|
||||
|
||||
@ -5,11 +7,11 @@ RSpec.describe 'rswag:specs:swaggerize' do
|
||||
let(:swagger_root) { Rails.root.to_s + '/swagger' }
|
||||
before do
|
||||
TestApp::Application.load_tasks
|
||||
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
|
||||
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root)
|
||||
end
|
||||
|
||||
it 'generates Swagger JSON files from integration specs' do
|
||||
expect { Rake::Task['rswag:specs:swaggerize'].invoke }.not_to raise_exception
|
||||
Rake::Task['rswag:specs:swaggerize'].invoke
|
||||
expect(File).to exist("#{swagger_root}/v1/swagger.json")
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# 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
|
||||
@ -50,54 +52,4 @@ 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
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.configure do |config|
|
||||
@ -5,7 +7,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
|
||||
@ -14,12 +16,22 @@ 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' => {
|
||||
swagger: '2.0',
|
||||
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',
|
||||
@ -40,19 +52,98 @@ RSpec.configure do |config|
|
||||
id: { type: 'integer' },
|
||||
title: { type: 'string' },
|
||||
content: { type: 'string', 'x-nullable': true },
|
||||
thumbnail: { type: 'string'}
|
||||
thumbnail: { type: 'string', 'x-nullable': true}
|
||||
},
|
||||
required: [ 'id', 'title', 'content', 'thumbnail' ]
|
||||
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']
|
||||
}
|
||||
},
|
||||
securityDefinitions: {
|
||||
basic_auth: {
|
||||
type: :basic
|
||||
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' }
|
||||
}
|
||||
},
|
||||
api_key: {
|
||||
type: :apiKey,
|
||||
name: 'api_key',
|
||||
in: :query
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"title": "API V1",
|
||||
"version": "v1"
|
||||
@ -88,20 +88,8 @@
|
||||
],
|
||||
"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": {
|
||||
@ -109,8 +97,21 @@
|
||||
},
|
||||
"422": {
|
||||
"description": "invalid request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/errors_object"
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/definitions/errors_object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/definitions/blog"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,14 +123,13 @@
|
||||
],
|
||||
"description": "Searches blogs by keywords",
|
||||
"operationId": "searchBlogs",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "keywords",
|
||||
"in": "query",
|
||||
"type": "string"
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@ -139,13 +139,63 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/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",
|
||||
"type": "string",
|
||||
"required": true
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"get": {
|
||||
@ -155,9 +205,6 @@
|
||||
],
|
||||
"description": "Retrieves a specific blog by id",
|
||||
"operationId": "getBlog",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "blog found",
|
||||
@ -172,15 +219,17 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"$ref": "#/definitions/blog"
|
||||
},
|
||||
"examples": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"id": 1,
|
||||
"title": "Hello world!",
|
||||
"content": "Hello world and hello universe. Thank you all very much!!!",
|
||||
"thumbnail": "thumbnail.png"
|
||||
"example": {
|
||||
"id": 1,
|
||||
"title": "Hello world!",
|
||||
"content": "Hello world and hello universe. Thank you all very much!!!",
|
||||
"thumbnail": "thumbnail.png"
|
||||
},
|
||||
"schema": {
|
||||
"$ref": "#/definitions/blog"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -195,8 +244,10 @@
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"required": true
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"put": {
|
||||
@ -206,25 +257,37 @@
|
||||
],
|
||||
"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",
|
||||
@ -257,25 +320,50 @@
|
||||
"x-nullable": true
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"x-nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"title",
|
||||
"content",
|
||||
"thumbnail"
|
||||
"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"
|
||||
]
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
"basic_auth": {
|
||||
"type": "basic"
|
||||
},
|
||||
"api_key": {
|
||||
"type": "apiKey",
|
||||
"name": "api_key",
|
||||
"in": "query"
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"basic_auth": {
|
||||
"type": "http",
|
||||
"scheme": "basic"
|
||||
},
|
||||
"api_key": {
|
||||
"type": "apiKey",
|
||||
"name": "api_key",
|
||||
"in": "query"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
160
test-app/swagger/v3/openapi.json
Normal file
160
test-app/swagger/v3/openapi.json
Normal file
@ -0,0 +1,160 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user