OpenAPI(原名Swagger)是目前比较流行的定义HTTP API的协议。但是OpenAPI的定义文件是方便机器处理的格式,不易编写和阅读。这里介绍一种使用go-swagger,根据Go代码生成OpenAPI定义文件的方法。该方法只使用Go代码来定义API,不强求Server或者Client也使用Go。
目前go-swagger
只能生成OpenAPI 2.0格式的定义。这个也是现在广泛使用的格式。go-swagger
未来会支持OpenAPI 3.0。
本文假设已经熟悉Go语法,只对go-swagger
的扩展部分进行详细解释。
假设要为一个宠物商店创建一套API。这套API支持定义宠物(Pet)。本文展示如何使用Go来定义这个API。
虽然go-swagger
不强求使用Go工程,不过为了便于利用补全和代码高亮,这里还是推荐为API单独创建一个Go工程。
首先是定义API的基础信息,比如这套API的名字,协议,支持的序列化格式,基础路径,以及一些安全信息。这些信息定义在petstore.go
文件里。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // Package petstore The API of the pet store.
//
// The pet store is a demo service to show how to use go-swagger generate
// OpenAPI (Swagger) Spec.
//
// Version: 1.0.0
// BasePath: /v1/petstore
// Schemes: https
// Consumes:
// - application/json
// Produces:
// - application/json
//
// swagger:meta
package petstore
//go:generate go run github.com/go-swagger/go-swagger/cmd/swagger@latest generate spec -o openapi.yaml
//go:generate go run github.com/go-swagger/go-swagger/cmd/swagger@latest validate openapi.yaml
|
根据Go的规范,包的说明,要以Package petstore
开头。该行的剩余部分,也就是The API of the pet store.
会作为OpenAPI的title
部分,其余非go-swagger
定义的注释,会作为OpenAPI的description
。
注意:如果包说明里不包含description
的部分,go-swagger
会把第一行原本作为title
的部分作为description
,并把title
留空。这样定义的OpenAPI文件不符合规范。所以请把两段描述写完整。
从Version:
行开始,是对go-swagger
的定义。这部分定义了生成的OpenAPI文件其他基础内容。具体可用的选项可以参照swagger:meta。最后一行的swagger:meta
表示这段注释用于go-swagger
生成基础信息。
之后的两行go:generate
用来生成OpenAPI文件。为了保证执行时不会缺少可执行文件,这里使用go run github.com/go-swagger/go-swagger/cmd/swagger@latest
来调用go-swagger
。如果可以保证执行环境已经安装了go-swagger
,这两行go run ...
可以直接简化为swagger
。简化后会提高生成时的执行速度。
安装go-swagger
可以参照官网。如果有Go环境,可以直接通过go install github.com/go-swagger/go-swagger/cmd/swagger@latest
安装。注意需要把$GOPATH/bin
加入到$PATH
里,方便执行。
第一行generate spec -o openapi.yaml
会根据当前目录的内容,生成OpenAPI文件。第二行validate openapi.yaml
会验证这个OpenAPI文件是否合规。
有了这个文件,就可以通过执行go generate .
来生成OpenAPI文件。执行后会生成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| $ cat openapi.yaml
basePath: /v1/petstore
consumes:
- application/json
info:
description: |-
The pet store is a demo service to show how to use go-swagger generate
OpenAPI (Swagger) Spec.
title: The API of the pet store.
version: 1.0.0
paths: {}
produces:
- application/json
schemes:
- https
swagger: "2.0"
|
我们还没有定义任何API,所以paths
字段为空。后面会逐步补充这个字段的内容。
之后在pet.go
文件里定义与Pet实例相关的API。
首先定义Pet
结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| // NewPet provides data to create a pet.
//
// swagger:model
type NewPet struct {
// The name of the pet.
//
// min length: 1
// max length: 64
// example: Yuki
Name string `json:"name"`
// The contact email for the pet.
//
// min length: 1
// max length: 64
// example: person@domain.com
// swagger:strfmt email
Contact string `json:"contact"`
}
// Pet provides data of a pet.
//
// swagger:model
type Pet struct {
// The pet id.
//
// example: 1
ID uint64 `json:"id"`
// The name of the pet.
//
// example: Yuki
Name string `json:"name"`
// The contact email for the pet.
//
// example: person@domain.com
// swagger: strfmt email
Contact string `json:"contact"`
}
|
其中NewPet
用于创建和修改Pet数据,Pet
用于展示Pet数据。这两个结构有一些细微差别,比如NewPet
不会具有ID
信息,另外Pet
不需要定义输入限制。在简单场景下,可以合并为一个结构,或者将NewPet
匿名嵌入Pet
,简化结构。
swagger:model
会被go-swagger
处理为预定义好的结构,并将定义放入definitions
里。这个结构会在多个API操作方法间复用。
go-swagger
会根据Go结构的类型,生成对应的类型。每一个字段的注释,可以指定更详细的值约束,比如例子中的min length: 1
和max length: 64
,就约定了对应字段字符串的长度应该在[1, 64]
之间。还可以使用swagger:strfmt
对字符串值做进一步的语义约束。
为了在处理出错可以返回另外的结构,还需要定义错误结构:
1
2
3
4
| type Error struct {
ID string `json:"error_id"`
Detail string `json:"error_detail"`
}
|
简单的结构可以不用写明所有的信息,甚至不用标记swagger:model
。go-swagger
会自动根据之后方法的定义,来查找对应的结构定义。
处理的结构定义好后,就可以定义具体的操作方法。为了覆盖最广泛的情况,这里以更新Pet
的方法作为讲解例子。
1
2
3
4
5
6
7
| // swagger:route POST /pets/{pet_id} pet UpdatePet
//
// Update data of a pet.
//
// responses:
// 200: PetReply
// default: ErrorReply
|
swagger:route
用来定义一个具体的API方法,格式是swagger:route <method> <url> <tag> ... <operation id>
。其中tag
可以有0个或者任意个,给这个方法加上若干个标签。不管有多少tag
,最后一个参数operation id
是这个方法的名字。在一个API定义文件里,这个方法名字需要具有唯一性。
具体到例子里,swagger:router POST /pets/{pet_id} pet CreatePet
的含义如下:
- 遵循常用的API原则,将更新方法定义在
POST /pets/{pet_id}
端点。 - 给这个方法加入
pet
标签。 - 方法名字为
CreatePet
。
之后是方法的说明信息。
responses
是定义go-swagger
的返回信息,可以根据不同的HTTP Code定义不同的结构。例子里在操作正常也就是Code 200
时返回PetReply
结构,其他情况下返回ErrorReply
结构。
先放下输出结构,看一下如何定义输入结构。
1
2
3
4
5
6
7
8
9
10
11
| // swagger:parameters UpdatePet RetrievePet DeletePet
type PetIDArg struct {
// in: path
ID uint64 `json:"pet_id"`
}
// swagger:parameters CreatePet UpdatePet
type NewPetArg struct {
// in: body
NewPet *NewPet
}
|
swagger:parameters
用来定义个输入结构,格式是swagger:parameters <operation id> <operation id> ...
。其中operation id
是用到这个输入结构的方法名字。需要注意的是,可以有多个输入结构用在一个方法上,比如例子里的两个结构都引用了UpdatePet
。因为一个方法的输入,不仅有HTTP Post报文,还有URL里面的信息,甚至可能会从Header里拿信息。为了能复用这些结构,go-swagger
可以将不同部分的输入,定义到不同的结构里,并引用到同一个方法上。
例子里,PetIDArg
通过in: path
定义了路径参数pet_id
,其中pet_id
是用过json:"pet_id"
定义的。另一个结构NewPetArg
通过in: body
定义了Post报文结构NewPet
,其中NewPet
是上文描述过的一个结构。
之后是输出结构。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // PetReply returns a pet.
//
// swagger:response
type PetReply struct {
// in: body
Pet *Pet
}
// ErrorReply replys an error of API calling.
//
// swagger:response
type ErrorReply struct {
// in: body
Error Error
}
|
swagger:response
用来定义一个输出结构。其名字与swagger:route
定义的responses
相同。in: body
表示对应的字段用于返回的HTTP报文。
至此,Pet的更新方法定义完成。也解释了go-swagger
绝大部分内容。可以直接执行go generate .
或者swagger generate spec
查看输出。关于使用go-swagger
生成规范文件的更多细节,可以查阅官网。
作为例子,下面的pet.go
文件给出了Pet操作的所有定义。配合之前提到的petstore.go
文件,可以直接用来生成petstore
的OpenAPI定义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
| package petstore
// NewPet provides data to create a pet.
//
// swagger:model
type NewPet struct {
// The name of the pet.
//
// min length: 1
// max length: 64
// example: Yuki
Name string `json:"name"`
// The contact email for the pet.
//
// min length: 1
// max length: 64
// example: person@domain.com
// swagger:strfmt email
Contact string `json:"contact"`
}
// Pet provides data of a pet.
//
// swagger:model
type Pet struct {
// The pet id.
//
// example: 1
ID uint64 `json:"id"`
// The name of the pet.
//
// example: Yuki
Name string `json:"name"`
// The contact email for the pet.
//
// example: person@domain.com
// swagger: strfmt email
Contact string `json:"contact"`
}
// swagger:parameters UpdatePet RetrievePet DeletePet
type PetIDArg struct {
// in: path
ID uint64 `json:"pet_id"`
}
// swagger:parameters CreatePet UpdatePet
type NewPetArg struct {
// in: body
NewPet *NewPet
}
// PetReply returns a pet.
//
// swagger:response
type PetReply struct {
// in: body
Pet *Pet
}
// PetsReply returns a list of pets.
//
// swagger:response
type PetsReply struct {
// in: body
Pets []*Pet
}
type Error struct {
ID string `json:"error_id"`
Detail string `json:"error_detail"`
}
// ErrorReply replys an error of API calling.
//
// swagger:response
type ErrorReply struct {
// in: body
Error Error
}
type ErrorInput struct {
Error
Fields map[string]Error `json:"error_fields"`
}
// ErrorInputReply replys an error with details of input data.
//
// swagger:response
type ErrorInputReply struct {
// in: body
ErrorInput ErrorInput
}
// swagger:route GET /pets pet ListPet
//
// Get the list of pets.
//
// responses:
// 200: PetsReply
// default: ErrorReply
// swagger:route POST /pets pet CreatePet
//
// Create a new pet.
//
// responses:
// 200: PetReply
// default: ErrorReply
// swagger:route POST /pets/{pet_id} pet UpdatePet
//
// Update data of a pet.
//
// responses:
// 200: PetReply
// default: ErrorReply
// swagger:route GET /pets/{pet_id} pet RetrievePet
//
// Get data of a pet.
//
// responses:
// 200: PetReply
// default: ErrorReply
// swagger:route DELETE /pets/{pet_id} pet DeletePet
//
// Delete a pet.
//
// responses:
// 200: PetReply
// default: ErrorReply
|