Compare commits

..

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

163 changed files with 38693 additions and 4647 deletions

View File

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

View File

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

View File

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

View File

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

6
.gitignore vendored
View File

@ -2,9 +2,3 @@
**/*/log **/*/log
**/*/*.gem **/*/*.gem
**/*/*.sqlite3 **/*/*.sqlite3
**/*/public/assets
**/*/node_modules
*.swp
Gemfile.lock
/.idea/
**/.byebug_history

View File

@ -1 +0,0 @@
2.7.2

View File

@ -1,73 +1,10 @@
language: ruby nguage: ruby
dist: bionic
services:
- xvfb
rvm: rvm:
- 2.6.3 - 2.2.5
env: env:
- RAILS_VERSION=6.0.0 - "RAILS_VERSION=3.2.22"
- RAILS_VERSION=5.2.0 - "RAILS_VERSION=4.2.0"
- "RAILS_VERSION=5.0.0"
addons: cache: bundler
apt: install: bundle update
packages:
- libqtwebkit-dev
- libqtwebkit4
cache:
directories:
- /home/travis/.rvm/gems/ruby-2.6.3
install: ./ci/build.sh
script: ./ci/test.sh script: ./ci/test.sh
jobs:
include:
- stage: publish components
script: 'cd rswag-api'
if: tag IS present
deploy:
gemspec: rswag-api.gemspec
provider: rubygems
api_key: $RUBYGEMS_API_KEY
on:
branch: master
tags: true
- stage: publish components
script: 'cd rswag-specs'
if: tag IS present
deploy:
gemspec: rswag-specs.gemspec
provider: rubygems
api_key: $RUBYGEMS_API_KEY
on:
branch: master
tags: true
- stage: publish components
script: 'cd rswag-ui'
if: tag IS present
deploy:
gemspec: rswag-ui.gemspec
provider: rubygems
api_key: $RUBYGEMS_API_KEY
skip_cleanup: true
on:
branch: master
tags: true
- stage: publish rswag
script: 'cd rswag'
if: tag IS present
deploy:
gemspec: rswag.gemspec
provider: rubygems
api_key: $RUBYGEMS_API_KEY
on:
branch: master
tags: true

View File

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

View File

