Swagger从入门到精通

前言

编写目的

本文介绍如何使用Swagger编写API文档。通过阅读本文,你可以:

  • 了解swagger是什么
  • 掌握使用swagger编写API文档的基本方法

涉及范围

  • 本文包括对swagger specification(以下译作”规范“)的介绍,如何使用swaager协议编写出功能完整、结构清晰的API文档,以及项目实践中需要注意的问题。

  • swagger的生态完整,从文档生成、编辑、测试到各种语言的代码自动生成,都有很多开源工具支持。本文中不介绍这些工具的使用。


第1章 简介

1.1 Swagger

The World's Most Popular Framework for APIs.

Starting January 1st 2016 the Swagger Specification has been donated(捐赠) to the Open API Initiative (OAI) and is the foundation of the OpenAPI Specification.

Swagger(丝袜哥)给人第一印象就是【最(hen)流(niu)行(bai)】,不懂Swagger咱就out了。它的官方网站是http://swagger.io/。

Swagger是一个简单但功能强大的API表达工具。它具有地球上最大的API工具生态系统,数以千计的开发人员,使用几乎所有的现代编程语言,都在支持和使用Swagger。使用Swagger生成API,我们可以得到交互式文档,自动生成代码的SDK以及API的发现特性等。

现在,Swagger已经帮助包括Apigee, Getty图像, Intuit, LivingSocial, McKesson, 微软, Morningstar和PayPal等世界知名企业建立起了一套基于RESTful API的完美服务系统。

2.0版本已经发布,Swagger变得更加强大。值得感激的是,Swagger的源码100%开源在github

1.2 OpenAPI规范

OpenAPI规范是Linux基金会的一个项目,试图通过定义一种用来描述API格式或API定义的语言,来规范RESTful服务开发过程。OpenAPI规范帮助我们描述一个API的基本信息,比如:

  • 有关该API的一般性描述
  • 可用路径(/资源)
  • 在每个路径上的可用操作(获取/提交...)
  • 每个操作的输入/输出格式

目前V2.0版本的OpenAPI规范(也就是SwaggerV2.0规范)已经发布并开源在github上。该文档写的非常好,结构清晰,方便随时查阅。关于规范的学习和理解,本文最后还有个彩蛋。

1.3 为啥要使用OpenAPI规范?

  • OpenAPI规范这类API定义语言能够帮助你更简单、快速的表述API,尤其是在API的设计阶段作用特别突出
  • 根据OpenAPI规范编写的二进制文本文件,能够像代码一样用任何VCS工具管理起来
  • 一旦编写完成,API文档可以作为:
    • 需求和系统特性描述的根据
    • 前后台查询、讨论、自测的基础
    • 部分或者全部代码自动生成的根据
    • 其他重要的作用,比如开放平台开发者的手册...

1.4 如何编写API文档?

1.4.1 语言:JSON vs YAML

我们可以选择使用JSON或者YAML的语言格式来编写API文档。但是个人建议使用YAML来写,原因是它更简单。一图胜千言,先看用JSON写的文档:

 
  1. {

  2. "swagger": "2.0",

  3. "info": {

  4. "version": "1.0.0",

  5. "title": "Simple API",

  6. "description": "A simple API to learn how to write OpenAPI Specification"

  7. },

  8. "schemes": [

  9. "https"

  10. ],

  11. "host": "simple.api",

  12. "basePath": "/openapi101",

  13. "paths": {

  14. "/persons": {

  15. "get": {

  16. "summary": "Gets some persons",

  17. "description": "Returns a list containing all persons.",

  18. "responses": {

  19. "200": {

  20. "description": "A list of Person",

  21. "schema": {

  22. "type": "array",

  23. "items": {

  24. "properties": {

  25. "firstName": {

  26. "type": "string"

  27. },

  28. "lastName": {

  29. "type": "string"

  30. },

  31. "username": {

  32. "type": "string"

  33. }

  34. }

  35. }

  36. }

  37. }

  38. }

  39. }

  40. }

  41. }

  42. }

再来看看同一份API文档的YAML实现:

 
  1. swagger: "2.0"

  2.  
  3. info:

  4. version: 1.0.0

  5. title: Simple API

  6. description: A simple API to learn how to write OpenAPI Specification

  7.  
  8. schemes:

  9. - https

  10. host: simple.api

  11. basePath: /openapi101

  12.  
  13. paths:

  14. /persons:

  15. get:

  16. summary: Gets some persons

  17. description: Returns a list containing all persons.

  18. responses:

  19. 200:

  20. description: A list of Person

  21. schema:

  22. type: array

  23. items:

  24. required:

  25. - username

  26. properties:

  27. firstName:

  28. type: string

  29. lastName:

  30. type: string

  31. username:

  32. type: string

对于普通人来说,似乎用YAML更能够简化书写和阅读。这里我们并没有非此即彼的选择问题,因为:

  • 几乎所用支持OpenAPI规范的工具都支持YAML
  • 有很多的工具可以实现YAML-JSON之间的转换

所以,用自己喜欢的方式书写即可。(后面的示例文档也都是用YAML来写的。强烈推荐使用YAML。)

1.4.2 编辑器

编写API文档,其实我们只是在写一个简单的文本文件。我们可以用任何最普通的文档编辑器来写。但是为了提高效率,还是建议使用专业的编辑工具。众多工具中,最好的选择是Swagger Editor,它能够提供语法高亮、自动完成、即时预览等功能,非常强大。

左边编辑API文档,右边实时预览。下面这张动图展示编辑提升功能:

我们可以使用在线版本来编辑,也可以非常简单的本地部署,更多细节请参考另一篇文档开源仓库上的说明。


第2章 从零开始

这一章主要介绍API的基本组成部分,包括提供给API消费者(所有可能访问API的个体,下简称“消费者”)的的不同HTTP请求方法、路径,请求和消息体中的参数,以及返回给消费者的不同HTTP状态及响应消息体。

2.1 最简单的例子

我们从一个最简单(几乎没有东西)的API文档开始:

 
  1. swagger: " rel="nofollow"2.0"

  2.  
  3. info:

  4. version: 1.0.0

  5. title: Simple API

  6. description: A simple API to learn how to write OpenAPI Specification

  7.  
  8. schemes:

  9. - https

  10. host: simple.api

  11. basePath: /openapi101

  12.  
  13. paths: {}

这个文档的内容分成四部分,下面分别来说明。

2.1.1 OpenAPI规范的版本号

首先我们要通过一个swagger属性来声明OpenAPI规范的版本。

swagger: "2.0"

你没看错,是swagger,上面已经介绍了,OpenAPI规范是基于Swagger的,在未来的版本中,这个属性可能会换成别的。目前这个属性的值,暂时只能填写为2.0

2.1.2 API描述信息

然后我们需要说明一下API文档的相关信息,比如API文档版本(注意不同于上面的规范版本)、API文档名称已经可选的描述信息。

 
  1. info:

  2. version: 1.0.0

  3. title: Simple API

  4. description: A simple API to learn how to write OpenAPI Specification

2.1.3 API的URL

作为web API,一个很重要的信息就是用来给消费者使用的根URL,可以用协议(http或者https)、主机名、根路径来描述:

 
  1. schemes:

  2. - https

  3. host: simple.api

  4. basePath: /openapi101

这这个例子中,消费者把https://simple.api/open101作为根节点来访问各种API。因为和具体环境有关,不涉及API描述的根本内容,所以这部分信息是可选的。

2.1.4 API的操作(operation)

这个例子中,我们没有写API的操作,用一个YAML的空对象{}先占个位置。

2.2 定义一个API操作

如果我们要展示一组用户信息,可以这样描述:

 
  1. swagger: "2.0"

  2.  
  3. info:

  4. version: 1.0.0

  5. title: Simple API

  6. description: A simple API to learn how to write OpenAPI Specification

  7.  
  8. schemes:

  9. - https

  10. host: simple.api

  11. basePath: /openapi101

  12.  
  13. paths:

  14. /persons:

  15. get:

  16. summary: Gets some persons

  17. description: Returns a list containing all persons.

  18. responses:

  19. 200:

  20. description: A list of Person

  21. schema:

  22. type: array

  23. items:

  24. required:

  25. - username

  26. properties:

  27. firstName:

  28. type: string

  29. lastName:

  30. type: string

  31. username:

  32. type: string

2.2.1 添加一个路径(path)

我们添加一个/persons路径,用来访问一组用户信息:

 
  1. paths:

  2. /persons:

2.2.2 在路径中添加一个HTTP方法

在每个路径中,我们可以添加任意的HTTP动词来操作所需要的资源。

比如需要展示一组用户信息,我们可以在/persons路径中添加get方法,同时还可以填写一些简单的描述信息(summary)或者说明该方法的一段长篇大论(description)。

 
  1. get:

  2. summary: Gets some persons

  3. description: Returns a list containing all persons.

这样一来,我们调 get https://simple.api/open101/persons方法就能获取一个用户信息列表了。

2.2.3 定义响应(response)类型

对于每个方法(或操作),我们都可以在响应(responses)中添加任意的HTTP状态码(比如200 OK 或者 404 Not Found等)。这个例子中我们添加上200的响应:

 
  1. responses:

  2. 200:

  3. description: A list of Person

2.2.4 定义响应内容

get /persons这个接口返回一组用户信息,我们通过响应消息中的模式(schema)属性来描述清楚具体的返回内容。

一组用户信息就是一个用户信息对象的数组(array),每一个数组元素则是一个用户信息对象(object),该对象包含三个string类型的属性:姓氏名字用户名,其中用户名必须提供(required)。

 
  1. schema:

  2. type: array

  3. items:

  4. required:

  5. - username

  6. properties:

  7. firstName:

  8. type: string

  9. lastName:

  10. type: string

  11. username:

  12. type: string

2.3 定义请求参数(query parameters)

用户太多,我们不想一股脑全部输出出来。这个时候,分页输出是个不错的选择,我们可以通过添加请求参数来实现。

 
  1. swagger: "2.0"

  2.  
  3. info:

  4. version: 1.0.0

  5. title: Simple API

  6. description: A simple API to learn how to write OpenAPI Specification

  7.  
  8. schemes:

  9. - https

  10. host: simple.api

  11. basePath: /openapi101

  12.  
  13. paths:

  14. /persons:

  15. get:

  16. summary: Gets some persons

  17. description: Returns a list containing all persons. The list supports paging.

  18. #START############################################################################

  19. parameters:

  20. - name: pageSize

  21. in: query

  22. description: Number of persons returned

  23. type: integer

  24. - name: pageNumber

  25. in: query

  26. description: Page number

  27. type: integer

  28. # END ############################################################################

  29. responses:

  30. 200:

  31. description: A list of Person

  32. schema:

  33. type: array

  34. items:

  35. required:

  36. - username

  37. properties:

  38. firstName:

  39. type: string

  40. lastName:

  41. type: string

  42. username:

  43. type: string

2.3.1 在get方法中增加请求参数

首先我们在 get 方法中增加一个参数属性:

 
  1. paths:

  2. /persons:

  3. get:

  4. summary: Gets some persons

  5. description: Returns a list containing all persons. The list supports paging.

  6. #START############################################################################

  7. parameters:

  8. # END ############################################################################

2.3.2 添加分页参数

在参数列表中,我们添加两个名字(name)分别叫做pageSizepageNumber的整型(integer)参数,并作简单描述:

 
  1. parameters:

  2. #START############################################################################

  3. - name: pageSize

  4. in: query

  5. description: Number of persons returned

  6. type: integer

  7. - name: pageNumber

  8. in: query

  9. description: Page number

  10. type: integer

  11. # END ##############################################################################

  12. responses:

