eapi

package module
v0.4.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 21, 2023 License: MIT Imports: 29 Imported by: 0

README

eAPI

一个通过分析代码生成 OpenAPI 文档的工具

介绍

eAPI 通过分析 AST 生成 接口文档前端代码。与 swaggo/swag 等工具不同之处在于,eAPI 无需编写注解即可使用。另外,eAPI 还支持生成 Typescript 类型代码 和 前端接口请求代码。

eAPI 首先解析出代码中的路由(方法/路径)声明,得到接口的 Path、Method 及对应的 Handler 函数。然后再对 Handler 函数进行解析,得到 请求参数(Query/FormData/JSON-Payload等)、响应数据等信息。最终生成一份符合 OpenAPI 3 标准的 JSON 文档。

eAPI 目前支持了 gin, echo 框架的文档生成,其他主流框架在计划中。如果你需要将 eAPI 应用在其他未被支持的框架,可以通过编写自定义插件的方式进行实现,或者给我们提交 PR。

安装

go install github.com/gotomicro/eapi/cmd/eapi@latest

如何使用

  1. 创建配置文件

在代码根目录创建配置文件 eapi.yaml:

plugin: gin # 目前支持 gin 和 echo
output: docs
dir: .
  1. 生成文档

在代码根目录执行命令:

$ eapi

执行完成后会在 docs 目录下生成 openapi.json 文件。

完整的配置说明

配置

如下是完整的配置文件示例:

output: docs # 输出文档的目录
plugin: gin # gin | echo . 取决于你使用的框架,目前支持了 gin 和 echo
dir: '.' # 需要解析的代码目录

# 可选. 请求/响应数据中依赖的类型对应的包
depends:
 - github.com/gotomicro/gotoant
 - gorm.io/datatypes

# 可选. 插件配置. 用于自定义请求响应的函数调用
properties:
  # 自定义请求参数绑定
  request:
    - type: '*server/pkg/handler.CustomContext'
      method: 'Bind'
      return:
        data:
          type: 'args[0]' # 指定第一个函数参数为请求参数
  # 自定义响应函数
  response:
    - type: '*server/pkg/handler.CustomContext'
      method: 'JSONOK'
      return:
        contentType: 'application/json'  # 指定响应的 content-type
        data: # 这是一个嵌套的数据格式示例 '{"code":0,"msg":"hello",data:{...}}'
          type: 'object'
          properties:
            code:
              type: 'number'
            msg:
              type: 'string'
            data:
              optional: true # 是否可选. 默认 false
              type: 'args[0]' # 指定为第一个函数参数
        status: 200 # 指定为 200 状态码

# 可选. 配置代码生成器
generators:
  - name: ts # 生成器名称. 暂时只支持 "ts" (用于生成 typescript 类型)
    output: ./src/types # 输出文件的目录. 执行完成之后会在该目录下生成TS类型文件

Properties

properties 用于配置自定义请求参数绑定函数和响应输出函数。

自定义请求参数绑定函数

配置示例:

properties:
  # 自定义请求参数绑定
  request:
    - type: '*server/pkg/handler.CustomContext'
      method: 'Bind'
      return:
        data:
          type: 'args[0]' # 指定第一个函数参数为请求参数
自定义响应输出函数

配置示例:

response:
    - type: '*server/pkg/handler.CustomContext' # 方法所在的 package/receiver
      method: 'JSONOK'
      return:
        contentType: 'application/json'  # 指定响应的 content-type
        data: # 这是一个嵌套的数据格式示例 '{"code":0,"msg":"hello",data:{...}}'
          type: 'object'
          properties:
            code:
              type: 'number'
            msg:
              type: 'string'
            data:
              optional: true # 是否可选. 默认 false
              type: 'args[0]' # 指定为第一个函数参数
        status: 200 # 指定为 200 状态码

其中,data type 可选值为:

  • string
  • number
  • integer
  • boolean
  • array
  • file
  • object