@ -1,83 +0,0 @@
# 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
cd rswag
```
## Build
Set up your machine:
```
./ci/build.sh
```
Or manually
```
bundle
cd test-app
bundle exec rake db:setup
cd -
cd rswag-ui
npm install
cd -
```
## Test
Initialize the rswag-ui repo with assets.
```
ci/build.sh
```
Make sure the tests pass:
```
./ci/test.sh
```
or manually
```
cd test-app
bundle exec rspec
```
Make your change. Add tests for your change. Make the tests pass:
```
bundle exec rspec
```
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)
Update the changelog.md, putting the new version number in and moving the Unreleased marker.
Merge the changes into master you wish to release.
Add and push a new git tag, annotated tags preferred:
```
git tag -s 2.0.6 -m 'v2.0.6'
```
Travis will detect the tag and release all gems with that tag version number.

42
Gemfile
View File

@ -1,51 +1,29 @@
# frozen_string_literal: true source "https://rubygems.org"
source 'https://rubygems.org'
# Allow the rails version to come from an ENV setting so Travis can test multiple versions. # 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/ # See http://www.schneems.com/post/50991826838/testing-against-multiple-rails-versions/
rails_version = ENV['RAILS_VERSION'] || '5.2.4.2' rails_version = ENV['RAILS_VERSION'] || '3.2.22'
gem 'rails', rails_version.to_s gem 'rails', "#{rails_version}"
case rails_version.split('.').first case rails_version.split('.').first
when '3' when '3'
gem 'strong_parameters' gem 'strong_parameters'
when '4', '5', '6' when '4', '5'
gem 'responders' gem 'responders'
end end
case rails_version.split('.').first gem 'sqlite3'
when '3', '4', '5'
gem 'sqlite3', '~> 1.3.6'
when '6'
gem 'sqlite3', '~> 1.4.1'
end
gem 'rswag-api', path: './rswag-api' gem 'rswag-api', path: './rswag-api'
gem 'rswag-ui', path: './rswag-ui' gem 'rswag-ui', path: './rswag-ui'
group :development, :test do # To use debugger
gem 'rswag-specs', path: './rswag-specs' # gem 'debugger'
end
group :test do group :test do
gem 'capybara'
gem 'geckodriver-helper'
gem 'generator_spec'
gem 'rspec-rails'
gem 'selenium-webdriver'
gem 'test-unit' gem 'test-unit'
gem 'rspec-rails'
gem 'generator_spec'
gem 'rswag-specs', path: './rswag-specs'
end end
group :development do
gem 'rubocop'
end
group :assets do
gem 'mini_racer'
gem 'uglifier'
end
gem 'byebug'
gem 'puma'

146
Gemfile.lock Normal file
View File

@ -0,0 +1,146 @@
PATH
remote: ./rswag-api
specs:
rswag-api (1.0.0)
rails (>= 3.1, < 5.1)
PATH
remote: ./rswag-specs
specs:
rswag-specs (1.0.0)
json-schema (~> 2.2)
rails (>= 3.1, < 5.1)
rspec-rails (>= 2.14, < 4)
PATH
remote: ./rswag-ui
specs:
rswag-ui (1.0.0)
rails (>= 3.1, < 5.1)
GEM
remote: https://rubygems.org/
specs:
actionmailer (3.2.22)
actionpack (= 3.2.22)
mail (~> 2.5.4)
actionpack (3.2.22)
activemodel (= 3.2.22)
activesupport (= 3.2.22)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
rack (~> 1.4.5)
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
activemodel (3.2.22)
activesupport (= 3.2.22)
builder (~> 3.0.0)
activerecord (3.2.22)
activemodel (= 3.2.22)
activesupport (= 3.2.22)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activeresource (3.2.22)
activemodel (= 3.2.22)
activesupport (= 3.2.22)
activesupport (3.2.22)
i18n (~> 0.6, >= 0.6.4)
multi_json (~> 1.0)
addressable (2.4.0)
arel (3.0.3)
builder (3.0.4)
diff-lcs (1.2.5)
erubis (2.7.0)
generator_spec (0.9.3)
activesupport (>= 3.0.0)
railties (>= 3.0.0)
hike (1.2.3)
i18n (0.7.0)
journey (1.0.4)
json (1.8.3)
json-schema (2.7.0)
addressable (>= 2.4)
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.25.1)
multi_json (1.12.1)
polyglot (0.3.5)
power_assert (0.3.1)
rack (1.4.7)
rack-cache (1.6.1)
rack (>= 0.4)
rack-ssl (1.3.4)
rack
rack-test (0.6.3)
rack (>= 1.0)
rails (3.2.22)
actionmailer (= 3.2.22)
actionpack (= 3.2.22)
activerecord (= 3.2.22)
activeresource (= 3.2.22)
activesupport (= 3.2.22)
bundler (~> 1.0)
railties (= 3.2.22)
railties (3.2.22)
actionpack (= 3.2.22)
activesupport (= 3.2.22)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
rake (11.3.0)
rdoc (3.12.2)
json (~> 1.4)
rspec-core (3.5.4)
rspec-support (~> 3.5.0)
rspec-expectations (3.5.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0)
rspec-mocks (3.5.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0)
rspec-rails (3.5.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 3.5.0)
rspec-expectations (~> 3.5.0)
rspec-mocks (~> 3.5.0)
rspec-support (~> 3.5.0)
rspec-support (3.5.0)
sprockets (2.2.3)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.12)
strong_parameters (0.2.3)
actionpack (~> 3.0)
activemodel (~> 3.0)
activesupport (~> 3.0)
railties (~> 3.0)
test-unit (3.2.1)
power_assert
thor (0.19.1)
tilt (1.4.1)
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.51)
PLATFORMS
ruby
DEPENDENCIES
generator_spec
rails (= 3.2.22)
rspec-rails
rswag-api!
rswag-specs!
rswag-ui!
sqlite3
strong_parameters
test-unit

View File

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

670
README.md
View File

@ -1,69 +1,15 @@
rswag rswag (formerly swagger_rails)
========= =========
[![Build Status](https://travis-ci.org/rswag/rswag.svg?branch=master)](https://travis-ci.org/rswag/rswag) [![Build Status](https://travis-ci.org/domaindrivendev/rswag.svg?branch=master)](https://travis-ci.org/domaindrivendev/rswag)
[![Maintainability](https://api.codeclimate.com/v1/badges/1175b984edc4610f82ab/maintainability)](https://codeclimate.com/github/rswag/rswag/maintainability)
OpenApi 3.0 and Swagger 2.0 compatible! [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.
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.
Rswag extends rspec-rails "request specs" with a Swagger-based DSL for describing and testing API operations. You describe your API operations with a succinct, intuitive syntax, and it automaticaly runs the tests. Once you have green tests, run a rake task to auto-generate corresponding Swagger files and expose them as JSON endpoints. Rswag also provides an embedded version of the awesome [swagger-ui](https://github.com/swagger-api/swagger-ui) that's powered by the exposed JSON. This toolchain makes it seamless to go from integration specs, which youre probably doing in some form already, to living documentation for your API consumers.
And that's not all ... 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. 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.
## Compatibility ##
|Rswag Version|Swagger (OpenAPI) Spec.|swagger-ui|
|----------|----------|----------|
|[master](https://github.com/rswag/rswag/tree/master)|3.0.3|3.52.5|
|[2.3.0](https://github.com/rswag/rswag/tree/2.3.0)|3.0.3|3.23.11|
|[2.2.0](https://github.com/rswag/rswag/tree/2.2.0)|2.0|3.18.2|
|[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 ## ## Getting Started ##
1. Add this line to your applications _Gemfile_: 1. Add this line to your applications _Gemfile_:
@ -72,36 +18,13 @@ Once you have an API that can describe itself in Swagger, you've opened the trea
gem 'rswag' gem 'rswag'
``` ```
or if you like to avoid loading rspec in other bundler groups load the rswag-specs component separately.
Note: Adding it to the :development group is not strictly necessary, but without it, generators and rake tasks must be preceded by RAILS_ENV=test.
```ruby
# Gemfile
gem 'rswag-api'
gem 'rswag-ui'
group :development, :test do
gem 'rspec-rails'
gem 'rswag-specs'
end
```
2. Run the install generator 2. Run the install generator
```ruby ```ruby
rails g rswag:install rails g rswag:install
``` ```
Or run the install generators for each package separately if you installed Rswag as separate gems, as indicated above:
```ruby
rails g rswag:api:install
rails g rswag:ui:install
RAILS_ENV=test rails g rswag:specs:install
```
3. Create an integration spec to describe and test your API. 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 ```ruby
# spec/integration/blogs_spec.rb # spec/integration/blogs_spec.rb
@ -113,7 +36,7 @@ There is also a generator which can help get you started `rails generate rspec:s
post 'Creates a blog' do post 'Creates a blog' do
tags 'Blogs' tags 'Blogs'
consumes 'application/json' consumes 'application/json', 'application/xml'
parameter name: :blog, in: :body, schema: { parameter name: :blog, in: :body, schema: {
type: :object, type: :object,
properties: { properties: {
@ -138,9 +61,9 @@ There is also a generator which can help get you started `rails generate rspec:s
path '/blogs/{id}' do path '/blogs/{id}' do
get 'Retrieves a blog' do get 'Retrieves a blog' do
tags 'Blogs', 'Another Tag' tags 'Blogs'
produces 'application/json', 'application/xml' produces 'application/json', 'application/xml'
parameter name: :id, in: :path, type: :string parameter name: :id, :in => :path, :type => :string
response '200', 'blog found' do response '200', 'blog found' do
schema type: :object, schema type: :object,
@ -159,30 +82,17 @@ There is also a generator which can help get you started `rails generate rspec:s
let(:id) { 'invalid' } let(:id) { 'invalid' }
run_test! run_test!
end end
response '406', 'unsupported accept header' do
let(:'Accept') { 'application/foo' }
run_test!
end
end end
end end
end end
``` ```
4. Generate the Swagger JSON file(s) 4. Generate the Swagger JSON file(s)
```ruby ```ruby
rake rswag:specs:swaggerize rake rswag:specs:swaggerize
``` ```
This common command is also aliased as `rake rswag`.
Or if you installed your gems separately:
```
RAILS_ENV=test rails rswag
```
5. Spin up your app and check out the awesome, auto-generated docs at _/api-docs_! 5. Spin up your app and check out the awesome, auto-generated docs at _/api-docs_!
## The rspec DSL ## ## The rspec DSL ##
@ -193,17 +103,6 @@ If you've used [Swagger](http://swagger.io/specification) before, then the synta
Take special note of the __run_test!__ method that's called within each response block. This tells rswag to create and execute a corresponding example. It builds and submits a request based on parameter descriptions and corresponding values that have been provided using the rspec "let" syntax. For example, the "post" description in the example above specifies a "body" parameter called "blog". It also lists 2 different responses. For the success case (i.e. the 201 response), notice how "let" is used to set the blog parameter to a value that matches the provided schema. For the failure case (i.e. the 422 response), notice how it's set to a value that does not match the provided schema. When the test is executed, rswag also validates the actual response code and, where applicable, the response body against the provided [JSON Schema](http://json-schema.org/documentation.html). Take special note of the __run_test!__ method that's called within each response block. This tells rswag to create and execute a corresponding example. It builds and submits a request based on parameter descriptions and corresponding values that have been provided using the rspec "let" syntax. For example, the "post" description in the example above specifies a "body" parameter called "blog". It also lists 2 different responses. For the success case (i.e. the 201 response), notice how "let" is used to set the blog parameter to a value that matches the provided schema. For the failure case (i.e. the 422 response), notice how it's set to a value that does not match the provided schema. When the test is executed, rswag also validates the actual response code and, where applicable, the response body against the provided [JSON Schema](http://json-schema.org/documentation.html).
If you want to do additional validation on the response, pass a block to the __run_test!__ method:
```ruby
response '201', 'blog created' do
run_test! do |response|
data = JSON.parse(response.body)
expect(data['title']).to eq('foo')
end
end
```
If you'd like your specs to be a little more explicit about what's going on here, you can replace the call to __run_test!__ with equivalent "before" and "it" blocks: If you'd like your specs to be a little more explicit about what's going on here, you can replace the call to __run_test!__ with equivalent "before" and "it" blocks:
```ruby ```ruby
@ -220,67 +119,9 @@ response '201', 'blog created' do
end end
``` ```
### Null Values ###
This library is currently using JSON::Draft4 for validation of response models. Nullable properties can be supported with the non-standard property 'x-nullable' to a definition to allow null/nil values to pass. Or you can add the new standard ```nullable``` property to a definition.
```ruby
describe 'Blogs API' do
path '/blogs' do
post 'Creates a blog' do
...
response '200', 'blog found' do
schema type: :object,
properties: {
id: { type: :integer },
title: { type: :string, nullable: true }, # preferred syntax
content: { type: :string, 'x-nullable': true } # legacy syntax, but still works
}
....
end
end
end
end
```
### Support for oneOf, anyOf or AllOf schemas ###
Open API 3.0 now supports more flexible schema validation with the ```oneOf```, ```anyOf``` and ```allOf``` directives. rswag will handle these definitions and validate them properly.
Notice the ```schema``` inside the ```response``` section. Placing a ```schema``` method inside the response will validate (and fail the tests)
if during the integration test run the endpoint response does not match the response schema. This test validation can handle anyOf and allOf as well. See below:
```ruby
path '/blogs/flexible' do
post 'Creates a blog flexible body' do
tags 'Blogs'
description 'Creates a flexible blog from provided data'
operationId 'createFlexibleBlog'
consumes 'application/json'
produces 'application/json'
parameter name: :blog, in: :body, schema: {
oneOf: [
{ '$ref' => '#/components/schemas/blog' },
{ '$ref' => '#/components/schemas/flexible_blog' }
]
}
response '201', 'flexible blog created' do
schema oneOf: [{ '$ref' => '#/components/schemas/blog' }, { '$ref' => '#/components/schemas/flexible_blog' }]
run_test!
end
end
end
```
This automatic schema validation is a powerful feature of rswag.
### Global Metadata ### ### Global Metadata ###
In addition to paths, operations and responses, Swagger also supports global API metadata. When you install rswag, a file called _swagger_helper.rb_ is added to your spec folder. This is where you define one or more Swagger documents and provide global metadata. Again, the format is based on Swagger so most of the global fields supported by the top level ["Swagger" object](http://swagger.io/specification/#swaggerObject) can be provided with each document definition. As an example, you could define a Swagger document for each version of your API and in each case specify a title, version string. In Open API 3.0 the pathing and server definitions have changed a bit [Swagger host/basePath](https://swagger.io/docs/specification/api-host-and-base-path/): In addition to paths, operations and responses, Swagger also supports global API metadata. When you install rswag, a file called _swagger_helper.rb_ is added to your spec folder. This is where you define one or more Swagger documents and provide global metadata. Again, the format is based on Swagger so most of the global fields supported by the top level ["Swagger" object](http://swagger.io/specification/#swaggerObject) can be provided with each document definition. As an example, you could define a Swagger document for each version of your API and in each case specify a title, version string and URL basePath:
```ruby ```ruby
# spec/swagger_helper.rb # spec/swagger_helper.rb
@ -289,52 +130,31 @@ RSpec.configure do |config|
config.swagger_docs = { config.swagger_docs = {
'v1/swagger.json' => { 'v1/swagger.json' => {
openapi: '3.0.1', swagger: '2.0',
info: { info: {
title: 'API V1', title: 'API V1',
version: 'v1', version: 'v1'
description: 'This is the first version of my API'
}, },
servers: [ basePath: '/api/v1'
{
url: 'https://{defaultHost}',
variables: {
defaultHost: {
default: 'www.example.com'
}
}
}
]
}, },
'v2/swagger.yaml' => { 'v2/swagger.json' => {
openapi: '3.0.1', swagger: '2.0',
info: { info: {
title: 'API V2', title: 'API V2',
version: 'v2', version: 'v2'
description: 'This is the second version of my API'
}, },
servers: [ basePath: '/api/v2'
{
url: 'https://{defaultHost}',
variables: {
defaultHost: {
default: 'www.example.com'
}
}
}
]
} }
} }
end end
``` ```
#### Supporting multiple versions of API #### __NOTE__: By default, the paths, operations and responses defined in your spec files will be associated with the first Swagger document in _swagger_helper.rb_. If you're using multiple documents, you'll need to tag the individual specs with their target document name:
By default, the paths, operations and responses defined in your spec files will be associated with the first Swagger document in _swagger_helper.rb_. If your API has multiple versions, you should be using separate documents to describe each of them. In order to assign a file with a given version of API, you'll need to add the ```swagger_doc``` tag to each spec specifying its target document name:
```ruby ```ruby
# spec/integration/v2/blogs_spec.rb # spec/integration/v2/blogs_spec.rb
describe 'Blogs API', swagger_doc: 'v2/swagger.yaml' do describe 'Blogs API', swagger_doc: 'v2/swagger.json' do
path '/blogs' do path '/blogs' do
... ...
@ -344,108 +164,6 @@ describe 'Blogs API', swagger_doc: 'v2/swagger.yaml' do
end end
``` ```
#### Formatting the description literals: ####
Swagger supports the Markdown syntax to format strings. This can be especially handy if you were to provide a long description of a given API version or endpoint. Use [this guide](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) for reference.
__NOTE:__ There is one difference between the official Markdown syntax and Swagger interpretation, namely tables. To create a table like this:
| Column1 | Collumn2 |
| ------- | -------- |
| cell1 | cell2 |
you should use the folowing syntax, making sure there are no whitespaces at the start of any of the lines:
```
&#13;
| Column1 | Collumn2 |&#13;
| ------- | -------- |&#13;
| cell1 | cell2 |&#13;
&#13;
```
### Specifying/Testing API Security ###
Swagger allows for the specification of different security schemes and their applicability to operations in an API.
To leverage this in rswag, you define the schemes globally in _swagger_helper.rb_ and then use the "security" attribute at the operation level to specify which schemes, if any, are applicable to that operation.
Swagger supports :basic, :bearer, :apiKey and :oauth2 and :openIdConnect scheme types. See [the spec](https://swagger.io/docs/specification/authentication/) for more info, as this underwent major changes between Swagger 2.0 and Open API 3.0
```ruby
# spec/swagger_helper.rb
RSpec.configure do |config|
config.swagger_root = Rails.root.to_s + '/swagger'
config.swagger_docs = {
'v1/swagger.json' => {
... # note the new Open API 3.0 compliant security structure here, under "components"
components: {
securitySchemes: {
basic_auth: {
type: :http,
scheme: :basic
},
api_key: {
type: :apiKey,
name: 'api_key',
in: :query
}
}
}
}
}
end
# spec/integration/blogs_spec.rb
describe 'Blogs API' do
path '/blogs' do
post 'Creates a blog' do
tags 'Blogs'
security [ basic_auth: [] ]
...
response '201', 'blog created' do
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
run_test!
end
response '401', 'authentication failed' do
let(:Authorization) { "Basic #{::Base64.strict_encode64('bogus:bogus')}" }
run_test!
end
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
## Configuration & Customization ## ## Configuration & Customization ##
The steps described above will get you up and running with minimal setup. However, rswag offers a lot of flexibility to customize as you see fit. Before exploring the various options, you'll need to be aware of it's different components. The following table lists each of them and the files that get added/updated as part of a standard install. The steps described above will get you up and running with minimal setup. However, rswag offers a lot of flexibility to customize as you see fit. Before exploring the various options, you'll need to be aware of it's different components. The following table lists each of them and the files that get added/updated as part of a standard install.
@ -453,8 +171,8 @@ The steps described above will get you up and running with minimal setup. Howeve
|Gem|Description|Added/Updated| |Gem|Description|Added/Updated|
|---------|-----------|-------------| |---------|-----------|-------------|
|__rswag-specs__|Swagger-based DSL for rspec & accompanying rake task for generating Swagger files|_spec/swagger_helper.rb_| |__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 the 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_| |__rswag-ui__ |Rails Engine that includes [swagger-ui](https://github.com/swagger-api/swagger-ui) and powers it from the Swagger endpoints|_config/initializers/rswag-ui.rb, config/routes.rb_|
### Output Location for Generated Swagger Files ### ### Output Location for Generated Swagger Files ###
@ -468,65 +186,32 @@ RSpec.configure do |config|
end 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 ### ### Referenced Parameters and Schema Definitions ###
Swagger allows you to describe JSON structures inline with your operation descriptions OR as referenced globals. 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:
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 ```ruby
# spec/swagger_helper.rb # spec/swagger_helper.rb
config.swagger_docs = { config.swagger_docs = {
'v1/swagger.json' => { 'v1/swagger.json' => {
openapi: '3.0.0', swagger: '2.0',
info: { info: {
title: 'API V1' title: 'API V1'
}, },
components: { definitions: {
schemas: { errors_object: {
errors_object: { type: 'object',
type: 'object', properties: {
properties: { errors: { '$ref' => '#/definitions/errors_map' }
errors: { '$ref' => '#/components/schemas/errors_map' } }
} },
}, errors_map: {
errors_map: { type: 'object',
type: 'object', additionalProperties: {
additionalProperties: { type: 'array',
type: 'array', items: { type: 'string' }
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]
} }
} }
} }
@ -540,10 +225,8 @@ describe 'Blogs API' do
post 'Creates a blog' do post 'Creates a blog' do
parameter name: :new_blog, in: :body, schema: { '$ref' => '#/components/schemas/new_blog' }
response 422, 'invalid request' do response 422, 'invalid request' do
schema '$ref' => '#/components/schemas/errors_object' schema '$ref' => '#/definitions/errors_object'
... ...
end end
@ -555,239 +238,11 @@ describe 'Blogs API' do
post 'Creates a comment' do post 'Creates a comment' do
response 422, 'invalid request' do response 422, 'invalid request' do
schema '$ref' => '#/components/schemas/errors_object' schema '$ref' => '#/definitions/errors_object'
... ...
end 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:
```ruby
# spec/integration/comments_spec.rb
describe 'Blogs API' do
path '/blogs/{blog_id}/comments' do
post 'Creates a comment' do
response 422, 'invalid request' do
header 'X-Rate-Limit-Limit', type: :integer, description: 'The number of allowed requests in the current period'
header 'X-Rate-Limit-Remaining', type: :integer, description: 'The number of remaining requests in the current period'
...
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
path '/blogs/{blog_id}' do
get 'Retrieves a blog' do
response 200, 'blog found' do
examples 'application/json' => {
id: 1,
title: 'Hello world!',
content: '...'
}
...
end
```
### Enable auto generation examples from responses ###
To enable examples generation from responses add callback above run_test! like:
```
after do |example|
example.metadata[:response][:content] = {
'application/json' => {
example: JSON.parse(response.body, symbolize_names: true)
}
}
end
```
#### 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 provide the document tag:
```ruby
describe 'Blogs API' do
path '/blogs/{blog_id}' do
get 'Retrieves a blog' do
# documentation is now disabled for this response only
response 200, 'blog found', document: false do
...
```
You can also reenable documentation for specific responses only:
```ruby
# documentation is now disabled
describe 'Blogs API', document: false do
path '/blogs/{blog_id}' do
get 'Retrieves a blog' do
# documentation is reenabled for this response only
response 200, 'blog found', document: true do
...
end
response 401, 'special case' do
...
end
```
##### rswag helper methods #####
<!--
There are some helper methods to help with documenting request bodies.
```ruby
describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
let(:api_key) { 'fake_key' }
path '/blogs' do
post 'Creates a blog' do
tags 'Blogs'
description 'Creates a new blog from provided data'
operationId 'createBlog'
consumes 'application/json'
produces 'application/json'
request_body_json schema: { '$ref' => '#/components/schemas/blog' },
examples: :blog
request_body_text_plain
request_body_xml schema: { '$ref' => '#/components/schemas/blog' }
let(:blog) { { blog: { title: 'foo', content: 'bar' } } }
response '201', 'blog created' do
schema '$ref' => '#/components/schemas/blog'
run_test!
end
response '422', 'invalid request' do
schema '$ref' => '#/components/schemas/errors_object'
let(:blog) { { blog: { title: 'foo' } } }
run_test! do |response|
expect(response.body).to include("can't be blank")
end
end
end
end
end
```
In the above example, we see methods ```request_body_json``` ```request_body_plain``` ```request_body_xml```.
These methods can be used to describe json, plain text and xml body. They are just wrapper methods to setup posting JSON, plain text or xml into your endpoint.
The simplest most common usage is for json formatted body to use the schema: to specify the location of the schema for the request body
and the examples: :blog which will create a named example "blog" under the "requestBody / content / application/json / examples" section.
Again, documenting request response examples changed in Open API 3.0. The example above would generate a swagger.json snippet that looks like this:
```json
...
{"requestBody": {
"required": true,
"content": {
"application/json": {
"examples": {
"blog": { // takes the name from examples: :blog above
"value": { //this is open api 3.0 structure -> https://swagger.io/docs/specification/adding-examples/
"blog": { // here is the actual JSON payload that is submitted to the service, and shows up in swagger UI as an example
"title": "foo",
"content": "bar"
}
}
}
},
"schema": {
"$ref": "#/components/schemas/blog"
}
},
"test/plain": {
"schema": {
"type": "string"
}
},
"application/xml": {
"schema": {
"$ref": "#/components/schemas/blog"
}
}
}
},
}
```
*NOTE:* for this example request body to work in the tests properly, you need to ``let`` a variable named *blog*.
The variable with the matching name (blog in this case) is eval-ed and captured to be placed in the examples section.
This ```let``` value is used in the integration test to run the test AND captured and injected into the requestBody section.
##### rswag response examples #####
In the same way that requestBody examples can be captured and injected into the swagger output, response examples can also be captured.
Using the above example, when the integration test is run - the swagger would include the following snippet providing more useful real world examples
capturing the response from the execution of the integration test. Again 3.0 swagger changed the structure of how these are documented.
```json
... "responses": {
"201": {
"description": "blog created",
"content": {
"application/json": {
"example": {
"id": 1,
"title": "foo",
"content": "bar",
"thumbnail": null
},
"schema": {
"$ref": "#/components/schemas/blog"
}
}
}
},
"422": {
"description": "invalid request",
"content": {
"application/json": {
"example": {
"errors": {
"content": [
"can't be blank"
]
}
},
"schema": {
"$ref": "#/components/schemas/errors_object"
}
}
}
}
}
```
-->
### Route Prefix for Swagger JSON Endpoints ### ### 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_: The functionality to expose Swagger files, such as those generated by rswag-specs, as JSON endpoints is implemented as a Rails Engine. As with any Engine, you can change it's mount prefix in _routes.rb_:
@ -808,7 +263,7 @@ GET http://<hostname>/your-custom-prefix/v1/swagger.json
### Root Location for Swagger Files ### ### 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 ```ruby
Rswag::Api.configure do |c| Rswag::Api.configure do |c|
@ -831,22 +286,7 @@ Rswag::Api.configure do |c|
end 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. 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 Authoriation 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 ### ### Enable Swagger Endpoints for swagger-ui ###
@ -859,17 +299,6 @@ Rswag::Ui.configure do |c|
end 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 ### ### 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_: Similar to rswag-api, you can customize the swagger-ui path by changing it's mount prefix in _routes.rb_:
@ -885,7 +314,7 @@ end
### Customizing the swagger-ui ### ### Customizing the swagger-ui ###
The swagger-ui provides several options for customizing it's behavior, all of which are documented here https://github.com/swagger-api/swagger-ui/tree/2.x#swaggerui. If you need to tweak these or customize the overall look and feel of your swagger-ui, then you'll need to provide your own version of index.html. You can do this with the following generator. The swagger-ui provides several options for customizing it's behavior, all of which are documented here https://github.com/swagger-api/swagger-ui#swaggerui. If you need to tweak these or customize the overall look and feel of your swagger-ui, then you'll need to provide your own version of index.html. You can do this with the following generator.
```ruby ```ruby
rails g rswag:ui:custom rails g rswag:ui:custom
@ -893,24 +322,3 @@ rails g rswag:ui:custom
``` ```
This will add a local version that you can modify at _app/views/rswag/ui/home/index.html.erb_ This will add a local version that you can modify at _app/views/rswag/ui/home/index.html.erb_
### Serve UI Assets Directly from your Web Server
Rswag ships with an embedded version of the [swagger-ui](https://github.com/swagger-api/swagger-ui), which is a static collection of JavaScript and CSS files. These assets are served by the rswag-ui middleware. However, for optimal performance you may want to serve them directly from your web server (e.g. Apache or NGINX). To do this, you'll need to copy them to the web server root. This is the "public" folder in a typical Rails application.
```
bundle exec rake rswag:ui:copy_assets[public/api-docs]
```
__NOTE:__: The provided subfolder MUST correspond to the UI mount prefix - "api-docs" by default.
Notes to test swagger output locally with swagger editor
```
docker pull swaggerapi/swagger-editor
```
```
docker run -d -p 80:8080 swaggerapi/swagger-editor
```
This will run the swagger editor in the docker daemon and can be accessed
at ```http://localhost```. From here, you can use the UI to load the generated swagger.json to validate the output.

View File

@ -1,24 +0,0 @@
#!/usr/bin/env bash
ROOT_PATH=$PWD
set -e # abort if anything fails
echo '####################'
echo 'bundle'
echo '####################'
echo ''
echo '##### all #####'
bundle install
echo '####################'
echo 'npm'
echo '####################'
echo ''
echo '##### rswag-ui #####'
cd $ROOT_PATH/rswag-ui
npm install
# Cleanup
cd $ROOT_PATH

58
ci/deploy.sh Executable file
View File

@ -0,0 +1,58 @@
#!/usr/bin/env bash
ROOT_PATH=$PWD
set -e # abort if anything fails
bundle check || bundle
echo '####################'
echo 'Build Gems'
echo '####################'
echo ''
echo '##### rswag-api #####'
cd $ROOT_PATH/rswag-api
gem build rswag-api.gemspec
echo '##### rswag-specs #####'
cd $ROOT_PATH/rswag-specs
gem build rswag-specs.gemspec
echo '##### rswag-ui #####'
cd $ROOT_PATH/rswag-ui
gem build rswag-ui.gemspec
echo '##### rswag #####'
cd $ROOT_PATH/rswag
gem build rswag.gemspec
echo '####################'
echo 'Push to RubyGems'
echo '####################'
echo ''
echo 'Type the version no, followed by [ENTER]:'
read version
echo '##### rswag-api #####'
cd $ROOT_PATH/rswag-api
gem push rswag-api-$version.gem
echo '##### rswag-specs #####'
cd $ROOT_PATH/rswag-specs
gem push rswag-specs-$version.gem
echo '##### rswag-ui #####'
cd $ROOT_PATH/rswag-ui
gem push rswag-ui-$version.gem
echo '##### rswag #####'
cd $ROOT_PATH/rswag
gem push rswag-$version.gem
# Cleanup
cd $ROOT_PATH
# Create git tag
git tag v$version
git push origin v$version

View File

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

View File

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

View File

@ -1,3 +1,4 @@
require 'rswag/api/version'
require 'rswag/api/configuration' require 'rswag/api/configuration'
require 'rswag/api/engine' require 'rswag/api/engine'

View File

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

View File

@ -1,6 +1,4 @@
require 'json' require 'json'
require 'yaml'
require 'rack/mime'
module Rswag module Rswag
module Api module Api
@ -16,16 +14,13 @@ module Rswag
filename = "#{@config.resolve_swagger_root(env)}/#{path}" filename = "#{@config.resolve_swagger_root(env)}/#{path}"
if env['REQUEST_METHOD'] == 'GET' && File.file?(filename) if env['REQUEST_METHOD'] == 'GET' && File.file?(filename)
swagger = parse_file(filename) swagger = load_json(filename)
@config.swagger_filter.call(swagger, env) unless @config.swagger_filter.nil? @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 [ return [
'200', '200',
headers, { 'Content-Type' => 'application/json' },
[ body ] [ JSON.dump(swagger) ]
] ]
end end
@ -34,29 +29,9 @@ module Rswag
private private
def parse_file(filename)
if /\.ya?ml$/ === filename
load_yaml(filename)
else
load_json(filename)
end
end
def load_yaml(filename)
YAML.safe_load(File.read(filename))
end
def load_json(filename) def load_json(filename)
JSON.parse(File.read(filename)) JSON.parse(File.read(filename))
end end
def unload_swagger(filename, swagger)
if /\.ya?ml$/ === filename
YAML.dump(swagger)
else
JSON.dump(swagger)
end
end
end end
end end
end end

View File

@ -0,0 +1,5 @@
module Rswag
module Api
VERSION = '1.0.1'
end
end

View File

@ -1,19 +1,20 @@
# frozen_string_literal: true $:.push File.expand_path("../lib", __FILE__)
$LOAD_PATH.push File.expand_path('../lib', __FILE__) # Maintain your gem's version:
require 'rswag/api/version'
# Describe your gem and declare its dependencies: # Describe your gem and declare its dependencies:
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = 'rswag-api' s.name = "rswag-api"
s.version = ENV['TRAVIS_TAG'] || '0.0.0' s.version = Rswag::Api::VERSION
s.authors = ['Richie Morris', 'Greg Myers', 'Jay Danielian'] s.authors = ["Richie Morris"]
s.email = ['domaindrivendev@gmail.com'] s.email = ["domaindrivendev@gmail.com"]
s.homepage = 'https://github.com/rswag/rswag' s.homepage = "https://github.com/domaindrivendev/rswag"
s.summary = 'A Rails Engine that exposes OpenAPI (formerly called Swagger) files as JSON endpoints' s.summary = "A Rails Engine that exposes 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.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.license = "MIT"
s.files = Dir['{lib}/**/*'] + ['MIT-LICENSE', 'Rakefile'] s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
s.add_dependency 'railties', '>= 3.1', '< 7.1' s.add_dependency "rails", ">= 3.1", "< 5.1"
end end

View File

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

View File

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

View File

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

View File

@ -37,42 +37,6 @@ module Rswag
end end
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 context "given a path that doesn't map to any swagger file" do
let(:env) { env_defaults.merge('PATH_INFO' => 'foobar.json') } let(:env) { env_defaults.merge('PATH_INFO' => 'foobar.json') }
before do before do
@ -97,7 +61,7 @@ module Rswag
it 'locates files at the provided swagger_root' do it 'locates files at the provided swagger_root' do
expect(response.length).to eql(3) expect(response.length).to eql(3)
expect(response[1]).to include( 'Content-Type' => 'application/json') expect(response[1]).to include( 'Content-Type' => 'application/json')
expect(response[2].join).to include('"openapi":"3.0.1"') expect(response[2].join).to include('"swagger":"2.0"')
end end
end end
@ -112,21 +76,6 @@ module Rswag
expect(response[2].join).to include('"host":"tempuri.org"') expect(response[2].join).to include('"host":"tempuri.org"')
end end
end end
context 'when a path maps to a yaml swagger file' do
let(:env) { env_defaults.merge('PATH_INFO' => 'v1/swagger.yml') }
it 'returns a 200 status' do
expect(response.length).to eql(3)
expect(response.first).to eql('200')
end
it 'returns contents of the swagger file' do
expect(response.length).to eql(3)
expect(response[1]).to include( 'Content-Type' => 'text/yaml')
expect(response[2].join).to include('title: API V1')
end
end
end end
end end
end end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,43 +1,25 @@
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
RSpec.configure do |config| RSpec.configure do |config|
# Specify a root folder where Swagger JSON files are generated # Specify a root folder where Swagger JSON files are generated
# NOTE: If you're using the rswag-api to serve API descriptions, you'll need # 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 # to ensure that it's confiugred to server Swagger from the same folder
config.swagger_root = Rails.root.join('swagger').to_s config.swagger_root = Rails.root.to_s + '/swagger'
# Define one or more Swagger documents and provide global metadata for each one # Define one or more Swagger documents and provide global metadata for each one
# When you run the 'rswag:specs:swaggerize' rake task, the complete Swagger will # When you run the 'rswag:specs:to_swagger' rake task, the complete Swagger will
# be generated at the provided relative path under swagger_root # be generated at the provided relative path under swagger_root
# By default, the operations defined in spec files are added to the first # By default, the operations defined in spec files are added to the first
# document below. You can override this behavior by adding a swagger_doc tag to the # document below. You can override this behavior by adding a swagger_doc tag to the
# the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json' # the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
config.swagger_docs = { config.swagger_docs = {
'v1/swagger.yaml' => { 'v1/swagger.json' => {
openapi: '3.0.1', swagger: '2.0',
info: { info: {
title: 'API V1', title: 'API V1',
version: 'v1' version: 'v1'
}, },
paths: {}, paths: {}
servers: [
{
url: 'https://{defaultHost}',
variables: {
defaultHost: {
default: 'www.example.com'
}
}
}
]
} }
} }
# Specify the format of the output Swagger file when running 'rswag:specs:swaggerize'.
# The swagger_docs configuration option has the filename including format in
# the key, this may want to be changed to avoid putting yaml in json files.
# Defaults to json. Accepts ':json' and ':yaml'.
config.swagger_format = :yaml
end end

View File

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

View File

@ -1,27 +1,20 @@
# frozen_string_literal: true
require 'rspec/core' require 'rspec/core'
require 'rswag/specs/version'
require 'rswag/specs/example_group_helpers' require 'rswag/specs/example_group_helpers'
require 'rswag/specs/example_helpers' require 'rswag/specs/example_helpers'
require 'rswag/specs/configuration'
require 'rswag/specs/railtie' if defined?(Rails::Railtie) require 'rswag/specs/railtie' if defined?(Rails::Railtie)
module Rswag module Rswag
module Specs module Specs
# Extend RSpec with a swagger-based DSL # Extend RSpec with a swagger-based DSL
::RSpec.configure do |c| ::RSpec.configure do |c|
c.add_setting :swagger_root c.add_setting :swagger_root
c.add_setting :swagger_docs c.add_setting :swagger_docs
c.add_setting :swagger_dry_run
c.add_setting :swagger_format
c.extend ExampleGroupHelpers, type: :request c.extend ExampleGroupHelpers, type: :request
c.include ExampleHelpers, type: :request c.include ExampleHelpers, type: :request
end end
def self.config
@config ||= Configuration.new(RSpec.configuration)
end
# Support Rails 3+ and RSpec 2+ (sigh!) # Support Rails 3+ and RSpec 2+ (sigh!)
RAILS_VERSION = Rails::VERSION::MAJOR RAILS_VERSION = Rails::VERSION::MAJOR
RSPEC_VERSION = RSpec::Core::Version::STRING.split('.').first.to_i RSPEC_VERSION = RSpec::Core::Version::STRING.split('.').first.to_i

View File

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

View File

@ -1,21 +1,20 @@
# frozen_string_literal: true
module Rswag module Rswag
module Specs module Specs
module ExampleGroupHelpers module ExampleGroupHelpers
def path(template, metadata = {}, &block)
metadata[:path_item] = { template: template } def path(path, &block)
describe(template, metadata, &block) api_metadata = { path: path}
describe(path, api_metadata, &block)
end end
[:get, :post, :patch, :put, :delete, :head, :options, :trace].each do |verb| [ :get, :post, :patch, :put, :delete, :head ].each do |verb|
define_method(verb) do |summary, &block| define_method(verb) do |summary, &block|
api_metadata = { operation: { verb: verb, summary: summary } } api_metadata = { operation: { verb: verb, summary: summary } }
describe(verb, api_metadata, &block) describe(verb, api_metadata, &block)
end end
end end
[:operationId, :deprecated, :security].each do |attr_name| [ :operationId, :deprecated, :security ].each do |attr_name|
define_method(attr_name) do |value| define_method(attr_name) do |value|
metadata[:operation][attr_name] = value metadata[:operation][attr_name] = value
end end
@ -24,36 +23,27 @@ module Rswag
# NOTE: 'description' requires special treatment because ExampleGroup already # NOTE: 'description' requires special treatment because ExampleGroup already
# defines a method with that name. Provide an override that supports the existing # defines a method with that name. Provide an override that supports the existing
# functionality while also setting the appropriate metadata if applicable # functionality while also setting the appropriate metadata if applicable
def description(value = nil) def description(value=nil)
return super() if value.nil? return super() if value.nil?
metadata[:operation][:description] = value metadata[:operation][:description] = value
end end
# These are array properties - note the splat operator # 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| define_method(attr_name) do |*value|
metadata[:operation][attr_name] = value metadata[:operation][attr_name] = value
end end
end end
def parameter(attributes) def parameter(attributes)
if attributes[:in] && attributes[:in].to_sym == :path attributes[:required] = true if attributes[:in].to_sym == :path
attributes[:required] = true metadata[:operation][:parameters] ||= []
end metadata[:operation][:parameters] << attributes
if metadata.key?(:operation)
metadata[:operation][:parameters] ||= []
metadata[:operation][:parameters] << attributes
else
metadata[:path_item][:parameters] ||= []
metadata[:path_item][:parameters] << attributes
end
end end
def response(code, description, metadata = {}, &block) def response(code, description, &block)
metadata[:response] = { code: code, description: description } api_metadata = { response: { code: code, description: description } }
context(description, metadata, &block) context(description, api_metadata, &block)
end end
def schema(value) def schema(value)
@ -62,23 +52,10 @@ module Rswag
def header(name, attributes) def header(name, attributes)
metadata[:response][:headers] ||= {} metadata[:response][:headers] ||= {}
metadata[:response][:headers][name] = attributes metadata[:response][:headers][name] = attributes
end end
# NOTE: Similar to 'description', 'examples' need to handle the case when def run_test!
# being invoked with no params to avoid overriding 'examples' method of
# rspec-core ExampleGroup
def examples(example = nil)
return super() if example.nil?
metadata[:response][:content] =
example.each_with_object({}) do |(mime, example_object), memo|
memo[mime] = { example: example_object }
end
end
def run_test!(&block)
# NOTE: rspec 2.x support # NOTE: rspec 2.x support
if RSPEC_VERSION < 3 if RSPEC_VERSION < 3
before do before do
@ -86,8 +63,7 @@ module Rswag
end end
it "returns a #{metadata[:response][:code]} response" do it "returns a #{metadata[:response][:code]} response" do
assert_response_matches_metadata(metadata) assert_response_matches_metadata(example.metadata)
block.call(response) if block_given?
end end
else else
before do |example| before do |example|
@ -95,8 +71,7 @@ module Rswag
end end
it "returns a #{metadata[:response][:code]} response" do |example| it "returns a #{metadata[:response][:code]} response" do |example|
assert_response_matches_metadata(example.metadata, &block) assert_response_matches_metadata(example.metadata)
example.instance_exec(response, &block) if block_given?
end end
end end
end end

View File

@ -1,33 +1,42 @@
# frozen_string_literal: true
require 'rswag/specs/request_factory' require 'rswag/specs/request_factory'
require 'rswag/specs/response_validator' require 'rswag/specs/response_validator'
module Rswag module Rswag
module Specs module Specs
module ExampleHelpers module ExampleHelpers
def submit_request(metadata)
request = RequestFactory.new.build_request(metadata, self) def submit_request(api_metadata)
factory = RequestFactory.new(api_metadata, global_metadata(api_metadata[:swagger_doc]))
if RAILS_VERSION < 5 if RAILS_VERSION < 5
send( send(
request[:verb], api_metadata[:operation][:verb],
request[:path], factory.build_fullpath(self),
request[:payload], factory.build_body(self),
request[:headers] factory.build_headers(self)
) )
else else
send( send(
request[:verb], api_metadata[:operation][:verb],
request[:path], factory.build_fullpath(self),
params: request[:payload], {
headers: request[:headers] params: factory.build_body(self),
headers: factory.build_headers(self)
}
) )
end end
end end
def assert_response_matches_metadata(metadata) def assert_response_matches_metadata(api_metadata)
ResponseValidator.new.validate!(metadata, response) validator = ResponseValidator.new(api_metadata, global_metadata(api_metadata[:swagger_doc]))
validator.validate!(response)
end
private
def global_metadata(swagger_doc)
swagger_docs = ::RSpec.configuration.swagger_docs
swagger_doc.nil? ? swagger_docs.values.first : swagger_docs[swagger_doc]
end end
end end
end end

View File

@ -1,26 +0,0 @@
# frozen_string_literal: true
require 'json-schema'
module Rswag
module Specs
class ExtendedSchema < JSON::Schema::Draft4
def initialize
super
@attributes['type'] = ExtendedTypeAttribute
@uri = URI.parse('http://tempuri.org/rswag/specs/extended_schema')
@names = ['http://tempuri.org/rswag/specs/extended_schema']
end
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)
super
end
end
JSON::Validator.register_validator(ExtendedSchema.new)
end
end

View File

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

View File

@ -1,126 +1,75 @@
# frozen_string_literal: true
require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/conversions'
require 'json' require 'json'
module Rswag module Rswag
module Specs module Specs
class RequestFactory class RequestFactory
def initialize(config = ::Rswag::Specs.config)
@config = config def initialize(api_metadata, global_metadata)
@api_metadata = api_metadata
@global_metadata = global_metadata
end end
def build_request(metadata, example) def build_fullpath(example)
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc]) @api_metadata[:path].dup.tap do |t|
parameters = expand_parameters(metadata, swagger_doc, example) t.prepend(@global_metadata[:basePath] || '')
parameters_in(:path).each { |p| t.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s) }
t.concat(build_query_string(example))
end
end
{}.tap do |request| def build_query_string(example)
add_verb(request, metadata) query_string = parameters_in(:query)
add_path(request, metadata, swagger_doc, parameters, example) .map { |p| build_query_string_part(p, example.send(p[:name])) }
add_headers(request, metadata, swagger_doc, parameters, example) .join('&')
add_payload(request, parameters, example)
query_string.empty? ? '' : "?#{query_string}"
end
def build_body(example)
body_parameter = parameters_in(:body).first
body_parameter.nil? ? '' : example.send(body_parameter[:name]).to_json
end
def build_headers(example)
headers = Hash[ parameters_in(:header).map { |p| [ p[:name], example.send(p[:name]).to_s ] } ]
headers.tap do |h|
produces = @api_metadata[:operation][:produces] || @global_metadata[:produces]
consumes = @api_metadata[:operation][:consumes] || @global_metadata[:consumes]
h['ACCEPT'] = produces.join(';') unless produces.nil?
h['CONTENT_TYPE'] = consumes.join(';') unless consumes.nil?
end end
end end
private private
def expand_parameters(metadata, swagger_doc, example) def parameters_in(location)
operation_params = metadata[:operation][:parameters] || [] (@api_metadata[:operation][:parameters] || [])
path_item_params = metadata[:path_item][:parameters] || [] .map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references
security_params = derive_security_params(metadata, swagger_doc) .concat(resolve_api_key_parameters)
.select { |p| p[:in] == location }
# NOTE: Use of + instead of concat to avoid mutation of the metadata object
(operation_params + path_item_params + security_params)
.map { |p| p['$ref'] ? resolve_parameter(p['$ref'], swagger_doc) : p }
.uniq { |p| p[:name] }
.reject { |p| p[:required] == false && !example.respond_to?(p[:name]) }
end end
def derive_security_params(metadata, swagger_doc) def resolve_parameter(ref)
requirements = metadata[:operation][:security] || swagger_doc[:security] || [] defined_params = @global_metadata[:parameters]
scheme_names = requirements.flat_map(&:keys) key = ref.sub('#/parameters/', '')
schemes = security_version(scheme_names, swagger_doc) raise "Referenced parameter '#{ref}' must be defined" unless defined_params && defined_params[key]
defined_params[key]
schemes.map do |scheme|
param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }
param.merge(type: :string, required: requirements.one?)
end
end end
def security_version(scheme_names, swagger_doc) def resolve_api_key_parameters
if doc_version(swagger_doc).start_with?('2') @api_key_params ||= begin
(swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values global_requirements = (@global_metadata[:security] || {})
else # Openapi3 requirements = global_requirements.merge(@api_metadata[:operation][:security] || {})
if swagger_doc.key?(:securityDefinitions) definitions = (@global_metadata[:securityDefinitions] || {}).slice(*requirements.keys)
ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: securityDefinitions is replaced in OpenAPI3! Rename to components/securitySchemes (in swagger_helper.rb)') definitions.values.select { |d| d[:type] == :apiKey }
swagger_doc[:components] ||= { securitySchemes: swagger_doc[:securityDefinitions] }
swagger_doc.delete(:securityDefinitions)
end
components = swagger_doc[:components] || {}
(components[:securitySchemes] || {}).slice(*scheme_names).values
end
end
def resolve_parameter(ref, swagger_doc)
key = key_version(ref, swagger_doc)
definitions = definition_version(swagger_doc)
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
def add_path(request, metadata, swagger_doc, parameters, example)
template = (swagger_doc[:basePath] || '') + metadata[:path_item][:template]
request[:path] = template.tap do |path_template|
parameters.select { |p| p[:in] == :path }.each do |p|
path_template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
end
parameters.select { |p| p[:in] == :query }.each_with_index do |p, i|
path_template.concat(i.zero? ? '?' : '&')
path_template.concat(build_query_string_part(p, example.send(p[:name])))
end
end end
end end
def build_query_string_part(param, value) def build_query_string_part(param, value)
name = param[:name] return "#{param[: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
name = param[:name]
case param[:collectionFormat] case param[:collectionFormat]
when :ssv when :ssv
"#{name}=#{value.join(' ')}" "#{name}=#{value.join(' ')}"
@ -134,96 +83,6 @@ module Rswag
"#{name}=#{value.join(',')}" # csv is default "#{name}=#{value.join(',')}" # csv is default
end end
end end
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] }
# Accept header
produces = metadata[:operation][:produces] || swagger_doc[:produces]
if produces
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]
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]
end,
pair[1]
]
end
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)
request[:payload] = build_form_payload(parameters, example)
else
request[:payload] = build_json_payload(parameters, example)
end
end
def build_form_payload(parameters, example)
# See http://seejohncode.com/2012/04/29/quick-tip-testing-multipart-uploads-with-rspec/
# Rather that serializing with the appropriate encoding (e.g. multipart/form-data),
# Rails test infrastructure allows us to send the values directly as a hash
# 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]
end
def build_json_payload(parameters, example)
body_param = parameters.select { |p| p[:in] == :body }.first
return nil unless body_param
raise(MissingParameterError, body_param[:name]) unless example.respond_to?(body_param[:name])
example.send(body_param[:name]).to_json
end
def doc_version(doc)
doc[:openapi] || doc[:swagger] || '3'
end
end
class MissingParameterError < StandardError
attr_reader :body_param
def initialize(body_param)
@body_param = body_param
end
def message
<<~MSG
Missing parameter '#{body_param}'
Please check your spec. It looks like you defined a body parameter,
but did not declare usage via let. Try adding:
let(:#{body_param}) {}
MSG
end
end end
end end
end end

View File

@ -1,73 +1,34 @@
# frozen_string_literal: true
require 'active_support/core_ext/hash/slice'
require 'json-schema' require 'json-schema'
require 'json'
require 'rswag/specs/extended_schema'
module Rswag module Rswag
module Specs module Specs
class ResponseValidator class ResponseValidator
def initialize(config = ::Rswag::Specs.config)
@config = config def initialize(api_metadata, global_metadata)
@api_metadata = api_metadata
@global_metadata = global_metadata
end end
def validate!(metadata, response) def validate!(response)
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc]) validate_code!(response.code)
validate_body!(response.body)
validate_code!(metadata, response)
validate_headers!(metadata, response.headers)
validate_body!(metadata, swagger_doc, response.body)
end end
private private
def validate_code!(metadata, response) def validate_code!(code)
expected = metadata[:response][:code].to_s if code.to_s != @api_metadata[:response][:code].to_s
if response.code != expected raise UnexpectedResponse, "Expected response code '#{code}' to match '#{@api_metadata[:response][:code]}'"
raise UnexpectedResponse,
"Expected response code '#{response.code}' to match '#{expected}'\n" \
"Response body: #{response.body}"
end end
end end
def validate_headers!(metadata, headers) def validate_body!(body)
expected = (metadata[:response][:headers] || {}).keys schema = @api_metadata[:response][:schema]
expected.each do |name| return if schema.nil?
raise UnexpectedResponse, "Expected response header #{name} to be present" if headers[name.to_s].nil? begin
end JSON::Validator.validate!(schema.merge(@global_metadata), body)
end rescue JSON::Schema::ValidationError => ex
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}"
def validate_body!(metadata, swagger_doc, body)
response_schema = metadata[:response][:schema]
return if response_schema.nil?
version = @config.get_swagger_doc_version(metadata[:swagger_doc])
schemas = definitions_or_component_schemas(swagger_doc, version)
validation_schema = response_schema
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
.merge(schemas)
errors = JSON::Validator.fully_validate(validation_schema, body)
return unless errors.any?
raise UnexpectedResponse,
"Expected response body to match schema: #{errors[0]}\n" \
"Response body: #{JSON.pretty_generate(JSON.parse(body))}"
end
def definitions_or_component_schemas(swagger_doc, version)
if version.start_with?('2')
swagger_doc.slice(:definitions)
else # Openapi3
if swagger_doc.key?(:definitions)
ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: definitions is replaced in OpenAPI3! Rename to components/schemas (in swagger_helper.rb)')
swagger_doc.slice(:definitions)
else
components = swagger_doc[:components] || {}
{ components: components }
end
end end
end end
end end

