# API style guide

> 原文：[https://docs.gitlab.com/ee/development/api_styleguide.html](https://docs.gitlab.com/ee/development/api_styleguide.html)

*   [Instance variables](#instance-variables)
*   [Entities](#entities)
*   [Documentation](#documentation)
*   [Methods and parameters description](#methods-and-parameters-description)
*   [Declared parameters](#declared-parameters)
    *   [Exclude parameters from parent namespaces](#exclude-parameters-from-parent-namespaces)
    *   [When to use `declared(params)`](#when-to-use-declaredparams)
*   [Array types](#array-types)
    *   [Automatic coercion of nil inputs](#automatic-coercion-of-nil-inputs)
*   [Using HTTP status helpers](#using-http-status-helpers)
*   [Using API path helpers in GitLab Rails codebase](#using-api-path-helpers-in-gitlab-rails-codebase)
*   [Custom Validators](#custom-validators)
    *   [Using custom validators](#using-custom-validators)
    *   [Adding a new custom validator](#adding-a-new-custom-validator)
*   [Internal API](#internal-api)
*   [Avoiding N+1 problems](#avoiding-n1-problems)
    *   [Verifying with tests](#verifying-with-tests)
*   [Testing](#testing)

# API style guide[](#api-style-guide "Permalink")

该样式指南建议 API 开发的最佳做法.

## Instance variables[](#instance-variables "Permalink")

请不要使用实例变量，不需要它们（我们不需要像在 Rails 视图中那样访问它们），可以使用局部变量.

## Entities[](#entities "Permalink")

始终使用[实体](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/api/entities)来呈现端点的有效负载.

## Documentation[](#documentation "Permalink")

API 端点必须随附[文档](documentation/styleguide.html#api) ，除非[文档](documentation/styleguide.html#api)在内部或在功能标志后面. 这些文档应位于同一合并请求中，或者在严格必要的情况下，应与原始合并请求具有相同的里程碑.

## Methods and parameters description[](#methods-and-parameters-description "Permalink")

每种方法都必须使用[Grape DSL](https://github.com/ruby-grape/grape#describing-methods)进行描述（有关示例，请参见[https://gitlab.com/gitlab-org/gitlab/blob/master/lib/api/environments.rb](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/api/environments.rb) ）：

*   `desc`的方法的总结. 您应该将其传递给其他细节，例如：
    *   添加端点时的 GitLab 版本. 如果它在功能标志后面，请提及： *该功能由：feature_flag_symbol 功能标志控制.*
    *   如果不赞成使用端点，那么，何时删除它
*   `params`方法参数. 这充当[参数的](https://github.com/ruby-grape/grape#parameter-validation-and-coercion)描述， [验证和强制](https://github.com/ruby-grape/grape#parameter-validation-and-coercion)

一个很好的例子如下：

```
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 
```

## Declared parameters[](#declared-parameters "Permalink")

> Grape 允许您仅访问由`params`块声明的`params` . 它过滤掉已传递但不允许的参数.

– [https://github.com/ruby-grape/grape#declared](https://github.com/ruby-grape/grape#declared)

### Exclude parameters from parent namespaces[](#exclude-parameters-from-parent-namespaces "Permalink")

> 默认情况下， `declared(params)`包括在所有父名称空间中定义的参数.

– [https://github.com/ruby-grape/grape#include-parent-namespaces](https://github.com/ruby-grape/grape#include-parent-namespaces)

在大多数情况下，您将希望从父名称空间中排除参数：

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

### When to use `declared(params)`[](#when-to-use-declaredparams "Permalink")

You should always use `declared(params)` when you pass the parameters hash as arguments to a method call.

例如：

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

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

> **注意：** `Hashie::Mash` `declared(params)`返回一个`Hashie::Mash`对象，您必须在其上调用`.to_h` .

但是，当我们访问单个元素时，可以直接使用`params[key]` .

例如：

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

## Array types[](#array-types "Permalink")

在 Grape v1.3 +中，必须使用`coerce_with`块定义数组类型，否则当从 API 请求传递字符串时，参数将无法验证. 有关更多详细信息，请参见[Grape 升级文档](https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#ensure-that-array-types-have-explicit-coercions) .

### Automatic coercion of nil inputs[](#automatic-coercion-of-nil-inputs "Permalink")

在 Grape v1.3.3 之前，具有`nil`值的 Array 参数将自动强制为空 Array. 但是，由于[v1.3.3 中的拉取请求，](https://github.com/ruby-grape/grape/pull/2040)情况不再如此. 例如，假设您定义一个具有可选参数的 PUT `/test`请求：

```
optional :user_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The user ids for this rule' 
```

通常情况下，把一个请求`/test?user_ids`会导致葡萄传递`params`的`{ user_ids: nil }` .

这可能会导致端点期望为空数组且无法正确处理`nil`输入的错误. 为了保留以前的行为，有一个辅助方法`coerce_nil_params_to_array!` 在所有 API 调用的`before`块中使用的代码：

```
before do
  coerce_nil_params_to_array!
end 
```

进行此更改后，对 PUT `/test?user_ids`的请求将使 Grape 传递的`params`为`{ user_ids: [] }` .

[Grape Tracker 中](https://github.com/ruby-grape/grape/issues/2068)存在[一个未解决的问题，](https://github.com/ruby-grape/grape/issues/2068)可以使此操作更容易.

## Using HTTP status helpers[](#using-http-status-helpers "Permalink")

对于非 200 HTTP 响应，请使用`lib/api/helpers.rb`提供的帮助`lib/api/helpers.rb`以确保行为正确（ `not_found!` ， `no_content!`等）. 这些将`throw` Grape 并中止端点的执行.

对于`DELETE`请求，通常还应该`destroy_conditionally!`地使用`destroy_conditionally!` 默认情况下，helper 会在成功时返回`204 No Content`响应，或者在给定的`If-Unmodified-Since`标头超出范围时返回`412 Precondition Failed` . 该助手在传递的资源上调用`#destroy` ，但是您也可以通过传递一个块来实现自定义删除方法.

## Using API path helpers in GitLab Rails codebase[](#using-api-path-helpers-in-gitlab-rails-codebase "Permalink")

因为我们支持[在相对 URL 下安装 GitLab](../install/relative_url.html) ，所以在使用 Grape 生成的 API 路径帮助程序时必须考虑到这一点. 任何此类 API 路径帮助程序用法都必须包装在`expose_path`帮助程序调用中.

例如：

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

## Custom Validators[](#custom-validators "Permalink")

为了验证 API 请求中的某些参数，我们先验证它们，然后再进一步发送它们（例如 Gitaly）. 以下是[自定义验证器](https://GitLab.com/gitlab-org/gitlab/-/tree/master/lib/api/validations/validators) ，到目前为止，我们已经添加了它们以及如何使用它们. 我们还编写了有关如何添加新的自定义验证器的指南.

### Using custom validators[](#using-custom-validators "Permalink")

*   `FilePath`:

    GitLab 支持我们需要遍历文件路径的各种功能. [`FilePath`验证器](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/api/validations/validators/file_path.rb)针对不同情况验证参数值. 主要是，它使用`File::Separator`检查路径是否是相对路径，是否包含`../../`相对遍历，以及路径是否是绝对路径，例如`/etc/passwd/` .

*   `Git SHA`:

    [`Git SHA`验证器](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/api/validations/validators/git_sha.rb)检查 Git SHA 参数是否为有效的 SHA. 它通过使用[`commit.rb`](https://gitlab.com/gitlab-org/gitlab/-/commit/b9857d8b662a2dbbf54f46ecdcecb44702affe55#d1c10892daedb4d4dd3d4b12b6d071091eea83df_30_30)文件中提到的正则表达式进行检查.

*   `Absence`:

    [`Absence`验证器](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/api/validations/validators/absence.rb)检查给定参数哈希中是否缺少特定参数.

*   `IntegerNoneAny`:

    [`IntegerNoneAny`验证器](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/api/validations/validators/integer_none_any.rb)检查给定参数的值是`Integer` ， `None`还是`Any` . 它仅允许上述任何一个值在请求中前进.

*   `ArrayNoneAny`:

    [`ArrayNoneAny`验证器](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/api/validations/validators/array_none_any.rb)检查给定参数的值是`Array` ， `None`还是`Any` . 它仅允许上述任何一个值在请求中前进.

### Adding a new custom validator[](#adding-a-new-custom-validator "Permalink")

自定义验证器是在将参数发送到平台进行进一步处理之前对其进行验证的好方法. 如果我们在一开始就识别出无效的参数，它将在服务器和平台之间来回保存一些时间.

如果您需要添加自定义验证器，它将被添加到[`validators`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/api/validations/validators)目录中自己的文件中. 由于我们使用[Grape](https://github.com/ruby-grape/grape)添加 API，因此我们继承了`Grape::Validations::Base`类中的`Grape::Validations::Base`类. 现在，您要做的就是定义`validate_param!` 该方法具有两个参数： `params`哈希和要验证的`param`名称.

该方法的主体进行了验证参数值的艰苦工作，并将适当的错误消息返回给调用方方法.

最后，我们使用以下行注册验证器：

```
Grape::Validations.register_validator(<validator name as symbol>, ::API::Helpers::CustomValidators::<YourCustomValidatorClassName>) 
```

添加验证器后，请确保将其`rspec`添加到其在[`validators`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/lib/api/validations/validators)目录中的自己的文件中.

## Internal API[](#internal-api "Permalink")

[内部 API](./internal_api.html)已记录供内部使用. 请保持最新状态，以便我们了解不同组件正在使用哪些端点.

## Avoiding N+1 problems[](#avoiding-n1-problems "Permalink")

为了避免在 API 端点中返回记录集合时常见的 N + 1 问题，我们应该使用预先加载.

在 API 中执行此操作的标准方法是让模型实现一个名为`with_api_entity_associations`的范围，该范围将预加载 API 中返回的关联和数据. 在[`Issue`模型中](https://gitlab.com/gitlab-org/gitlab/blob/2fedc47b97837ea08c3016cf2fb773a0300a4a25/app/models/issue.rb#L62)可以看到此范围的示例.

在同一个模型的 API 中有多个实体的情况下（例如`UserBasic` ， `User`和`UserPublic` ），您应谨慎使用此范围. 可能是您针对最基本的实体进行了优化，并在该范围上建立了连续的实体.

当在 Todos API 中返回时， `with_api_entity_associations`范围还将[自动](https://gitlab.com/gitlab-org/gitlab/blob/19f74903240e209736c7668132e6a5a735954e7c/app/models/todo.rb#L34)为`Todo` *目标* [预先加载数据](https://gitlab.com/gitlab-org/gitlab/blob/19f74903240e209736c7668132e6a5a735954e7c/app/models/todo.rb#L34) .

有关预加载的更多上下文和讨论，请参阅[此合并请求](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/25711) ， [该合并请求](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/25711)引入了作用域.

### Verifying with tests[](#verifying-with-tests "Permalink")

当 API 端点返回集合时，无论现在还是将来，始终添加一个测试以验证 API 端点没有 N + 1 问题. 我们可以使用[`ActiveRecord::QueryRecorder`](query_recorder.html)做到这一点.

Example:

```
def make_api_request
  get api('/foo', personal_access_token: pat)
end

it 'avoids N+1 queries', :request_store do
  # Firstly, record how many PostgreSQL queries the endpoint will make
  # when it returns a single record
  create_record

  control = ActiveRecord::QueryRecorder.new { make_api_request }

  # Now create a second record and ensure that the API does not execute
  # any more queries than before
  create_record

  expect { make_api_request }.not_to exceed_query_limit(control)
end 
```

## Testing[](#testing "Permalink")

在编写新的 API 端点的测试，可以考虑使用模式[夹具](./testing_guide/best_practices.html#fixtures)位于`/spec/fixtures/api/schemas` . 您可以`expect`响应匹配给定的架构：

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

另请参阅在测试中[验证 N + 1 性能](#verifying-with-tests) .