此外,还可以将函数入参作为参数类型,eAPI 会自动解析对应的参数类型。比如 args[0] 代表函数第一个参数。

完整的配置参考 https://github.com/link-duan/eapi/blob/main/plugins/common/config.go 下面的 DataSchema 类型声明。

代码生成器配置

如果需要使用代码生成功能,需要在配置文件内添加如下配置:

# 可选
generators:
  - name: ts # 生成器名称. 暂时支持 "ts" | "umi" 
    output: ./src/types # 输出文件的目录. 执行完成之后会在该目录下生成TS类型文件
umi-request 请求代码生成

umi 代码生成器用于生成适用于使用 umi.js 框架的前端接口请求代码及 TypeScript 类型。 示例配置:

generators:
  - name: umi
    output: ./src/requests # 输出文件的目录
Typescript 类型生成

ts 代码生成器用于生成 TypeScript 类型代码。 示例配置:

generators:
  - name: ts
    output: ./src/types # 输出文件的目录

注解

如果你需要对文档的内容进行更精细化的调整(比如接口标题、字段是否必选等),那么你需要使用到注解。

默认情况

如果没有写注解,eAPI 也会帮你生成关于接口的必要信息。对应关系如下:

接口信息 默认值
接口的 summary (标题) pkg.HandlerName handler 函数所在的包名和函数名共同组成接口标题。如果有注释,会默认使用注释作为标题
接口描述 handler 函数的注释(非注解部分)
Path/Query/Form参数 根据代码生成。比如 gin 里面的 ctx.Query("q") 会被解析为 query 参数 q 。如果在这行代码上面加上注释,则会被作为这个参数的描述
请求 Body 根据代码生成。比如 gin 里面的 ctx.Bind(&request) 参数绑定
Model 字段描述 字段注释
接口地址 根据代码里面的路由声明自动解析

@summary

允许写在 handler 函数的上方。用于设置接口的 summary(或者叫标题)。

示例

// @summary 创建 XXX 资源
func Create(c *gin.Context) {
  // your code
}

@required

用于设置字段是否必填。允许写在 struct 字段注释里 或者 获取请求参数注释里。

注意:最新的 OpenAPI 标准中,没有对 可选字段 提供支持,只能设置必选字段。

示例1

struct字段注释

type XxRequest struct {
  // 我是字段描述
  // @required
  Title string `json:"title"`
}

在这个示例里面,"我是字段描述” 会被显示为文档中的字段描述,并且会被显示为必填字段。

示例2

请求参数获取

// Create 创建 XXX 资源接口
func Create(c *gin.Context) {
  // 分类 ID
  // @required
  categoryId := c.Param("categoryId")
  // @required
  arg0 := c.Query("arg0")
  arg1 := c.Query("arg1")
}

在这个示例里面有三个参数:

  • categoryId Path参数 字段描述:"分类 ID" 字段必选
  • arg0 Query参数 无字段描述 必选
  • arg0 Query参数 无字段描述 非必选

@consume

用于设置接口请求 body 的 content-type 。默认为 application/json。允许写在 handler 函数注释里面。

示例

// @consume application/xml
func Create(c *gin.Context) {
  var request view.CreateXxRequest
  err = c.Bind(&request)
  // ...
}

在上面这个示例里面,请求参数 request 会被认为是 XML 格式。

@produce

用于设置接口响应 body 的 content-type 。默认为 application/json。允许写在 handler 函数注释里面。

示例

// @produce application/xml
func Create(c *gin.Context) {
  var res view.CreateXxResponse
  // ...
  c.JSON(http.StatusOK, res)
}

在上面这个示例里面,响应 Body 数据 res 会被认为是 XML 格式。

@ignore

用于忽略不需要展示在文档中的接口。允许写在以下位置:

  1. 代码文件头部:会忽略整个文件的接口
  2. 代码块上方:忽略代码块内的接口