View File

@ -1,83 +1,47 @@
# frozen_string_literal: true
require 'active_support/core_ext/hash/deep_merge' require 'active_support/core_ext/hash/deep_merge'
require 'rspec/core/formatters/base_text_formatter' require 'rspec/core/formatters/base_text_formatter'
require 'swagger_helper' require 'swagger_helper'
module Rswag module Rswag
module Specs module Specs
class SwaggerFormatter < ::RSpec::Core::Formatters::BaseTextFormatter class SwaggerFormatter
# NOTE: rspec 2.x support # NOTE: rspec 2.x support
if RSPEC_VERSION > 2 if RSPEC_VERSION > 2
::RSpec::Core::Formatters.register self, :example_group_finished, :stop ::RSpec::Core::Formatters.register self, :example_group_finished, :stop
end end
def initialize(output, config = Rswag::Specs.config) def initialize(output)
@output = output @output = output
@config = config @swagger_root = ::RSpec.configuration.swagger_root
raise ConfigurationError, 'Missing swagger_root. See swagger_helper.rb' if @swagger_root.nil?
@swagger_docs = ::RSpec.configuration.swagger_docs || []
raise ConfigurationError, 'Missing swagger_docs. See swagger_helper.rb' if @swagger_docs.empty?
@output.puts 'Generating Swagger docs ...' @output.puts 'Generating Swagger docs ...'
end end
def example_group_finished(notification) def example_group_finished(notification)
# NOTE: rspec 2.x support # NOTE: rspec 2.x support
metadata = if RSPEC_VERSION > 2 if RSPEC_VERSION > 2
notification.group.metadata metadata = notification.group.metadata
else else
notification.metadata metadata = notification.metadata
end
# !metadata[:document] won't work, since nil means we should generate
# docs.
return if metadata[:document] == false
return unless metadata.key?(:response)
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 end
return unless metadata.has_key?(:response)
swagger_doc = get_swagger_doc(metadata[:swagger_doc])
swagger_doc.deep_merge!(metadata_to_swagger(metadata)) swagger_doc.deep_merge!(metadata_to_swagger(metadata))
end end
def stop(_notification = nil) def stop(notification=nil)
@config.swagger_docs.each do |url_path, doc| @swagger_docs.each do |url_path, doc|
unless doc_version(doc).start_with?('2') file_path = File.join(@swagger_root, url_path)
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) dirname = File.dirname(file_path)
FileUtils.mkdir_p dirname unless File.exist?(dirname) FileUtils.mkdir_p dirname unless File.exists?(dirname)
File.open(file_path, 'w') do |file| File.open(file_path, 'w') do |file|
file.write(pretty_generate(doc)) file.write(JSON.pretty_generate(doc))
end end
@output.puts "Swagger doc generated at #{file_path}" @output.puts "Swagger doc generated at #{file_path}"
@ -86,119 +50,30 @@ module Rswag
private private
def pretty_generate(doc) def get_swagger_doc(tag)
if @config.swagger_format == :yaml return @swagger_docs.values.first if tag.nil?
clean_doc = yaml_prepare(doc) raise ConfigurationError, "Unknown swagger_doc '#{tag}'" unless @swagger_docs.has_key?(tag)
YAML.dump(clean_doc) @swagger_docs[tag]
else # config errors are thrown in 'def swagger_format', no throw needed here
JSON.pretty_generate(doc)
end
end
def yaml_prepare(doc)
json_doc = JSON.pretty_generate(doc)
JSON.parse(json_doc)
end end
def metadata_to_swagger(metadata) def metadata_to_swagger(metadata)
response_code = metadata[:response][:code] 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] verb = metadata[:operation][:verb]
operation = metadata[:operation] operation = metadata[:operation]
.reject { |k, _v| k == :verb } .reject { |k,v| k == :verb }
.merge(responses: { response_code => response }) .merge(responses: { response_code => response })
path_template = metadata[:path_item][:template] {
path_item = metadata[:path_item] paths: {
.reject { |k, _v| k == :template } metadata[:path] => {
.merge(verb => operation) 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
class ConfigurationError < StandardError; end
end end
end end

View File

@ -0,0 +1,5 @@
module Rswag
module Specs
VERSION = '1.0.1'
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,101 +0,0 @@
# frozen_string_literal: true
require 'rswag/specs/configuration'
module Rswag
module Specs
RSpec.describe Configuration do
subject { described_class.new(rspec_config) }
let(:rspec_config) do
OpenStruct.new(swagger_root: swagger_root, swagger_docs: swagger_docs, swagger_format: swagger_format)
end
let(:swagger_root) { 'foobar' }
let(:swagger_docs) do
{
'v1/swagger.json' => { info: { title: 'v1' } },
'v2/swagger.json' => { info: { title: 'v2' } }
}
end
let(:swagger_format) { :yaml }
describe '#swagger_root' do
let(:response) { subject.swagger_root }
context 'provided in rspec config' do
it { expect(response).to eq('foobar') }
end
context 'not provided' do
let(:swagger_root) { nil }
it { expect { response }.to raise_error ConfigurationError }
end
end
describe '#swagger_docs' do
let(:response) { subject.swagger_docs }
context 'provided in rspec config' do
it { expect(response).to be_an_instance_of(Hash) }
end
context 'not provided' do
let(:swagger_docs) { nil }
it { expect { response }.to raise_error ConfigurationError }
end
context 'provided but empty' do
let(:swagger_docs) { {} }
it { expect { response }.to raise_error ConfigurationError }
end
end
describe '#swagger_format' do
let(:response) { subject.swagger_format }
context 'provided in rspec config' do
it { expect(response).to be_an_instance_of(Symbol) }
end
context 'unsupported format provided' do
let(:swagger_format) { :xml }
it { expect { response }.to raise_error ConfigurationError }
end
context 'not provided' do
let(:swagger_format) { nil }
it { expect(response).to eq(:json) }
end
end
describe '#get_swagger_doc(tag=nil)' do
let(:swagger_doc) { subject.get_swagger_doc(tag) }
context 'no tag provided' do
let(:tag) { nil }
it 'returns the first doc in rspec config' do
expect(swagger_doc).to eq(info: { title: 'v1' })
end
end
context 'tag provided' do
context 'matching doc' do
let(:tag) { 'v2/swagger.json' }
it 'returns the matching doc in rspec config' do
expect(swagger_doc).to eq(info: { title: 'v2' })
end
end
context 'no matching doc' do
let(:tag) { 'foobar' }
it { expect { swagger_doc }.to raise_error ConfigurationError }
end
end
end
end
end
end

View File

@ -1,31 +1,29 @@
# frozen_string_literal: true
require 'rswag/specs/example_group_helpers' require 'rswag/specs/example_group_helpers'
module Rswag module Rswag
module Specs module Specs
RSpec.describe ExampleGroupHelpers do
subject { double('example_group') }
describe ExampleGroupHelpers do
subject { double('example_group') }
let(:api_metadata) { {} }
before do before do
subject.extend ExampleGroupHelpers subject.extend ExampleGroupHelpers
allow(subject).to receive(:describe) allow(subject).to receive(:describe)
allow(subject).to receive(:context) allow(subject).to receive(:context)
allow(subject).to receive(:metadata).and_return(api_metadata) allow(subject).to receive(:metadata).and_return(api_metadata)
end end
let(:api_metadata) { {} }
describe '#path(path)' do describe '#path(path)' do
before { subject.path('/blogs') } before { subject.path('/blogs') }
it "delegates to 'describe' with 'path' metadata" do it "delegates to 'describe' with 'path' metadata" do
expect(subject).to have_received(:describe).with( expect(subject).to have_received(:describe).with(
'/blogs', path_item: { template: '/blogs' } '/blogs', path: '/blogs'
) )
end end
end end
describe '#get|post|patch|put|delete|head|options|trace(verb, summary)' do describe '#get|post|patch|put|delete|head(verb, summary)' do
before { subject.post('Creates a blog') } before { subject.post('Creates a blog') }
it "delegates to 'describe' with 'operation' metadata" do it "delegates to 'describe' with 'operation' metadata" do
@ -35,7 +33,33 @@ module Rswag
end end
end end
describe '#tags|description|operationId|consumes|produces|schemes|deprecated(value)' do
let(:api_metadata) { { operation: {} } }
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
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 describe '#tags|description|operationId|consumes|produces|schemes|deprecated|security(value)' do
let(:api_metadata) { { operation: {} } }
before do before do
subject.tags('Blogs', 'Admin') subject.tags('Blogs', 'Admin')
subject.description('Some description') subject.description('Some description')
@ -46,16 +70,15 @@ module Rswag
subject.deprecated(true) subject.deprecated(true)
subject.security(api_key: []) subject.security(api_key: [])
end end
let(:api_metadata) { { operation: {} } }
it "adds to the 'operation' metadata" do it "adds to the 'operation' metadata" do
expect(api_metadata[:operation]).to match( expect(api_metadata[:operation]).to match(
tags: ['Blogs', 'Admin'], tags: [ 'Blogs', 'Admin' ],
description: 'Some description', description: 'Some description',
operationId: 'createBlog', operationId: 'createBlog',
consumes: ['application/json', 'application/xml'], consumes: [ 'application/json', 'application/xml' ],
produces: ['application/json', 'application/xml'], produces: [ 'application/json', 'application/xml' ],
schemes: ['http', 'https'], schemes: [ 'http', 'https' ],
deprecated: true, deprecated: true,
security: { api_key: [] } security: { api_key: [] }
) )
@ -63,47 +86,27 @@ module Rswag
end end
describe '#parameter(attributes)' do describe '#parameter(attributes)' do
context "when called at the 'path' level" do let(:api_metadata) { { operation: {} } }
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 context 'always' do
expect(api_metadata[:path_item][:parameters]).to match(
[name: :blog, in: :body, schema: { type: 'object' }]
)
end
end
context "when called at the 'operation' level" do
before { subject.parameter(name: :blog, in: :body, schema: { type: 'object' }) } before { subject.parameter(name: :blog, in: :body, schema: { type: 'object' }) }
let(:api_metadata) { { path_item: {}, operation: {} } } # i.e. operation defined
it "adds to the 'operation parameters' metadata" do it "adds to the 'operation parameters' metadata" do
expect(api_metadata[:operation][:parameters]).to match( expect(api_metadata[:operation][:parameters]).to match(
[name: :blog, in: :body, schema: { type: 'object' }] [ name: :blog, in: :body, schema: { type: 'object' } ]
) )
end end
end end
context "'path' parameter" do context "'path' parameter" do
before { subject.parameter(name: :id, in: :path) } before { subject.parameter(name: :id, in: :path) }
let(:api_metadata) { { operation: {} } }
it "automatically sets the 'required' flag" do it "automatically sets the 'required' flag" do
expect(api_metadata[:operation][:parameters]).to match( expect(api_metadata[:operation][:parameters]).to match(
[name: :id, in: :path, required: true] [ name: :id, in: :path, required: true ]
) )
end end
end end
context "when 'in' parameter key is not defined" do
before { subject.parameter(name: :id) }
let(:api_metadata) { { operation: {} } }
it "does not require the 'in' parameter key" do
expect(api_metadata[:operation][:parameters]).to match([name: :id])
end
end
end end
describe '#response(code, description)' do describe '#response(code, description)' do
@ -117,8 +120,8 @@ module Rswag
end end
describe '#schema(value)' do describe '#schema(value)' do
before { subject.schema(type: 'object') }
let(:api_metadata) { { response: {} } } let(:api_metadata) { { response: {} } }
before { subject.schema(type: 'object') }
it "adds to the 'response' metadata" do it "adds to the 'response' metadata" do
expect(api_metadata[:response][:schema]).to match(type: 'object') expect(api_metadata[:response][:schema]).to match(type: 'object')
@ -126,8 +129,8 @@ module Rswag
end end
describe '#header(name, attributes)' do describe '#header(name, attributes)' do
before { subject.header('Date', type: 'string') }
let(:api_metadata) { { response: {} } } let(:api_metadata) { { response: {} } }
before { subject.header('Date', type: 'string') }
it "adds to the 'response headers' metadata" do it "adds to the 'response headers' metadata" do
expect(api_metadata[:response][:headers]).to match( expect(api_metadata[:response][:headers]).to match(
@ -135,30 +138,6 @@ module Rswag
) )
end end
end end
describe '#examples(example)' do
let(:mime) { 'application/json' }
let(:json_example) do
{
mime => {
foo: 'bar'
}
}
end
let(:api_metadata) { { response: {} } }
before do
subject.examples(json_example)
end
it "adds to the 'response examples' metadata" do
expect(api_metadata[:response][:content]).to match(
mime => {
example: json_example[mime]
}
)
end
end
end end
end end
end end

View File

@ -1,22 +1,29 @@
# frozen_string_literal: true
require 'rswag/specs/example_helpers' require 'rswag/specs/example_helpers'
module Rswag module Rswag
module Specs module Specs
RSpec.describe ExampleHelpers do
subject { double('example') }
before do describe ExampleHelpers do
subject.extend(ExampleHelpers) let(:api_metadata) do
allow(Rswag::Specs).to receive(:config).and_return(config) {
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc) path: '/blogs/{blog_id}/comments/{id}',
stub_const('Rswag::Specs::RAILS_VERSION', 3) operation: {
end verb: :put,
let(:config) { double('config') } summary: 'Updates a blog',
let(:swagger_doc) do parameters: [
{ name: :blog_id, in: :path, type: 'integer' },
{ name: 'id', in: :path, type: 'integer' },
{ name: 'q1', in: :query, type: 'string' },
{ name: :blog, in: :body, schema: { type: 'object' } }
],
security: {
api_key: []
}
}
}
end
let(:global_metadata) do
{ {
swagger: '2.0',
securityDefinitions: { securityDefinitions: {
api_key: { api_key: {
type: :apiKey, type: :apiKey,
@ -27,42 +34,30 @@ module Rswag
} }
end end
let(:metadata) do subject { double('example') }
{
path_item: { template: '/blogs/{blog_id}/comments/{id}' }, before do
operation: { subject.extend ExampleHelpers
verb: :put, allow(subject).to receive(:blog_id).and_return(1)
summary: 'Updates a blog', allow(subject).to receive(:id).and_return(2)
consumes: ['application/json'], allow(subject).to receive(:q1).and_return('foo')
parameters: [ allow(subject).to receive(:api_key).and_return('fookey')
{ name: :blog_id, in: :path, type: 'integer' }, allow(subject).to receive(:blog).and_return(text: 'Some comment')
{ name: 'id', in: :path, type: 'integer' }, allow(subject).to receive(:global_metadata).and_return(global_metadata)
{ name: 'q1', in: :query, type: 'string' }, allow(subject).to receive(:put)
{ name: :blog, in: :body, schema: { type: 'object' } }
],
security: [
{ api_key: [] }
]
}
}
end end
describe '#submit_request(metadata)' do describe '#submit_request(api_metadata)' do
before do before do
allow(subject).to receive(:blog_id).and_return(1) stub_const('Rails::VERSION::MAJOR', 3)
allow(subject).to receive(:id).and_return(2) subject.submit_request(api_metadata)
allow(subject).to receive(:q1).and_return('foo')
allow(subject).to receive(:api_key).and_return('fookey')
allow(subject).to receive(:blog).and_return(text: 'Some comment')
allow(subject).to receive(:put)
subject.submit_request(metadata)
end end
it "submits a request built from metadata and 'let' values" do it "submits a request built from metadata and 'let' values" do
expect(subject).to have_received(:put).with( expect(subject).to have_received(:put).with(
'/blogs/1/comments/2?q1=foo&api_key=fookey', '/blogs/1/comments/2?q1=foo&api_key=fookey',
'{"text":"Some comment"}', "{\"text\":\"Some comment\"}",
{ 'CONTENT_TYPE' => 'application/json' } {}
) )
end end
end end

View File

@ -1,419 +1,215 @@
# frozen_string_literal: true
require 'rswag/specs/request_factory' require 'rswag/specs/request_factory'
module Rswag module Rswag
module Specs module Specs
RSpec.describe RequestFactory do
subject { RequestFactory.new(config) }
before do describe RequestFactory do
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc) let(:api_metadata) do
end
let(:config) { double('config') }
let(:swagger_doc) { { swagger: '2.0' } }
let(:example) { double('example') }
let(:metadata) do
{ {
path_item: { template: '/blogs' }, path: '/blogs/{blog_id}/comments/{id}',
operation: { verb: :get } operation: {
verb: :put,
summary: 'Updates a blog',
parameters: [
{ name: :blog_id, in: :path, type: 'integer' },
{ name: 'id', in: :path, type: 'integer' }
]
}
} }
end end
let(:global_metadata) { {} }
describe '#build_request(metadata, example)' do subject { RequestFactory.new(api_metadata, global_metadata) }
let(:request) { subject.build_request(metadata, example) }
it 'builds request hash for given example' do let(:example) { double('example') }
expect(request[:verb]).to eq(:get) before do
expect(request[:path]).to eq('/blogs') allow(example).to receive(:blog_id).and_return(1)
end allow(example).to receive(:id).and_return('2')
end
context "'path' parameters" do describe '#build_fullpath(example)' do
before do let(:path) { subject.build_fullpath(example) }
metadata[:path_item][:template] = '/blogs/{blog_id}/comments/{id}'
metadata[:operation][:parameters] = [
{ name: 'blog_id', in: :path, type: :number },
{ name: 'id', in: :path, type: :number }
]
allow(example).to receive(:blog_id).and_return(1)
allow(example).to receive(:id).and_return(2)
end
it 'builds the path from example values' do context 'always' do
expect(request[:path]).to eq('/blogs/1/comments/2') it "builds a path using metadata and example values" do
expect(path).to eq('/blogs/1/comments/2')
end end
end end
context "'query' parameters" do context "'query' parameters" do
before do before do
metadata[:operation][:parameters] = [ api_metadata[:operation][:parameters] << { name: 'q1', in: :query, type: 'string' }
{ name: 'q1', in: :query, type: :string }, api_metadata[:operation][:parameters] << { name: 'q2', in: :query, type: 'string' }
{ name: 'q2', in: :query, type: :string }
]
allow(example).to receive(:q1).and_return('foo') allow(example).to receive(:q1).and_return('foo')
allow(example).to receive(:q2).and_return('bar') allow(example).to receive(:q2).and_return('bar')
end end
it 'builds the query string from example values' do it "appends a query string using metadata and example values" do
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar') expect(path).to eq('/blogs/1/comments/2?q1=foo&q2=bar')
end end
end end
context "'query' parameters of type 'array'" do context "'query' parameter of type 'array'" do
before do before do
metadata[:operation][:parameters] = [ api_metadata[:operation][:parameters] << {
{ name: 'things', in: :query, type: :array, collectionFormat: collection_format } name: 'things',
] in: :query,
allow(example).to receive(:things).and_return(['foo', 'bar']) type: :array,
collectionFormat: collectionFormat
}
allow(example).to receive(:things).and_return([ 'foo', 'bar' ])
end end
context 'collectionFormat = csv' do context 'collectionFormat = csv' do
let(:collection_format) { :csv } let(:collectionFormat) { :csv }
it 'formats as comma separated values' do it "formats as comma separated values" do
expect(request[:path]).to eq('/blogs?things=foo,bar') expect(path).to eq('/blogs/1/comments/2?things=foo,bar')
end end
end end
context 'collectionFormat = ssv' do context 'collectionFormat = ssv' do
let(:collection_format) { :ssv } let(:collectionFormat) { :ssv }
it 'formats as space separated values' do it "formats as space separated values" do
expect(request[:path]).to eq('/blogs?things=foo bar') expect(path).to eq('/blogs/1/comments/2?things=foo bar')
end end
end end
context 'collectionFormat = tsv' do context 'collectionFormat = tsv' do
let(:collection_format) { :tsv } let(:collectionFormat) { :tsv }
it 'formats as tab separated values' do it "formats as tab separated values" do
expect(request[:path]).to eq('/blogs?things=foo\tbar') expect(path).to eq('/blogs/1/comments/2?things=foo\tbar')
end end
end end
context 'collectionFormat = pipes' do context 'collectionFormat = pipes' do
let(:collection_format) { :pipes } let(:collectionFormat) { :pipes }
it 'formats as pipe separated values' do it "formats as pipe separated values" do
expect(request[:path]).to eq('/blogs?things=foo|bar') expect(path).to eq('/blogs/1/comments/2?things=foo|bar')
end end
end end
context 'collectionFormat = multi' do context 'collectionFormat = multi' do
let(:collection_format) { :multi } let(:collectionFormat) { :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') expect(path).to eq('/blogs/1/comments/2?things=foo&things=bar')
end end
end end
end end
context "'header' parameters" do context "global definition for 'api_key in query'" do
before do before do
metadata[:operation][:parameters] = [{ name: 'Api-Key', in: :header, type: :string }] global_metadata[:securityDefinitions] = { api_key: { type: :apiKey, name: 'api_key', in: :query } }
allow(example).to receive(:'Api-Key').and_return('foobar') allow(example).to receive(:api_key).and_return('fookey')
end end
it 'adds names and example values to headers' do context 'global requirement' do
expect(request[:headers]).to eq({ 'Api-Key' => 'foobar' }) before { global_metadata[:security] = { api_key: [] } }
end
end
context 'optional parameters not provided' do it "appends the api_key using metadata and example value" do
before do expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
metadata[:operation][:parameters] = [
{ name: 'q1', in: :query, type: :string, required: false },
{ name: 'Api-Key', in: :header, type: :string, required: false }
]
end
it 'builds request hash without them' do
expect(request[:path]).to eq('/blogs')
expect(request[:headers]).to eq({})
end
end
context 'consumes content' do
before do
metadata[:operation][:consumes] = ['application/json', 'application/xml']
end
context "no 'Content-Type' provided" do
it "sets 'CONTENT_TYPE' header to first in list" do
expect(request[:headers]).to eq('CONTENT_TYPE' => 'application/json')
end end
end end
context "explicit 'Content-Type' provided" do context 'operation-specific requirement' do
before do before { api_metadata[:operation][:security] = { api_key: [] } }
allow(example).to receive(:'Content-Type').and_return('application/xml')
end
it "sets 'CONTENT_TYPE' header to example value" do it "appends the api_key using metadata and example value" do
expect(request[:headers]).to eq('CONTENT_TYPE' => 'application/xml') expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
end
end
context 'JSON payload' do
before do
metadata[:operation][:parameters] = [{ name: 'comment', in: :body, schema: { type: 'object' } }]
allow(example).to receive(:comment).and_return(text: 'Some comment')
end
it "serializes first 'body' parameter to JSON string" do
expect(request[:payload]).to eq('{"text":"Some comment"}')
end
end
context 'missing body parameter' do
before do
metadata[:operation][:parameters] = [{ name: 'comment', in: :body, schema: { type: 'object' } }]
allow(example).to receive(:comment).and_raise(NoMethodError, "undefined method 'comment'")
allow(example).to receive(:respond_to?).with(:'Content-Type')
allow(example).to receive(:respond_to?).with('comment').and_return(false)
end
it 'uses the referenced metadata to build the request' do
expect do
request[:payload]
end.to raise_error(Rswag::Specs::MissingParameterError, /Missing parameter 'comment'/)
end
end
context 'form payload' do
before do
metadata[:operation][:consumes] = ['multipart/form-data']
metadata[:operation][:parameters] = [
{ name: 'f1', in: :formData, type: :string },
{ name: 'f2', in: :formData, type: :string }
]
allow(example).to receive(:f1).and_return('foo blah')
allow(example).to receive(:f2).and_return('bar blah')
end
it 'sets payload to hash of names and example values' do
expect(request[:payload]).to eq(
'f1' => 'foo blah',
'f2' => 'bar blah'
)
end
end
end
context 'produces content' do
before do
metadata[:operation][:produces] = ['application/json', 'application/xml']
end
context "no 'Accept' value provided" do
it "sets 'HTTP_ACCEPT' header to first in list" do
expect(request[:headers]).to eq('HTTP_ACCEPT' => 'application/json')
end
end
context "explicit 'Accept' value provided" do
before do
allow(example).to receive(:Accept).and_return('application/xml')
end
it "sets 'HTTP_ACCEPT' header to example value" do
expect(request[:headers]).to eq('HTTP_ACCEPT' => 'application/xml')
end
end
end
context 'basic auth' do
context 'swagger 2.0' do
before do
swagger_doc[:securityDefinitions] = { basic: { type: :basic } }
metadata[:operation][:security] = [basic: []]
allow(example).to receive(:Authorization).and_return('Basic foobar')
end
it "sets 'HTTP_AUTHORIZATION' header to example value" do
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
end
end
context 'openapi 3.0.1' do
let(:swagger_doc) { { openapi: '3.0.1' } }
before do
swagger_doc[:components] = { securitySchemes: { basic: { type: :basic } } }
metadata[:operation][:security] = [basic: []]
allow(example).to receive(:Authorization).and_return('Basic foobar')
end
it "sets 'HTTP_AUTHORIZATION' header to example value" do
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
end
end
context 'openapi 3.0.1 upgrade notice' do
let(:swagger_doc) { { openapi: '3.0.1' } }
before do
allow(ActiveSupport::Deprecation).to receive(:warn)
swagger_doc[:securityDefinitions] = { basic: { type: :basic } }
metadata[:operation][:security] = [basic: []]
allow(example).to receive(:Authorization).and_return('Basic foobar')
end
it 'warns the user to upgrade' do
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
expect(ActiveSupport::Deprecation).to have_received(:warn)
.with('Rswag::Specs: WARNING: securityDefinitions is replaced in OpenAPI3! Rename to components/securitySchemes (in swagger_helper.rb)')
expect(swagger_doc[:components]).to have_key(:securitySchemes)
end
end
end
context 'apiKey' do
before do
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: key_location } }
metadata[:operation][:security] = [apiKey: []]
allow(example).to receive(:api_key).and_return('foobar')
end
context 'in query' do
let(:key_location) { :query }
it 'adds name and example value to the query string' do
expect(request[:path]).to eq('/blogs?api_key=foobar')
end
end
context 'in header' do
let(:key_location) { :header }
it 'adds name and example value to the headers' do
expect(request[:headers]).to eq('api_key' => 'foobar')
end
end
context 'in header with auth param already added' do
let(:key_location) { :header }
before do
metadata[:operation][:parameters] = [
{ name: 'q1', in: :query, type: :string },
{ name: 'api_key', in: :header, type: :string }
]
allow(example).to receive(:q1).and_return('foo')
allow(example).to receive(:api_key).and_return('foobar')
end
it 'adds authorization parameter only once' do
expect(request[:headers]).to eq('api_key' => 'foobar')
expect(metadata[:operation][:parameters].size).to eq 2
end
end
end
context 'oauth2' do
before do
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
it "sets 'HTTP_AUTHORIZATION' header to example value" do
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Bearer foobar')
end
end
context 'paired security requirements' do
before do
swagger_doc[:securityDefinitions] = {
basic: { type: :basic },
api_key: { type: :apiKey, name: 'api_key', in: :query }
}
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
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
before do
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
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
end
end
context 'referenced parameters' do
context 'swagger 2.0' do
before do
swagger_doc[:parameters] = { q1: { name: 'q1', in: :query, type: :string } }
metadata[:operation][:parameters] = [{ '$ref' => '#/parameters/q1' }]
allow(example).to receive(:q1).and_return('foo')
end
it 'uses the referenced metadata to build the request' do
expect(request[:path]).to eq('/blogs?q1=foo')
end
end
context 'openapi 3.0.1' do
let(:swagger_doc) { { openapi: '3.0.1' } }
before do
swagger_doc[:components] = { parameters: { q1: { name: 'q1', in: :query, type: :string } } }
metadata[:operation][:parameters] = [{ '$ref' => '#/components/parameters/q1' }]
allow(example).to receive(:q1).and_return('foo')
end
it 'uses the referenced metadata to build the request' do
expect(request[:path]).to eq('/blogs?q1=foo')
end
end
context 'openapi 3.0.1 upgrade notice' do
let(:swagger_doc) { { openapi: '3.0.1' } }
before do
allow(ActiveSupport::Deprecation).to receive(:warn)
swagger_doc[:parameters] = { q1: { name: 'q1', in: :query, type: :string } }
metadata[:operation][:parameters] = [{ '$ref' => '#/parameters/q1' }]
allow(example).to receive(:q1).and_return('foo')
end
it 'warns the user to upgrade' do
expect(request[:path]).to eq('/blogs?q1=foo')
expect(ActiveSupport::Deprecation).to have_received(:warn)
.with('Rswag::Specs: WARNING: #/parameters/ refs are replaced in OpenAPI3! Rename to #/components/parameters/')
expect(ActiveSupport::Deprecation).to have_received(:warn)
.with('Rswag::Specs: WARNING: parameters is replaced in OpenAPI3! Rename to components/parameters (in swagger_helper.rb)')
end end
end end
end end
context 'global basePath' do context 'global basePath' do
before { swagger_doc[:basePath] = '/api' } before { global_metadata[:basePath] = '/foobar' }
it 'prepends to the path' do it 'prepends the basePath' do
expect(request[:path]).to eq('/api/blogs') expect(path).to eq('/foobar/blogs/1/comments/2')
end
end
end
describe '#build_body(example)' do
let(:body) { subject.build_body(example) }
context "no 'body' parameter" do
it "returns ''" do
expect(body).to eq('')
end end
end end
context 'global consumes' do context "'body' parameter" 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
before do before do
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: :query } } api_metadata[:operation][:parameters] << { name: 'comment', in: :body, schema: { type: 'object' } }
swagger_doc[:security] = [apiKey: []] allow(example).to receive(:comment).and_return(text: 'Some comment')
allow(example).to receive(:api_key).and_return('foobar')
end end
it 'applieds the scheme by default' do it 'returns the example value as a json string' do
expect(request[:path]).to eq('/blogs?api_key=foobar') expect(body).to eq("{\"text\":\"Some comment\"}")
end
end
context "referenced 'body' parameter" do
before do
api_metadata[:operation][:parameters] << { '$ref' => '#/parameters/comment' }
global_metadata[:parameters] = {
'comment' => { name: 'comment', in: :body, schema: { type: 'object' } }
}
allow(example).to receive(:comment).and_return(text: 'Some comment')
end
it 'returns the example value as a json string' do
expect(body).to eq("{\"text\":\"Some comment\"}")
end
end
end
describe '#build_headers' do
let(:headers) { subject.build_headers(example) }
context "no 'header' params" do
it 'returns an empty hash' do
expect(headers).to eq({})
end
end
context "'header' params" do
before do
api_metadata[:operation][:parameters] << { name: 'Api-Key', in: :header, type: 'string' }
allow(example).to receive(:'Api-Key').and_return('foobar')
end
it 'returns a hash of names with example values' do
expect(headers).to eq({ 'Api-Key' => 'foobar' })
end
end
context 'consumes & produces' do
before do
api_metadata[:operation][:consumes] = [ 'application/json', 'application/xml' ]
api_metadata[:operation][:produces] = [ 'application/json', 'application/xml' ]
end
it "includes corresponding 'Accept' & 'Content-Type' headers" do
expect(headers).to match(
'ACCEPT' => 'application/json;application/xml',
'CONTENT_TYPE' => 'application/json;application/xml'
)
end
end
context 'global consumes & produces' do
let(:global_metadata) do
{
consumes: [ 'application/json', 'application/xml' ],
produces: [ 'application/json', 'application/xml' ]
}
end
it "includes corresponding 'Accept' & 'Content-Type' headers" do
expect(headers).to match(
'ACCEPT' => 'application/json;application/xml',
'CONTENT_TYPE' => 'application/json;application/xml'
)
end end
end end
end end

View File

@ -1,122 +1,69 @@
# frozen_string_literal: true
require 'rswag/specs/response_validator' require 'rswag/specs/response_validator'
module Rswag module Rswag
module Specs module Specs
RSpec.describe ResponseValidator do
subject { ResponseValidator.new(config) }
before do describe ResponseValidator do
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc) let(:api_metadata) { { response: { code: 200 } } }
allow(config).to receive(:get_swagger_doc_version).and_return('2.0') let(:global_metadata) { {} }
end
let(:config) { double('config') }
let(:swagger_doc) { {} }
let(:example) { double('example') }
let(:metadata) do
{
response: {
code: 200,
headers: { 'X-Rate-Limit-Limit' => { type: :integer } },
schema: {
type: :object,
properties: { text: { type: :string } },
required: ['text']
}
}
}
end
describe '#validate!(metadata, response)' do subject { ResponseValidator.new(api_metadata, global_metadata) }
let(:call) { subject.validate!(metadata, response) }
let(:response) do
OpenStruct.new(
code: '200',
headers: { 'X-Rate-Limit-Limit' => '10' },
body: '{"text":"Some comment"}'
)
end
context 'response matches metadata' do describe '#validate!(response)' do
it { expect { call }.to_not raise_error } let(:call) { subject.validate!(response) }
end
context 'response code differs from metadata' do context "no 'schema' provided" do
before { response.code = '400' } context 'response code matches' do
it { expect { call }.to raise_error(/Expected response code/) } let(:response) { OpenStruct.new(code: 200, body: '') }
end it { expect { call }.to_not raise_error }
context 'response headers differ from metadata' do
before { response.headers = {} }
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/) }
end
context 'referenced schemas' do
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' }
end
it 'uses the referenced schema to validate the response body' do
expect { call }.to raise_error(/Expected response body/)
end
end end
context 'openapi 3.0.1' do context 'response code does not match' do
context 'components/schemas' do let(:response) { OpenStruct.new(code: 201, body: '') }
before do it { expect { call }.to raise_error UnexpectedResponse }
allow(ActiveSupport::Deprecation).to receive(:warn) end
allow(config).to receive(:get_swagger_doc_version).and_return('3.0.1') end
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 context "'schema' provided" do
expect { call }.to raise_error(/Expected response body/) before do
end api_metadata[:response][:schema] = {
end type: 'object',
properties: { text: { type: 'string' } },
required: [ 'text' ]
}
end
context 'deprecated definitions' do context 'response code & body matches' do
before do let(:response) { OpenStruct.new(code: 200, body: "{\"text\":\"Some comment\"}") }
allow(ActiveSupport::Deprecation).to receive(:warn) it { expect { call }.to_not raise_error }
allow(config).to receive(:get_swagger_doc_version).and_return('3.0.1') end
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 context 'response code matches & body does not' do
expect { call }.to raise_error(/Expected response body/) let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some comment\"}") }
expect(ActiveSupport::Deprecation).to have_received(:warn) it { expect { call }.to raise_error UnexpectedResponse }
.with('Rswag::Specs: WARNING: definitions is replaced in OpenAPI3! Rename to components/schemas (in swagger_helper.rb)') end
end end
end
context "referenced 'schema' provided" do
before do
api_metadata[:response][:schema] = { '$ref' => '#/definitions/author' }
global_metadata[:definitions] = {
author: {
type: 'object',
properties: { name: { type: 'string' } },
required: [ 'name' ]
}
}
end
context 'response code & body matches' do
let(:response) { OpenStruct.new(code: 200, body: "{\"name\":\"Some name\"}") }
it { expect { call }.to_not raise_error }
end
context 'response code matches & body does not' do
let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some name\"}") }
it { expect { call }.to raise_error UnexpectedResponse }
end end
end end
end end