这样一来,消费者就可以通过 get /persons?pageSize=20&pageNumber=2 来访问第2页的用户信息(不超过20条)了。

2.4 定义路径参数(path parameter)

有时候我们想要根据用户名来查找用户信息,这时我们需要增加一个接口操作,比如可以添加一个类似 /persons/{username} 的操作来获取用户信息。注意,{username} 是在请求路径中的参数。

 
  1. swagger: "2.0"

  2.  
  3. info:

  4. version: 1.0.0

  5. title: Simple API

  6. description: A simple API to learn how to write OpenAPI Specification

  7.  
  8. schemes:

  9. - https

  10. host: simple.api

  11. basePath: /openapi101

  12.  
  13. paths:

  14. /persons:

  15. get:

  16. summary: Gets some persons

  17. description: Returns a list containing all persons. The list supports paging.

  18. parameters:

  19. - name: pageSize

  20. in: query

  21. description: Number of persons returned

  22. type: integer

  23. - name: pageNumber

  24. in: query

  25. description: Page number

  26. type: integer

  27. responses:

  28. 200:

  29. description: A list of Person

  30. schema:

  31. type: array

  32. items:

  33. required:

  34. - username

  35. properties:

  36. firstName:

  37. type: string

  38. lastName:

  39. type: string

  40. username:

  41. type: string

  42.  
  43. #START############################################################################

  44. /persons/{username}:

  45. get:

  46. summary: Gets a person

  47. description: Returns a single person for its username

  48. parameters:

  49. - name: username

  50. in: path

  51. required: true

  52. description: The person's username

  53. type: string

  54. responses:

  55. 200:

  56. description: A Person

  57. schema:

  58. required:

  59. - username

  60. properties:

  61. firstName:

  62. type: string

  63. lastName:

  64. type: string

  65. username:

  66. type: string

  67. 404:

  68. description: The Person does not exists.

  69. # END ############################################################################

  70.  

2.4.1 添加一个 get /persons/{username} 操作

首先我们在 /persons 路径后面,增加一个 /persons/{username} 的路径,并定义一个 get (操作)方法。

 
  1. swagger: "2.0"

  2.  
  3. info:

  4. version: 1.0.0

  5. title: Simple API

  6. description: A simple API to learn how to write OpenAPI Specification

  7.  
  8. schemes:

  9. - https

  10. host: simple.api

  11. basePath: /openapi101

  12.  
  13. paths:

  14. /persons:

 
  1. username:

  2. type: string

  3. #START############################################################################

  4. /persons/{username}:

  5. get:

  6. summary: Gets a person

  7. description: Returns a single person for its username

  8. # END ############################################################################

2.4.2 定义路径参数 username

因为 {username} 是路径参数,我们需要先像请求参数一样将它添加到 parameters 属性中,注意名称应该同上面大括号( { } ) 里面的名称一致。并通过 in 这个属性,来表示它是一个路径(path)参数。

 
  1. parameters:

  2. - name: username

  3. in: path

  4. required: true

  5. description: The person's username

  6. type: string

  7.  

定义路径参数时很容易出现的问题就是忘记:required: true,Swagger的自动完成功能中没有包含这个属性定义。 如果没有写 require 属性,默认值是 false,也就是说 username 参数时可选的。可事实上,作为路径参数,它是必需的。

2.4.3 定义响应消息

别忘了获取单个用户信息也需要填写 200 响应消息,响应消息体的内容就是之前描述过的用户信息(用户信息列表中的一个元素):

 
  1. responses:

  2. 200:

  3. description: A Person

  4. schema:

  5. required:

  6. - username

  7. properties:

  8. firstName:

  9. type: string

  10. lastName:

  11. type: string

  12. username:

  13. type: string

当然,API的提供者会对 username 进行校验,如果查无此人,应该返回 404 的异常状态。所以我们再加上 404 状态的响应:

 
  1. 404:

  2. description: The Person does not exists.

2.5 定义消息体参数(body parameter)

当我们需要添加一个用户信息时,我们需要一个能够提供 post /persons 的API操作。

 
  1. swagger: "2.0"

  2.  
  3. info:

  4. version: 1.0.0

  5. title: Simple API

  6. description: A simple API to learn how to write OpenAPI Specification

  7.  
  8. schemes:

  9. - https

  10. host: simple.api

  11. basePath: /openapi101

  12.  
  13. paths:

  14. /persons:

  15. get:

  16. summary: Gets some persons

  17. description: Returns a list containing all persons. The list supports paging.

  18. parameters:

  19. - name: pageSize

  20. in: query

  21. description: Number of persons returned

  22. type: integer

  23. - name: pageNumber

  24. in: query

  25. description: Page number

  26. type: integer

  27. responses:

  28. 200:

  29. description: A list of Person

  30. schema:

  31. type: array

  32. items:

  33. required:

  34. - username

  35. properties:

  36. firstName:

  37. type: string

  38. lastName:

  39. type: string

  40. username:

  41. type: string

  42. #START############################################################################

  43. post:

  44. summary: Creates a person

  45. description: Adds a new person to the persons list.

  46. parameters:

  47. - name: person

  48. in: body

  49. description: The person to create.

  50. schema:

  51. required:

  52. - username

  53. properties:

  54. firstName:

  55. type: string

  56. lastName:

  57. type: string

  58. username:

  59. type: string

  60. responses:

  61. 204:

  62. description: Persons succesfully created.

  63. 400:

  64. description: Persons couldn't have been created.

  65. # END ############################################################################

  66. /persons/{username}:

  67. get:

  68. summary: Gets a person

  69. description: Returns a single person for its username.

  70. parameters:

  71. - name: username

  72. in: path

  73. required: true

  74. description: The person's username

  75. type: string

  76. responses:

  77. 200:

  78. description: A Person

  79. schema:

  80. required:

  81. - username

  82. properties:

  83. firstName:

  84. type: string

  85. lastName:

  86. type: string

  87. username:

  88. type: string

  89. 404:

  90. description: The Person does not exists.

2.5.1 添加一个 post /persons 操作

首先在 /persons 路径下廷加一个 post 操作:

 
  1. paths:

  2. /persons:

 
  1. post:

  2. summary: Creates a person

  3. description: Adds a new person to the persons list.

2.5.2 定义消息体参数

接下来我们给 post 方法添加参数,通过 in 属性显式说明参数是在 body 中的。参数的定义参考 get /persons/{username} 的 200 响应消息体参数,也就是包含用户的姓氏、名字、用户名。

 
  1. parameters:

  2. - name: person

  3. in: body

  4. description: The person to create.

  5. schema:

  6. required:

  7. - username

  8. properties:

  9. firstName:

  10. type: string

  11. lastName:

  12. type: string

  13. username:

  14. type: string

2.5.3 定义响应消息

最后不要忘记定义 post 操作的响应消息。

 
  1. responses:

  2. 204:

  3. description: Persons succesfully created.

  4. 400:

  5. description: Persons couldn't have been created.

  6.  

第3章 文档瘦身

现在我们已经学会了编写API文档的基本方法。不过上面的例子中存在一些重复,这对于程序员的嗅觉来说,就是代码的“坏味道”。这一章我们一起学习如何通过抽取可重用的定义(definitions)来简化API文档。

3.1 简化数据模型

我们认真观察第2章最后输出的API文档,很容易发现 Person 的定义出现了三次,非常的不 DRY☹。

 
  1. swagger: "2.0"

  2.  
  3. info:

  4. version: 1.0.0

  5. title: Simple API

  6. description: A simple API to learn how to write OpenAPI Specification

  7.  
  8. schemes:

  9. - https

  10. host: simple.api

  11. basePath: /openapi101

  12.  
  13. paths:

  14. /persons:

  15. get:

  16. summary: Gets some persons

  17. description: Returns a list containing all persons. The list supports paging.

  18. parameters:

  19. - name: pageSize

  20. in: query

  21. description: Number of persons returned

  22. type: integer

  23. - name: pageNumber

  24. in: query

  25. description: Page number

  26. type: integer

  27. responses:

  28. 200:

  29. description: A list of Person

  30. schema:

  31. type: array

  32. items:

  33. #START 第1次定义###################################################################

  34. required:

  35. - username

  36. properties:

  37. firstName:

  38. type: string

  39. lastName:

  40. type: string

  41. username:

  42. type: string

  43. # END 第1次定义###################################################################

  44. post:

  45. summary: Creates a person

  46. description: Adds a new person to the persons list.

  47. parameters:

  48. - name: person

  49. in: body

  50. description: The person to create.

  51. schema:

  52. #START 第2次定义###################################################################

  53. required:

  54. - username

  55. properties:

  56. firstName:

  57. type: string

  58. lastName:

  59. type: string

  60. username:

  61. type: string

  62. # END 第2次定义###################################################################

  63. responses:

  64. 204:

  65. description: Persons succesfully created.

  66. 400:

  67. description: Persons couldn't have been created.

  68. /persons/{username}:

  69. get:

  70. summary: Gets a person

  71. description: Returns a single person for its username.

  72. parameters:

  73. - name: username

  74. in: path

  75. required: true

  76. description: The person's username

  77. type: string

  78. responses:

  79. 200:

  80. description: A Person

  81. schema:

  82. #START 第3次定义###################################################################

  83. required:

  84. - username

  85. properties:

  86. firstName:

  87. type: string

  88. lastName:

  89. type: string

  90. username:

  91. type: string

  92. # END 第3次定义###################################################################

  93. 404:

  94. description: The Person does not exists.

现在,我们通过可重用的定义 (definition)来重构这个文档:

 
  1. swagger: "2.0"

  2.  
  3. info:

  4. version: 1.0.0

  5. title: Simple API

  6. description: A simple API to learn how to write OpenAPI Specification

  7.  
  8. schemes:

  9. - https

  10. host: simple.api

  11. basePath: /openapi101

  12.  
  13. paths:

  14. /persons:

  15. get:

  16. summary: Gets some persons

  17. description: Returns a list containing all persons. The list supports paging.

  18. parameters:

  19. - name: pageSize

  20. in: query

  21. description: Number of persons returned

  22. type: integer

  23. - name: pageNumber

  24. in: query

  25. description: Page number

  26. type: integer

  27. responses:

  28. 200:

  29. description: A list of Person

  30. schema:

  31. #START############################################################################

  32. $ref: "#/definitions/Persons"

  33. # END ############################################################################

  34. post:

  35. summary: Creates a person

  36. description: Adds a new person to the persons list.

  37. parameters:

  38. - name: person

  39. in: body

  40. description: The person to create.

  41. schema:

  42. #START############################################################################

  43. $ref: "#/definitions/Person"

  44. # END ############################################################################

  45. responses:

  46. 204:

  47. description: Persons succesfully created.

  48. 400:

  49. description: Persons couldn't have been created.

  50. /persons/{username}:

  51. get:

  52. summary: Gets a person

  53. description: Returns a single person for its username.

  54. parameters:

  55. - name: username

  56. in: path

  57. required: true

  58. description: The person's username

  59. type: string

  60. responses:

  61. 200:

  62. description: A Person

  63. schema:

  64. #START############################################################################

  65. $ref: "#/definitions/Person"

  66. # END ############################################################################

  67. 404:

  68. description: The Person does not exists.

  69.  
  70. #START 新增定义####################################################################

  71. definitions:

  72. Person:

  73. required:

  74. - username

  75. properties:

  76. firstName:

  77. type: string

  78. lastName:

  79. type: string

  80. username:

  81. type: string

  82. Persons:

  83. type: array

  84. items:

  85. $ref: "#/definitions/Person"

  86. # END 新增定义####################################################################

文档简化了很多。这得益于OpenAPI规范中关于定义(definition)的章节中允许我们“一处定义,处处使用”。

