链接
链接是 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.42info: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
关键字的作用。
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"
语法是 _参数名: 值_
或 requestBody: 值
。参数名称和请求体是目标操作的。无需列出所有参数,只需列出遵循链接所需的参数即可。类似地,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._参数名_
指定查询参数的值。该参数必须在操作的 parameters
部分中定义,否则无法评估。参数名称区分大小写。
$request.path._参数名_
指定路径参数的值。该参数必须在操作的 parameters
部分中定义,否则无法评估。参数名称区分大小写。
$request.header._头名称_
指定请求头的值。此头必须在操作的 parameters
部分中定义,否则无法评估。头名称不区分大小写。
$request.body
整个请求体。
$request.body_#/foo/bar_
由 JSON 指针指定的请求体的一部分。
$statusCode
响应的 HTTP 状态码。例如,200 或 404。
$response.header._头名称_
指定响应头的完整值,作为字符串。头名称不区分大小写。该头无需在响应的 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-based 索引访问数组元素。但没有通配符语法,因此 $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'