fotinakis / swagger-blocks

Define and serve live-updating Swagger JSON for Ruby apps.
MIT License
622 stars 98 forks source link

Swagger::Blocks

Build Status Gem Version

Swagger::Blocks is a DSL for pure Ruby code blocks that can be turned into JSON.

It helps you write API docs in the Swagger style in Ruby and then automatically build JSON that is compatible with Swagger UI.

Features

Swagger UI demo

http://petstore.swagger.io/

swagger-sample

Installation

Add this line to your application's Gemfile:

gem 'swagger-blocks'

Or install directly with gem install swagger-blocks.

Swagger 2.0 example (Rails)

This is a simplified example based on the objects in the Petstore Swagger Sample App. For a more complex and complete example, see the swagger_v2_blocks_spec.rb file.

Also note that Rails is not required, you can use Swagger::Blocks in plain Ruby objects.

PetsController

class PetsController < ActionController::Base
  include Swagger::Blocks

  swagger_path '/pets/{id}' do
    operation :get do
      key :summary, 'Find Pet by ID'
      key :description, 'Returns a single pet if the user has access'
      key :operationId, 'findPetById'
      key :tags, [
        'pet'
      ]
      parameter do
        key :name, :id
        key :in, :path
        key :description, 'ID of pet to fetch'
        key :required, true
        key :type, :integer
        key :format, :int64
      end
      response 200 do
        key :description, 'pet response'
        schema do
          key :'$ref', :Pet
        end
      end
      response :default do
        key :description, 'unexpected error'
        schema do
          key :'$ref', :ErrorModel
        end
      end
    end
  end
  swagger_path '/pets' do
    operation :get do
      key :summary, 'All Pets'
      key :description, 'Returns all pets from the system that the user has access to'
      key :operationId, 'findPets'
      key :produces, [
        'application/json',
        'text/html',
      ]
      key :tags, [
        'pet'
      ]
      parameter do
        key :name, :tags
        key :in, :query
        key :description, 'tags to filter by'
        key :required, false
        key :type, :array
        items do
          key :type, :string
        end
        key :collectionFormat, :csv
      end
      parameter do
        key :name, :limit
        key :in, :query
        key :description, 'maximum number of results to return'
        key :required, false
        key :type, :integer
        key :format, :int32
      end
      response 200 do
        key :description, 'pet response'
        schema do
          key :type, :array
          items do
            key :'$ref', :Pet
          end
        end
      end
      response :default do
        key :description, 'unexpected error'
        schema do
          key :'$ref', :ErrorModel
        end
      end
    end
    operation :post do
      key :description, 'Creates a new pet in the store.  Duplicates are allowed'
      key :operationId, 'addPet'
      key :produces, [
        'application/json'
      ]
      key :tags, [
        'pet'
      ]
      parameter do
        key :name, :pet
        key :in, :body
        key :description, 'Pet to add to the store'
        key :required, true
        schema do
          key :'$ref', :PetInput
        end
      end
      response 200 do
        key :description, 'pet response'
        schema do
          key :'$ref', :Pet
        end
      end
      response :default do
        key :description, 'unexpected error'
        schema do
          key :'$ref', :ErrorModel
        end
      end
    end
  end

  # ...
end

Models

Pet model

class Pet < ActiveRecord::Base
  include Swagger::Blocks

  swagger_schema :Pet do
    key :required, [:id, :name]
    property :id do
      key :type, :integer
      key :format, :int64
    end
    property :name do
      key :type, :string
    end
    property :tag do
      key :type, :string
    end
  end

  swagger_schema :PetInput do
    allOf do
      schema do
        key :'$ref', :Pet
      end
      schema do
        key :required, [:name]
        property :id do
          key :type, :integer
          key :format, :int64
        end
      end
    end
  end

  # ...
end

Error model

class ErrorModel  # Notice, this is just a plain ruby object.
  include Swagger::Blocks

  swagger_schema :ErrorModel do
    key :required, [:code, :message]
    property :code do
      key :type, :integer
      key :format, :int32
    end
    property :message do
      key :type, :string
    end
  end
end

Docs controller

To integrate these definitions with Swagger UI, we need a docs controller that can serve the JSON definitions.

resources :apidocs, only: [:index]
class ApidocsController < ActionController::Base
  include Swagger::Blocks

  swagger_root do
    key :swagger, '2.0'
    info do
      key :version, '1.0.0'
      key :title, 'Swagger Petstore'
      key :description, 'A sample API that uses a petstore as an example to ' \
                        'demonstrate features in the swagger-2.0 specification'
      key :termsOfService, 'http://helloreverb.com/terms/'
      contact do
        key :name, 'Wordnik API Team'
      end
      license do
        key :name, 'MIT'
      end
    end
    tag do
      key :name, 'pet'
      key :description, 'Pets operations'
      externalDocs do
        key :description, 'Find more info here'
        key :url, 'https://swagger.io'
      end
    end
    key :host, 'petstore.swagger.wordnik.com'
    key :basePath, '/api'
    key :consumes, ['application/json']
    key :produces, ['application/json']
  end

  # A list of all classes that have swagger_* declarations.
  SWAGGERED_CLASSES = [
    PetsController,
    Pet,
    ErrorModel,
    self,
  ].freeze

  def index
    render json: Swagger::Blocks.build_root_json(SWAGGERED_CLASSES)
  end
