api_styleguide.md 4.62 KB
Newer Older
Rémy Coutable's avatar
Rémy Coutable committed
1 2
# API styleguide

Rémy Coutable's avatar
Rémy Coutable committed
3
This styleguide recommends best practices for API development.
Rémy Coutable's avatar
Rémy Coutable committed
4

5 6 7 8 9 10 11 12 13
## Instance variables

Please do not use instance variables, there is no need for them (we don't need
to access them as we do in Rails views), local variables are fine.

## Entities

Always use an [Entity] to present the endpoint's payload.

14 15 16 17 18 19
## Documentation

API endpoints must come with [documentation](documentation/styleguide.md#api), unless it is internal or behind a feature flag.
The docs should be in the same merge request, or, if strictly necessary,
in a follow-up with the same milestone as the original merge request.  

20 21 22
## Methods and parameters description

Every method must be described using the [Grape DSL](https://github.com/ruby-grape/grape#describing-methods)
23
(see <https://gitlab.com/gitlab-org/gitlab/blob/master/lib/api/environments.rb>
24 25 26 27
for a good example):

- `desc` for the method summary. You should pass it a block for additional
  details such as:
28
  - The GitLab version when the endpoint was added. If it is behind a feature flag, mention that instead: _This feature is gated by the :feature\_flag\_symbol feature flag._
Rémy Coutable's avatar
Rémy Coutable committed
29
  - If the endpoint is deprecated, and if so, when will it be removed
30

31
- `params` for the method parameters. This acts as description,
Rémy Coutable's avatar
Rémy Coutable committed
32
  [validation, and coercion of the parameters]
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

A good example is as follows:

```ruby
desc 'Get all broadcast messages' do
  detail 'This feature was introduced in GitLab 8.12.'
  success Entities::BroadcastMessage
end
params do
  optional :page,     type: Integer, desc: 'Current page number'
  optional :per_page, type: Integer, desc: 'Number of messages per page'
end
get do
  messages = BroadcastMessage.all

  present paginate(messages), with: Entities::BroadcastMessage
end
```

52
## Declared parameters
Rémy Coutable's avatar
Rémy Coutable committed
53 54

> Grape allows you to access only the parameters that have been declared by your
55
`params` block. It filters out the parameters that have been passed, but are not
Rémy Coutable's avatar
Rémy Coutable committed
56 57
allowed.

58
<https://github.com/ruby-grape/grape#declared>
Rémy Coutable's avatar
Rémy Coutable committed
59

60
### Exclude parameters from parent namespaces
Rémy Coutable's avatar
Rémy Coutable committed
61

62
> By default `declared(params)`includes parameters that were defined in all
Rémy Coutable's avatar
Rémy Coutable committed
63 64
parent namespaces.

65
<https://github.com/ruby-grape/grape#include-parent-namespaces>
Rémy Coutable's avatar
Rémy Coutable committed
66

67
In most cases you will want to exclude parameters from the parent namespaces:
Rémy Coutable's avatar
Rémy Coutable committed
68 69 70 71 72

```ruby
declared(params, include_parent_namespaces: false)
```

73
### When to use `declared(params)`
Rémy Coutable's avatar
Rémy Coutable committed
74

75
You should always use `declared(params)` when you pass the parameters hash as
Rémy Coutable's avatar
Rémy Coutable committed
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
arguments to a method call.

For instance:

```ruby
# bad
User.create(params) # imagine the user submitted `admin=1`... :)

# good
User.create(declared(params, include_parent_namespaces: false).to_h)
```

>**Note:**
`declared(params)` return a `Hashie::Mash` object, on which you will have to
call `.to_h`.

Rémy Coutable's avatar
Rémy Coutable committed
92
But we can use `params[key]` directly when we access single elements.
Rémy Coutable's avatar
Rémy Coutable committed
93 94 95 96 97 98 99 100

For instance:

```ruby
# good
Model.create(foo: params[:foo])
```

101 102 103 104 105 106
## Using HTTP status helpers

For non-200 HTTP responses, use the provided helpers in `lib/api/helpers.rb` to ensure correct behaviour (`not_found!`, `no_content!` etc.). These will `throw` inside Grape and abort the execution of your endpoint.

For `DELETE` requests, you should also generally use the `destroy_conditionally!` helper which by default returns a `204 No Content` response on success, or a `412 Precondition Failed` response if the given `If-Unmodified-Since` header is out of range. This helper calls `#destroy` on the passed resource, but you can also implement a custom deletion method by passing a block.

107 108 109 110 111 112 113 114 115 116 117 118
## Using API path helpers in GitLab Rails codebase

Because we support [installing GitLab under a relative URL], one must take this
into account when using API path helpers generated by Grape. Any such API path
helper usage must be in wrapped into the `expose_path` helper call.

For instance:

```haml
- endpoint = expose_path(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: @issue.iid))
```

119 120 121 122 123
## Internal API

The [internal API](./internal_api.md) is documented for internal use. Please keep it up to date so we know what endpoints
different components are making use of.

Maxime Orefice's avatar
Maxime Orefice committed
124
[Entity]: https://gitlab.com/gitlab-org/gitlab/blob/master/lib/api/entities
Rémy Coutable's avatar
Rémy Coutable committed
125
[validation, and coercion of the parameters]: https://github.com/ruby-grape/grape#parameter-validation-and-coercion
126
[installing GitLab under a relative URL]: https://docs.gitlab.com/ee/install/relative_url.html
127 128 129 130 131 132 133 134

## Testing

When writing tests for new API endpoints, consider using a schema [fixture](./testing_guide/best_practices.md#fixtures) located in `/spec/fixtures/api/schemas`. You can `expect` a response to match a given schema:

```ruby
expect(response).to match_response_schema('merge_requests')
```