/apipie-rails

Rails API documentation tool

Primary LanguageRubyApache License 2.0Apache-2.0

What?

Every API needs a documentation otherwise no one know how to use it. It is not easy to maintain such documentation always up to date if it develops over time. Additionaly to this most programmers don’t like writing documentation and so it could happen that your API is useless because of outdated documentation. If the documentation is just text completely separated from code then there is no simple way to verify its correctness.

This gem adds some new methods to Rails controllers that can be used to describe resources which are exposed by our API. It is meant to be used for RESTful web services and because we are talking about Rails you probably know what it is all about.

Informations you entered with provided DSL are used to generate online documentation and validate values of incoming requests parameters.

Why?

Documentation will be closer to your code and thus less prone to become obsolete. You get documentation frontend without effort. And if you want your own design you can do it anytime because json API for your API documentation is provided. There are no limits on how to use those informations, the builtin web interface is only one of possible options.

Request parameters will be validated before they will get in touch with yours controller action or model code. And you inform API users about expected parameters values. This information will be always actual because otherwise your tests fail (of course you have tests!).

This is how the web interface look like. Try it live on restapi-likes.rhcloud.com. Example users resource description are taken from Twitter© API.

How?

Gemfile

Add apipie gem to your gemfile and run bundle.

gem 'apipie-rails'

For bleeding edge version use GitHub version

gem 'apipie-rails', :git => 'git://github.com/Pajk/apipie-rails.git'

Config file

Create configuration file in e.g. /config/initializers/apipie.rb. You can set application name, footer text, API and documentation base URL and turn off validations. You can also choose your favorite markup language of full descriptions.

app_name

Name of your application used in breadcrumbs navigation.

copyright

Copyright information (shown in page footer).

doc_base_url

Documentation frontend base url.

api_base_url

Base url of your API, most probably /api.

validate

Parameters validation is turned off when set to false.

app_info

Application long description.

reload_controllers

Set to enable/disable reloading controllers (and the documentation with it), by default enabled in development.

api_controllers_matcher

For reloading to work properly you need to specify where your API controllers are.

markup

You can choose markup language for descriptions of your application, resources and methods. RDoc is the default but you can choose from Apipie::Markup::Markdown.new or Apipie::Markup::Textile.new. In order to use Markdown you need Redcarpet gem and for Textile you need RedCloth. Add those to your gemfile and run bundle if you want to use them. You can also add any other markup language processor.

Example:

Apipie.configure do |config|
  config.app_name = "Test app"
  config.copyright = "© 2012 Pavel Pokorny"
  config.doc_base_url = "/apidoc"
  config.api_base_url = "/api"
  config.validate = false
  config.markup = Apipie::Markup::Markdown.new
  config.reload_controllers = true
  config.api_controllers_matcher = File.join(Rails.root, "app", "controllers", "**","*.rb")
  config.app_info = <<-DOC
    This is where you can inform user about your application and API
    in general.
  DOC
end

Routes

Add apipie to your routes.rb, that’s all.

Resource Description

Resource can be described in corresponding controller by calling resource_description method with block. Parameters described here are used for every method unless they are overridden with NilValidator. Parameters are inherited in controller hierarchy. So for example you can define in base ApplicationController parameters for authentication and they will be checked in every request.

name

You can force resource to display under some alias.

short

One line short description.

path

Relative URL path of this resource.

version

Version of this resource API, use arbitrary string.

param

The very same parameter description as you will use method description. Generally use this for parameters that apply to every method in the controller (such as :user hash). It is possible to hide it for individual methods by setting it’s validator to nil.

description

The full multiline description of the resource and yours API options

Example:

class UsersController < ApplicationController

  resource_description do
    name 'Members'
    short 'Site members'
    path '/users'
    version '1.0 - 3.4.2012'
    param :id, Fixnum, :desc => "User ID", :required => false
    param :user, Hash, :desc => 'Param description for all methods' do
      param :username, String, :required => true,
            :desc => "Username for login"
      param :password, String, :required => true,
            :desc => "Password for login"
    end
    description <<-DOC
      Full description of this resource.
    DOC
  end
  #...
end

Method Description

Then describe methods available to your API.

api

Say how is this method exposed and provide short description. The first parameter is HTTP method (one of :GET/:POST/:PUT/:DELETE). The second parameter is relative URL path which is mapped to this method. The last parameter is methods short description. You can use this api method more than once for one method. It could be useful when there are more routes mapped to it.

param

Look at Parameter description section for details.

error

Describe each possible error that can happend what calling this method. HTTP response code and description can be provided.

description

Full method description which will be converted to HTML by choosen markup language processor.

example

Provide example of server response, whole communication or response type. It will be formatted as code.

see

Provide reference to another method, this has to be string with controller_name#method_name.

Example:

api :GET, "/users/:id", "Show user profile"
error :code => 401, :desc => "Unauthorized"
error :code => 404, :desc => "Not Found"
param :session, String, :desc => "user is logged in", :required => true
param :regexp_param, /^[0-9]* years/, :desc => "regexp param"
param :array_param, [100, "one", "two", 1, 2], :desc => "array validator"
param :boolean_param, [true, false], :desc => "array validator with boolean"
param :proc_param, lambda { |val|
  val == "param value" ? true : "The only good value is 'param value'."
}, :desc => "proc validator"
description "method description"
example " 'user': {...} "
see "users#showme"
def show
  #...
end

Parameter Description

Use param to describe every possible parameter. You can use Hash validator in cooperation with block given to param method to describe nested parameters.

name

The first argument is parameter name as a symbol.

validator

Second parameter is parameter validator, choose one from section Validators.

desc

Parameter description.

required

Set this true/false to make it required/optional. Default is optional

Example:

param :user, Hash, :desc => "User info" do
  param :username, String, :desc => "Username for login", :required => true
  param :password, String, :desc => "Password for login", :required => true
  param :membership, ["standard","premium"], :desc => "User membership"
end
def create
  #...
end

Validators

Every parameter needs to have associated validator. For now there are some basic validators. You can always provide your own to reach complex results. If validations are enabled (default state) the parameters of every request are validated. If the value is wrong a ArgumentError exception is raised and can be rescued and processed. It contains some description of parameter value expectations. Validations can be turned off in configuration file.

TypeValidator

Check the parameter type. Only String, Hash and Array are supported for the sake of simplicity. Read more to to find out how to add your own validator.

param :session, String, :desc => "user is logged in", :required => true
param :facts, Hash, :desc => "Additional optional facts about the user"

RegexpValidator

Check parameter value against given regular expression.

param :regexp_param, /^[0-9]* years/, :desc => "regexp param"

ArrayValidator

Check if parameter value is included given array.

param :array_param, [100, "one", "two", 1, 2], :desc => "array validator"

ProcValidator

If you need more complex validation and you know you won’t reuse it you can use Proc/lambda validator. Provide your own Proc taking value of parameter as the only argument. Return true if value pass validation or return some text about what is wrong. _Don’t use the keyword return if you provide instance of Proc (with lambda it is ok), just use the last statement return property of ruby_.

param :proc_param, lambda { |val|
  val == "param value" ? true : "The only good value is 'param value'."
}, :desc => "proc validator"

HashValidator

You can describe hash parameters in depth if you provide a block with description of nested values.

param :user, Hash, :desc => "User info" do
  param :username, String, :desc => "Username for login", :required => true
  param :password, String, :desc => "Password for login", :required => true
  param :membership, ["standard","premium"], :desc => "User membership"
end

NilValidator

In fact there is any NilValidator but setting it to nil can be used to override parameters described on resource level.

Example:

param :user, nil
def destroy
  #...
end

Adding custom validator

Only basic validators are included but it is really easy to add your own. Create new initializer with subclass of Apipie::Validator::BaseValidator. Two methods are required to implement - instance method validate(value) and class method build(param_description, argument, options, block).

When searching for validator build method of every subclass of Apipie::Validator::BaseValidator is called. The first one whitch return constructed validator object is used.

Example: Adding IntegerValidator

We want to check if parameter value is an integer like this:

param :id, Integer, :desc => "Company ID"

So we create apipie_validators.rb initializer with this content:

class IntegerValidator < Apipie::Validator::BaseValidator

  def initialize(param_description, argument)
    super(param_description)
    @type = argument
  end

  def validate(value)
    return false if value.nil?
    !!(value.to_s =~ /^[-+]?[0-9]+$/)
  end

  def self.build(param_description, argument, options, block)
    if argument == Integer || argument == Fixnum
      self.new(param_description, argument)
    end
  end

  def error
    "Parameter \#{@param_name} expecting to be \#{@type.name},
    got: \#{@error_value.class.name}"
  end

  def description
    "Parameter has to be #{@type}."
  end
end

Parameters of the build method:

param_description

Instance of Apipie::ParamDescription contains all given informations about validated parameter.

argument

Specified validator, in our example it is Integer

options

Hash with specified options, for us just {:desc => "Company ID"}

block

Block converted into Proc, use it as you desire. In this example nil.

Markup

The default markup language is RDoc. It can be changed in config file (config.markup=) to one of these:

Markdown

Use Apipie::Markup::Markdown.new. You need Redcarpet gem.

Textile

Use Apipie::Markup::Textile.new. You need RedCloth gem.

Or provide you own object with to_html(text) method. For inspiration this is how Textile look like:

class Textile
  def initialize
    require 'RedCloth'
  end
  def to_html(text)
    RedCloth.new(text).to_html
  end
end

Static page

To generate static version of documentation run rake apipie:static task. It will create folder with one-page documentation in your public directory. Rename or delete it if you want to use ‘normal’ dynamic version again.

Future features

  • Dynamic CLI client (read info from documentation API)

  • Add documentation json API description to readme

  • Possibility to create requests from web documentation and check responses