end

The special part of this controller is this line:

render json: Swagger::Blocks.build_root_json(SWAGGERED_CLASSES)

That is the only line necessary to build the full root Swagger object JSON and all definitions underneath it. You simply pass in a list of all the "swaggered" classes in your app.

If you want to include controllers outside the standard path just use the full class path including module names, like:

SWAGGERED_CLASSES = [
  Api::V1::PetsController,
  self,
]

If you are receiving a "swagger_root must be declared" error make sure you are including "self" in your SWAGGERED_CLASSES definition, as shown above.

Now, simply point Swagger UI at /apidocs and everything should Just Work™. If you change any of the Swagger block definitions, you can simply refresh Swagger UI to see the changes.

Security handling

To support Swagger's definitions for API key auth or OAuth2, use security_definition in your swagger_root:

  swagger_root do
    key :swagger, '2.0'

    # ...

    security_definition :api_key do
      key :type, :apiKey
      key :name, :api_key
      key :in, :header
    end
    security_definition :petstore_auth do
      key :type, :oauth2
      key :authorizationUrl, 'http://swagger.io/api/oauth/dialog'
      key :flow, :implicit
      scopes do
        key 'write:pets', 'modify pets in your account'
        key 'read:pets', 'read your pets'
      end
    end
  end

You can then apply security requirement objects to the entire swagger_root, or to individual operations:

  swagger_path '/pets/{id}' do
    operation :get do

      # ...

      security do
        key :api_key, []
      end
      security do
        key :petstore_auth, ['write:pets', 'read:pets']
      end
    end
  end

Nested complex objects

The key block simply takes the value you give and puts it directly into the final JSON object. So, if you need to set more complex objects, you can just do:

  key :foo, {some_complex: {nested_object: true}}

Parameter referencing

It is possible to reference parameters rather than explicitly define them in every action in which they are used.

To define a reusable parameter, declare it within swagger_root To reference the parameter, call it within a swagger_path or operation node

swagger_root do
  key :swagger, '2.0'
  # ...
  parameter :species do
    key :name, :species
    key :description, 'Species of this pet'
  end
end

swagger_path '/pets/' do
  operation :post do
    parameter :species
    # ...
  end
end

Inline keys

It is possible to omit numerous key calls using inline hash keys on any block.

All three calls are equivalent:

parameter do
  key :paramType, :path
  key :name, :petId
  key :description, 'ID of pet that needs to be fetched'
  key :type, :string
end
parameter paramType: :path, name: :petId do
  key :description, 'ID of pet that needs to be fetched'
  key :type, :string
end
parameter paramType: :path,
          name: :petId,
          description: 'ID of pet that needs to be fetched',
          type: :string

These inline keys can be used on any block, not just parameter blocks.

Writing JSON to a file

If you are not serving the JSON directly and need to write it to a file for some reason, you can easily use build_root_json for that as well:

swagger_data = Swagger::Blocks.build_root_json(SWAGGERED_CLASSES)
File.open('swagger.json', 'w') { |file| file.write(swagger_data.to_json) }

Overriding attributes

If certain attributes must be customized on-the-fly, you can merge a hash containing the customized values on the returned JSON. You can wrap build_root_json inside your own method:

def build_and_override_root_json(overrides = {})
  Swagger::Blocks.build_root_json(SWAGGERED_CLASSES).merge(overrides)
end

Reducing boilerplate

To create reusable parameters, please see parameter referencing.

Most APIs have some common responses for 401s, 404s, etc. Rather than declaring these responses over and over, you can create a reusable module.

module SwaggerResponses
  module AuthenticationError
    def self.extended(base)
      base.response 401 do
        key :description, 'not authorized'
        schema do
          key :'$ref', :AuthenticationError
        end
      end
    end
  end
end

Now, you just need to extend it:

operation :post do
  extend SwaggerResponses::AuthenticationError
  # ...
  response 200 do
    # ...
  end
end

Reference

See the swagger_v2_blocks_spec.rb for examples of more complex features and declarations possible.

Swagger 1.2

The old Swagger 1.2 spec is not supported in swagger-blocks >= 2.0.0, but you may use 1.4.0.

Contributing

  1. Fork it ( https://github.com/fotinakis/swagger-blocks/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Throw a ★ on it! :)

Filing issues

Please DO file an issue:

Please DO NOT file an issue:

Release notes

Credits

Thanks to @ali-graham for contributing support for Swagger 2.0.

Original idea inspired by @richhollis's swagger-docs gem.