ogen
Go 语言的 OpenAPI v3 代码生成器。
安装
go get -d github.com/ogen-go/ogen
使用方法
//go:generate go run github.com/ogen-go/ogen/cmd/ogen --target target/dir -package api --clean schema.json
或使用容器:
docker run --rm \
--volume ".:/workspace" \
ghcr.io/ogen-go/ogen:latest --target workspace/petstore --clean workspace/petstore.yml
特性
- 无反射或
interface{}
- JSON 编码是代码生成的,经过优化并使用 go-faster/jx 以提高速度并克服
encoding/json
的限制 - 根据规范生成验证代码
- JSON 编码是代码生成的,经过优化并使用 go-faster/jx 以提高速度并克服
- 代码生成的静态基数树路由器
- 无需更多样板代码
- 结构体从 OpenAPI v3 规范生成
- 参数、头部、URL 查询根据规范解析为结构体
- 字符串格式如
uuid
、date
、date-time
、uri
直接由 Go 类型表示
- 静态类型的客户端和服务器
- 方便支持可选、可空和可选可空字段
- 不再使用指针
- 生成 Optional[T]、Nullable[T] 或 OptionalNullable[T] 包装器及辅助函数
- 特殊处理数组,使用与规范相关的
nil
语义- 当数组是可选时,
nil
表示值不存在 - 当可空时,
nil
表示值为nil
- 当必需时,
nil
目前与[]
相同,但实际上是无效的 - 如果既可空又必需,将生成包装器(待实现)
- 当数组是可选时,
- 为 oneOf 生成联合类型
- 通过类型检测原始类型(
string
、number
) - 如果在模式中定义,则使用判别字段
- 如果可能,通过唯一字段推断类型
- 通过类型检测原始类型(
- 在生成的类型中添加额外的 Go 结构体字段标签
- OpenTelemetry 追踪和指标
从模式生成的结构体示例:
// Pet 描述 #/components/schemas/Pet。
type Pet struct {
Birthday time.Time `json:"birthday"`
Friends []Pet `json:"friends"`
ID int64 `json:"id"`
IP net.IP `json:"ip"`
IPV4 net.IP `json:"ip_v4"`
IPV6 net.IP `json:"ip_v6"`
Kind PetKind `json:"kind"`
Name string `json:"name"`
Next OptData `json:"next"`
Nickname NilString `json:"nickname"`
NullStr OptNilString `json:"nullStr"`
Rate time.Duration `json:"rate"`
Tag OptUUID `json:"tag"`
TestArray1 [][]string `json:"testArray1"`
TestDate OptTime `json:"testDate"`
TestDateTime OptTime `json:"testDateTime"`
TestDuration OptDuration `json:"testDuration"`
TestFloat1 OptFloat64 `json:"testFloat1"`
TestInteger1 OptInt `json:"testInteger1"`
TestTime OptTime `json:"testTime"`
Type OptPetType `json:"type"`
URI url.URL `json:"uri"`
UniqueID uuid.UUID `json:"unique_id"`
}
生成的服务器接口示例:
// Server 处理 OpenAPI v3 规范描述的操作。
type Server interface {
PetGetByName(ctx context.Context, params PetGetByNameParams) (Pet, error)
// ...
}
生成的客户端方法签名示例:
type PetGetByNameParams struct {
Name string
}
// GET /pet/{name}
func (c *Client) PetGetByName(ctx context.Context, params PetGetByNameParams) (res Pet, err error)
泛型
ogen
生成泛型包装器而不是使用指针。
例如,OptNilString
是可选(无值)且可为 null
的 string
。
// OptNilString 是可选的可空字符串。
type OptNilString struct {
Value string
Set bool
Null bool
}
生成了多个便利的辅助方法和函数,其中一些如下:
func (OptNilString) Get() (v string, ok bool)
func (OptNilString) IsNull() bool
func (OptNilString) IsSet() bool
func NewOptNilString(v string) OptNilString
递归类型
如果 ogen
遇到无法在 Go 中表示的递归类型,将使用指针作为备选方案。
联合类型
对于 oneOf
,生成联合类型。ID
是 [string, integer]
之一,将表示如下:
type ID struct {
Type IDType
String string
Int int
}
// 还有一些辅助函数:
func NewStringID(v string) ID
func NewIntID(v int) ID
扩展属性
OpenAPI 支持规范扩展,
它们作为模式字段实现,始终以 x-
为前缀。
服务器名称
可选地,可以通过 x-ogen-server-name
指定服务器名称,例如:
{
"openapi": "3.0.3",
"servers": [
{
"x-ogen-server-name": "production",
"url": "https://{region}.example.com/{val}/v1",
},
{
"x-ogen-server-name": "prefix",
"url": "/{val}/v1",
},
{
"x-ogen-server-name": "const",
"url": "https://cdn.example.com/v1"
}
],
(...)
自定义类型名称
可选地,可以通过 x-ogen-name
指定类型名称,例如:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"x-ogen-name": "Name",
"properties": {
"foobar": {
"$ref": "#/$defs/FooBar"
}
},
"$defs": {
"FooBar": {
"x-ogen-name": "FooBar",
"type": "object",
"properties": {
"foo": {
"type": "string"
}
}
}
}
}
自定义字段名称
可选地,可以通过 x-ogen-properties
指定类型名称,例如:
components:
schemas:
Node:
type: object
properties:
parent:
$ref: "#/components/schemas/Node"
child:
$ref: "#/components/schemas/Node"
x-ogen-properties:
parent:
name: "Prev"
child:
name: "Next"
生成的源代码如下:
// Ref: #/components/schemas/Node
type Node struct {
Prev *Node `json:"parent"`
Next *Node `json:"child"`
}
额外的结构体字段标签
可选地,可以通过 x-oapi-codegen-extra-tags
指定额外的 Go 结构体字段标签,例如:
components:
schemas:
Pet:
type: object
required:
- id
properties:
id:
type: integer
format: int64
x-oapi-codegen-extra-tags:
gorm: primaryKey
valid: customIdValidator
生成的源代码如下:
// Ref: #/components/schemas/Pet
type Pet struct {
ID int64 `gorm:"primaryKey" valid:"customNameValidator" json:"id"`
}
流式 JSON 编码
默认情况下,ogen 在解码之前将整个 JSON 主体加载到内存中。
可选地,可以通过 x-ogen-json-streaming
启用流式 JSON 编码,例如:
requestBody:
required: true
content:
application/json:
x-ogen-json-streaming: true
schema:
type: array
items:
type: number
操作分组
可选地,可以对操作进行分组,以便为每组操作生成处理程序接口。 这对于组织大型 API 的操作很有用。
可以通过 x-ogen-operation-group
指定路径或单个操作的分组,例如:
paths:
/images:
x-ogen-operation-group: Images
get:
operationId: listImages
...
/images/{imageID}:
x-ogen-operation-group: Images
get:
operationId: getImageByID
...
/users:
x-ogen-operation-group: Users
get:
operationId: listUsers
...
生成的处理程序接口如下:
// x-ogen-operation-group: Images
type ImagesHandler interface {
ListImages(ctx context.Context, req *ListImagesRequest) (*ListImagesResponse, error)
GetImageByID(ctx context.Context, req *GetImagesByIDRequest) (*GetImagesByIDResponse, error)
}
// x-ogen-operation-group: Users
type UsersHandler interface {
ListUsers(ctx context.Context, req *ListUsersRequest) (*ListUsersResponse, error)
}
type Handler interface {
ImagesHandler
UsersHandler
// 所有未分组的操作将在此接口上
}
JSON
代码生成提供了非常高效和灵活的 JSON 编码和解码:
// Decode 从 JSON 解码 Error。
func (s *Error) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode Error to nil")
}
return d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "code":
if err := func() error {
v, err := d.Int64()
s.Code = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"code\"")
}
case "message":
if err := func() error {
v, err := d.Str()
s.Message = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"message\"")
}
default:
return d.Skip()
}
return nil
})
}