OpenAPI Specification 系列笔记 I
上个月入了一个新的项目,所以一些学习的进度之类的都被打乱了,可能最近这几个月都会以熟悉新工具作为主要的学习目标,眼下过一遍 swagger/OpenAPI 的一些内容
之前的话其实或多或少的也会接触一些 swagger 的部分,不过主要都是通过 UI 了解一下已经实现的 endpoint——其实就是基于 code first,然后扫描 endpoint 生成对应 UI 的实现。所以我其实没有手写过对应的 specs,只是大概对 specs 有一定的了解
关于具体的 spec,有一个 **OpenAPI Map,**这个网站是一个比较好用的可视化工具,可以具体看到每个 section 下面包含合法的属性
如果想看参考的话,GH 的链接如下:
https://github.com/GoldenaArcher/open-api-3.x-study
Specs
完整的 **OpenAPI Specification,**即官网地址在这: OpenAPI Specification,学习档案参考的主要是 3.0 的内容,目前我看到的大部分工具,更新比较快的对 3.1 的支持是 beta,更新比较慢的却是不支持的。我个人建议先看一下自己要用的工具,如果能用更新比较快/主流的工具,用 3.1 是未来的趋向
用 OpenAPI Spec 最大的原因就在于,可以比较直观地观测到 API 所有信息,下面两个截图展示了 endpoint、params 和 body:
API Specs 的优点
- 容易理解
- 可以生成 server/client 代码,同时进行数据校验
- mock 服务端去返回案例 responses
- 是一个广泛接受的标准
swagger & openapi
Swagger 起初是一个简单的开源规范(open source specification),包含 Swagger UI、Swagger Editor 和 Swagger Codegen。不过在 2015 年,Swagger 被 SmartBear 收购,并将规范更名为 OpenAPI Specification,但仍保留了 Swagger 这个商标/名称
目前,Swagger 指的是一系列工具,而 OpenAPI 则专指这一套规范
swagger 工具
下面两个截图都是在 cloud 截图的:
可以看到,主流的开源工具还是只有 Swagger UI、Swagger Editor 和 Swagger Codegen。Hub 是一个付费的支持,我记得的功能是集成了所有的免费功能,然后再提供了一些文档的生成管理共享等
use open api
code first approach
Swagger Hub 可以通过这个链接玩一下:https://swagger.io/tools/swaggerhub-explore/,这种方法大体就是ping一下对应的endpoint,然后swagger hub 可以自动生成对应的 request 和 response,如下面几个案例,通过 ping https://swapi.dev/,https://reqres.in/,https://dummyjson.com/ 等其他几个链接生成的文档:
如果没办法查看 owner,登陆 https://app.swaggerhub.com/welcome 去验证一下是不是登陆了,成功登陆的话是可以查看文档的:
使用 swagger hub 去生成对应的文档会有几个问题:
- id 是 hard coded
- 可能会缺乏对应的 fields/parameters
比如说 post 请求中的 input,一些 query parameters 等,毕竟 swagger hub 不可能穷举所有有可能的 path 去测试,这种情况下使用的 URL 中没有对应的 parameters,swagger hub 就不会生成对应的参数 - 如果 API 不是公网可访问,那么 swagger hub 就无法 ping 到
- 需要验证的时候,会需要手动配置对应的登陆参数
code first 这种比较适合对代码没有直接访问权限,但是需要生成对应文档的团队——有可能对接的 vendor 没有文档,所以需要生成文档供自己团队使用;或者是老项目没有文档,但是使用的框架也不支持用其他的插件生成对应的 specs
design first approach
如果手上没有现成的代码,需要生成对应的 specs 让不同的团队 review,那么 design first 是一个很好的方法。design first 可以生成不同语言的模板代码,缺点就在于需要手动写所有的定义
swagger 本身是支持 yaml 和 json 两种格式的,不过我在后面的文档里会用 yaml——一方面教程用的是 yaml,另外一方面我发现使用 yaml 却是比 json 方便一些,也短一些
如果想把 json 转 yaml,或者 yaml 转 json,可以找一些网站实现对应的功能,如:
最简 OpenAI specification
这三个是生成文档所需的最少 fields:
# the version of the OAS of this documentation
openapi: 3.1.0
# provide a general information about the API, only 2 mandatory fields are included
info:
# human readable description of the project
title: A minimal OpenAPI document
# version of the project
version: 0.0.1
paths: {} # no endpoints defined, empty array
效果如下:
⚠️:刚开始的时候我是在网页里面实现的,后来发现 vscode 里的 swagger 支持也蛮好的,所以后期会换到 vscode
info
info
如其名,用来描述整个 API 的 metadata,其中 title
和 version
是必选项:
info:
title: EazyShop Products APIs Definition
version: 0.0.1
# below are optional fields
# summary is only supported after version 3.1.0, which is supported with latest swagger editor
summary: a summary
description: a detailed description of the API
# a link point to the terms of serfice for the API, must be the format of a URL
termsOfService: "http://example.com/terms/"
# contact information for the exposed API
contact:
name: "API Support"
url: "http://www.example.com/support"
email: "support@example.com"
# licence information for the exposed API
license:
name: "Apache 2.0"
url: "https://www.apache.org/licenses/LICENSE-2.0.html"
servers
这是一个可选项,用来描述当前服务器。一般来说,如果想直接用 swagger 来调用 crul 指令,可以将对应 server 信息放上去,然后切换环境调用
具体的调用方式有两种:
简单版
servers: - url: "https://api.example.com/v1" description: "Production server" - url: "https://staging-api.example.com/v1" description: "Staging server" - url: "http://localhost:8080/v1" description: "Development server"
复杂版
servers: - url: "https://{environment}.example.com/{version}:{port}" description: "Variable environment server" variables: environment: default: "api" # optional fields, enum wil restrict the selection # without enum, it'll be a input enum: - "api" - "staging-api" - "dev-api" # optional field # descriptions: a string value associated to the description port: enum: - "8443" - "443" # defaulte value is required default: "8443" version: default: "v1"
效果如下:
paths
这个是 API endpoint 在的地方了,下面这个截图是官方文档上的,现实 paths 下面具体有什么属性:
path object - GET
没啥好说的,直接丢实现和截图了:
paths:
/categories:
get:
summary: Get all categories
description: Returns a list of all categories
responses:
"200":
description: A list of categories
content:
application/json:
schema:
type: array
items:
type: object
properties:
categoryId:
type: integer
name:
type: string
parameters
parameters
是与 summary
, response
同层级的属性, query parameter
主要是通过 in: query
确定。换句话说, path parameter
就是通过 in: path
指定
- query parameter
parameters: - name: categoryId in: query schema: type: integer minimum: 1 maximum: 2147483647 # maximum value for a 32-bit signed integer description: The ID of the category to filter by required: false example: 101
- path parameter
/categories/{categoryId}: get: summary: Get a category by ID description: Returns a single category by its ID parameters: - name: categoryId in: path required: true schema: type: integer minimum: 1 maximum: 2147483647 # maximum value for a 32-bit signed integer description: The ID of the category to retrieve responses: "200": description: A single category object content: application/json: schema: type: object properties: categoryId: type: integer name: type: string
path object - POST
这个结构和 GET
差的不是很多,但是 GET
没有 requestBody
从这个环节其实可以看出来,openAPI specs 会慢慢开始出现一些重复实现的对象,这点在后面会讲到需要怎么解决——主要是重复声明的 object
问题
/orders:
post:
summary: Create a new order
description: Creates a new order with the provided details
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
userId:
type: integer
description: The ID of the user placing the order
productIds:
type: array
items:
type: object
properties:
productId:
type: integer
quantity:
type: integer
price:
type: number
format: float
description: List of product IDs in the order
totalAmount:
type: number
format: float
description: Total amount for the order
responses:
"201":
description: Order created successfully
content:
application/json:
schema:
type: object
properties:
orderId:
type: integer
path object - PUT
与 POST
很相似的实现方法:
put:
summary: Update an existing order
description: Updates an existing order with the provided details
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
orderId:
type: integer
description: The ID of the order to update
products:
type: array
items:
type: object
properties:
productId:
type: integer
quantity:
type: integer
price:
type: number
format: float
address:
type: object
properties:
street:
type: string
city:
type: string
state:
type: string
zipcode:
type: string
totalAmount:
type: number
format: float
responses:
"204":
description: Order updated successfully
path object - delete
delete:
summary: Delete an order
description: Deletes an existing order by its ID
parameters:
- name: orderId
in: query
required: true
schema:
type: integer
minimum: 1
maximum: 2147483647 # maximum value for a 32-bit signed integer
description: The ID of the order to delete
example: 303
responses:
"204":
description: Order deleted successfully
descriptions
这个部分总体来说还是比较简单的,这里提一下主要是因为可以使用 markdown 格式去写,实现如下:
description: |
# About Us
***EazyShop*** is a leading e-commerce platform that offers a wide range of products at competitive prices. Our _API_ allows developers to integrate EazyShop's product catalog into their applications seamlessly. You will get an `Affiliate commission` for selling our products.
# Categories supported
- Electronics
- Mobile Phones
- Laptops
- Tablets
- Fashion
- Home & Garden
- Sports & Outdoors
- Toys & Hobbies
example
这个属性在参数和返回对象中用的比较多,主要提供 example 案例让使用者可以更加直观的看到返回对象
parameters:
- name: categoryId
in: query
schema:
type: integer
minimum: 1
maximum: 2147483647 # maximum value for a 32-bit signed integer
examples:
mobiles:
value: 101
laptops:
value: 102
tablets:
value: 103
description: The ID of the category to filter by
required: true
# example: 101
examples
and example
cannot be used at the same time
tags
tags
提供了一个比价好的归类方法,现在所有的 endpoints 都是在 default
的分类下,看起来不是很直观:
添加了 tags
后可以更好的实现分类:
需要注意的有两点:
根目录下必须要有对应的
tags
比如案例中,根目录里没有出现
users
,那么下面的 endpoints 就无法添加users
这个分类一个 endpoint 可以属于多个
tags