3.1.1 添加定义 (definitions)项

我们首先在API文档的尾部添加一个定义 (definitions)项(其实它也可以放在文档的任意位置,只不过大家习惯放在文档末尾):

 
  1. 404:

  2. description: The Person does not exists.

  3. #START############################################################################

  4. definitions:

  5. # END ############################################################################

3.1.2 增加一个可重用的(对象)定义

然后我们增加一个 Person 对象的定义:

 
  1. definitions:

  2. #START############################################################################

  3. Person:

  4. required:

  5. - username

  6. properties:

  7. firstName:

  8. type: string

  9. lastName:

  10. type: string

  11. username:

  12. type: string

  13. # END ############################################################################

3.1.3 引用一个定义来增加另一个定义

在定义项中,我们可以立即引用刚才定义好的 Person 来增加另一个定义PersonsPersons 是一个 Person 对象的数组。与之前直接定义的不同之处是,我们增加了一个引用reference)属性,也就是 $ref 来引用 Person 。

 
  1. Persons:

  2. type: array

  3. items:

  4. $ref: "#/definitions/Person"

3.1.4 在响应消息中使用定义

一旦定义好了 Person ,我们可以把原来在响应消息中相应的定义字段替换掉。

3.1.4.1 get/persons

原来:

 
  1. responses:

  2. 200:

  3. description: A list of Person

  4. schema:

  5. type: array

  6. items:

  7. required:

  8. - username

  9. properties:

  10. firstName:

  11. type: string

  12. lastName:

  13. type: string

现在:

 
  1. responses:

  2. 200:

  3. description: A list of Person

  4. schema:

  5. $ref: "#/definitions/Persons"

3.1.4.2 get/persons/{username}

原来:

 
  1. responses:

  2. 200:

  3. description: A Person

  4. schema:

  5. required:

  6. - username

  7. properties:

  8. firstName:

  9. type: string

  10. lastName:

  11. type: string

  12. username:

  13. type: string

现在:

 
  1. responses:

  2. 200:

  3. description: A Person

  4. schema:

  5. $ref: "#/definitions/Person"

3.1.5 在参数中使用定义

不仅仅在消息中可以使用定义,在参数中也可以使用。

3.1.5.1 post /persons

原来:

 
  1. post:

  2. summary: Creates a person

  3. description: Adds a new person to the persons list.

  4. parameters:

  5. - name: person

  6. in: body

  7. description: The person to create.

  8. schema:

  9. required:

  10. - username

  11. properties:

  12. firstName:

  13. type: string

  14. lastName:

  15. type: string

  16. username:

  17. type: string

现在:

 
  1. post:

  2. summary: Creates a person

  3. description: Adds a new person to the persons list.

  4. parameters:

  5. - name: person

  6. in: body

  7. description: The person to create.

  8. schema:

  9. $ref: "#/definitions/Person"

3.2 简化响应消息

我们看到了引用 ($ref)的作用,接下来我们再把它用到响应消息的定义中:

 
  1. swagger: "2.0"

  2.  
  3. info:

  4. version: 1.0.0

  5. title: Simple API

  6. description: A simple API to learn how to write OpenAPI Specification

  7.  
  8. schemes:

  9. - https

  10. host: simple.api

  11. basePath: /openapi101

  12.  
  13. paths:

  14. /persons:

  15. get:

  16. summary: Gets some persons

  17. description: Returns a list containing all persons. The list supports paging.

  18. parameters:

  19. - name: pageSize

  20. in: query

  21. description: Number of persons returned

  22. type: integer

  23. - name: pageNumber

  24. in: query

  25. description: Page number

  26. type: integer

  27. responses:

  28. 200:

  29. description: A list of Person

  30. schema:

  31. $ref: "#/definitions/Persons"

  32. #START############################################################################

  33. 500:

  34. $ref: "#/responses/Standard500ErrorResponse"

  35. # END ############################################################################

  36. post:

  37. summary: Creates a person

  38. description: Adds a new person to the persons list.

  39. parameters:

  40. - name: person

  41. in: body

  42. description: The person to create.

  43. schema:

  44. $ref: "#/definitions/Person"

  45. responses:

  46. 204:

  47. description: Persons succesfully created.

  48. 400:

  49. description: Persons couldn't have been created.

  50. #START############################################################################

  51. 500:

  52. $ref: "#/responses/Standard500ErrorResponse"

  53. # END ############################################################################

  54.  
  55. /persons/{username}:

  56. get:

  57. summary: Gets a person

  58. description: Returns a single person for its username.

  59. parameters:

  60. - name: username

  61. in: path

  62. required: true

  63. description: The person's username

  64. type: string

  65. responses:

  66. 200:

  67. description: A Person

  68. schema:

  69. $ref: "#/definitions/Person"

  70. 404:

  71. description: The Person does not exists.

  72. #START############################################################################

  73. 500:

  74. $ref: "#/responses/Standard500ErrorResponse"

  75. # END ############################################################################

  76.  
  77. definitions:

  78. Person:

  79. required:

  80. - username

  81. properties:

  82. firstName:

  83. type: string

  84. lastName:

  85. type: string

  86. username:

  87. type: string

  88. Persons:

  89. type: array

  90. items:

  91. $ref: "#/definitions/Person"

  92. #START############################################################################

  93. Error:

  94. properties:

  95. code:

  96. type: string

  97. message:

  98. type: string

  99.  
  100. responses:

  101. Standard500ErrorResponse:

  102. description: An unexpected error occured.

  103. schema:

  104. $ref: "#/definitions/Error"

  105. # END ############################################################################

3.2.1 定义可重用的HTTP 500 响应

发生HTTP 500错误时,假如我们希望每一个API操作都返回一个带有错误码(error code)和描述信息(message)的响应,我们可以这样做:

 
  1. paths:

  2. /persons:

  3. get:

  4. summary: Gets some persons

  5. description: Returns a list containing all persons. The list supports paging.

  6. parameters:

  7. - name: pageSize

  8. in: query

  9. description: Number of persons returned

  10. type: integer

  11. - name: pageNumber

  12. in: query

  13. description: Page number

  14. type: integer

  15. responses:

  16. 200:

  17. description: A list of Person

  18. schema:

  19. $ref: "#/definitions/Persons"

  20. #START############################################################################

  21. 500:

  22. description: An unexpected error occured.

  23. schema:

  24. properties:

  25. code:

  26. type: string

  27. message:

  28. type: string

  29. # END ############################################################################

  30. post:

  31. summary: Creates a person

  32. description: Adds a new person to the persons list.

  33. parameters:

  34. - name: person

  35. in: body

  36. description: The person to create.

  37. schema:

  38. $ref: "#/definitions/Person"

  39. responses:

  40. 204:

  41. description: Persons succesfully created.

  42. 400:

  43. description: Persons couldn't have been created.

  44. #START############################################################################

  45. 500:

  46. description: An unexpected error occured.

  47. schema:

  48. properties:

  49. code:

  50. type: string

  51. message:

  52. type: string

  53. # END ############################################################################

  54.  
  55. /persons/{username}:

  56. get:

  57. summary: Gets a person

  58. description: Returns a single person for its username.

  59. parameters:

  60. - name: username

  61. in: path

  62. required: true

  63. description: The person's username

  64. type: string

  65. responses:

  66. 200:

  67. description: A Person

  68. schema:

  69. $ref: "#/definitions/Person"

  70. 404:

  71. description: The Person does not exists.

  72. #START############################################################################

  73. 500:

  74. description: An unexpected error occured.

  75. schema:

  76. properties:

  77. code:

  78. type: string

  79. message:

  80. type: string

  81. # END ############################################################################

3.2.2 增加一个Error定义

按照“一处定义、处处引用”的原则,我们可以在定义项中增加 Error 的定义:

 
  1. definitions:

  2. Person:

  3. required:

  4. - username

  5. properties:

  6. firstName:

  7. type: string

  8. lastName:

  9. type: string

  10. username:

  11. type: string

  12. Persons:

  13. type: array

  14. items:

  15. $ref: "#/definitions/Person"

  16. #START############################################################################

  17. Error:

  18. properties:

  19. code:

  20. type: string

  21. message:

  22. type: string

  23. # END ############################################################################

而且我们也学会了使用引用$ref),所以我们可以这样写:

 
  1. paths:

  2. /persons:

  3. get:

  4. summary: Gets some persons

  5. description: Returns a list containing all persons. The list supports paging.

  6. parameters:

  7. - name: pageSize

  8. in: query

  9. description: Number of persons returned

  10. type: integer

  11. - name: pageNumber

  12. in: query

  13. description: Page number

  14. type: integer

  15. responses:

  16. 200:

  17. description: A list of Person

  18. schema:

  19. $ref: "#/definitions/Persons"

  20. 500:

  21. description: An unexpected error occured.

  22. schema:

  23. #START############################################################################

  24. $ref: "#/definitions/Error"

  25. # END ############################################################################

  26. post:

  27. summary: Creates a person

  28. description: Adds a new person to the persons list.

  29. parameters:

  30. - name: person

  31. in: body

  32. description: The person to create.

  33. schema:

  34. $ref: "#/definitions/Person"

  35. responses:

  36. 204:

  37. description: Persons succesfully created.

  38. 400:

  39. description: Persons couldn't have been created.

  40. 500:

  41. description: An unexpected error occured.

  42. schema:

  43. #START############################################################################

  44. $ref: "#/definitions/Error"

  45. # END ############################################################################

  46. /persons/{username}:

  47. get:

  48. summary: Gets a person

  49. description: Returns a single person for its username.

  50. parameters:

  51. - name: username

  52. in: path

  53. required: true

  54. description: The person's username

  55. type: string

  56. responses:

  57. 200:

  58. description: A Person

  59. schema:

  60. $ref: "#/definitions/Person"

  61. 404:

  62. description: The Person does not exists.

  63. 500:

  64. description: An unexpected error occured.

  65. schema:

  66. #START############################################################################

  67. $ref: "#/definitions/Error"

  68. # END ############################################################################

3.2.3 定义一个可重用的响应消息

上面的文档中,还是有一些重复的内容。我们可以根据OpenAPI规范中的responses章节的描述,通过定义一个可重用的响应消息,来进一步简化文档。

 
  1. definitions:

  2. Person:

  3. required:

  4. - username

  5. properties:

  6. firstName:

  7. type: string

  8. lastName:

  9. type: string

  10. username:

  11. type: string

  12. Persons:

  13. type: array

  14. items:

  15. $ref: "#/definitions/Person"

  16. Error:

  17. properties:

  18. code:

  19. type: string

  20. message:

  21. type: string

  22. #START############################################################################

  23. responses:

  24. Standard500ErrorResponse:

  25. description: An unexpected error occured.

  26. schema:

  27. $ref: "#/definitions/Error"

  28. # END ############################################################################

注意:响应消息中引用了 Error 的定义。

3.2.4 使用已定义的响应消息

我们还是通过引用$ref)来使用一个已经定义好的响应消息,比如:

3.2.4.1 get /users

 
  1. responses:

  2. 200:

  3. description: A list of Person

  4. schema:

  5. $ref: "#/definitions/Persons"

  6. #START############################################################################

  7. 500:

  8. $ref: "#/responses/Standard500ErrorResponse"

  9. # END ############################################################################

3.2.4.2 post/users

 
  1. responses:

  2. 204:

  3. description: Persons succesfully created.

  4. 400:

  5. description: Persons couldn't have been created.

  6. #START############################################################################

  7. 500:

  8. $ref: "#/responses/Standard500ErrorResponse"

  9. # END ############################################################################

  10.  