View File

@ -1,68 +1,60 @@
# frozen_string_literal: true
require 'rswag/specs/swagger_formatter' require 'rswag/specs/swagger_formatter'
require 'ostruct' require 'ostruct'
module Rswag module Rswag
module Specs module Specs
RSpec.describe SwaggerFormatter do
subject { described_class.new(output, config) }
# Mock out some infrastructure describe SwaggerFormatter do
before do # Mock infrastructure - output, RSpec.configuration etc.
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(:output) { double('output').as_null_object }
let(:swagger_root) { File.expand_path('tmp/swagger', __dir__) } let(:swagger_root) { File.expand_path('../tmp', __FILE__) }
let(:swagger_docs) do
{
'v1/swagger.json' => { info: { version: 'v1' } }
}
end
let(:config) { OpenStruct.new(swagger_root: swagger_root, swagger_docs: swagger_docs) }
before { allow(RSpec).to receive(:configuration).and_return(config) }
subject { described_class.new(output) }
describe '::new(output)' do
context 'swagger_root not configured' do
let(:swagger_root) { nil }
it { expect { subject }.to raise_error ConfigurationError }
end
context 'swagger_docs not configured' do
let(:swagger_docs) { nil }
it { expect { subject }.to raise_error ConfigurationError }
end
end
describe '#example_group_finished(notification)' do describe '#example_group_finished(notification)' do
before do # Mock notification parameter
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
subject.example_group_finished(notification)
end
let(:notification) { OpenStruct.new(group: OpenStruct.new(metadata: api_metadata)) }
let(:api_metadata) do let(:api_metadata) do
{ {
path_item: { template: '/blogs', parameters: [{ type: :string }] }, path: '/blogs',
operation: { verb: :post, summary: 'Creates a blog', parameters: [{ type: :string }] }, operation: { verb: :post, summary: 'Creates a blog' },
response: response_metadata, response: { code: '201', description: 'blog created' }
document: document
} }
end end
let(:response_metadata) { { code: '201', description: 'blog created', headers: { type: :string }, schema: { '$ref' => '#/definitions/blog' } } } let(:notification) { OpenStruct.new(group: OpenStruct.new(metadata: api_metadata)) }
context 'with the document tag set to false' do let(:call) { subject.example_group_finished(notification) }
let(:swagger_doc) { { swagger: '2.0' } }
let(:document) { false }
it 'does not update the swagger doc' do context 'single swagger_doc' do
expect(swagger_doc).to match({ swagger: '2.0' }) before { call }
end
end
context 'with the document tag set to anything but false' do it 'converts metadata to swagger and merges into the doc' do
let(:swagger_doc) { { swagger: '2.0' } } expect(swagger_docs.values.first).to match(
# anything works, including its absence when specifying responses. info: { version: 'v1' },
let(:document) { nil }
it 'converts to swagger and merges into the corresponding swagger doc' do
expect(swagger_doc).to match(
swagger: '2.0',
paths: { paths: {
'/blogs' => { '/blogs' => {
parameters: [{ type: :string }],
post: { post: {
parameters: [{ type: :string }],
summary: 'Creates a blog', summary: 'Creates a blog',
responses: { responses: {
'201' => { '201' => { description: 'blog created' }
description: 'blog created',
headers: { type: :string },
schema: { '$ref' => '#/definitions/blog' }
}
} }
} }
} }
@ -71,305 +63,61 @@ module Rswag
end end
end end
context 'with metadata upgrades for 3.0' do context 'multiple swagger_docs' do
let(:swagger_doc) do let(:swagger_docs) do
{ {
openapi: '3.0.1', 'v1/swagger.json' => {},
basePath: '/foo', 'v2/swagger.json' => {}
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 end
let(:document) { nil }
it 'converts query and path params, type: to schema: { type: }' do context "no 'swagger_doc' tag" do
expect(swagger_doc.slice(:paths)).to match( before { call }
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 it 'merges into the first doc' do
let(:response_metadata) do expect(swagger_docs.values.first).to have_key(:paths)
{
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
end end
context 'with empty content' do context "matching 'swagger_doc' tag" do
let(:swagger_doc) do before do
{ api_metadata[:swagger_doc] = 'v2/swagger.json'
openapi: '3.0.1', call
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 end
it 'converts query and path params, type: to schema: { type: }' do it 'merges into the matched doc' do
expect(swagger_doc.slice(:paths)).to match( expect(swagger_docs.values.last).to have_key(:paths)
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
end end
it 'converts basePath, schemas and host to urls' do context "non matching 'swagger_doc' tag" do
expect(swagger_doc.slice(:servers)).to match( before { api_metadata[:swagger_doc] = 'foobar' }
servers: { it { expect { call }.to raise_error ConfigurationError }
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 end
end end
describe '#stop' do describe '#stop' do
let(:notification) { double('notification') }
let(:swagger_docs) do
{
'v1/swagger.json' => { info: { version: 'v1' } },
'v2/swagger.json' => { info: { version: 'v2' } },
}
end
before do before do
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root) FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
allow(config).to receive(:swagger_docs).and_return(
'v1/swagger.json' => doc_1,
'v2/swagger.json' => doc_2
)
allow(config).to receive(:swagger_format).and_return(swagger_format)
subject.stop(notification) subject.stop(notification)
end end
let(:doc_1) { { info: { version: 'v1' } } } it 'writes the swagger_doc(s) to file' do
let(:doc_2) { { info: { version: 'v2' } } } expect(File).to exist("#{swagger_root}/v1/swagger.json")
let(:swagger_format) { :json } expect(File).to exist("#{swagger_root}/v2/swagger.json")
let(:notification) { double('notification') }
context 'with default format' do
it 'writes the swagger_doc(s) to file' do
expect(File).to exist("#{swagger_root}/v1/swagger.json")
expect(File).to exist("#{swagger_root}/v2/swagger.json")
expect { JSON.parse(File.read("#{swagger_root}/v2/swagger.json")) }.not_to raise_error
end
end
context 'with yaml format' do
let(:swagger_format) { :yaml }
it 'writes the swagger_doc(s) as yaml' do
expect(File).to exist("#{swagger_root}/v1/swagger.json")
expect { JSON.parse(File.read("#{swagger_root}/v1/swagger.json")) }.to raise_error(JSON::ParserError)
# Psych::DisallowedClass would be raised if we do not pre-process ruby symbols
expect { YAML.safe_load(File.read("#{swagger_root}/v1/swagger.json")) }.not_to raise_error
end
end
context 'with oauth3 upgrades' do
let(:doc_2) do
{
paths: {
'/path/' => {
get: {
summary: 'Retrieve Nested Paths',
tags: ['nested Paths'],
produces: ['application/json'],
consumes: ['application/xml', 'application/json'],
parameters: [{
in: :body,
schema: { foo: :bar }
}, {
in: :headers
}]
}
}
}
}
end
it 'removes remaining consumes/produces' do
expect(doc_2[:paths]['/path/'][:get].keys).to eql([:summary, :tags, :parameters, :requestBody])
end
it 'duplicates params in: :body to requestBody from consumes list' do
expect(doc_2[:paths]['/path/'][:get][:parameters]).to eql([{ in: :headers }])
expect(doc_2[:paths]['/path/'][:get][:requestBody]).to eql(content: {
'application/xml' => { schema: { foo: :bar } },
'application/json' => { schema: { foo: :bar } }
})
end
end
context 'with oauth3 formData' do
let(:doc_2) do
{
paths: {
'/path/' => {
post: {
summary: 'Retrieve Nested Paths',
tags: ['nested Paths'],
produces: ['application/json'],
consumes: ['multipart/form-data'],
parameters: [{
in: :formData,
schema: { type: :file }
},{
in: :headers
}]
}
}
}
}
end
it 'removes remaining consumes/produces' do
expect(doc_2[:paths]['/path/'][:post].keys).to eql([:summary, :tags, :parameters, :requestBody])
end
it 'duplicates params in: :formData to requestBody from consumes list' do
expect(doc_2[:paths]['/path/'][:post][:parameters]).to eql([{ in: :headers }])
expect(doc_2[:paths]['/path/'][:post][:requestBody]).to eql(content: {
'multipart/form-data' => { schema: { type: :file } }
})
end
end end
after do after do
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root) FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
end end
end end
end end

View File

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

View File

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

View File

@ -0,0 +1,11 @@
module Rswag
module Ui
class HomeController < ActionController::Base
def index
@swagger_endpoints = Rswag::Ui.config.swagger_endpoints
render :index, layout: false
end
end
end
end

View File

@ -0,0 +1,126 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="icon" type="image/png" href="/assets/swagger-ui/images/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="/assets/swagger-ui/images/favicon-16x16.png" sizes="16x16" />
<link href='/assets/swagger-ui/css/typography.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='/assets/swagger-ui/css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='/assets/swagger-ui/css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='/assets/swagger-ui/css/reset.css' media='print' rel='stylesheet' type='text/css'/>
<link href='/assets/swagger-ui/css/print.css' media='print' rel='stylesheet' type='text/css'/>
<script src='/assets/swagger-ui/lib/object-assign-pollyfill.js' type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/jquery-1.8.0.min.js' type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/jquery.slideto.min.js' type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/jquery.wiggle.min.js' type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/handlebars-4.0.5.js' type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/lodash.min.js' type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/backbone-min.js' type='text/javascript'></script>
<script src='/assets/swagger-ui/swagger-ui.min.js' type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/highlight.9.1.0.pack.js' type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/highlight.9.1.0.pack_extended.js' type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/jsoneditor.min.js' type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/marked.js' type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/swagger-oauth.js' type='text/javascript'></script>
<!-- Some basic translations -->
<!-- <script src='/assets/swagger-ui/lang/translator.js' type='text/javascript'></script> -->
<!-- <script src='/assets/swagger-ui/lang/ru.js' type='text/javascript'></script> -->
<!-- <script src='/assets/swagger-ui/lang/en.js' type='text/javascript'></script> -->
<script type="text/javascript">
$(function () {
hljs.configure({
highlightSizeThreshold: 5000
});
// Pre load translate...
if(window.SwaggerTranslator) {
window.SwaggerTranslator.translate();
}
window.swaggerUi = new SwaggerUi({
url: '<%= @swagger_endpoints.first.path %>',
dom_id: "swagger-ui-container",
supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
onComplete: function(swaggerApi, swaggerUi){
if(typeof initOAuth == "function") {
initOAuth({
clientId: "your-client-id",
clientSecret: "your-client-secret-if-required",
realm: "your-realms",
appName: "your-app-name",
scopeSeparator: " ",
additionalQueryStringParams: {}
});
}
if(window.SwaggerTranslator) {
window.SwaggerTranslator.translate();
}
},
onFailure: function(data) {
log("Unable to Load SwaggerUI");
},
docExpansion: "list",
jsonEditor: false,
defaultModelRendering: 'schema',
showRequestHeaders: false
});
window.swaggerUi.load();
function log() {
if ('console' in window) {
console.log.apply(console, arguments);
}
}
});
</script>
</head>
<body class="swagger-section">
<div id='header'>
<div class="swagger-ui-wrap">
<a id="logo" href="http://swagger.io"><img class="logo__img" alt="swagger" height="30" width="30" src="/assets/swagger-ui/images/logo_small.png" /><span class="logo__title">swagger</span></a>
<form id='api_selector'>
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"/></div>
<div id='auth_container'></div>
<select id="select_document">
<% @swagger_endpoints.each do |endpoint| %>
<option value='<%= endpoint.path %>'><%= endpoint.title %></option>
<% end %>
</select>
<script type="text/javascript">
// Refresh the swagger-ui when a new document is selected
$('#select_document').change(function () {
$('#input_baseUrl').val($(this).val());
window.swaggerUi.headerView.showCustom();
});
</script>
<style>
#select_document {
border: none;
height: 1.85em;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
-o-border-radius: 4px;
-ms-border-radius: 4px;
-khtml-border-radius: 4px;
font-size: 0.85em;
font-weight: bold;
color: white;
background-color: #547f00;
}
</style>
</form>
</div>
</div>
<div id="message-bar" class="swagger-ui-wrap" data-sw-translate>&nbsp;</div>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
</body>
</html>

View File

@ -0,0 +1,3 @@
Rswag::Ui::Engine.routes.draw do
root to: 'home#index'
end

View File

@ -3,10 +3,10 @@ require 'rails/generators'
module Rswag module Rswag
module Ui module Ui
class CustomGenerator < Rails::Generators::Base class CustomGenerator < Rails::Generators::Base
source_root File.expand_path('../../../../../../lib/rswag/ui', __FILE__) source_root File.expand_path('../../../../../../app/views/rswag/ui/home', __FILE__)
def add_custom_index def add_custom_index
copy_file('index.erb', 'app/views/rswag/ui/home/index.html.erb') copy_file('index.html.erb', 'app/views/rswag/ui/home/index.html.erb')
end end
end end
end end

View File

@ -2,13 +2,8 @@ Rswag::Ui.configure do |c|
# List the Swagger endpoints that you want to be documented through the swagger-ui # List the Swagger endpoints that you want to be documented through the swagger-ui
# The first parameter is the path (absolute or relative to the UI host) to the corresponding # The first parameter is the path (absolute or relative to the UI host) to the corresponding
# endpoint and the second is a title that will be displayed in the document selector # JSON endpoint and the second is a title that will be displayed in the document selector
# NOTE: If you're using rspec-api to expose Swagger files (under swagger_root) as JSON or YAML endpoints, # NOTE: If you're using rspec-api to expose Swagger files (under swagger_root) as JSON endpoints,
# then the list below should correspond to the relative paths for those endpoints # then the list below should correspond to the relative paths for those endpoints
c.swagger_endpoint '/api-docs/v1/swagger.json', 'API V1 Docs'
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 end

View File

@ -1,3 +1,4 @@
require 'rswag/ui/version'
require 'rswag/ui/configuration' require 'rswag/ui/configuration'
require 'rswag/ui/engine' require 'rswag/ui/engine'

View File

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

View File

@ -1,44 +1,17 @@
require 'ostruct' require 'ostruct'
require 'rack'
module Rswag module Rswag
module Ui module Ui
class Configuration class Configuration
attr_reader :template_locations attr_reader :swagger_endpoints
attr_accessor :basic_auth_enabled
attr_accessor :config_object
attr_accessor :oauth_config_object
attr_reader :assets_root
def initialize def initialize
@template_locations = [ @swagger_endpoints = []
# preffered override location
"#{Rack::Directory.new('').root}/swagger/index.erb",
# backwards compatible override location
"#{Rack::Directory.new('').root}/app/views/rswag/ui/home/index.html.erb",
# default location
File.expand_path('../index.erb', __FILE__)
]
@assets_root = File.expand_path('../../../../node_modules/swagger-ui-dist', __FILE__)
@config_object = {}
@oauth_config_object = {}
@basic_auth_enabled = false
end end
def swagger_endpoint(url, name) def swagger_endpoint(path, title)
@config_object[:urls] ||= [] @swagger_endpoints << OpenStruct.new(path: path, title: title)
@config_object[:urls] << { url: url, name: name }
end 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 end
end end

View File

@ -1,25 +1,13 @@
require 'rswag/ui/middleware'
require 'rswag/ui/basic_auth'
module Rswag module Rswag
module Ui module Ui
class Engine < ::Rails::Engine class Engine < ::Rails::Engine
isolate_namespace Rswag::Ui isolate_namespace Rswag::Ui
initializer 'rswag-ui.initialize' do |app| initializer 'rswag-ui.initialize' do |app|
middleware.use Rswag::Ui::Middleware, Rswag::Ui.config if app.config.respond_to?(:assets)
app.config.assets.precompile += [ 'swagger-ui/*' ]
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
end end
rake_tasks do
load File.expand_path('../../../tasks/rswag-ui_tasks.rake', __FILE__)
end
end end
end end
end end

View File

@ -1,103 +0,0 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<!-- Workaround for https://github.com/swagger-api/swagger-editor/issues/1371 -->
<script>
if (window.navigator.userAgent.indexOf("Edge") > -1) {
console.log("Removing native Edge fetch in favor of swagger-ui's polyfill")
window.fetch = undefined;
}
</script>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function () {
var configObject = JSON.parse('<%= config_object.to_json %>');
var oauthConfigObject = JSON.parse('<%= oauth_config_object.to_json %>');
// Apply mandatory parameters
configObject.dom_id = "#swagger-ui";
configObject.presets = [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset];
configObject.layout = "StandaloneLayout";
// If oauth2RedirectUrl isn't specified, use the built-in default
if (!configObject.hasOwnProperty("oauth2RedirectUrl"))
configObject.oauth2RedirectUrl = window.location.href.replace("index.html", "oauth2-redirect.html");
// Build a system
const ui = SwaggerUIBundle(configObject);
// Apply OAuth config
ui.initOAuth(oauthConfigObject);
}
</script>
</body>
</html>

View File

@ -1,44 +0,0 @@
module Rswag
module Ui
class Middleware < Rack::Static
def initialize(app, config)
@config = config
super(app, urls: [ '' ], root: config.assets_root )
end
def call(env)
if base_path?(env)
redirect_uri = env['SCRIPT_NAME'].chomp('/') + '/index.html'
return [ 301, { 'Location' => redirect_uri }, [ ] ]
end
if index_path?(env)
return [ 200, { 'Content-Type' => 'text/html' }, [ render_template ] ]
end
super
end
private
def base_path?(env)
env['REQUEST_METHOD'] == "GET" && env['PATH_INFO'] == "/"
end
def index_path?(env)
env['REQUEST_METHOD'] == "GET" && env['PATH_INFO'] == "/index.html"
end
def render_template
file = File.new(template_filename)
template = ERB.new(file.read)
template.result(@config.get_binding)
end
def template_filename
@config.template_locations.find { |filename| File.exists?(filename) }
end
end
end
end

View File

@ -0,0 +1,5 @@
module Rswag
module Ui
VERSION = '1.0.1'
end
end

View File

@ -1,12 +0,0 @@
namespace :rswag do
namespace :ui do
desc 'TODO'
task :copy_assets, [ :dest ] do |t, args|
dest = args[:dest]
FileUtils.rm_r(dest, force: true)
FileUtils.mkdir_p(dest)
FileUtils.cp_r(Dir.glob("#{Rswag::Ui.config.assets_root}/{*.js,*.png,*.css}"), dest)
end
end
end

View File

@ -1,13 +0,0 @@
{
"name": "rswag-ui",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"swagger-ui-dist": {
"version": "3.52.5",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.52.5.tgz",
"integrity": "sha512-8z18eX8G/jbTXYzyNIaobrnD7PSN7yU/YkSasMmajrXtw0FGS64XjrKn5v37d36qmU3o1xLeuYnktshRr7uIFw=="
}
}
}

View File

@ -1,8 +0,0 @@
{
"name": "rswag-ui",
"version": "1.0.0",
"private": true,
"dependencies": {
"swagger-ui-dist": "3.52.5"
}
}

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,125 @@
/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,250 @@
.swagger-section #header a#logo {
font-size: 1.5em;
font-weight: bold;
text-decoration: none;
background: transparent url(../images/logo.png) no-repeat left center;
padding: 20px 0 20px 40px;
}
#text-head {
font-size: 80px;
font-family: 'Roboto', sans-serif;
color: #ffffff;
float: right;
margin-right: 20%;
}
.navbar-fixed-top .navbar-nav {
height: auto;
}
.navbar-fixed-top .navbar-brand {
height: auto;
}
.navbar-header {
height: auto;
}
.navbar-inverse {
background-color: #000;
border-color: #000;
}
#navbar-brand {
margin-left: 20%;
}
.navtext {
font-size: 10px;
}
.h1,
h1 {
font-size: 60px;
}
.navbar-default .navbar-header .navbar-brand {
color: #a2dfee;
}
/* tag titles */
.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a {
color: #393939;
font-family: 'Arvo', serif;
font-size: 1.5em;
}
.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover {
color: black;
}
.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 {
color: #525252;
padding-left: 0px;
display: block;
clear: none;
float: left;
font-family: 'Arvo', serif;
font-weight: bold;
}
.navbar-default .navbar-collapse,
.navbar-default .navbar-form {
border-color: #0A0A0A;
}
.container1 {
width: 1500px;
margin: auto;
margin-top: 0;
background-image: url('../images/shield.png');
background-repeat: no-repeat;
background-position: -40px -20px;
margin-bottom: 210px;
}
.container-inner {
width: 1200px;
margin: auto;
background-color: rgba(223, 227, 228, 0.75);
padding-bottom: 40px;
padding-top: 40px;
border-radius: 15px;
}
.header-content {
padding: 0;
width: 1000px;
}
.title1 {
font-size: 80px;
font-family: 'Vollkorn', serif;
color: #404040;
text-align: center;
padding-top: 40px;
padding-bottom: 100px;
}
#icon {
margin-top: -18px;
}
.subtext {
font-size: 25px;
font-style: italic;
color: #08b;
text-align: right;
padding-right: 250px;
}
.bg-primary {
background-color: #00468b;
}
.navbar-default .nav > li > a,
.navbar-default .nav > li > a:focus {
color: #08b;
}
.navbar-default .nav > li > a,
.navbar-default .nav > li > a:hover {
color: #08b;
}
.navbar-default .nav > li > a,
.navbar-default .nav > li > a:focus:hover {
color: #08b;
}
.text-faded {
font-size: 25px;
font-family: 'Vollkorn', serif;
}
.section-heading {
font-family: 'Vollkorn', serif;
font-size: 45px;
padding-bottom: 10px;
}
hr {
border-color: #00468b;
padding-bottom: 10px;
}
.description {
margin-top: 20px;
padding-bottom: 200px;
}
.description li {
font-family: 'Vollkorn', serif;
font-size: 25px;
color: #525252;
margin-left: 28%;
padding-top: 5px;
}
.gap {
margin-top: 200px;
}
.troubleshootingtext {
color: rgba(255, 255, 255, 0.7);
padding-left: 30%;
}
.troubleshootingtext li {
list-style-type: circle;
font-size: 25px;
padding-bottom: 5px;
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
}
.block.response_body.json:hover {
cursor: pointer;
}
.backdrop {
color: blue;
}
#myModal {
height: 100%;
}
.modal-backdrop {
bottom: 0;
position: fixed;
}
.curl {
padding: 10px;
font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
font-size: 0.9em;
max-height: 400px;
margin-top: 5px;
overflow-y: auto;
background-color: #fcf6db;
border: 1px solid #e5e0c6;
border-radius: 4px;
}
.curl_title {
font-size: 1.1em;
margin: 0;
padding: 15px 0 5px;
font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
font-weight: 500;
line-height: 1.1;
}
.footer {
display: none;
}
.swagger-section .swagger-ui-wrap h2 {
padding: 0;
}
h2 {
margin: 0;
margin-bottom: 5px;
}
.markdown p {
font-size: 15px;
font-family: 'Arvo', serif;
}
.swagger-section .swagger-ui-wrap .code {
font-size: 15px;
font-family: 'Arvo', serif;
}
.swagger-section .swagger-ui-wrap b {
font-family: 'Arvo', serif;
}
#signin:hover {
cursor: pointer;
}
.dropdown-menu {
padding: 15px;
}
.navbar-right .dropdown-menu {
left: 0;
right: auto;
}
#signinbutton {
width: 100%;
height: 32px;
font-size: 13px;
font-weight: bold;
color: #08b;
}
.navbar-default .nav > li .details {
color: #000000;
text-transform: none;
font-size: 15px;
font-weight: normal;
font-family: 'Open Sans', sans-serif;
font-style: italic;
line-height: 20px;
top: -2px;
}
.navbar-default .nav > li .details:hover {
color: black;
}
#signout {
width: 100%;
height: 32px;
font-size: 13px;
font-weight: bold;
color: #08b;
}