func registerRoutes(g *gin.RouteGroup) {
  // @ignore
  {
    // 代码块内的路由都会被忽略
    g.GET("/v1/resources", handler.ResourceList)
    g.POST("/v1/resources", handler.ResourceCreate)
  }

  // 代码块外面的不会被忽略
  g.GET("/v1/pods", handler.PodList)
}

上面这个示例中,代码块内的两个接口都会被忽略,不会展示在文档中。而代码块外面的 GET /api/pods 接口则不会被忽略。

  1. 函数注释:忽略函数内的接口
// @ignore
func registerRoutes() {
  g.GET("/v1/resources", handler.ResourceList)
  g.POST("/v1/resources", handler.ResourceCreate)
  g.GET("/v1/pods", handler.PodList)
}

上面这个示例中,registerRoutes 函数内的接口都会被忽略,不会展示在文档中。

@tags

用于设置接口的 Tag 。允许写在 handler 函数的注释、路由定义处的代码块注释、路由定义所在函数注释。设置了相同的 Tag 会在文档内展示在同一个分类下面。

示例

package router

// @tags User
func Create(c *gin.Context) {
	// ...
}

func registerRoute2(r *gin.RouterGroup) {
   // @tags Shop 
   {
      r.GET("/goods", GoodsList)
   }
}

// @tags Hello
func registerRoute(r *gin.RouterGroup) {
	r.GET("/hello", Hello)
}

如上代码所示,三种注释均有效。

如果同时使用了上面三种中的多种注解,优先级为 第一种 > 第二种 > 第三种。

@id

用于设置接口的 operationId 。 允许写在 handler 函数注释内。默认值为 handler 所在包名 + 函数名

operationId 除了会被应用在文档内,还会被作为生成的前端代码的函数名。

package user

// @id CreateUser
func Create(c *gin.Context) {
   // ...
}

在上面这个示例中,Create 接口的 operationId 默认为 user.Create,但由于设置了 @id 注解,所以 operationId 为 "CreateUser" 。

@deprecated

用于标记字段或者接口为弃用。允许用于字段注释和 handler 函数注释内。

示例

type User struct {
  // Use NewField instead
  // @deprecated
  OldField string `json:"xx"`
}

// @deprecated
func Create(c *gin.Context) {
  // ...
}

@security

用于设置接口鉴权 (Security Requirement) ,参考 https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-requirement-object

// @security oauth2 pets:write pets:read
func XxxHandler() {
	// ...
}

对应的 securitySchemes 配置示例:

openapi:
  info:
    title: This is an Example
    description: Example description for Example
  securitySchemes:
    oauth2:
      type: oauth2
      flows:
        implicit:
          authorizationUrl: "https://example.org/api/oauth/dialog"
          scopes:
            "pets:write": "modify pets in your account"
            "pets:read": "read your pets"

通常需要配合 securitySchemes 使用,参考 https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object

在上面示例中,User.OldField 字段会被标记为弃用,Create 函数对应的接口会被标记为弃用。

预览

  1. Clickvisual 项目
  1. gin 示例
  1. echo 示例

Documentation

Index

Constants

View Source
const (
	MimeTypeJson           = "application/json"
	MimeApplicationXml     = "application/xml"
	MimeTypeXml            = "text/xml"
	MimeTypeFormData       = "multipart/form-data"
	MimeTypeFormUrlencoded = "application/x-www-form-urlencoded"
)

Variables

This section is empty.

Functions

func ConvertStrToBasicType

func ConvertStrToBasicType(str string, t *types.Basic) interface{}

func InspectPackage

func InspectPackage(pkg *packages.Package, visit func(pkg *packages.Package) bool)

func NormalizeComment

func NormalizeComment(text, trimStart string) string

func ReadGoMod

func ReadGoMod(pkgPath string) (mod *modfile.File, err error)

Types

type A