3.2.4.3 get/users/{username}

 
  1. responses:

  2. 200:

  3. description: A Person

  4. schema:

  5. $ref: "#/definitions/Person"

  6. 404:

  7. description: The Person does not exists.

  8. #START############################################################################

  9. 500:

  10. $ref: "#/responses/Standard500ErrorResponse"

  11. # END ############################################################################

3.3 简化参数定义

类似数据模型、响应消息的简化,参数定义的简化也很容易。

 
  1. swagger: "2.0"

  2.  
  3. info:

  4. version: 1.0.0

  5. title: Simple API

  6. description: A simple API to learn how to write OpenAPI Specification

  7.  
  8. schemes:

  9. - https

  10. host: simple.api

  11. basePath: /openapi101

  12.  
  13. paths:

  14. /persons:

  15. get:

  16. summary: Gets some persons

  17. description: Returns a list containing all persons. The list supports paging.

  18. #START############################################################################

  19. parameters:

  20. - $ref: "#/parameters/pageSize"

  21. - $ref: "#/parameters/pageNumber"

  22. # END ############################################################################

  23. responses:

  24. 200:

  25. description: A list of Person

  26. schema:

  27. $ref: "#/definitions/Persons"

  28. 500:

  29. $ref: "#/responses/Standard500ErrorResponse"

  30. post:

  31. summary: Creates a person

  32. description: Adds a new person to the persons list.

  33. parameters:

  34. - name: person

  35. in: body

  36. description: The person to create.

  37. schema:

  38. $ref: "#/definitions/Person"

  39. responses:

  40. 204:

  41. description: Person succesfully created.

  42. 400:

  43. description: Person couldn't have been created.

  44. 500:

  45. $ref: "#/responses/Standard500ErrorResponse"

  46.  
  47. /persons/{username}:

  48. #START############################################################################

  49. parameters:

  50. - $ref: "#/parameters/username"

  51. # END ############################################################################

  52. get:

  53. summary: Gets a person

  54. description: Returns a single person for its username.

  55. responses:

  56. 200:

  57. description: A Person

  58. schema:

  59. $ref: "#/definitions/Person"

  60. 404:

  61. $ref: "#/responses/PersonDoesNotExistResponse"

  62. 500:

  63. $ref: "#/responses/Standard500ErrorResponse"

  64. delete:

  65. summary: Deletes a person

  66. description: Delete a single person identified via its username

  67. responses:

  68. 204:

  69. description: Person successfully deleted.

  70. 404:

  71. $ref: "#/responses/PersonDoesNotExistResponse"

  72. 500:

  73. $ref: "#/responses/Standard500ErrorResponse"

  74.  
  75. /persons/{username}/friends:

  76. #START############################################################################

  77. parameters:

  78. - $ref: "#/parameters/username"

  79. # END ############################################################################

  80. get:

  81. summary: Gets a person's friends

  82. description: Returns a list containing all persons. The list supports paging.

  83. #START############################################################################

  84. parameters:

  85. - $ref: "#/parameters/pageSize"

  86. - $ref: "#/parameters/pageNumber"

  87. # END ############################################################################

  88. responses:

  89. 200:

  90. description: A person's friends list

  91. schema:

  92. $ref: "#/definitions/Persons"

  93. 404:

  94. $ref: "#/responses/PersonDoesNotExistResponse"

  95. 500:

  96. $ref: "#/responses/Standard500ErrorResponse"

  97.  
  98. definitions:

  99. Person:

  100. required:

  101. - username

  102. properties:

  103. firstName:

  104. type: string

  105. lastName:

  106. type: string

  107. username:

  108. type: string

  109. Persons:

  110. type: array

  111. items:

  112. $ref: "#/definitions/Person"

  113. Error:

  114. required:

  115. - code

  116. - message

  117. properties:

  118. code:

  119. type: string

  120. message:

  121. type: string

  122.  
  123. responses:

  124. Standard500ErrorResponse:

  125. description: An unexpected error occured.

  126. schema:

  127. $ref: "#/definitions/Error"

  128. PersonDoesNotExistResponse:

  129. description: Person does not exist.

  130.  
  131. #START############################################################################

  132. parameters:

  133. username:

  134. name: username

  135. in: path

  136. required: true

  137. description: The person's username

  138. type: string

  139. pageSize:

  140. name: pageSize

  141. in: query

  142. description: Number of persons returned

  143. type: integer

  144. pageNumber:

  145. name: pageNumber

  146. in: query

  147. description: Page number

  148. type: integer

  149. # END ############################################################################

3.3.1 路径参数只定义一次

如果我们现在想要删除一个用户的信息,就需要增加一个 delete /persons/{username} 的操作,可以这样:

 
  1. /persons/{username}:

  2. get:

  3. summary: Gets a person

  4. description: Returns a single person for its username.

  5. parameters:

  6. #START############################################################################

  7. - name: username

  8. in: path

  9. required: true

  10. description: The person's username

  11. type: string

  12. # END ############################################################################

  13. responses:

  14. 200:

  15. description: A Person

  16. schema:

  17. $ref: "#/definitions/Person"

  18. 404:

  19. $ref: "#/responses/PersonDoesNotExistResponse"

  20. 500:

  21. $ref: "#/responses/Standard500ErrorResponse"

  22. delete:

  23. summary: Deletes a person

  24. description: Delete a single person identified via its username

  25. parameters:

  26. #START############################################################################

  27. - name: username

  28. in: path

  29. required: true

  30. description: The person's username

  31. type: string

  32. # END ############################################################################

  33. responses:

  34. 204:

  35. description: Person successfully deleted.

  36. 404:

  37. $ref: "#/responses/PersonDoesNotExistResponse"

  38. 500:

  39. $ref: "#/responses/Standard500ErrorResponse"

但是上面两次对参数 username 的定义,却让人有点难受。好消息是我们可以定义路径级别的参数(之前都是定义在操作级别。)

 
  1. #START############################################################################

  2. /persons/{username}:

  3. parameters:

  4. - name: username

  5. in: path

  6. required: true

  7. description: The person's username

  8. type: string

  9. # END ############################################################################

  10. get:

  11. summary: Gets a person

  12. description: Returns a single person for its username.

  13. responses:

  14. 200:

  15. description: A Person

  16. schema:

  17. $ref: "#/definitions/Person"

  18. 404:

  19. $ref: "#/responses/PersonDoesNotExistResponse"

  20. 500:

  21. $ref: "#/responses/Standard500ErrorResponse"

  22. delete:

  23. summary: Deletes a person

  24. description: Delete a single person identified via its username

  25. responses:

  26. 204:

  27. description: Person successfully deleted.

  28. 404:

  29. $ref: "#/responses/PersonDoesNotExistResponse"

  30. 500:

  31. $ref: "#/responses/Standard500ErrorResponse"

  32.  

3.3.2 定义可重用的参数

如果我们想根据用户名查找该用户的朋友圈,可以添加一个 get /persons/{username}/friends 的操作。根据前面所学的内容,第一反应应该这样写:

 
  1. /persons/{username}/friends:

  2. parameters:

  3. - name: username

  4. in: path

  5. required: true

  6. description: The person's username

  7. type: string

  8. get:

  9. summary: Gets a person's friends

  10. description: Returns a list containing all persons. The list supports paging.

  11. parameters:

  12. - name: pageSize

  13. in: query

  14. description: Number of persons returned

  15. type: integer

  16. - name: pageNumber

  17. in: query

  18. description: Page number

  19. type: integer

  20. responses:

  21. 200:

  22. description: A person's friends list

  23. schema:

  24. $ref: "#/definitions/Persons"

  25. 404:

  26. $ref: "#/responses/PersonDoesNotExistResponse"

  27. 500:

  28. $ref: "#/responses/Standard500ErrorResponse"

  29.  

可以看到,关于 username 、pageSize 、pageNumber 的定义跟前面的 /person/{username} 、 get /persons 中的定义重复。如何消除重复呢?

3.3.2.1 定义可重用的参数

根据3.1和3.2中的内容,我们可以参考OpenAPI规范,融汇贯通。

 
  1. parameters:

  2. username:

  3. name: username

  4. in: path

  5. required: true

  6. description: The person's username

  7. type: string

  8. pageSize:

  9. name: pageSize

  10. in: query

  11. description: Number of persons returned

  12. type: integer

  13. pageNumber:

  14. name: pageNumber

  15. in: query

  16. description: Page number

  17. type: integer

  18.  

3.3.2.2 使用定义参数

借助万能的引用$ref),这都是小菜一碟。比如:

3.3.2.2.1 get /persons

原来:

 
  1. /persons:

  2. get:

  3. summary: Gets some persons

  4. description: Returns a list containing all persons. The list supports paging.

  5. parameters:

  6. #START############################################################################

  7. - name: pageSize

  8. in: query

  9. description: Number of persons returned

  10. type: integer

  11. - name: pageNumber

  12. in: query

  13. description: Page number

  14. type: integer

  15. # END ############################################################################

现在:

 
  1. /persons:

  2. get:

  3. summary: Gets some persons

  4. description: Returns a list containing all persons. The list supports paging.

  5. parameters:

  6. #START############################################################################

  7. - $ref: "#/parameters/pageSize"

  8. - $ref: "#/parameters/pageNumber"

  9. # END ############################################################################

3.3.2.2.2 get 和 delete /persons/{username}

原来:

 
  1. /persons/{username}:

  2. parameters:

  3. #START############################################################################

  4. - name: username

  5. in: path

  6. required: true

  7. description: The person's username

  8. type: string

  9. # END ############################################################################

  10.  

现在:

 
  1. /persons/{username}:

  2. parameters:

  3. #START############################################################################

  4. - $ref: "#/parameters/username"

  5. # END ############################################################################

3.3.2.2.3 get /persons/{username}/friends

原来:

 
  1. /persons/{username}/friends:

  2. parameters:

  3. #START############################################################################

  4. - name: username

  5. in: path

  6. required: true

  7. description: The person's username

  8. type: string

  9. # END ############################################################################

  10. get:

  11. summary: Gets a person's friends

  12. description: Returns a list containing all persons. The list supports paging.

  13. parameters:

  14. #START############################################################################

  15. - name: pageSize

  16. in: query

  17. description: Number of persons returned

  18. type: integer

  19. - name: pageNumber

  20. in: query

  21. description: Page number

  22. type: integer

  23. # END ############################################################################

现在:

 
  1. /persons/{username}/friends:

  2. parameters:

  3. #START############################################################################

  4. - $ref: "#/parameters/username"

  5. # END ############################################################################

  6. get:

  7. summary: Gets a person's friends

  8. description: Returns a list containing all persons. The list supports paging.

  9. parameters:

  10. #START############################################################################

  11. - $ref: "#/parameters/pageSize"

  12. - $ref: "#/parameters/pageNumber"

  13. # END ############################################################################

  14.  

第4章 深入了解一下

通过前面的练习,我们可以写出一篇结构清晰、内容精炼的API文档了。可是OpenAPI规范还给我们提供了更多的便利和惊喜,等着我们去了解和掌握。这一章主要介绍用于定义属性和数据模型的高级方法。

4.1 私人定制

Primitive data types in the Swagger Specification are based on the types supported by the JSON-Schema Draft 4. Models are described using the Schema Object which is a subset of JSON Schema Draft 4.OpenAPI Specification Data Types

使用 JSON Schema Draft 4,我们可以定义任意类型的各种属性,举例说明。

4.1.1 字符串(Strings)长度和格式

当定义个字符串属性时,我们可以定制它的长度及格式:

属性类型描述
minLengthnumber字符串最小长度
maxLengthnumber字符串最大长度
patternstring正则表达式 (如果你暂时还不熟悉正则表达式,可以看看 Regex 101)

如果我们规定用户名是长度介于8~64,而且只能由小写字母和数字来构成,那么我们可以这样写:

 
  1. username:

  2. type: string

  3. pattern: "[a-z0-9]{8,64}"

  4. minLength: 8

  5. maxLength: 64

4.1.2 日期和时间

日期和时间的处理参考 RFC 3339 ,我们唯一要做的就是写对格式:

格式属性包含内容属性示例
dateISO8601 full-date2016-04-01
dateTimeISO8601 date-time2016-04-16T16:06:05Z

如果我们在 Person 的定义中增加 生日 和 上次登录时间 时间戳,我们可以这样写:

 
  1. dateOfBirth:

  2. type: string

  3. format: date

  4. lastTimeOnline:

  5. type: string

  6. format: dateTime

如果想深入掌握API中处理日期和时间的方法,我们应该继续阅读 Jason Harmon 写的文章《 5 laws of API dates and times 》。

4.1.3 数字类型和范围

当我们定义一个数字类型的属性时,我们可以规定它是一个整型、长型、浮点型或者双浮点型。

名称类型格式
integerintegerint32
longintegerint64
floatnumberfloat
doublenumberdouble

和字符串一样,我们也可以定义数字属性的范围,比如:

属性类型描述
minimumnumber最小值
maximumnumber最大值
exclusiveMinimumboolean数值必须 > 最小值
exclusiveMaximumboolean数值必须 < 最大值
multipleOfnumber数值必须是multipleOf的整数倍

如果我们规定 pageSize 必须是整数,必须 > 0 且 <=100,还必须是 10 的整数倍,可以这样写:

 
  1. pageSize:

  2. name: pageSize

  3. in: query

  4. description: Number of persons returned

  5. type: integer

  6. format: int32

  7. minimum: 0

  8. exclusiveMinimum: true

  9. maximum: 100

  10. exclusiveMaximum: false

  11. multipleOf: 10

4.1.4 枚举类型

我们还可以定义枚举类型,比如定义 Error 时,我们可以这样写:

 
  1. code:

  2. type: string

  3. enum:

  4. - DBERR

  5. - NTERR

  6. - UNERR

code 的值只能从三个枚举值中选择。

4.1.5 数值的大小和唯一性

数字的大小和唯一性通过下面这些属性来定义:

属性类型描述
minItemsnumber数值中的最小元素个数
maxItemnumber数值中的最大元素个数
uniqueItemsboolean标示数组中的元素是否唯一

比如我们定义一个用户数组 Persons,希望返回的用户信息条数介于10~100之间,而且不能有重复的用户信息,我们可以这样写:

 
  1. Persons:

  2. properties:

  3. items:

  4. type: array

  5. minItems: 10

  6. maxItems: 100

  7. uniqueItems: true

  8. items:

  9. $ref: "#/definitions/Person"

4.1.6 二进制数据

可以用 string 类型来表示二进制数据:

格式属性包含
byteBase64编码字符
binary任意十进制的数据序列

比如我们需要在用户信息中增加一个头像属性(avatarBase64PNG)用base64编码的PNG图片来表示,可以这样写:

 
  1. avatarBase64PNG:

  2. type: string

  3. format: byte

4.2 高级数据定义

4.2.1 读写操作同一定义的数据

有时候我们读取资源信息的内容会比我们写入资源信息的内容(属性)更多,这很常见。是不是意味着我们必须专门为读取资源和写入资源分别定义不同的数据模型呢?幸运的是,OpenAPI规范中提供了 readOnly 字段来帮我们解决整问题。比如:

 
  1. lastTimeOnline:

  2. type: string

  3. format: dateTime

  4. readOnly: true

上面这个例子中,上次在线时间lastTimeOnline )是 Person 的一个属性,我们获取用户信息时需要这个属性。但是很明显,在创建用户时,我们不能把这个属性 post 到服务器。于是我们可以把它标记为 readOnly

4.2.2 组合定义确保一致性

一致性设计是在编写API文档时需要重点考虑的问题。比如我们在获取一组用户信息时,需要同时获取页面信息

totalItems, totalPage, pageSize, currentPage)等,而且这些信息必须在根节点上。

怎么办呢?首先想到的做法就是:

 
  1. PagedPersonsV1:

  2. properties:

  3. items:

  4. type: array

  5. items:

  6. $ref: "#/definitions/Person"

  7. totalItems:

  8. type: integer

  9. totalPages:

  10. type: integer

  11. pageSize:

  12. type: integer

  13. currentPage:

  14. type: integer

如果其他API操作也需要这些页面信息,那就意味着这些属性必须一遍又一遍的定义。不仅重复体力劳动,而且还很危险:比如忘记了其中的一两个属性,或者需要添加一个新的属性进来,那就是霰弹式的修改,想想都很悲壮。

稍微好一点的做法,就是根据前面学习的内容,把这几个属性抽取出来,建立一个 Paging 模型,“一处定义、处处使用”:

 
  1. PagedPersonsV2:

  2. properties:

  3. items:

  4. type: array

  5. items:

  6. $ref: "#/definitions/Person"

  7. paging:

  8. $ref: "#/definitions/Paging"

  9.  
  10. Paging:

  11. properties:

  12. totalItems:

  13. type: integer

  14. totalPages:

  15. type: integer

  16. pageSize:

  17. type: integer

  18. currentPage:

  19. type: integer

但是,页面属性都不再位于 根节点!与我们前面设定的要求不一样了。怎么破?

JSON Schema v4 property中定义的allOf,能帮我们解围:

 
  1. PagedPersons:

  2. allOf:

  3. - $ref: "#/definitions/Persons"

  4. - $ref: "#/definitions/Paging"

上面这个例子表示,PagedPersons 根节点下,具有将 Persons 和 Paging 展开 后的全部属性。

allOf同样可以使用行内的数据定义,比如:

 
  1. PagedCollectingItems:

  2. allOf:

  3. - properties:

  4. items:

  5. type: array

  6. minItems: 10

  7. maxItems: 100

  8. uniqueItems: true

  9. items:

  10. $ref: "#/definitions/CollectingItem"

  11. - $ref: "#/definitions/Paging"

4.2.3 数据模型的继承(TODO)

目前各工具支持程度不高,待续


第5章 输入输出模型

这一章主要介绍如何定义高度精确化的参数和响应消息等。

5.1 高级参数定义

5.1.1 必带参数和可选参数

我们已经知道使用关键字required来定义一个必带参数。

5.1.1.1 定义必带参数和可选参数

在一个参数中,required是一个 boolean 型的可选值。它的默认值是 false 。

比如在某个操作中,username 是必填参数:

 
  1. username:

  2. name: username

  3. in: path

  4. #START############################################################################

  5. required: true

  6. # END ############################################################################

  7. description: The person's username

  8. type: string

  9.  

5.1.1.2 定义必带属性和可选属性

根据定义,required是一个字符串列表,列表中包含各必带参数名。如果某个参数在这张列表中找不到,那就说明它不是必带参数。如果没有定义required,就说明所有参数都是可选。如果required定义在一个HTTP请求上,这说明所有的请求参数都是必填。

在 POST 、persons 中有 Person 的定义,在这里 username 这个属性是必带的,我们可以指定它为required,其他非必带字段则不指定:

 
  1. Person:

  2. #START############################################################################

  3. required:

  4. - username

  5. # END ############################################################################

  6. properties:

  7. firstName:

  8. type: string

  9. lastName:

  10. type: string

  11. username:

  12. type: string

  13. pattern: '[a-z0-9]{8,64}'

  14. minLength: 8

  15. maxLength: 64

  16. dateOfBirth:

  17. type: string

  18. format: date

  19. lastTimeOnline:

  20. type: string

  21. format: date-time

  22. readOnly: true

  23. avatarBase64PNG:

  24. type: string

  25. format: byte

  26. default: data:image/png;base64,i……

  27. spokenLanguages:

  28. $ref: '#/definitions/SpokenLanguages'

5.1.2 带默认值的参数

通过关键字default,我们可以定义一个参数的默认值。当这个参数不可得(请求未带或者服务器未返回)时,这个参数就取默认值。因此设定了某个参数的默认值后,它是否required就没意义了。

5.1.2.1 定义参数的默认值

我们定义参数 pageSize 的默认值为 20 ,那么如果请求时没有填写 pageSize ,服务器也会默认返回 20 个元素。

 
  1. pageSize:

  2. name: pageSize

  3. in: query

  4. description: Number of persons returned

  5. type: integer

  6. format: int32

  7. minimum: 0

  8. exclusiveMinimum: true

  9. maximum: 100

  10. exclusiveMaximum: false

  11. multipleOf: 10

  12. #START############################################################################

  13. default: 20

  14. # END ############################################################################

5.1.2.2 定义属性的默认值

我们在定义 Person 对象时,希望给每个用户一个默认头像,也就是要给 avatarBase64PNG 属性一个默认值。

默认头像:

 
  1. Person:

  2. required:

  3. - username

  4. properties:

  5. firstName:

  6. type: string

  7. lastName:

  8. type: string

  9. username:

  10. type: string

  11. pattern: '[a-z0-9]{8,64}'

  12. minLength: 8

  13. maxLength: 64

  14. dateOfBirth:

  15. type: string

  16. format: date

  17. lastTimeOnline:

  18. type: string

  19. format: date-time

  20. readOnly: true

  21. avatarBase64PNG:

  22. type: string

  23. format: byte

  24. #START############################################################################

  25. default: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA……rkJggg==

  26. # END ############################################################################

  27. spokenLanguages:

  28. $ref: '#/definitions/SpokenLanguages'

5.1.3 带空值的参数

在 GET /persons 时,如果我们想添加一个参数来过滤“是否通过实名认证”的用户,应该怎么做呢?首先想到的是这样:GET /persons?page=2&includeVerifiedUsers=true ,问题是 includeVerifiedUsers 语义已经如此清晰,而让 “=true”显得很多余。我们能不能直接用:GET /persons?page=2&includeVerifiedUsers 呢?

要做到这种写法,我们需要一个关键字 allowEmptyValue。我们定义 includeVerifiedUsers 时允许它为空。那么如果我们请求 GET /persons?page=2&includeVerifiedUsers 则表示需要过滤“实名认证”用户,如果我们直接请求 GET /persons?page=2 则表示不过滤:

 
  1. includeNonVerifiedUsers:

  2. name: includeNonVerifiedUsers

  3. in: query

  4. type: boolean

  5. default: false

  6. #START############################################################################

  7. allowEmptyValue: true

  8. # END ############################################################################

5.1.4 参数组

设计API的时候,我们经常会遇到在 GET 请求中需要携带一组请求参数的情况。如何在API文档章呈现呢?很简单,我们只需要设定参数类型(type) 为array,并选择合适的 组合格式(collectionFormat)就行了。

COLLECTIONFORMAT描述
csv (default value)Comma separated values(逗号分隔) foo,bar
ssvSpace separated values(空格分隔) foo bar
tsvTab separated values(反斜杠分隔) foo\tbar
pipesPipes separated values(竖线分隔) `foo\bar`
multi单属性可以取多个值,比如 foo=bar&foo=baz. 只适用于查询参数和表单参数。

比如我们想根据多种参数(username , firstname , lastname , lastTimeOnline )等来对 Person 进行带排序的查询。我们需要一个这样的API请求: GET /persons?sort=-lastTimeOnline|+firtname|+lastname 。用于排序的参数是 sort ,+表示升序,-表示降序。

相应的API文档,可以这样写:

 
  1. sortPersons:

  2. name: sort

  3. in: query

  4. type: array

  5. uniqueItems: true

  6. minItems: 1

  7. maxItems: 3

  8. collectionFormat: pipes

  9. items:

  10. type: string

  11. pattern: '[-+](username|lastTimeOnline|firstname|lastname)'