View File

@ -0,0 +1,14 @@
/* Google Font's Droid Sans */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 400;
src: local('Droid Sans'), local('DroidSans'), url('../fonts/DroidSans.ttf'), format('truetype');
}
/* Google Font's Droid Sans Bold */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 700;
src: local('Droid Sans Bold'), local('DroidSans-Bold'), url('../fonts/DroidSans-Bold.ttf'), format('truetype');
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

View File

@ -0,0 +1,53 @@
'use strict';
/* jshint quotmark: double */
window.SwaggerTranslator.learn({
"Warning: Deprecated":"Advertència: Obsolet",
"Implementation Notes":"Notes d'implementació",
"Response Class":"Classe de la Resposta",
"Status":"Estatus",
"Parameters":"Paràmetres",
"Parameter":"Paràmetre",
"Value":"Valor",
"Description":"Descripció",
"Parameter Type":"Tipus del Paràmetre",
"Data Type":"Tipus de la Dada",
"Response Messages":"Missatges de la Resposta",
"HTTP Status Code":"Codi d'Estatus HTTP",
"Reason":"Raó",
"Response Model":"Model de la Resposta",
"Request URL":"URL de la Sol·licitud",
"Response Body":"Cos de la Resposta",
"Response Code":"Codi de la Resposta",
"Response Headers":"Capçaleres de la Resposta",
"Hide Response":"Amagar Resposta",
"Try it out!":"Prova-ho!",
"Show/Hide":"Mostrar/Amagar",
"List Operations":"Llista Operacions",
"Expand Operations":"Expandir Operacions",
"Raw":"Cru",
"can't parse JSON. Raw result":"no puc analitzar el JSON. Resultat cru",
"Example Value":"Valor d'Exemple",
"Model Schema":"Esquema del Model",
"Model":"Model",
"apply":"aplicar",
"Username":"Nom d'usuari",
"Password":"Contrasenya",
"Terms of service":"Termes del servei",
"Created by":"Creat per",
"See more at":"Veure més en",
"Contact the developer":"Contactar amb el desenvolupador",
"api version":"versió de la api",
"Response Content Type":"Tipus de Contingut de la Resposta",
"fetching resource":"recollint recurs",
"fetching resource list":"recollins llista de recursos",
"Explore":"Explorant",
"Show Swagger Petstore Example Apis":"Mostrar API d'Exemple Swagger Petstore",
"Can't read from server. It may not have the appropriate access-control-origin settings.":"No es pot llegir del servidor. Potser no teniu la configuració de control d'accés apropiada.",
"Please specify the protocol for":"Si us plau, especifiqueu el protocol per a",
"Can't read swagger JSON from":"No es pot llegir el JSON de swagger des de",
"Finished Loading Resource Information. Rendering Swagger UI":"Finalitzada la càrrega del recurs informatiu. Renderitzant Swagger UI",
"Unable to read api":"No es pot llegir l'api",
"from path":"des de la ruta",
"server returned":"el servidor ha retornat"
});

