intridea/grape — pull request #620 by mbleigh (2014-04-20 02:08:45+00:00)
Title: Proposal: Implementable Documentation

I've been pondering on this one for a while, so I'm just going to lay out some of the fundamentals I've been thinking about and go from there. Currently we have `desc` and `params` and some arbitrary conventions that help feed `grape-swagger`. I think we can do better. This proposal is somewhat based on some hacks we've done at Divshot and partially based on what I think makes sense.

The heart of this proposal is making `desc` a block-accepting method that yields a documentation DSL. With this DSL, `params` may be (optionally) nested inside the `desc` block or provided separately.

## Standardized Error Format

It is useful to have both human and machine readable error conditions for APIs. Currently Grape has a simple `{"error":""}` format but this is often not enough information by itself. I propose the following format:

```json
{
"error":"machine_readable_code",
"error_description":"Human readable error description goes here.",
"context": {
"optional":"Information about the error goes here."
}
}
```

This format is compatible with the OAuth 2.0 error response formats (which is why I named things as I did) with the addition of `context`, which might provide (for example) which parameters were invalid.

This format would replace the current default error formatter and in an endpoint would look something like:

```ruby
error! 400, :something_bad, "Something bad has happened, get help!"
```

I'm moving the `status` to the front of the queue here both because it allows us to potentially implement in a backwards-compatible way and because the HTTP Status is one of the most important pieces of info that can be communicated and I now think it was a mistake to make it an optional parameter. You should always think about and provide a sane HTTP status on error.

## Declarative Response Modes

For the purposes of documentation, automated tooling, etc. it is very useful to know what the characteristics of all possible responses to an endpoint are. I propose that the `desc` DSL include `success` and `failure` methods that work like so:

```ruby
desc "Post a status update." do
detail "This would be a much longer description of what the endpoint does, perhaps spanning multiple lines etc."
success Tweet::Entity, 201, "The newly created status update."
failure :not_logged_in, 401, "You must be logged in to perform that action."

params do
requires :text,
end
end
post :status do
fail! :not_logged_in unless current_user
present Tweet.create(params.text)
end
```

I believe that a DSL like this will create great at-a-glance documentation in the code itself while also providing all the metadata needed to generate documentation. Unlike the existing "do whatever you want in the desc hash" setup, the failure modes and success modes can actually affect endpoint behavior.

The `fail!` method works much like `error!` except it only allows failure modes that have already been defined in `desc` blocks.

## Reusing Failure Modes

It will often be convenient to reuse a failure mode. This will be done the same way that it is today with `params`:

```ruby
desc do
failure :admin_required, 401, "You must be an administrator to access this resource."
end
namespace :admin do
before{ fail! :admin_required unless current_user.admin? }
end
```

Of course you can do this on `scope` as well to avoid creating a new namespace:

```ruby
desc do
failure :authentication_required, 401, "You must be logged in to access that resource."
end
scope :authenticated do
before{ fail! :authentication_required unless current_user }
end
```

## Entity Definition

The second argument of the `success` call above is an entity class. The only thing required of an entity class is that it respond to `entity_schema` and return a hash that looks something like this:

```ruby
{
name: 'User',
desc: 'A user of the system.',
attributes: {
attr_name: {type: String, desc: 'A brief description of the thing', example: 'Sample Value', default: 'Default Value'}
}
}
```

This would be built in to `Grape::Entity` obviously, but could be easily implemented in other representation systems as well.

## Markdown and More

The DSL above provides great facilities for terse description of API endpoints. However, for more in-depth content putting that in the `.rb` files doesn't really make sense. This is less thought through, but here's one option:

1. Anywhere that a string can be passed as descriptive text in the `desc` DSL, also accept a symbol.
2. Have a means of loading descriptive text based on symbols (this also opens up i8n which is nice)

This should basically work by loading a big hash, as you'd expect.

What I think I would want to do, personally, is build a system based on Markdown that would parse out H1s to get key names. I was going to go into detail but I think that actually makes more sense as a separate library.

## Expanding Documentation Output

With a more robust documentation system built into Grape, it would be easy to support all of the various API description libraries (we have Swagger now, but could then support Blueprint and others without much trouble).

OK, that's the longest issue description I've ever written. Thoughts? I want some feedback before I'd start diving into implementation.