type A int
const (
	A1 A = iota + 1
	A2
	A3
)

type API

type API struct {
	Method   string
	FullPath string
	Spec     *APISpec
}

func NewAPI

func NewAPI(method string, fullPath string) *API

func (*API) Operation

func (r *API) Operation() *spec.Operation

type APISpec

type APISpec struct {
	Consumes []string
	*spec.Operation
}

func NewAPISpec

func NewAPISpec() *APISpec

func (*APISpec) LoadFromComment

func (s *APISpec) LoadFromComment(ctx *Context, comment *Comment)

func (*APISpec) LoadFromFuncDecl

func (s *APISpec) LoadFromFuncDecl(ctx *Context, funcDecl *ast.FuncDecl)

LoadFromFuncDecl load annotations/description from comments of handler function

type APIs

type APIs []*API

type Analyzer

type Analyzer struct {
	// contains filtered or unexported fields
}

func NewAnalyzer

func NewAnalyzer(k *koanf.Koanf) *Analyzer

func (*Analyzer) APIs

func (a *Analyzer) APIs() *APIs

func (*Analyzer) AddRoutes

func (a *Analyzer) AddRoutes(items ...*API)

func (*Analyzer) Depends

func (a *Analyzer) Depends(pkgNames ...string) *Analyzer

func (*Analyzer) Doc

func (a *Analyzer) Doc() *spec.T

func (*Analyzer) Plugin

func (a *Analyzer) Plugin(plugins ...Plugin) *Analyzer

func (*Analyzer) Process

func (a *Analyzer) Process(packagePath string) *Analyzer

type CallInfo

type CallInfo struct {
	Type   string
	Method string
}

type CallRule

type CallRule struct {
	Rules map[string][]string // typeName to function-names
}

func NewCallRule

func NewCallRule() *CallRule

func (*CallRule) WithRule

func (c *CallRule) WithRule(typeName string, fnNames ...string) *CallRule

type Comment

type Comment struct {
	Annotations []annotation.Annotation
	// contains filtered or unexported fields
}

func ParseComment

func ParseComment(commentGroup *ast.CommentGroup, fSet *token.FileSet) *Comment

func (*Comment) ApplyToSchema

func (c *Comment) ApplyToSchema(schema *spec.SchemaRef)

func (*Comment) Consumes

func (c *Comment) Consumes() []string

func (*Comment) Deprecated

func (c *Comment) Deprecated() bool

func (*Comment) ID

func (c *Comment) ID() string

func (*Comment) Ignore

func (c *Comment) Ignore() bool

func (*Comment) Produces

func (c *Comment) Produces() []string

func (*Comment) Required

func (c *Comment) Required() bool

func (*Comment) Security

func (c *Comment) Security() *spec.SecurityRequirements

func (*Comment) Summary

func (c *Comment) Summary() string

func (*Comment) Tags

func (c *Comment) Tags() []string

func (*Comment) Text

func (c *Comment) Text() string

func (*Comment) TextPointer

func (c *Comment) TextPointer() *string

func (*Comment) TrimPrefix

func (c *Comment) TrimPrefix(prefix string) string

TrimPrefix trim comment prefix and return trimmed string

type CommentStack

type CommentStack struct {
	// contains filtered or unexported fields
}

func NewCommentStack

func NewCommentStack(parent *CommentStack, comment *Comment) *CommentStack

func (*CommentStack) LookupAnnotations

func (e *CommentStack) LookupAnnotations(annotType annotation.Type) []annotation.Annotation

func (*CommentStack) LookupTags

func (e *CommentStack) LookupTags() []string

func (*CommentStack) ResolveByAnnotation

func (e *CommentStack) ResolveByAnnotation(annotType annotation.Type) *CommentStack

type Config

type Config struct {
	Plugin  string
	Dir     string
	Output  string
	Depends []string
	OpenAPI OpenAPIConfig

	Generators []*GeneratorConfig
}

