Skip to content

Commit

Permalink
refactor(cmd/web/restdoc): 采用 go/types 代替 go/ast 的使用
Browse files Browse the repository at this point in the history
  • Loading branch information
caixw committed Jan 2, 2024
1 parent bf6b43d commit 35ed034
Show file tree
Hide file tree
Showing 30 changed files with 1,696 additions and 717 deletions.
10 changes: 5 additions & 5 deletions cmd/web/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ require (
github.com/getkin/kin-openapi v0.122.0
github.com/issue9/assert/v3 v3.1.0
github.com/issue9/cmdopt v0.13.0
github.com/issue9/localeutil v0.26.0
github.com/issue9/logs/v7 v7.4.0
github.com/issue9/localeutil v0.26.1
github.com/issue9/logs/v7 v7.4.1
github.com/issue9/query/v3 v3.1.2
github.com/issue9/sliceutil v0.15.0
github.com/issue9/source v0.8.0
github.com/issue9/term/v3 v3.2.4
github.com/issue9/term/v3 v3.2.5
github.com/issue9/version v1.0.7
github.com/issue9/web v0.86.2
golang.org/x/mod v0.14.0
golang.org/x/text v0.14.0
golang.org/x/tools v0.16.0
golang.org/x/tools v0.16.1
gopkg.in/yaml.v3 v3.0.1
)