View File

@ -0,0 +1,56 @@
'use strict';
/* jshint quotmark: double */
window.SwaggerTranslator.learn({
"Warning: Deprecated":"Warning: Deprecated",
"Implementation Notes":"Implementation Notes",
"Response Class":"Response Class",
"Status":"Status",
"Parameters":"Parameters",
"Parameter":"Parameter",
"Value":"Value",
"Description":"Description",
"Parameter Type":"Parameter Type",
"Data Type":"Data Type",
"Response Messages":"Response Messages",
"HTTP Status Code":"HTTP Status Code",
"Reason":"Reason",
"Response Model":"Response Model",
"Request URL":"Request URL",
"Response Body":"Response Body",
"Response Code":"Response Code",
"Response Headers":"Response Headers",
"Hide Response":"Hide Response",
"Headers":"Headers",
"Try it out!":"Try it out!",
"Show/Hide":"Show/Hide",
"List Operations":"List Operations",
"Expand Operations":"Expand Operations",
"Raw":"Raw",
"can't parse JSON. Raw result":"can't parse JSON. Raw result",
"Example Value":"Example Value",
"Model Schema":"Model Schema",
"Model":"Model",
"Click to set as parameter value":"Click to set as parameter value",
"apply":"apply",
"Username":"Username",
"Password":"Password",
"Terms of service":"Terms of service",
"Created by":"Created by",
"See more at":"See more at",
"Contact the developer":"Contact the developer",
"api version":"api version",
"Response Content Type":"Response Content Type",
"Parameter content type:":"Parameter content type:",
"fetching resource":"fetching resource",
"fetching resource list":"fetching resource list",
"Explore":"Explore",
"Show Swagger Petstore Example Apis":"Show Swagger Petstore Example Apis",
"Can't read from server. It may not have the appropriate access-control-origin settings.":"Can't read from server. It may not have the appropriate access-control-origin settings.",
"Please specify the protocol for":"Please specify the protocol for",
"Can't read swagger JSON from":"Can't read swagger JSON from",
"Finished Loading Resource Information. Rendering Swagger UI":"Finished Loading Resource Information. Rendering Swagger UI",
"Unable to read api":"Unable to read api",
"from path":"from path",
"server returned":"server returned"
});