现在我们就能搞定 GET /persons?sort=-lastTimeOnline|+firtname|+lastname 这种请求了。当然,我们还可以指定排序的默认值,锦上添花。

 
  1. sortPersons:

  2. name: sort

  3. in: query

  4. type: array

  5. uniqueItems: true

  6. minItems: 1

  7. maxItems: 3

  8. collectionFormat: pipes

  9. items:

  10. type: string

  11. pattern: '[-+](username|lastTimeOnline|firstname|lastname)'

  12. #START############################################################################

  13. default:

  14. - -lastTimeOnline

  15. - +username

  16. # END ############################################################################

5.1.5 消息头(Header)参数

参数,按照位置来分,不仅仅包含路径参数、请求参数和消息体参数,还包括消息头参数和表单参数等。比如我们可以在HTTP请求的消息头上加一个 User-Agent (用于跟踪、调试或者其他),可以这样定义它:

 
  1. userAgent:

  2. name: User-Agent

  3. type: string

  4. in: header

  5. required: true

然后像使用其他参数一样使用它:

 
  1. paths:

  2. /persons:

  3. parameters:

  4. - $ref: '#/parameters/userAgent'

5.1.6 表单参数

有些 js-less-browser 的老浏览器不支持 POST JSON数据,比如在创建用户时,只能够以这样个格式请求:

 
  1. POST /js-less-persons

  2.  
  3. username=apihandyman&firstname=API&lastname=Handyman

没有问题,丝袜哥可以搞定。我们只需要把各个属性的in关键字定义为formData,然后设置consumes的媒体类型为application/x-www-form-urlencoded即可。

 
  1. post:

  2. summary: Creates a person

  3. description: For JS-less partners

  4. #START############################################################################

  5. consumes:

  6. - application/x-www-form-urlencoded

  7. # END ############################################################################

  8. produces:

  9. - text/html

  10. parameters:

  11. - name: username

  12. #START############################################################################

  13. in: formData

  14. # END ############################################################################

  15. required: true

  16. pattern: '[a-z0-9]{8,64}'

  17. minLength: 8

  18. maxLength: 64

  19. type: string

  20. - name: firstname

  21. #START############################################################################

  22. in: formData

  23. # END ############################################################################

  24. type: string

  25. - name: lastname

  26. in: formData

  27. type: string

  28. - name: dateOfBirth

  29. #START############################################################################

  30. in: formData

  31. # END ############################################################################

  32. type: string

  33. format: date

  34. responses:

  35. '204':

  36. description: Person succesfully created.

5.1.7 文件参数

当我们要处理一个请求,输入类型是 文件 时,我们需要:

  • 使用 multipart/form-data 媒体类型;

  • 设置参数的 in关键字为 formData

  • 设置参数的类型(type)为 file

比如:

 
  1. /images:

  2. parameters:

  3. - $ref: '#/parameters/userAgent'

  4. post:

  5. summary: Uploads an image

  6. consumes:

  7. - multipart/form-data

  8. parameters:

  9. - name: image

  10. in: formData

  11. type: file

  12. responses:

  13. '200':

  14. description: Image's ID

  15. schema:

  16. properties:

  17. imageId:

  18. type: string

  19.  

有时候我们想限定输入文件的类型(后缀),很不幸的是,根据现在V2.0的规范暂时还做不到☹

The spec doesn’t allow specifying a content type for specific form data parameters. It’s a limitation of the spec.Ron Ratovsky comment in Swagger UI 609 issue

5.1.8 参数的媒体类型

一个API可以消费各种不同的媒体类型,比如说最常见的是 application/json 类型的数据,当然这不是API唯一支持的类型。我们可以在文档的根节点 或者一个操作的根节点 下添加关键字 consumes,来定义这个操作能够消费的媒体类型。

比如我们的API全部都接受JSON和YAML的数据,那我们可以在文档的根节点下添加:

 
  1. consumes:

  2. - application/json

  3. - application/x-yaml

如果某个操作(比如上传图片的操作)很特殊,它可以通过自己添加 consumes来覆盖全局设置:

 
  1. /images:

  2. parameters:

  3. - $ref: '#/parameters/userAgent'

  4. post:

  5. summary: Uploads an image

  6. #START############################################################################

  7. consumes:

  8. - multipart/form-data

  9. # END ############################################################################

  10. parameters:

  11. - name: image

  12. in: formData

  13. type: file

  14. responses:

  15. '200':

  16. description: Image's ID

  17. schema:

  18. properties:

  19. imageId:

  20. type: string

  21.  

5.2 高级响应消息定义

5.2.1 不带消息体的响应消息

不带消息体的响应很常见,比如HTTP 204 状态响应本身就表示服务器返回不带任何消息内容的成功消息。

要定义一个不带消息体的响应很简单,我们只需要写响应状态和描述就行了:

 
  1. post:

  2. summary: Creates a person

  3. description: Adds a new person to the persons list.

  4. parameters:

  5. - name: person

  6. in: body

  7. required: true

  8. description: The person to create.

  9. schema:

  10. $ref: '#/definitions/Person'

  11. responses:

  12. #START############################################################################

  13. '204':

  14. description: Person succesfully created.

  15. # END ############################################################################

5.2.2 响应消息中的必带参数和可选参数

与请求消息中类似,我们使用required参数来表示,比如请求一个用户信息时, 服务器必须返回username,可以这样写:

 
  1. Person:

  2. #START############################################################################

  3. required:

  4. - username

  5. # END ############################################################################

  6. properties:

  7. firstName:

  8. type: string

  9. lastName:

  10. type: string

  11. username:

  12. type: string

  13. pattern: '[a-z0-9]{8,64}'

  14. minLength: 8

  15. maxLength: 64

  16. dateOfBirth:

  17. type: string

  18. format: date

  19. lastTimeOnline:

  20. type: string

  21. format: date-time

  22. readOnly: true

5.2.3 响应消息头

API的返回结果不仅仅体现下HTTP状态和响应消息体,还可以在响应消息头上做文章。比如我们可以限定一个API的使用次数和使用时间段,在响应消息头中,增加一个属性X-Rate-Limit-Remaining 来表示API可调用的剩余次数,增加另一个属性 X-Rate-Limit-Reset 来表示API的有效截止时间。

 
  1. post:

  2. summary: Creates a person

  3. description: Adds a new person to the persons list.

  4. parameters:

  5. - name: person

  6. in: body

  7. required: true

  8. description: The person to create.

  9. schema:

  10. $ref: '#/definitions/Person'

  11. responses:

  12. '204':

  13. description: Person succesfully created.

  14. headers:

  15. #START############################################################################

  16. X-Rate-Limit-Remaining:

  17. type: integer

  18. X-Rate-Limit-Reset:

  19. type: string

  20. format: date-time

  21. # END ############################################################################

美中不足的是,对于这种响应消息头的修改,目前2.0规范暂时还不支持“一次定义、处处使用”☹

5.2.4 默认响应消息

我们在定义响应消息时,通常会列举不同的HTTP状态结果。如果有些状态不在我们API文档的定义范围(比如服务器需要返回 993 的状态),该怎么处理呢?这时需要通过关键字default来定义一个默认响应消息,用于各种定义之外的状态响应,比如:

 
  1. delete:

  2. summary: Deletes a person

  3. description: Delete a single person identified via its username

  4. responses:

  5. '204':

  6. description: Person successfully deleted.

  7. headers:

  8. X-Rate-Limit-Remaining:

  9. type: integer

  10. X-Rate-Limit-Reset:

  11. type: string

  12. format: date-time

  13. '404':

  14. $ref: '#/responses/PersonDoesNotExistResponse'

  15. '500':

  16. $ref: '#/responses/Standard500ErrorResponse'

  17. #START############################################################################

  18. default:

  19. $ref: '#/responses/TotallyUnexpectedResponse'

  20. # END ############################################################################

目前这个配置也不支持“一次定义,处处使用” 。☹

5.2.5 响应消息的媒体类型

与请求消息一样,我们也可以定义响应消息所支持的媒体类型,不同的是我们要用到关键字 produces(与请求消息中的consumes相对,由此可见,API文档描述的主体是服务提供者)。

比如,我们可以在文档的根路径下全局设置:

 
  1. produces:

  2. - application/json

  3. - application/x-yaml

也可以在某个操作的根路径下覆盖设置:

 
  1. /images/{imageId}:

  2. parameters:

  3. - $ref: '#/parameters/userAgent'

  4. get:

  5. summary: Gets an image

  6. parameters:

  7. - name: imageId

  8. in: path

  9. required: true

  10. type: string

  11. #START############################################################################

  12. produces:

  13. - image/png

  14. - image/gif

  15. - image/jpeg

  16. - application/json

  17. - application/x-yaml

  18. # END ############################################################################

  19. responses:

  20. '200':

  21. description: The image

  22. headers:

  23. X-Rate-Limit-Remaining:

  24. type: integer

  25. X-Rate-Limit-Reset:

  26. type: string

  27. format: date-time

  28. '404':

  29. description: Image do not exists

  30. headers:

  31. X-Rate-Limit-Remaining:

  32. type: integer

  33. X-Rate-Limit-Reset:

  34. type: string

  35. format: date-time

  36. '500':

  37. $ref: '#/responses/Standard500ErrorResponse'

  38. default:

  39. $ref: '#/responses/TotallyUnexpectedResponse'

5.3 定义某个参数只存在于响应消息中

如前章节4.2.1中已经提到的,定义一个对象,其中某个属性我们只希望在响应消息中携带,而不希望在请求消息中携带,应该用readOnly关键字来表示。考虑到内容的完整性,这里再介绍一下。

比如 Person 对象中的 lastTimeOnline 这个属性,注册用户时我们不需要填写,但是在获取用户信息时,需要提供给服务消费者:

 
  1.  
  2. Person:

  3. required:

  4. - username

  5. properties:

  6. firstName:

  7. type: string

  8. lastName:

  9. type: string

  10. username:

  11. type: string

  12. pattern: '[a-z0-9]{8,64}'

  13. minLength: 8

  14. maxLength: 64

  15. dateOfBirth:

  16. type: string

  17. format: date

  18. lastTimeOnline:

  19. type: string

  20. format: date-time

  21. #START############################################################################

  22. readOnly: true

  23. # END ############################################################################


第6章 不要让API裸奔

这一章主要介绍API文档中如何描述安全相关的内容。

6.1 定义安全

安全相关内容的定义一般放在API文档根目录下的securityDefinition 中,它包括一组具体的命名安全项,每一个命名安全定义可能包括下面三种安全类型之一:basicapiKey ,oauth2

6.1.1 基础鉴权(Basic Authentication)

要定义一个基础(basic) 鉴权,我们只需要将type设置为 basic 即可:

 
  1. securityDefinitions:

  2. UserSecurity:

  3. type: basic

  4. AdminSecurity:

  5. type: basic

  6. MediaSecurity:

  7. type: basic

这个例子中,我们定义了三种安全说明(UserSecurityAdminSecurity, MediaSecurity),都属于基础鉴权。

6.1.2 API秘钥鉴权(API Key)

要定义一个API秘钥鉴权,我们需要:

  • 设置type为 apiKey
  • 通过关键字in指示api秘钥所在位置。通常api秘钥会放在消息头、请求参数或者消息体中
  • 给安全项命个名字
 
  1. securityDefinitions:

  2. UserSecurity:

  3. type: apiKey

  4. in: header

  5. name: SIMPLE-API-KEY

  6. AdminSecurity:

  7. type: apiKey

  8. in: header

  9. name: ADMIN-API-KEY

  10. MediaSecurity:

  11. type: apiKey

  12. in: query

  13. name: MEDIA-API-KEY