type Context

type Context struct {
	Env *Environment
	// contains filtered or unexported fields
}

func (*Context) APIs

func (c *Context) APIs() *APIs

func (*Context) AddAPI

func (c *Context) AddAPI(items ...*API)

func (*Context) Block

func (c *Context) Block() *Context

func (*Context) CommentStack

func (c *Context) CommentStack() *CommentStack

func (*Context) Doc

func (c *Context) Doc() *spec.T

func (*Context) File

func (c *Context) File() *ast.File

func (*Context) GetCallInfo

func (c *Context) GetCallInfo(n ast.Node) (string, string, error)

GetCallInfo returns the package or type and name associated with a call expression

e.g. GetCallInfo(`c.GET("/ping", ...)`) returns ("*github/gin-gonic/gin.RouterGroup", "GET", nil)

func (*Context) GetDefinition

func (c *Context) GetDefinition(pkg, name string) Definition

func (*Context) GetFuncFromAstNode

func (c *Context) GetFuncFromAstNode(n ast.Node) *types.Func

func (*Context) GetHeadingCommentOf

func (c *Context) GetHeadingCommentOf(pos token.Pos) *ast.CommentGroup

func (*Context) GetSchemaByExpr

func (c *Context) GetSchemaByExpr(expr ast.Expr, contentType string) *spec.SchemaRef

func (*Context) GetTrailingCommentOf

func (c *Context) GetTrailingCommentOf(pos token.Pos) *ast.CommentGroup

func (*Context) LineColumn

func (c *Context) LineColumn(pos token.Pos) string

func (*Context) MatchCall

func (c *Context) MatchCall(n ast.Node, rule *CallRule, callback func(call *ast.CallExpr, typeName, fnName string))

func (*Context) NewEnv

func (c *Context) NewEnv() *Context

func (*Context) Package

func (c *Context) Package() *packages.Package

func (*Context) ParseComment

func (c *Context) ParseComment(commentGroup *ast.CommentGroup) *Comment

func (*Context) ParseStatusCode

func (c *Context) ParseStatusCode(status ast.Expr) int

func (*Context) ParseType

func (c *Context) ParseType(t types.Type) Definition

func (*Context) WithFile

func (c *Context) WithFile(file *ast.File) *Context

func (*Context) WithPackage

func (c *Context) WithPackage(pkg *packages.Package) *Context

type Definition

type Definition interface {
	Pkg() *packages.Package
	File() *ast.File
	Key() string
	// contains filtered or unexported methods
}

type Definitions

type Definitions map[string]Definition

func (*Definitions) Get

func (d *Definitions) Get(key string) Definition

func (*Definitions) Set

func (d *Definitions) Set(def Definition)

type Entrypoint

type Entrypoint struct {
	// contains filtered or unexported fields
}

func NewEntrypoint

func NewEntrypoint(plugins ...Plugin) *Entrypoint

func (*Entrypoint) Run

func (e *Entrypoint) Run(args []string)

type Environment

type Environment struct {
	// contains filtered or unexported fields
}

func NewEnvironment

func NewEnvironment(parent *Environment) *Environment

func (*Environment) Assign

func (e *Environment) Assign(k, v interface{}) *Environment

func (*Environment) Define

func (e *Environment) Define(k, v interface{}) *Environment

func (*Environment) Lookup

func (e *Environment) Lookup(k interface{}) interface{}

func (*Environment) Resolve

func (e *Environment) Resolve(k interface{}) *Environment

type FieldNameParser

type FieldNameParser func(fieldName string, field *ast.Field) string

type FuncDefinition

type FuncDefinition struct {
	Decl *ast.FuncDecl
	// contains filtered or unexported fields
}

func NewFuncDefinition

func NewFuncDefinition(pkg *packages.Package, file *ast.File, decl *ast.FuncDecl) *FuncDefinition

func (*FuncDefinition) File