View File

@ -0,0 +1,53 @@
'use strict';
/* jshint quotmark: double */
window.SwaggerTranslator.learn({
"Warning: Deprecated":"Advertencia: Obsoleto",
"Implementation Notes":"Notas de implementación",
"Response Class":"Clase de la Respuesta",
"Status":"Status",
"Parameters":"Parámetros",
"Parameter":"Parámetro",
"Value":"Valor",
"Description":"Descripción",
"Parameter Type":"Tipo del Parámetro",
"Data Type":"Tipo del Dato",
"Response Messages":"Mensajes de la Respuesta",
"HTTP Status Code":"Código de Status HTTP",
"Reason":"Razón",
"Response Model":"Modelo de la Respuesta",
"Request URL":"URL de la Solicitud",
"Response Body":"Cuerpo de la Respuesta",
"Response Code":"Código de la Respuesta",
"Response Headers":"Encabezados de la Respuesta",
"Hide Response":"Ocultar Respuesta",
"Try it out!":"Pruébalo!",
"Show/Hide":"Mostrar/Ocultar",
"List Operations":"Listar Operaciones",
"Expand Operations":"Expandir Operaciones",
"Raw":"Crudo",
"can't parse JSON. Raw result":"no puede parsear el JSON. Resultado crudo",
"Example Value":"Valor de Ejemplo",
"Model Schema":"Esquema del Modelo",
"Model":"Modelo",
"apply":"aplicar",
"Username":"Nombre de usuario",
"Password":"Contraseña",
"Terms of service":"Términos de Servicio",
"Created by":"Creado por",
"See more at":"Ver más en",
"Contact the developer":"Contactar al desarrollador",
"api version":"versión de la api",
"Response Content Type":"Tipo de Contenido (Content Type) de la Respuesta",
"fetching resource":"buscando recurso",
"fetching resource list":"buscando lista del recurso",
"Explore":"Explorar",
"Show Swagger Petstore Example Apis":"Mostrar Api Ejemplo de Swagger Petstore",
"Can't read from server. It may not have the appropriate access-control-origin settings.":"No se puede leer del servidor. Tal vez no tiene la configuración de control de acceso de origen (access-control-origin) apropiado.",
"Please specify the protocol for":"Por favor, especificar el protocola para",
"Can't read swagger JSON from":"No se puede leer el JSON de swagger desde",
"Finished Loading Resource Information. Rendering Swagger UI":"Finalizada la carga del recurso de Información. Mostrando Swagger UI",
"Unable to read api":"No se puede leer la api",
"from path":"desde ruta",
"server returned":"el servidor retornó"
});