在这个例子中,我们定义了三个apiKey类型的安全项:

  • UserSecurity 定义了一个名为SIMPLE-API-KEY 的参数在消息头(header)
  • AdminSecurity 定义了一个名为 ADMIN-API-KEY 的参数在消息头 (header)
  • MediaSecurity 定义了一个名为MEDIA-API-KEY的参数在请求参数中

6.1.3 Oauth2鉴权

6.1.3.1 流程(Flow)和URL

当我们定义个 Oauth2 类型的安全项上,我们通常会定义Oauth2 的流程(flow)和并根据选定的流程配置相应的鉴权地址(authorizationUrl)和/或令牌地址(tokenUrl)

流程所需要的URL
implicitauthorizationUrl(鉴权地址)
passwordtokenUrl(令牌地址)
applicationtokenUrl
accessCodeauthorizationUrl and tokenUrl

比如:

 
  1. securityDefinitions:

  2. OauthSecurity:

  3. type: oauth2

  4. flow: accessCode

  5. authorizationUrl: 'https://oauth.simple.api/authorization'

  6. tokenUrl: 'https://oauth.simple.api/token'

在这个例子中,我们定义了一个 Oauth2 的安全项,配置的流程是 accessCode 方式,同时配置了鉴权地址和令牌地址。

6.1.3.2 作用范围(scope)

我们借助关键字scopes并通过哈希键值对来还可以配置 Oauth2 安全项的作用范围(scope),键值对的表示作用范围名称;是它的相关描述,比如:

 
  1. securityDefinitions:

  2. OauthSecurity:

  3. type: oauth2

  4. flow: accessCode

  5. authorizationUrl: 'https://oauth.simple.api/authorization'

  6. tokenUrl: 'https://oauth.simple.api/token'

  7. scopes:

  8. admin: Admin scope

  9. user: User scope

  10. media: Media scope

在这个例子中,我们给 OauthSecurity 安全项添加了三个作用范围(admin ,user ,media)。

6.2 使用安全定义

现在我们已经在securityDefinition 中定义好了安全项,现在我们可以将将它们应用到文档中了。使用的时候,我们通过security关键字,把安全项添加进去。

6.2.1 基础鉴权

6.2.1.1 API级别

 
  1. securityDefinitions:

  2. UserSecurity:

  3. type: basic

  4. AdminSecurity:

  5. type: basic

  6. MediaSecurity:

  7. type: basic

  8. #START############################################################################

  9. security:

  10. - UserSecurity: []

  11. # END ############################################################################

  12. paths:

  13. /persons:

在这个例子中,我们在API文档的根路径下直接使用了安全项 UserSecurity,它的作用范围是整个API文档。

6.2.1.2 操作级别

比如我们在添加或者修改用户信息时,需要进行管理员鉴权,可以在 POST /persons 操作中增加安全项:

 
  1. post:

  2. summary: Creates a person

  3. description: Adds a new person to the persons list.

  4. #START############################################################################

  5. security:

  6. - AdminSecurity: []

  7. # END ############################################################################

而在上传图片时,需要进行媒体操作鉴权,可以在 POST /images 操作中增加安全项:

 
  1. /images:

  2. parameters:

  3. - $ref: '#/parameters/userAgent'

  4. post:

  5. summary: Uploads an image

  6. #START############################################################################

  7. security:

  8. - MediaSecurity: []

  9. # END ############################################################################

6.2.2 API秘钥鉴权

使用方式和基础鉴权一样,可以在API级别和操作级别使用:

 
  1. securityDefinitions:

  2. UserSecurity:

  3. type: apiKey

  4. in: header

  5. name: SIMPLE-API-KEY

  6. AdminSecurity:

  7. type: apiKey

  8. in: header

  9. name: ADMIN-API-KEY

  10. MediaSecurity:

  11. type: apiKey

  12. in: query

  13. name: media-api-key

  14. #START############################################################################

  15. security:

  16. - UserSecurity: []

  17. # END ############################################################################

  18. paths:

  19. /persons:

 
  1. post:

  2. summary: Creates a person

  3. description: Adds a new person to the persons list.

  4. security:

  5. - AdminSecurity: []

6.2.3 Oauth2鉴权

Oauth2 鉴权的使用和上面的两种鉴权方式基本相同,不同之处在于我们可以指定它的哪一个作用范围(scope)。

比如API级别的鉴权:

 
  1. securityDefinitions:

  2. OauthSecurity:

  3. type: oauth2

  4. flow: accessCode

  5. authorizationUrl: 'https://oauth.simple.api/authorization'

  6. tokenUrl: 'https://oauth.simple.api/token'

  7. scopes:

  8. admin: Admin scope

  9. user: User scope

  10. media: Media scope

  11. #START############################################################################

  12. security:

  13. - OauthSecurity:

  14. - user

  15. # END ############################################################################

  16. paths:

  17. /persons:

操作级别的鉴权:

 
  1. post:

  2. summary: Creates a person

  3. description: Adds a new person to the persons list.

  4. security:

  5. - OauthSecurity:

  6. - admin

在这个例子中,作用范围 admin 将覆盖全局配置的作用范围 user 。

6.3 使用多种安全配置

OpenAPI规范并没有限定我们只能使用一种安全项。下面的例子将展示如何使用多种安全配置。

6.3.1 安全定义

 
  1. securityDefinitions:

  2. OauthSecurity:

  3. type: oauth2

  4. flow: accessCode

  5. authorizationUrl: 'https://oauth.simple.api/authorization'

  6. tokenUrl: 'https://oauth.simple.api/token'

  7. scopes:

  8. admin: Admin scope

  9. user: User scope

  10. MediaSecurity:

  11. type: apiKey

  12. in: query

  13. name: media-api-key

  14. LegacySecurity:

  15. type: basic

这个例子中,我们定义了三种鉴权方式。

6.3.2 全局安全配置

 
  1. security:

  2. - OauthSecurity:

  3. - user

  4. - LegacySecurity: []

这个配置的意思是用户可以通过两种方式中的任意一种来访问我们提供的API接口。

6.3.3 覆盖全局配置

 
  1. post:

  2. summary: Creates a person

  3. description: Adds a new person to the persons list.

  4. security:

  5. - OauthSecurity:

  6. - admin

  7. - LegacySecurity: []

在 POST /persons 操作中,OauthSecurity 的作用范围被覆写为admin。此时用户可以通过admin 的Oauth2 或者legacySecurity 来鉴权使用这个操作。

 
  1. /images:

  2. parameters:

  3. - $ref: '#/parameters/userAgent'

  4. post:

  5. summary: Uploads an image

  6. security:

  7. - MediaSecurity: []

在 POST/images 操作中,用MediaSecurity 整体覆写了全局安全项,用户只能通过 MediaSecurity 鉴权使用这个操作。


第7章 让文档的可读性更好

7.1 分类标签(Tags)

通过关键字tags我们可以对文档中接口进行归类,tags的本质是一个字符串列表。tags定义在文档的根路径下。

7.1.1 单标签

比如说 GET /persons 属于用户(Person) 这个分类的,那么我们可以给它贴个标签:

 
  1. paths:

  2. /persons:

  3. parameters:

  4. - $ref: '#/parameters/userAgent'

  5. get:

  6. summary: Gets some persons

  7. description: Returns a list containing all persons. The list supports paging.

  8. operationId: searchUsers

  9. #START############################################################################

  10. tags:

  11. - Persons

  12. # END ############################################################################

7.1.2 多标签

一个操作也可以同时贴几个标签,比如:

 
  1. /js-less-consumer-persons:

  2. parameters:

  3. - $ref: '#/parameters/userAgent'

  4. post:

  5. summary: Creates a person

  6. description: For JS-less partners

  7. operationId: createUserJS

  8. deprecated: true

  9. tags:

  10. - JSLess

  11. - Persons

贴上标签后,在Swagger Editor和Swagger UI中能够自动归类,我们可以按照标签来筛选接口,试试吧?

7.2 无处不在的描述文字(Descriptions)

description这个属性几乎是无处不在,为了提高文档的可读性,我们应该在必要的地方都加上描述文字。

7.2.1 安全项的描述

 
  1. securityDefinitions:

  2. OauthSecurity:

  3. description: New Oauth security system. Do not use MediaSecurity or LegacySecurity.

  4. type: oauth2

  5. flow: accessCode

  6. authorizationUrl: 'https://oauth.simple.api/authorization'

  7. tokenUrl: 'https://oauth.simple.api/token'

  8. scopes:

  9. admin: Admin scope

  10. user: User scope

  11. MediaSecurity:

  12. description: Specific media security for backward compatibility. Use OauthSecurity instead.

  13. type: apiKey

  14. in: query

  15. name: media-api-key

  16. LegacySecurity:

  17. description: Legacy security system for backward compatibility. Use OauthSecurity instead.

  18. type: basic

7.2.2 模式(Schema)的描述

每一种模式(Schema),都会有一个标题(title)和一段描述,比如:

 
  1. definitions:

  2. Person:

  3. title: Human

  4. description: A person which can be the user itself or one of his friend

7.2.3 属性的描述

比如:

 
  1. properties:

  2. firstName:

  3. description: first name

  4. type: string

7.2.4 参数的描述

 
  1. paths:

  2. /persons:

  3. post:

  4. parameters:

  5. - name: person

  6. in: body

  7. required: true

  8. description: The person to create.

  9. schema:

  10. $ref: '#/definitions/Person'

 
  1. parameters:

  2. username:

  3. name: username

  4. in: path

  5. required: true

  6. description: The person's username

  7. type: string

  8. pageSize:

  9. name: pageSize

  10. in: query

  11. description: Number of persons returned

  12.  

7.2.5 操作的概述(summary)、描述和操作ID(operationId)

一个操作(Operation)通常都会包含概述和描述信息。而且我们还可以添加一个关键字operationId,这个关键字通常用来指示服务提供者对这个操作的处理函数的函数名。比如:

 
  1. paths:

  2. /persons:

  3. parameters:

  4. - $ref: '#/parameters/userAgent'

  5. get:

  6. summary: Gets some persons

  7. description: Returns a list containing all persons. The list supports paging.

  8. operationId: searchUsers

7.2.6 响应消息的描述

 
  1. paths:

  2. /persons:

  3. parameters:

  4. - $ref: '#/parameters/userAgent'

  5. get:

  6. ……

  7. ……

  8. responses:

  9. '200':

  10. description: A list of Person

 
  1. responses:

  2. Standard500ErrorResponse:

  3. description: An unexpected error occured.

7.2.7 响应消息头的描述

 
  1. headers:

  2. X-Rate-Limit-Remaining:

  3. description: How many calls consumer can do

  4. type: integer

  5. X-Rate-Limit-Reset:

  6. description: When rate limit will be reset

  7. type: string

  8. format: date-time

7.2.8 标签的描述

我们在API文档的根路径下添加了tags的定义,对于其中的每一个标签,我们都可以添加描述信息:

 
  1. tags:

  2. - name: Persons

  3. description: Everything you need to handle users and friends

7.3 在描述中使用Markdown语法

在绝大部分的description中,我们可以使用GFM(Github Flavored Markdown)语法。

7.3.1 多行描述

使用符号 | 然后在新行中打一个tab(注意:YAML的tab是两个空格 ),就可以编辑多行描述,比如:

 
  1. '/persons/{username}/collecting-items':

  2. parameters:

  3. - $ref: '#/parameters/username'

  4. - $ref: '#/parameters/userAgent'

  5. get:

  6. summary: Gets a person's collecting items list

  7. description: |

  8. Returns a list containing all items this person is looking for.

  9. The list supports paging.

  10.  

7.3.2 简单使用GFM