func (f *FuncDefinition) File() *ast.File

func (*FuncDefinition) Key

func (f *FuncDefinition) Key() string

func (*FuncDefinition) Pkg

func (f *FuncDefinition) Pkg() *packages.Package

type GeneratorConfig

type GeneratorConfig struct {
	Name   string
	File   string
	Output string
}

type ModFile

type ModFile struct {
	*modfile.File
}

func LoadModFileFrom

func LoadModFileFrom(packagePath string) (mod *ModFile, err error)

func (*ModFile) GetDep

func (m *ModFile) GetDep(moduleName string) *module.Version

type OpenAPIConfig

type OpenAPIConfig struct {
	OpenAPI         string           `yaml:"openapi"` // OpenAPI version 3.0.0|3.0.3|3.1.0
	Info            *spec.Info       `yaml:"info"`    // Required
	SecuritySchemes *SecuritySchemes `yaml:"securitySchemes"`
}

func (OpenAPIConfig) ApplyToDoc

func (c OpenAPIConfig) ApplyToDoc(doc *spec.T)

type ParamNameParser

type ParamNameParser func(field string, tags map[string]string) (name, in string)

type ParamParser

type ParamParser struct {
	// contains filtered or unexported fields
}

func NewParamParser

func NewParamParser(ctx *Context, nameParser ParamNameParser) *ParamParser

func (*ParamParser) Parse

func (p *ParamParser) Parse(expr ast.Expr) (params []*spec.Parameter)

Parse 根据 ast.Expr 解析出 []*spec.Parameter

type Plugin

type Plugin interface {
	Name() string
	Mount(k *koanf.Koanf) error
	Analyze(ctx *Context, node ast.Node)
}

Plugin 用于对解析逻辑进行扩展以支持不同的框架/模式

type RouteAnalyzer

type RouteAnalyzer func(ctx *Context, node ast.Node) (routes []*API)

type RouteGroup

type RouteGroup struct {
	Prefix string
}

type SchemaBuilder

type SchemaBuilder struct {
	// contains filtered or unexported fields
}

func NewSchemaBuilder

func NewSchemaBuilder(ctx *Context, contentType string) *SchemaBuilder

func (*SchemaBuilder) ParseExpr

func (s *SchemaBuilder) ParseExpr(expr ast.Expr) (schema *spec.SchemaRef)

func (*SchemaBuilder) WithFieldNameParser

func (s *SchemaBuilder) WithFieldNameParser(parser FieldNameParser) *SchemaBuilder

type SecuritySchemes

type SecuritySchemes map[string]*spec.SecurityScheme

type Stack

type Stack[T any] []T

func (*Stack[T]) Pop

func (s *Stack[T]) Pop() *T

func (*Stack[T]) Push

func (s *Stack[T]) Push(v T)

type TypeDefinition

type TypeDefinition struct {
	Spec *ast.TypeSpec

	// Enum items
	Enums []*spec.ExtendedEnumItem
	// contains filtered or unexported fields
}

func NewTypeDefinition

func NewTypeDefinition(pkg *packages.Package, file *ast.File, spec *ast.TypeSpec) *TypeDefinition

func (*TypeDefinition) File

func (t *TypeDefinition) File() *ast.File

func (*TypeDefinition) Key

func (t *TypeDefinition) Key() string

func (*TypeDefinition) ModelKey

func (t *TypeDefinition) ModelKey(typeArgs ...*spec.SchemaRef) string

func (*TypeDefinition) Pkg

func (t *TypeDefinition) Pkg() *packages.Package

func (*TypeDefinition) RefKey

func (t *TypeDefinition) RefKey(typeArgs ...*spec.SchemaRef) string

Directories

Path Synopsis
cmd
internal
plugins
gin
Package openapi3 parses and writes OpenAPI 3 specification documents.
Package openapi3 parses and writes OpenAPI 3 specification documents.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL