API style guide

原文:https://docs.gitlab.com/ee/development/api_styleguide.html

API style guide

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

Instance variables

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

Entities

始终使用实体来呈现端点的有效负载.

Documentation

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

Methods and parameters description

每种方法都必须使用Grape DSL进行描述(有关示例,请参见https://gitlab.com/gitlab-org/gitlab/blob/master/lib/api/environments.rb ):

  • desc的方法的总结. 您应该将其传递给其他细节,例如:
    • 添加端点时的 GitLab 版本. 如果它在功能标志后面,请提及: 该功能由:feature_flag_symbol 功能标志控制.
    • 如果不赞成使用端点,那么,何时删除它
  • params方法参数. 这充当参数的描述, 验证和强制

一个很好的例子如下:

  1. desc 'Get all broadcast messages' do
  2. detail 'This feature was introduced in GitLab 8.12.'
  3. success Entities::BroadcastMessage
  4. end
  5. params do
  6. optional :page, type: Integer, desc: 'Current page number'
  7. optional :per_page, type: Integer, desc: 'Number of messages per page'
  8. end
  9. get do
  10. messages = BroadcastMessage.all
  11. present paginate(messages), with: Entities::BroadcastMessage
  12. end

Declared parameters

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

https://github.com/ruby-grape/grape#declared

Exclude parameters from parent namespaces

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

https://github.com/ruby-grape/grape#include-parent-namespaces

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

  1. declared(params, include_parent_namespaces: false)

When to use declared(params)

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

例如:

  1. # bad
  2. User.create(params) # imagine the user submitted `admin=1`... :)
  3. # good
  4. User.create(declared(params, include_parent_namespaces: false).to_h)

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

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

例如:

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

Array types

在 Grape v1.3 +中,必须使用coerce_with块定义数组类型,否则当从 API 请求传递字符串时,参数将无法验证. 有关更多详细信息,请参见Grape 升级文档 .

Automatic coercion of nil inputs

在 Grape v1.3.3 之前,具有nil值的 Array 参数将自动强制为空 Array. 但是,由于v1.3.3 中的拉取请求,情况不再如此. 例如,假设您定义一个具有可选参数的 PUT /test请求:

  1. 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块中使用的代码:

  1. before do
  2. coerce_nil_params_to_array!
  3. end

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

Grape Tracker 中存在一个未解决的问题,可以使此操作更容易.

Using HTTP status helpers

对于非 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

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

例如:

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

Custom Validators

为了验证 API 请求中的某些参数,我们先验证它们,然后再进一步发送它们(例如 Gitaly). 以下是自定义验证器 ,到目前为止,我们已经添加了它们以及如何使用它们. 我们还编写了有关如何添加新的自定义验证器的指南.

Using custom validators

  • FilePath:

    GitLab 支持我们需要遍历文件路径的各种功能. FilePath验证器针对不同情况验证参数值. 主要是,它使用File::Separator检查路径是否是相对路径,是否包含../../相对遍历,以及路径是否是绝对路径,例如/etc/passwd/ .

  • Git SHA:

    Git SHA验证器检查 Git SHA 参数是否为有效的 SHA. 它通过使用commit.rb文件中提到的正则表达式进行检查.

  • Absence:

    Absence验证器检查给定参数哈希中是否缺少特定参数.

  • IntegerNoneAny:

    IntegerNoneAny验证器检查给定参数的值是IntegerNone还是Any . 它仅允许上述任何一个值在请求中前进.

  • ArrayNoneAny:

    ArrayNoneAny验证器检查给定参数的值是ArrayNone还是Any . 它仅允许上述任何一个值在请求中前进.

Adding a new custom validator

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

如果您需要添加自定义验证器,它将被添加到validators目录中自己的文件中. 由于我们使用Grape添加 API,因此我们继承了Grape::Validations::Base类中的Grape::Validations::Base类. 现在,您要做的就是定义validate_param! 该方法具有两个参数: params哈希和要验证的param名称.

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

最后,我们使用以下行注册验证器:

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

添加验证器后,请确保将其rspec添加到其在validators目录中的自己的文件中.

Internal API

内部 API已记录供内部使用. 请保持最新状态,以便我们了解不同组件正在使用哪些端点.

Avoiding N+1 problems

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

在 API 中执行此操作的标准方法是让模型实现一个名为with_api_entity_associations的范围,该范围将预加载 API 中返回的关联和数据. 在Issue模型中可以看到此范围的示例.

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

当在 Todos API 中返回时, with_api_entity_associations范围还将自动Todo 目标 预先加载数据 .

有关预加载的更多上下文和讨论,请参阅此合并请求该合并请求引入了作用域.

Verifying with tests

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

Example:

  1. def make_api_request
  2. get api('/foo', personal_access_token: pat)
  3. end
  4. it 'avoids N+1 queries', :request_store do
  5. # Firstly, record how many PostgreSQL queries the endpoint will make
  6. # when it returns a single record
  7. create_record
  8. control = ActiveRecord::QueryRecorder.new { make_api_request }
  9. # Now create a second record and ensure that the API does not execute
  10. # any more queries than before
  11. create_record
  12. expect { make_api_request }.not_to exceed_query_limit(control)
  13. end

Testing

在编写新的 API 端点的测试,可以考虑使用模式夹具位于/spec/fixtures/api/schemas . 您可以expect响应匹配给定的架构:

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

另请参阅在测试中验证 N + 1 性能 .