比如我们要强调,可以这样写:

 
  1. externalDocs:

  2. description: |

  3. **Complete** documentation describing how to use this API

  4. url: http://doc.simple.api/

7.3.3 带信息组的描述

 
  1. CollectingItem:

  2. discriminator: itemType

  3. required:

  4. - itemType

  5. properties:

  6. itemType:

  7. description: |

  8. An item can be of different type:

  9.  
  10. type | definition

  11. -----|-----------

  12. Vinyl| #/definitions/Vinyl

  13. VHS | #/definitions/VHS

  14. AudioCassette | #/definitions/AudioCassette

  15. type: string

  16. enum:

  17. - AudioCassette

  18. - Vinyl

  19. - VHS

7.3.4 带代码的描述

 
  1. swagger: '2.0'

  2.  
  3. info:

  4. version: 1.1.0

  5. title: Simple API

  6. description: |

  7. A simple API to learn how to write OpenAPI Specification.

  8. This file uses almost every single aspect of the [Open API Specification](https://openapis.org/).

  9. This API will use JSON.

  10. JSON looks like this:

  11.  
  12. ```JSON

  13. {

  14. "key": "value",

  15. "anotherKey": "anotherValue"

  16. }

  17.  
 
  1.  
  2. ### 7.4 示例数据(Examples)

  3.  
  4. 我们已经知道了用Schema来描述参数和属性,有的时候,用示例数据更有表现了。我们可以使用关键字`example`来给原子属性或者对象添加示例数据。

  5.  
  6. #### 7.4.1 原子属性的示例数据

  7.  
  8. ​```YAML

  9. properties:

  10. firstName:

  11. description: first name

  12. type: string

  13. #START############################################################################

  14. example: John

  15. # END ############################################################################

  16. lastTimeOnline:

  17. description: The last time this person was connected to the service as a

  18. type: string

  19. format: date-time

  20. readOnly: true

  21. #START############################################################################

  22. example: 2016-06-10T12:36:58.014Z

  23. # END ############################################################################

7.4.2 对象属性的示例数据

 
  1. Persons:

  2. title: Humans

  3. description: A list of users or friends

  4. required:

  5. - items

  6. properties:

  7. items:

  8. description: Array containg the list

  9. type: array

  10. minItems: 10

  11. maxItems: 100

  12. uniqueItems: true

  13. items:

  14. $ref: '#/definitions/Person'

  15. #START############################################################################

  16. example:

  17. - firstname: Robert

  18. lastname": Doe

  19. username": robdo

  20. dateOfBirth: 1970-01-28

  21. lastTimeOnline: 2016-04-10T14:36:58.014Z

  22. - firstname: Jane

  23. lastname: Doe

  24. username: jdoe123

  25. dateOfBirth: 1980-05-12

  26. lastTimeOnline: 2016-05-12T19:23:59.014Z

  27. # END ############################################################################

7.4.3 定义的示例数据

跟普通属性一样,定义的对象也能添加示例数据:

 
  1. MultilingualErrorMessage:

  2. title: MultiLingualMultiDeviceErrorMessage

  3. description: An multilingual error message (hashmap) with a long and a short description

  4. additionalProperties:

  5. $ref: '#/definitions/ErrorMessage'

  6. properties:

  7. defaultLanguage:

  8. $ref: '#/definitions/ErrorMessage'

  9. #START############################################################################

  10. example:

  11. defaultLanguage:

  12. longMessage: We're deeply sorry but an error occured

  13. shortMessage: Error

  14. fr:

  15. longMessage: Nous sommes désolé mais une erreur est survenu

  16. shortMessage: Erreur

  17. # END ############################################################################

  18.  

7.4.4 响应消息的示例数据

我们甚至可以添加响应消息级别的示例数据:

 
  1. '/persons/{username}/collecting-items':

  2. ……

  3. get:

  4. ……

  5. responses:

  6. '200':

  7. #START############################################################################

  8. examples:

  9. application/json:

  10. {

  11. "totalItems": 10,

  12. "totalPage": 4,

  13. "pageSize": 3,

  14. "currentPage": 2,

  15. "items":

  16. [

  17. {

  18. "itemType": "Vinyl",

  19. "maxPrice": 20,

  20. "imageId": "98096838-04eb-4bac-b32e-cd5b7196de71",

  21. "albumName": "Captain Future Original Soundtrack",

  22. "artist": "Yuji Ohno"

  23. },

  24. {

  25. "itemType": "VHS",

  26. "maxPrice": 10,

  27. "imageId": "b74469bc-e6a1-4a90-858a-88ef94079356",

  28. "movieTitle": "Star Crash",

  29. "director": "Luigi Cozzi"

  30. },

  31. {

  32. "itemType": "AudioCassette",

  33. "maxPrice": 10,

  34. "imageId": "b74469bc-e6a1-4a90-858a-88ef94079356",

  35. "albumName": "Star Wars",

  36. "artist": "John Williams"

  37. }

  38. ]

  39. }

  40. # END ############################################################################

  41. '404':

  42. $ref: '#/responses/PersonDoesNotExistResponse'

  43. '500':

  44. $ref: '#/responses/Standard500ErrorResponse'

  45. default:

  46. $ref: '#/responses/TotallyUnexpectedResponse'

7.4.5 示例数据的优先级

如果我们在各个级别(比如参数、对象、定义、响应消息)都添加了示例数据。支持OpenAPI规范的各解析工具都是以 最高级别 的定义为准。

7.5 标记为弃用

我们可以通过关键字deprecated置为 true 来标记接口的弃用状态,比如:

 
  1. /js-less-consumer-persons:

  2. parameters:

  3. - $ref: '#/parameters/userAgent'

  4. post:

  5. summary: Creates a person

  6. description: For JS-less partners

  7. operationId: createUserJS

  8. deprecated: true

7.6 链接到外部文档

一般来说,项目中不光只有一篇API文档,还应该有些描述application key,测试用例,操作链以及其他内容的文档,这些文档一般是单独成篇的。如果在描述某个接口时,我们想链接这些文档,可以通过关键字externalDoc来添加,例如:

 
  1. externalDocs:

  2. description: Complete documentation describing how to use this API

  3. url: http://doc.simple.api/

 
  1. /images:

  2. parameters:

  3. - $ref: '#/parameters/userAgent'

  4. post:

  5. summary: Uploads an image

  6. description: Upload an image, will return an image id.

  7. operationId: storeImage

  8. externalDocs:

  9. description: How to upload media

  10. url: http://doc.simple.api/media/upload


第8章 分而治之

根据前面几张的知识,我们已经可以轻松的构建一个复杂的API文档了。可是作为一个学过 Clean Code 的程序员,我们并不希望所有的接口、定义都在一个大而全的上帝文件里。这一章我们一起来学习拆分文件。

8.1 JSON指针

我们在第2章已经知道了怎么定义一个可重用的对象,已经如何使用定义,重温一下:

 
  1. /persons/{username}:

  2. get:

  3. summary: Gets a person

  4. description: Returns a single person for its username.

  5. parameters:

  6. - name: username

  7. in: path

  8. required: true

  9. description: The person's username

  10. type: string

  11. responses:

  12. 200:

  13. description: A Person

  14. schema:

  15. $ref: "#/definitions/Person"

  16. 404:

  17. description: The Person does not exists.

  18.  
  19. definitions:

  20. Person:

  21. required:

  22. - username

  23. properties:

  24. firstName:

  25. type: string

  26. lastName:

  27. type: string

  28. username:

  29. type: string

  30. Persons:

  31. type: array

  32. items:

  33. $ref: "#/definitions/Person"

  34.  

在使用的时候我们这样写:$ref: "#/definitions/Person" ,$ref就是JSON指针(参见定义RFC6901)。例子中的这个指针指向当前文档根路径(#下的definitions 下的 Person

JSON指针不仅可以指向当前文件,还可以指向其他文件。

8.2 基础拆分

8.2.1 引用本地文件

还是上面这个例子,我们在当前文档的同一目录下,新建的一个文件叫 person.yaml,然后把Person 的定义拆出来,放在其中。

 
  1. Person:

  2. required:

  3. - username

  4. properties:

  5. firstName:

  6. type: string

  7. lastName:

  8. type: string

  9. username:

  10. type: string

在当前文档中把引用 Person 定义的地方修改为:

$ref: "person.yaml#/Person"

8.2.2 编辑器报错

把上面的代码放到Swagger Editor中编辑,编辑器预览中可能会报错:

提示找不到person.yaml文件。这是因为我们没有指定正确的编辑器的指针解析基础路径(Pointer Resolution Base Path 。

解决的办法是:进入编辑器的 Preferences -> Preferences 菜单,修改Pointer Resolution Base Path为:

考虑到缓存的原因,如果错误依然存在,请刷新浏览器。

8.2.3 文件夹

如果引用子文件夹下的文件,我们可以这样写:

            $ref: "folder/person.yaml#/Person"

如果引用上级文件夹下的文件,我们可以这样写:

 
  1. Persons:

  2. type: array

  3. items:

  4. $ref: "../folder/person.yaml#/Person"

8.2.4 引用远程文件

如果我们想引用一个远程文件,应该怎么做呢?可以这样写:

  $ref: https://myserver.com/mypath/myfile.yaml#/example

但是需要注意的是:服务器必须提供跨域(CORS)访问服务

如果要通过本地服务器上的8080端口引用文件,我们可以这样写:

  $ref: "http://localhost:8080/folder/person.yaml#/Person"

考虑两种比较特别的情况:

8.2.4.1 远端文件引用"本地文件"

比如说,我们引用了:

  $ref: "http://localhost:8080/another-folder/persons.yaml#/Persons"

这个远端文件。但是persons.yaml又引用了与它在同一目录下的person.yaml文件,这个时候语法分析器会在localhost:8080上面找person.yaml文件,而不会查找我们本地的person.yaml

8.2.4.2 远端文件引用 ”更“远端文件

如果理解了8.2.4.1这个例子,我们就知道不管怎么引用,都是在相对于被引用的文件下来进行查找的。

8.2.5 整理一个文件中的多个定义

比如我们在一个文件中,需要把定义分成几类,可以这样做:

 
  1. SomeDefinitions:

  2. Person:

  3. required:

  4. - username

  5. properties:

  6. firstName:

  7. type: string

  8. lastName:

  9. type: string

  10. username:

  11. type: string

  12.  
  13. OtherDefinitions:

  14. Persons:

  15. type: array

  16. items:

  17. $ref: "#/SomeDefinitions/Person"

Person归属于SomeDefinitions这一类,Persons归属于OtherDefinitions这一类。那么我们在引用这两个定义时,分别应该这样书写:

            $ref: "definitions.yaml#/OtherDefinitions/Persons"
            $ref: "definitions.yaml#/SomeDefinitions/Person"

有点像命名空间的概念。

8.3 实战切分的思路

  • 结构化切分思路: 先把API文档的头部info切下来,放在info.yaml,然后分别切分 paths.yamldefinitions.yamlresponses.yamlparameters.yaml等文件,最后合并到main.yaml
  • 分层切分思路:分别按照功能层次,将文件切分为base.yamlcommon.yaml,然后是各个模块的xxx-paths.yaml,然后是各个定义的xxx-definitions.yaml
  • 其他思路……

需要注意的是,不管怎么切分,有一个原则必须谨记:

切分出来的子文档,必须遵循OpenAPI规范,能够通过编辑器的校验,不然切分得再漂亮也是徒劳。


The END???

NO! 接下来,就看你的了!

附录


Arnaud Lauret大神为了简化OpenAPI规范的阅读,维护了一个openapi-specification-visual-documentation的项目,通过动态导图的方式把规范完整呈现,懒人必备。点击此处查看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值