Expand All @@ -28,7 +28,7 @@ require (
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/issue9/cache v0.8.0 // indirect
github.com/issue9/cache v0.8.1 // indirect
github.com/issue9/config v0.6.1 // indirect
github.com/issue9/conv v1.3.4 // indirect
github.com/issue9/errwrap v0.3.1 // indirect
Expand Down
20 changes: 10 additions & 10 deletions cmd/web/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
github.com/issue9/assert/v3 v3.1.0 h1:oxLFXS7QnBKI4lB31pRoYO96yErkWAJtR7iv+LNjAPg=
github.com/issue9/assert/v3 v3.1.0/go.mod h1:yft/uaskRpwQTyBT3n1zRl91SR1wNlO4fLZHzOa4bdM=
github.com/issue9/cache v0.8.0 h1:4puBfouYYyprvEUrLGW3YsCEaXYNNiFbKhLfPp9/F4I=
github.com/issue9/cache v0.8.0/go.mod h1:avdNcf8037p4TmLMLaelqeXnnJbgyseLbMSJ2xh1w3U=
github.com/issue9/cache v0.8.1 h1:d0ak3hvGK/Jh4DKvIqwqmkrarBmdgfbEq5i85gXOT8M=
github.com/issue9/cache v0.8.1/go.mod h1:xEvqo+N/3EqqFYqNPdAinE5CFJIZjyJ2IoIGhMRlZdY=
github.com/issue9/cmdopt v0.13.0 h1:mLaa7R94a6MbCf+1GIt4YgFDW6fdt/nnL0AjgKgf8jQ=
github.com/issue9/cmdopt v0.13.0/go.mod h1:l//IcugcBwX+vCc2KrgC4ylU6rEzhjQyxno7hWoLCDE=
github.com/issue9/config v0.6.1 h1:9V6X09fDT6Wzd/89SxX1yNoydyxXev4JC2ClhCxQLac=
Expand All @@ -29,10 +29,10 @@ github.com/issue9/conv v1.3.4 h1:v1j/p1lVNW4u1yrbUxxNCb61iTFnF86s+KAwS65MsBs=
github.com/issue9/conv v1.3.4/go.mod h1:TXM2DyyJhzZMSwp9cxwFW/OhP5JRVZPMg5XE8OMzwUY=
github.com/issue9/errwrap v0.3.1 h1:8g4lYJaGnoiXyZ1oZyH/7zPDGgw5RNiE9Q6ri9kE6Z8=
github.com/issue9/errwrap v0.3.1/go.mod h1:HLR0e5iimd2aJXM9YrThOsRj3/6lMtk77lVp7zyvJ4E=
github.com/issue9/localeutil v0.26.0 h1:BviCBS/p39WWlyoa6swZ7yCPjfyNNIQZABGLdq5OJD4=
github.com/issue9/localeutil v0.26.0/go.mod h1:lx1IztYkUJC60QsjCpjUEuD0eLMu2PqCbSdYZcQgrss=
github.com/issue9/logs/v7 v7.4.0 h1:fgIBBc6DjMN5UKCKzbW3dc4ueofhJkkQS0DRIUVNrMU=
github.com/issue9/logs/v7 v7.4.0/go.mod h1:+aLERkBvHbu8Olg8Du1EFG4CaAK+0rqulYHmpVxgF00=
github.com/issue9/localeutil v0.26.1 h1:4T4508SlTKcKkNjaj6Fer+ZyMVOU+UBUu4DWk/Zq8+I=
github.com/issue9/localeutil v0.26.1/go.mod h1:JU83qFEhBswkZdLc3bWS719ku5QY1/mqU8vhsP9Znsw=
github.com/issue9/logs/v7 v7.4.1 h1:5hmxnYATWa7TiePFAuW7C0/ahjtGerbNQs2PoI6/eqU=
github.com/issue9/logs/v7 v7.4.1/go.mod h1:Es2hZwH7mncVlwpzMg8CErRdTkEjoMUOsivaqALdMWE=
github.com/issue9/mux/v7 v7.3.4 h1:scyq6beiwPzm2oftBY6hUe6WNBdGlz/5YlBRRpzEV0o=
github.com/issue9/mux/v7 v7.3.4/go.mod h1:Ycds7cp+Wdyi5W4WVmuUSRpNmoMxLpBRHZE6QN6IQWE=
github.com/issue9/query/v3 v3.1.2 h1:+ObxriMUTmv9qq8E4HWVByv9W2hnEXFw/FI69BlomO0=
Expand All @@ -45,8 +45,8 @@ github.com/issue9/sliceutil v0.15.0 h1:E6Xnl3FY5h0ZGNzyx1VEFAfGdParaq/BkX1QQR0uF
github.com/issue9/sliceutil v0.15.0/go.mod h1:n9meV7AamDhmehOBuV4GrxW3yw7O1cZmLx3Xizg1bps=
github.com/issue9/source v0.8.0 h1:D2OdhhIw49zAGc9NGhWDwDl1MulNvOxuMWLImMoR8Sw=
github.com/issue9/source v0.8.0/go.mod h1:2EHVCbKxWmiLgW44gbNeNunksR52IRLl6Ph+cr842ZE=
github.com/issue9/term/v3 v3.2.4 h1:bCY0uk8lLaY6XLbZdYQVl5e0CqJkGxV8EYJxJGFv8xU=
github.com/issue9/term/v3 v3.2.4/go.mod h1:f8L2Pdh6t/i1hfWFxrN9r2W1n4bNMEq/sKmS9RQSaAg=
github.com/issue9/term/v3 v3.2.5 h1:kAC5ynwTiqe/63E3JAV2Cb7eVxVno+L7VsmYgJyyLw0=
github.com/issue9/term/v3 v3.2.5/go.mod h1:Qm0PvOTggMvZUEsAmpiBr/uBaq5Qh/w4LQJcvznfAjc=
github.com/issue9/unique/v2 v2.0.0 h1:pLfaHYWPoLrpH8T7LMikwNePlKplA7qtK8roUF4uCC0=
github.com/issue9/unique/v2 v2.0.0/go.mod h1:s0109PBDKIm7457yUaYSX/4WzHmeoGiZMhEuOB5tREI=
github.com/issue9/version v1.0.7 h1:uKcWyuEWbcFdPeqmR2ivuy4/CsB0la0xkk3gxlMjxZs=
Expand Down Expand Up @@ -87,8 +87,8 @@ golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
133 changes: 127 additions & 6 deletions cmd/web/restdoc/openapi/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,142 @@

package openapi

import "github.com/getkin/kin-openapi/openapi3"
import (
"fmt"
"strings"

"github.com/getkin/kin-openapi/openapi3"
)

const ComponentSchemaPrefix = "#/components/schemas/"

// AddSchema 尝试添加一个 Schema 至 Components 中
//
// NOTE: 仅在 schema.Ref 不为空时才会保存。
func (doc *OpenAPI) AddSchema(schema *openapi3.SchemaRef) {
// NOTE: Components.Schemas 的键名不包含 #/components/schemas/ 前缀,
// 但是 SchemaRef.Ref 是包含此前缀的。

ref := strings.TrimPrefix(schema.Ref, ComponentSchemaPrefix)
if ref == "" {
return
}
schema.Ref = ComponentSchemaPrefix + ref

// AddSchema 添加一个 Schema 至 Components 中
func (doc *OpenAPI) AddSchema(ref string, schema *openapi3.Schema) {
doc.schemaLocker.Lock()
defer doc.schemaLocker.Unlock()

doc.doc.Components.Schemas[ref] = openapi3.NewSchemaRef("", schema)
if s, found := doc.doc.Components.Schemas[ref]; found {
if schema.Value == s.Value {
return
}
panic(fmt.Sprintf("添加同名的对象 %s", ref))
}
doc.doc.Components.Schemas[ref] = schema
}

// GetSchema 从 Components 中查找 ref 引用的 Schema 定义
func (doc *OpenAPI) GetSchema(ref string) (*openapi3.SchemaRef, bool) {
ref = strings.TrimPrefix(ref, ComponentSchemaPrefix)

doc.schemaLocker.RLock()
defer doc.schemaLocker.RUnlock()

r, found := doc.doc.Components.Schemas[ref]
return r, found
}

func NewSchemaRef(refID string, s *openapi3.Schema) *openapi3.SchemaRef {
if refID != "" && !strings.HasPrefix(refID, ComponentSchemaPrefix) {
refID = ComponentSchemaPrefix + refID
}
return openapi3.NewSchemaRef(refID, s)
}

// NewArraySchemaRef 将 ref 包装成数组
func NewArraySchemaRef(ref *openapi3.SchemaRef) *openapi3.SchemaRef {
s := openapi3.NewArraySchema()
s.Items = ref
return openapi3.NewSchemaRef("", s)
}

// NewDocSchemaRef 将 ref 附带上文档信息
//
// 这会以 AllOf 的形式形成一个新 [openapi3.SchemaRef] 对象,原有的 ref 不会被破坏。
func NewDocSchemaRef(ref *openapi3.SchemaRef, title, desc string) *openapi3.SchemaRef {
if ref.Ref == "" { // 非引用模式,表示该值仅调用方使用,直接修改值。
ref.Value.Title = title
ref.Value.Description = desc
return ref
}

if title != "" || desc != "" {
s := openapi3.NewSchema()
s.AllOf = openapi3.SchemaRefs{ref}
if desc != "" {
s.Description = desc
}
if title != "" {
s.Title = title
}
return NewSchemaRef("", s)
}

return ref
}

// NewNullableSchemaRef 将 ref 包装为一个允许为空的对象
func NewNullableSchemaRef(ref *openapi3.SchemaRef) *openapi3.SchemaRef {
if ref.Ref == "" { // 非引用模式,表示该值仅调用方使用,直接修改值。
ref.Value.Nullable = true
return ref
}

s := openapi3.NewSchema().WithNullable()
s.AllOf = openapi3.SchemaRefs{ref}
return NewSchemaRef("", s)
}

// NewXMLSchemaRef 包装 ref 为其指定 XML 属性
func NewXMLSchemaRef(ref *openapi3.SchemaRef, xml *openapi3.XML) *openapi3.SchemaRef {
if ref.Ref == "" { // 非引用模式,表示该值仅调用方使用,直接修改值。
ref.Value.XML = xml
return ref
}

if xml != nil {
s := openapi3.NewSchema()
s.AllOf = openapi3.SchemaRefs{ref}
s.XML = xml
return NewSchemaRef("", s)
}

return ref
}

func NewAttrSchemaRef(ref *openapi3.SchemaRef, title, desc string, xml *openapi3.XML, nullable bool) *openapi3.SchemaRef {
if ref == nil || (title == "" && desc == "" && xml == nil && !nullable) {
return ref
}

var s *openapi3.Schema
if ref.Ref == "" {
s = ref.Value
} else {
s = openapi3.NewSchema()
s.AllOf = openapi3.SchemaRefs{ref}
}

if title != "" {
s.Title = title
}
if desc != "" {
s.Description = desc
}
if xml != nil {
s.XML = xml
}
if nullable {
s.WithNullable()
}

return NewSchemaRef("", s)
}
61 changes: 61 additions & 0 deletions cmd/web/restdoc/openapi/schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT

package openapi

import (
"testing"

"github.com/getkin/kin-openapi/openapi3"
"github.com/issue9/assert/v3"
)

func TestOpenapi_Schema(t *testing.T) {
a := assert.New(t, false)
o := New("3.0.0")

ref := NewSchemaRef("", nil)
o.AddSchema(ref)
a.Length(o.doc.Components.Schemas, 0) // 空的 refID,添加不成功

ref = NewSchemaRef("abc", nil)
o.AddSchema(ref)
a.Length(o.doc.Components.Schemas, 1)

v1, found := o.GetSchema("abc")
a.True(found).NotNil(v1)
v2, found := o.GetSchema(ComponentSchemaPrefix + "abc")
a.True(found).NotNil(v2)
a.Equal(v1, v2).
Equal(v1, v2). // v1,v2 指向同一个对象
Equal(v1.Ref, ComponentSchemaPrefix+"abc")

// 同名,但都是 nil
a.NotPanic(func() {
ref = NewSchemaRef(ComponentSchemaPrefix+"abc", nil)
o.AddSchema(ref)
})

a.PanicString(func() {
ref = NewSchemaRef(ComponentSchemaPrefix+"abc", &openapi3.Schema{})
o.AddSchema(ref)
}, "添加同名的对象 abc")
}

func TestNewAttrSchemaRef(t *testing.T) {
a := assert.New(t, false)

ref := openapi3.NewSchemaRef("ref", openapi3.NewBoolSchema())
ref2 := NewAttrSchemaRef(ref, "", "", nil, false)
a.Equal(ref2, ref)

ref2 = NewAttrSchemaRef(ref, "", "123", nil, false)
a.NotEqual(ref2, ref).
Equal(ref2.Value.AllOf[0].Value, ref.Value).
Equal(ref2.Value.Description, "123")

ref2 = NewAttrSchemaRef(ref, "", "123", nil, true)
a.NotEqual(ref2, ref).
Equal(ref2.Value.AllOf[0].Value, ref.Value).
Equal(ref2.Value.Description, "123").
True(ref2.Value.Nullable)
}
17 changes: 9 additions & 8 deletions cmd/web/restdoc/parser/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package parser

import (
"context"
"errors"
"sort"
"strings"
Expand All @@ -15,7 +16,7 @@ import (
"github.com/issue9/web/cmd/web/restdoc/utils"
)

func (p *Parser) parseAPI(t *openapi.OpenAPI, currPath, suffix string, lines []string, ln int, filename string) {
func (p *Parser) parseAPI(ctx context.Context, t *openapi.OpenAPI, currPath, suffix string, lines []string, ln int, filename string) {
defer func() {
// NOTE: recover 用于处理 openapi3 的 panic,但是不带行号信息。
// 应当尽量在此之前查出错误。
Expand Down Expand Up @@ -60,19 +61,19 @@ LOOP:
case "@path": // @path name type *desc
p.addPath(opt, suffix, filename, ln+index)
case "@query": // @query object.path
p.addQuery(t, opt, currPath, suffix, filename, ln+index)
p.addQuery(ctx, t, opt, currPath, suffix, filename, ln+index)
case "@req": // @req text/* object.path *desc
p.parseRequest(opt, t, suffix, filename, currPath, ln+index)
p.parseRequest(ctx, opt, t, suffix, filename, currPath, ln+index)
case "@resp": // @resp 200 text/* object.path *desc
if !p.parseResponse(resps, t, suffix, filename, currPath, ln+index) {
if !p.parseResponse(ctx, resps, t, suffix, filename, currPath, ln+index) {
return
}
case "@resp-header": // @resp-header 200 h1 *desc
if !p.parseResponseHeader(resps, suffix, filename, currPath, ln+index) {
if !p.parseResponseHeader(resps, suffix, filename, ln+index) {
return
}
case "##": // 可能是 ## callback
delta := p.parseCallback(t, opt, currPath, suffix, lines[index:], ln+index, filename)
delta := p.parseCallback(ctx, t, opt, currPath, suffix, lines[index:], ln+index, filename)
index += delta
default:
if len(tag) > 1 && tag[0] == '@' {
Expand All @@ -95,13 +96,13 @@ LOOP:
t.AddAPI(p.prefix+path, opt, method)
}

func (p *Parser) addQuery(t *openapi.OpenAPI, opt *openapi3.Operation, currPath, suffix, filename string, ln int) {
func (p *Parser) addQuery(ctx context.Context, t *openapi.OpenAPI, opt *openapi3.Operation, currPath, suffix, filename string, ln int) {
if suffix == "" {
p.syntaxError("@query", 1, filename, ln)
return
}

s, err := p.schema.New(t, currPath, suffix, true)
s, err := p.schema.New(ctx, t, buildPath(currPath, suffix), true)
if err != nil {
var serr *schema.Error
if errors.As(err, &serr) {
Expand Down
3 changes: 2 additions & 1 deletion cmd/web/restdoc/parser/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package parser

import (
"context"
"testing"

"github.com/issue9/assert/v3"
Expand All @@ -23,7 +24,7 @@ func TestParser_parseAPI(t *testing.T) {
"@tag user",
"@path id id id desc",
}
p.parseAPI(doc, "github.com/issue9/web", "POST /admins/{id}", lines, 5, "example.go")
p.parseAPI(context.Background(), doc, "github.com/issue9/web", "POST /admins/{id}", lines, 5, "example.go")
path := doc.Doc().Paths.Find("/admins/{id}")
a.NotNil(path).
NotNil(path.Post).
Expand Down
Loading

0 comments on commit 35ed034

Please sign in to comment.