View File

@ -0,0 +1,54 @@
'use strict';
/* jshint quotmark: double */
window.SwaggerTranslator.learn({
"Warning: Deprecated":"Avertissement : Obsolète",
"Implementation Notes":"Notes d'implémentation",
"Response Class":"Classe de la réponse",
"Status":"Statut",
"Parameters":"Paramètres",
"Parameter":"Paramètre",
"Value":"Valeur",
"Description":"Description",
"Parameter Type":"Type du paramètre",
"Data Type":"Type de données",
"Response Messages":"Messages de la réponse",
"HTTP Status Code":"Code de statut HTTP",
"Reason":"Raison",
"Response Model":"Modèle de réponse",
"Request URL":"URL appelée",
"Response Body":"Corps de la réponse",
"Response Code":"Code de la réponse",
"Response Headers":"En-têtes de la réponse",
"Hide Response":"Cacher la réponse",
"Headers":"En-têtes",
"Try it out!":"Testez !",
"Show/Hide":"Afficher/Masquer",
"List Operations":"Liste des opérations",
"Expand Operations":"Développer les opérations",
"Raw":"Brut",
"can't parse JSON. Raw result":"impossible de décoder le JSON. Résultat brut",
"Example Value":"Exemple la valeur",
"Model Schema":"Définition du modèle",
"Model":"Modèle",
"apply":"appliquer",
"Username":"Nom d'utilisateur",
"Password":"Mot de passe",
"Terms of service":"Conditions de service",
"Created by":"Créé par",
"See more at":"Voir plus sur",
"Contact the developer":"Contacter le développeur",
"api version":"version de l'api",
"Response Content Type":"Content Type de la réponse",
"fetching resource":"récupération de la ressource",
"fetching resource list":"récupération de la liste de ressources",
"Explore":"Explorer",
"Show Swagger Petstore Example Apis":"Montrer les Apis de l'exemple Petstore de Swagger",
"Can't read from server. It may not have the appropriate access-control-origin settings.":"Impossible de lire à partir du serveur. Il se peut que les réglages access-control-origin ne soient pas appropriés.",
"Please specify the protocol for":"Veuillez spécifier un protocole pour",
"Can't read swagger JSON from":"Impossible de lire le JSON swagger à partir de",
"Finished Loading Resource Information. Rendering Swagger UI":"Chargement des informations terminé. Affichage de Swagger UI",
"Unable to read api":"Impossible de lire l'api",
"from path":"à partir du chemin",
"server returned":"réponse du serveur"
});

View File

@ -0,0 +1,56 @@
'use strict';
/* jshint quotmark: double */
window.SwaggerTranslator.learn({
"Warning: Deprecated":"ყურადღება: აღარ გამოიყენება",
"Implementation Notes":"იმპლემენტაციის აღწერა",
"Response Class":"რესპონს კლასი",
"Status":"სტატუსი",
"Parameters":"პარამეტრები",
"Parameter":"პარამეტრი",
"Value":"მნიშვნელობა",
"Description":"აღწერა",
"Parameter Type":"პარამეტრის ტიპი",
"Data Type":"მონაცემის ტიპი",
"Response Messages":"პასუხი",
"HTTP Status Code":"HTTP სტატუსი",
"Reason":"მიზეზი",
"Response Model":"რესპონს მოდელი",
"Request URL":"მოთხოვნის URL",
"Response Body":"პასუხის სხეული",
"Response Code":"პასუხის კოდი",
"Response Headers":"პასუხის ჰედერები",
"Hide Response":"დამალე პასუხი",
"Headers":"ჰედერები",
"Try it out!":"ცადე !",
"Show/Hide":"გამოჩენა/დამალვა",
"List Operations":"ოპერაციების სია",
"Expand Operations":"ოპერაციები ვრცლად",
"Raw":"ნედლი",
"can't parse JSON. Raw result":"JSON-ის დამუშავება ვერ მოხერხდა. ნედლი პასუხი",
"Example Value":"მაგალითი",
"Model Schema":"მოდელის სტრუქტურა",
"Model":"მოდელი",
"Click to set as parameter value":"პარამეტრისთვის მნიშვნელობის მისანიჭებლად, დააკლიკე",
"apply":"გამოყენება",
"Username":"მოხმარებელი",
"Password":"პაროლი",
"Terms of service":"მომსახურების პირობები",
"Created by":"შექმნა",
"See more at":"ნახე ვრცლად",
"Contact the developer":"დაუკავშირდი დეველოპერს",
"api version":"api ვერსია",
"Response Content Type":"პასუხის კონტენტის ტიპი",
"Parameter content type:":"პარამეტრის კონტენტის ტიპი:",
"fetching resource":"რესურსების მიღება",
"fetching resource list":"რესურსების სიის მიღება",
"Explore":"ნახვა",
"Show Swagger Petstore Example Apis":"ნახე Swagger Petstore სამაგალითო Api",
"Can't read from server. It may not have the appropriate access-control-origin settings.":"სერვერთან დაკავშირება ვერ ხერხდება. შეამოწმეთ access-control-origin.",
"Please specify the protocol for":"მიუთითეთ პროტოკოლი",
"Can't read swagger JSON from":"swagger JSON წაკითხვა ვერ მოხერხდა",
"Finished Loading Resource Information. Rendering Swagger UI":"რესურსების ჩატვირთვა სრულდება. Swagger UI რენდერდება",
"Unable to read api":"api წაკითხვა ვერ მოხერხდა",
"from path":"მისამართიდან",
"server returned":"სერვერმა დააბრუნა"
});

View File

@ -0,0 +1,52 @@
'use strict';
/* jshint quotmark: double */
window.SwaggerTranslator.learn({
"Warning: Deprecated":"Attenzione: Deprecato",
"Implementation Notes":"Note di implementazione",
"Response Class":"Classe della risposta",
"Status":"Stato",
"Parameters":"Parametri",
"Parameter":"Parametro",
"Value":"Valore",
"Description":"Descrizione",
"Parameter Type":"Tipo di parametro",
"Data Type":"Tipo di dato",
"Response Messages":"Messaggi della risposta",
"HTTP Status Code":"Codice stato HTTP",
"Reason":"Motivo",
"Response Model":"Modello di risposta",
"Request URL":"URL della richiesta",
"Response Body":"Corpo della risposta",
"Response Code":"Oggetto della risposta",
"Response Headers":"Intestazioni della risposta",
"Hide Response":"Nascondi risposta",
"Try it out!":"Provalo!",
"Show/Hide":"Mostra/Nascondi",
"List Operations":"Mostra operazioni",
"Expand Operations":"Espandi operazioni",
"Raw":"Grezzo (raw)",
"can't parse JSON. Raw result":"non è possibile parsare il JSON. Risultato grezzo (raw).",
"Model Schema":"Schema del modello",
"Model":"Modello",
"apply":"applica",
"Username":"Nome utente",
"Password":"Password",
"Terms of service":"Condizioni del servizio",
"Created by":"Creato da",
"See more at":"Informazioni aggiuntive:",
"Contact the developer":"Contatta lo sviluppatore",
"api version":"versione api",
"Response Content Type":"Tipo di contenuto (content type) della risposta",
"fetching resource":"recuperando la risorsa",
"fetching resource list":"recuperando lista risorse",
"Explore":"Esplora",
"Show Swagger Petstore Example Apis":"Mostra le api di esempio di Swagger Petstore",
"Can't read from server. It may not have the appropriate access-control-origin settings.":"Non è possibile leggere dal server. Potrebbe non avere le impostazioni di controllo accesso origine (access-control-origin) appropriate.",
"Please specify the protocol for":"Si prega di specificare il protocollo per",
"Can't read swagger JSON from":"Impossibile leggere JSON swagger da:",
"Finished Loading Resource Information. Rendering Swagger UI":"Lettura informazioni risorse termianta. Swagger UI viene mostrata",
"Unable to read api":"Impossibile leggere la api",
"from path":"da cartella",
"server returned":"il server ha restituito"
});

View File

@ -0,0 +1,53 @@
'use strict';
/* jshint quotmark: double */
window.SwaggerTranslator.learn({
"Warning: Deprecated":"警告: 廃止予定",
"Implementation Notes":"実装メモ",
"Response Class":"レスポンスクラス",
"Status":"ステータス",
"Parameters":"パラメータ群",
"Parameter":"パラメータ",
"Value":"値",
"Description":"説明",
"Parameter Type":"パラメータタイプ",
"Data Type":"データタイプ",
"Response Messages":"レスポンスメッセージ",
"HTTP Status Code":"HTTPステータスコード",
"Reason":"理由",
"Response Model":"レスポンスモデル",
"Request URL":"リクエストURL",
"Response Body":"レスポンスボディ",
"Response Code":"レスポンスコード",
"Response Headers":"レスポンスヘッダ",
"Hide Response":"レスポンスを隠す",
"Headers":"ヘッダ",
"Try it out!":"実際に実行!",
"Show/Hide":"表示/非表示",
"List Operations":"操作一覧",
"Expand Operations":"操作の展開",
"Raw":"Raw",
"can't parse JSON. Raw result":"JSONへ解釈できません. 未加工の結果",
"Model Schema":"モデルスキーマ",
"Model":"モデル",
"apply":"実行",
"Username":"ユーザ名",
"Password":"パスワード",
"Terms of service":"サービス利用規約",
"Created by":"Created by",
"See more at":"See more at",
"Contact the developer":"開発者に連絡",
"api version":"APIバージョン",
"Response Content Type":"レスポンス コンテンツタイプ",
"fetching resource":"リソースの取得",
"fetching resource list":"リソース一覧の取得",
"Explore":"Explore",
"Show Swagger Petstore Example Apis":"SwaggerペットストアAPIの表示",
"Can't read from server. It may not have the appropriate access-control-origin settings.":"サーバから読み込めません. 適切なaccess-control-origin設定を持っていない可能性があります.",
"Please specify the protocol for":"プロトコルを指定してください",
"Can't read swagger JSON from":"次からswagger JSONを読み込めません",
"Finished Loading Resource Information. Rendering Swagger UI":"リソース情報の読み込みが完了しました. Swagger UIを描画しています",
"Unable to read api":"APIを読み込めません",
"from path":"次のパスから",
"server returned":"サーバからの返答"
});

Some files were not shown because too many files have changed in this diff Show More