链接
链接是 OpenAPI 3.0 的新功能之一。使用链接,您可以描述一个操作返回的各种值如何用作其他操作的输入。这样,链接在操作之间提供了已知的关系和遍历机制。链接的概念与 超媒体 有些相似,但 OpenAPI 链接不需要在实际响应中存在链接信息。
何时使用链接?
考虑“创建用户”操作
1POST /users HTTP/1.12Host: example.com3Content-Type: application/json4
5{6 "name": "Alex",7 "age": 278}9
10which returns the ID of the created user:11
12HTTP/1.1 201 Created13Content-Type: application/json14
15{16 "id": 30517}
然后,此用户 ID 可用于读取、更新或删除用户:GET /users/305
、PATCH /users/305
和 DELETE /users/305
。使用链接,您可以指定“创建用户”返回的 id
值可以用作“获取用户”、“更新用户”和“删除用户”的参数。另一个示例是通过游标进行分页,其中响应包含一个用于检索下一个数据集的游标。
1GET /items?limit=1002
3 ⇩4
5{6 "metadata": {7 "previous": null,8 "next": "Q1MjAwNz",9 "count": 1010 },11 ...12}13
14 ⇩15
16GET /items?cursor=Q1MjAwNz&limit=100
但是,链接关系不一定在同一资源内,甚至不一定在同一 API 规范内。
定义链接
链接在每个响应的 links
部分中定义
1responses:2 "200":3 description: Created4 content: ...5 links: # <----6 ...7 "400":8 description: Bad request9 content: ...10 links: # <----11 ...
为了更好地理解这一点,让我们看一个完整的示例。此 API 定义了“创建用户”和“获取用户”操作,“创建用户”的结果用作“获取用户”的输入。
1openapi: 3.0.02info:3 version: 0.0.04 title: Links example5
6paths:7 /users:8 post:9 summary: Creates a user and returns the user ID10 operationId: createUser11 requestBody:12 required: true13 description: A JSON object that contains the user name and age.14 content:15 application/json:16 schema:17 $ref: "#/components/schemas/User"18 responses:19 "201":20 description: Created21 content:22 application/json:23 schema:24 type: object25 properties:26 id:27 type: integer28 format: int6429 description: ID of the created user.30 # -----------------------------------------------------31 # Links32 # -----------------------------------------------------33 links:34 GetUserByUserId: # <---- arbitrary name for the link35 operationId: getUser36 # or37 # operationRef: '#/paths/~1users~1{userId}/get'38 parameters:39 userId: "$response.body#/id"40
41 description: >42 The `id` value returned in the response can be used as43 the `userId` parameter in `GET /users/{userId}`.44 # -----------------------------------------------------45
46 /users/{userId}:47 get:48 summary: Gets a user by ID49 operationId: getUser50 parameters:51 - in: path52 name: userId53 required: true54 schema:55 type: integer56 format: int6457 responses:58 "200":59 description: A User object60 content:61 application/json:62 schema:63 $ref: "#/components/schemas/User"64
65components:66 schemas:67 User:68 type: object69 properties:70 id:71 type: integer72 format: int6473 readOnly: true74 name:75 type: string
links
部分包含命名的链接定义,在本例中,只有一个名为 GetUserByUserId 的链接。链接名称只能包含以下字符
1A..Z a..z 0..9 . _ -
每个链接包含以下信息
operationId
或operationRef
,指定目标操作。它可以是当前或外部 API 规范中的同一操作或不同操作。operationId
仅用于本地链接,operationRef
可以链接到本地和外部操作。parameters
和/或requestBody
部分,指定要传递给目标操作的值。使用 运行时表达式 语法从父操作中提取这些值。- (可选)如果目标操作与默认服务器不同,则应使用目标操作的
server
。 - (可选)此链接的
description
。可以使用 CommonMark 语法进行富文本表示。
本页的其余部分将详细介绍这些关键字。
operationId
如果目标操作指定了 operationId
,则链接可以指向此 ID,如上图所示。此方法仅适用于本地链接,因为 operationId
值是在当前 API 规范的范围内解析的。
operationRef
当 operationId
不可用时,可以使用 operationRef
。operationRef
是使用 JSON 引用语法(与 $ref
关键字使用的相同)对目标操作的引用。引用可以是本地的(在当前 API 规范内)
1operationRef: "#/paths/~1users~1{userId}/get"
或外部的
1operationRef: 'https://anotherapi.com/openapi.yaml#/paths/~1users~1{userId}/get'2operationRef: './operations/getUser.yaml'
在这里,字符串 #/paths/~1users~1{userId}/get
实际上表示 #/paths//users/{userId}/get
,但路径名称中的内部斜杠 / 需要转义为 ~1
,因为它们是特殊字符。
1#/paths/~1users~1{userId}/get2 │ │ │3 │ │ │4paths: │ │5 /users/{userId}: │6 get: ─────────────────┘7 ...
此语法可能难以阅读,因此我们建议仅将其用于外部链接。对于本地链接,更简单的方法是将 operationId
分配给所有操作并链接到这些 ID。
parameters 和 requestBody
链接最重要的部分是根据原始操作的值计算目标操作的输入。这就是 parameters
和 requestBody
关键字的用途。
1links:2 # GET /users/{userId}3 GetUserByUserId:4 operationId: getUser5 parameters:6 userId: "$response.body#/id"7
8 # POST /users/{userId}/manager with the manager ID in the request body9 SetManagerId:10 operationId: setUserManager11 requestBody: "$response.body#/id"
语法是 _parameter_name: value_
或 requestBody: value
。参数名称和请求体是目标操作的参数名称和请求体。不需要列出所有参数,只需列出为了遵循链接所需的参数。类似地,只有当目标操作有 请求体 并且链接的目的是定义请求体内容时,才使用 requestBody
。如果两个或多个参数具有相同的名称,请使用参数位置作为前缀,例如path、query、header 或 cookie,如下所示:
1parameters:2 path.id: ...3 query.id: ...
参数和 requestBody
的值可以通过以下方式定义:
- 运行时表达式,例如
$response.body#/id
,引用原始操作的请求或响应中的值。 - 包含嵌入式运行时表达式的字符串,例如
ID_{$response.body#/id}
。 - 硬编码的值,例如字符串、数字、数组等,例如
mystring
或true
。
如果需要为目标操作传递特定的已计算和硬编码参数组合,通常会使用常量值。
1paths:2 /date_ranges:3 get:4 summary: Get relative date ranges for the report.5 responses:6 '200':7 description: OK8 content:9 application/json:10 example: [Today, Yesterday, LastWeek, ThisMonth]11 links:12 ReportRelDate:13 operationId: getReport14 # Call "getReport" with the `rdate` parameter and with empty `start_date` and `end_date`15 parameters:16 rdate: '$response.body#/1'17 start_date: ''18 end_date: ''19
20 # GET /report?rdate=...21 # GET /report?start_date=...&end_date=...22 /report:23 get:24 operationId: getReport25 ...
运行时表达式语法
OpenAPI 运行时表达式是一种用于从操作的请求和响应中提取各种值的语法。链接使用运行时表达式来指定要传递给链接操作的参数值。这些表达式之所以被称为“运行时”,是因为这些值是从 API 调用的实际请求和响应中提取的,而不是从 API 规范中提供的示例值中提取的。下表描述了运行时表达式的语法。所有表达式都引用定义 links
的当前操作。
表达式
描述
$url
完整的请求 URL,包括查询字符串。
$method
请求 HTTP 方法,例如 GET 或 POST。
$request.query._param_name_
指定查询参数的值。该参数必须在操作的 parameters
部分中定义,否则无法评估。参数名称区分大小写。
$request.path._param_name_
指定路径参数的值。该参数必须在操作的 parameters
部分中定义,否则无法评估。参数名称区分大小写。
$request.header._header_name_
指定请求头的值。此标头必须在操作的 parameters
部分中定义,否则无法评估。标头名称不区分大小写。
$request.body
整个请求体。
$request.body_#/foo/bar_
由 JSON 指针指定的请求体的部分。
$statusCode
响应的 HTTP 状态代码。例如,200 或 404。
$response.header._header_name_
指定响应头的完整值,作为字符串。标头名称不区分大小写。标头不需要在响应的 headers
部分中定义。
$response.body
整个响应体。
$response.body_#/foo/bar_
由 JSON 指针指定的请求体的部分。
foo{$request.path.id}bar
将表达式括在 {}
花括号中以将其嵌入到字符串中。
注意
- 除非另有说明,否则评估后的表达式与引用的值的类型相同。
- 如果无法评估运行时表达式,则不会将任何参数值传递给目标操作。
示例
考虑以下请求和响应:
1GET /users?limit=2&total=true2Host: api.example.com3Accept: application/json
1HTTP/1.1 200 OK2Content-Type: application/json3X-Total-Count: 374
5{6 "prev_offset": 0,7 "next_offset": 2,8 "users": [9 {"id": 1, "name": "Alice"},10 {"id": 2, "name": "Bob"}11 ]12}
以下是一些运行时表达式示例及其评估的值:
表达式 | 结果 | 注释 |
---|---|---|
$url | http://api.example.com/users?limit=2&total=true | |
$method | GET | |
$request.query.total | true | total 必须定义为查询参数。 |
$statusCode | 200 | |
$response.header.x-total-count | 37 | 假设 X-Total-Count 定义为响应头。标头名称不区分大小写。 |
$response.body#/next_offset | 2 | |
$response.body#/users/0 | {"id": 1, "name": "Alice"} | JSON 指针(#/... 部分)使用从 0 开始的索引来访问数组元素。但是,没有通配符语法,因此 $response.body#/users/*/id 无效。 |
$response.body#/users/1 | {"id": 2, "name": "Bob"} | |
$response.body#/users/1/name | Bob | |
ID_{$response.body#/users/1/id} | ID_2 |
server
默认情况下,目标操作针对其默认服务器调用,即全局 servers
或特定于操作的 servers
。但是,可以使用 server
关键字通过链接覆盖服务器。server
具有与全局服务器相同的字段,但它是单个服务器,而不是数组。
1servers:2 - url: https://api.example.com3---4links:5 GetUserByUserId:6 operationId: getUser7 parameters:8 userId: "$response.body#/id"9 server:10 url: https://new-api.example.com/v2
重用链接
链接可以内联定义(如前面的示例所示),也可以放置在全局 components/links
部分中,并通过 $ref
从操作的 links
部分引用。如果多个操作以相同的方式链接到另一个操作,这将非常有用,引用有助于减少代码重复。在以下示例中,“创建用户”和“更新用户”操作都在响应体中返回用户 ID,并且此 ID 用于“获取用户”操作。源操作重用来自 components/links
的相同链接定义。
1paths:2 /users:3 post:4 summary: Create a user5 operationId: createUser6 ...7 responses:8 '201':9 description: Created10 content:11 application/json:12 schema:13 type: object14 properties:15 id:16 type: integer17 format: int6418 description: ID of the created user.19 links:20 GetUserByUserId:21 $ref: '#/components/links/GetUserByUserId' # <-------22
23 /user/{userId}:24 patch:25 summary: Update user26 operationId: updateUser27 ...28 responses:29 '200':30 description: The updated user object31 content:32 application/json:33 schema:34 $ref: '#/components/schemas/User'35 links:36 GetUserByUserId:37 $ref: '#/components/links/GetUserByUserId' # <-------38
39 get:40 summary: Get a user by ID41 operationId: getUser42 ...43
44components:45 links:46 GetUserByUserId: # <----- The $ref's above point here47 description: >48 The `id` value returned in the response can be used as49 the `userId` parameter in `GET /users/{userId}`.50 operationId: getUser51 parameters:52 userId: '$response.body#/id'