From c3507c4d3711c86f17751cd6db3d64b6ea979361 Mon Sep 17 00:00:00 2001 From: caixw Date: Tue, 5 Dec 2023 16:35:21 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E7=9A=84=20openapi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/web/go.mod | 2 +- cmd/web/go.sum | 4 +-- cmd/web/restdoc/openapi/openapi.go | 47 +++++++++++-------------- cmd/web/restdoc/openapi/openapi_test.go | 9 +++-- cmd/web/restdoc/openapi/path.go | 2 +- cmd/web/restdoc/openapi/path_test.go | 12 +++---- cmd/web/restdoc/parser/api_test.go | 2 +- cmd/web/restdoc/parser/body.go | 8 ++--- cmd/web/restdoc/parser/callback.go | 3 +- cmd/web/restdoc/parser/parser_test.go | 8 ++--- cmd/web/restdoc/parser/restdoc_test.go | 4 +-- cmd/web/restdoc/restdoc.go | 2 +- 12 files changed, 50 insertions(+), 53 deletions(-) diff --git a/cmd/web/go.mod b/cmd/web/go.mod index 44b9dca4..6256a849 100644 --- a/cmd/web/go.mod +++ b/cmd/web/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/caixw/gobuild v1.7.4 - github.com/getkin/kin-openapi v0.120.0 + 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/errwrap v0.3.1 diff --git a/cmd/web/go.sum b/cmd/web/go.sum index 1fb443d0..77399efd 100644 --- a/cmd/web/go.sum +++ b/cmd/web/go.sum @@ -6,8 +6,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= -github.com/getkin/kin-openapi v0.120.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= +github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9HOM10= +github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= diff --git a/cmd/web/restdoc/openapi/openapi.go b/cmd/web/restdoc/openapi/openapi.go index a8f3bd45..39e620f5 100644 --- a/cmd/web/restdoc/openapi/openapi.go +++ b/cmd/web/restdoc/openapi/openapi.go @@ -5,6 +5,7 @@ package openapi import ( "encoding/json" + "maps" "os" "path/filepath" "sync" @@ -25,19 +26,17 @@ type OpenAPI struct { func New(ver string) *OpenAPI { c := openapi3.NewComponents() c.Schemas = make(openapi3.Schemas) - c.Responses = make(openapi3.Responses) + c.Responses = make(openapi3.ResponseBodies) c.SecuritySchemes = make(openapi3.SecuritySchemes) - t := &openapi3.T{ + return &OpenAPI{doc: &openapi3.T{ OpenAPI: ver, Components: &c, Servers: make(openapi3.Servers, 0, 5), - Paths: make(openapi3.Paths, 100), + Paths: openapi3.NewPathsWithCapacity(100), Security: make(openapi3.SecurityRequirements, 0, 5), Tags: make(openapi3.Tags, 0, 10), - } - - return &OpenAPI{doc: t} + }} } func (doc *OpenAPI) Doc() *openapi3.T { return doc.doc } @@ -48,12 +47,10 @@ func (doc *OpenAPI) Doc() *openapi3.T { return doc.doc } func (doc *OpenAPI) SaveAs(path string) error { var m func(any) ([]byte, error) switch filepath.Ext(path) { - case ".yaml", ".yml": + case ".yaml", ".yml": // BUG: 依赖的 openapi3.Paths 不支持输出 yaml? m = yaml.Marshal case ".json": - m = func(v any) ([]byte, error) { - return json.MarshalIndent(v, "", "\t") - } + m = func(v any) ([]byte, error) { return json.MarshalIndent(v, "", "\t") } default: return web.NewLocaleError("only support yaml and json") } @@ -69,22 +66,20 @@ func (doc *OpenAPI) Merge(d *openapi3.T) { doc.Doc().Servers = append(doc.Doc().Servers, d.Servers...) doc.Doc().Security = append(doc.Doc().Security, d.Security...) doc.Doc().Tags = append(doc.Doc().Tags, d.Tags...) - cloneMap(d.Paths, doc.Doc().Paths) - if d.Components != nil { - cloneMap(d.Components.Schemas, doc.Doc().Components.Schemas) - cloneMap(d.Components.Parameters, doc.Doc().Components.Parameters) - cloneMap(d.Components.Headers, doc.Doc().Components.Headers) - cloneMap(d.Components.RequestBodies, doc.Doc().Components.RequestBodies) - cloneMap(d.Components.Responses, doc.Doc().Components.Responses) - cloneMap(d.Components.SecuritySchemes, doc.Doc().Components.SecuritySchemes) - cloneMap(d.Components.Examples, doc.Doc().Components.Examples) - cloneMap(d.Components.Links, doc.Doc().Components.Links) - cloneMap(d.Components.Callbacks, doc.Doc().Components.Callbacks) + if d.Paths != nil && d.Paths.Len() > 0 { + for k, v := range d.Paths.Map() { + doc.Doc().Paths.Set(k, v) + } } -} - -func cloneMap[K comparable, V any](src, dest map[K]V) { - for k, v := range src { - dest[k] = v + if d.Components != nil { + maps.Copy(doc.Doc().Components.Schemas, d.Components.Schemas) + maps.Copy(doc.Doc().Components.Parameters, d.Components.Parameters) + maps.Copy(doc.Doc().Components.Headers, d.Components.Headers) + maps.Copy(doc.Doc().Components.RequestBodies, d.Components.RequestBodies) + maps.Copy(doc.Doc().Components.Responses, d.Components.Responses) + maps.Copy(doc.Doc().Components.SecuritySchemes, d.Components.SecuritySchemes) + maps.Copy(doc.Doc().Components.Examples, d.Components.Examples) + maps.Copy(doc.Doc().Components.Links, d.Components.Links) + maps.Copy(doc.Doc().Components.Callbacks, d.Components.Callbacks) } } diff --git a/cmd/web/restdoc/openapi/openapi_test.go b/cmd/web/restdoc/openapi/openapi_test.go index 38a83678..8f6b8b3e 100644 --- a/cmd/web/restdoc/openapi/openapi_test.go +++ b/cmd/web/restdoc/openapi/openapi_test.go @@ -3,6 +3,7 @@ package openapi import ( + "net/http" "testing" "github.com/getkin/kin-openapi/openapi3" @@ -23,6 +24,10 @@ func TestOpenAPI_SaveAs(t *testing.T) { doc := New("3.0") a.NotNil(doc) + o := openapi3.NewOperation() + o.Responses = openapi3.NewResponses() + doc.AddAPI("/pet", o, http.MethodGet) + a.NotError(doc.SaveAs("./openapi.out.yaml")) a.NotError(doc.SaveAs("./openapi.out.json")) a.ErrorString(doc.SaveAs("./openapi.out.xml"), "only support yaml and json") @@ -33,9 +38,7 @@ func TestOpenAPI_Merge(t *testing.T) { d2 := &openapi3.T{} d2.Tags = append(d2.Tags, &openapi3.Tag{Name: "t1"}) - d2.Paths = openapi3.Paths{ - "/p1": &openapi3.PathItem{}, - } + d2.Paths = openapi3.NewPaths(openapi3.WithPath("/p1", &openapi3.PathItem{})) d2.Components = &openapi3.Components{ Schemas: openapi3.Schemas{ "Object": &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "object"}}, diff --git a/cmd/web/restdoc/openapi/path.go b/cmd/web/restdoc/openapi/path.go index efa3f05e..4cf907f7 100644 --- a/cmd/web/restdoc/openapi/path.go +++ b/cmd/web/restdoc/openapi/path.go @@ -45,7 +45,7 @@ func (doc *OpenAPI) addAPI(path, method string, o *openapi3.Operation) { return } - p := doc.doc.Paths[path] + p := doc.doc.Paths.Find(path) if index := slices.IndexFunc(p.Parameters, isPathParams); index >= 0 { return } diff --git a/cmd/web/restdoc/openapi/path_test.go b/cmd/web/restdoc/openapi/path_test.go index d920b8f1..5c412893 100644 --- a/cmd/web/restdoc/openapi/path_test.go +++ b/cmd/web/restdoc/openapi/path_test.go @@ -18,29 +18,29 @@ func TestOpenAPI_AddAPI(t *testing.T) { o := openapi3.NewOperation() o.Parameters = append(o.Parameters, &openapi3.ParameterRef{Value: openapi3.NewPathParameter("p1")}) doc.AddAPI("/path/{p1}", o, "GET") - a.Length(doc.Doc().Paths["/path/{p1}"].Parameters, 1). + a.Length(doc.Doc().Paths.Find("/path/{p1}").Parameters, 1). Length(o.Parameters, 0) // 上移至 path item o = openapi3.NewOperation() o.Parameters = append(o.Parameters, &openapi3.ParameterRef{Value: openapi3.NewPathParameter("p1")}) doc.AddAPI("/path/{p1}", o, "PUT") - a.Length(doc.Doc().Paths["/path/{p1}"].Parameters, 1). + a.Length(doc.Doc().Paths.Find("/path/{p1}").Parameters, 1). Length(o.Parameters, 1) // path item 上已经有了,不再上移 // 非路径参数 o = openapi3.NewOperation() o.Parameters = append(o.Parameters, &openapi3.ParameterRef{Value: openapi3.NewQueryParameter("q1")}) doc.AddAPI("/path/q1", o, "PUT,PATCH") - a.Length(doc.Doc().Paths["/path/q1"].Parameters, 0). + a.Length(doc.Doc().Paths.Find("/path/q1").Parameters, 0). Length(o.Parameters, 1). - NotNil(doc.Doc().Paths["/path/q1"].Put). - NotNil(doc.Doc().Paths["/path/q1"].Patch) + NotNil(doc.Doc().Paths.Find("/path/q1").Put). + NotNil(doc.Doc().Paths.Find("/path/q1").Patch) // any o = openapi3.NewOperation() o.Parameters = append(o.Parameters, &openapi3.ParameterRef{Value: openapi3.NewQueryParameter("q1")}) doc.AddAPI("/path/any", o, "Any") - p := doc.Doc().Paths["/path/any"] + p := doc.Doc().Paths.Find("/path/any") a.NotNil(p.Put). NotNil(p.Patch). NotNil(p.Delete). diff --git a/cmd/web/restdoc/parser/api_test.go b/cmd/web/restdoc/parser/api_test.go index f0346abc..1e45ad96 100644 --- a/cmd/web/restdoc/parser/api_test.go +++ b/cmd/web/restdoc/parser/api_test.go @@ -24,7 +24,7 @@ func TestParser_parseAPI(t *testing.T) { "@path id id id desc", } p.parseAPI(doc, "github.com/issue9/web", "POST /admins/{id}", lines, 5, "example.go") - path := doc.Doc().Paths["/admins/{id}"] + path := doc.Doc().Paths.Find("/admins/{id}") a.NotNil(path). NotNil(path.Post). Nil(path.Delete). diff --git a/cmd/web/restdoc/parser/body.go b/cmd/web/restdoc/parser/body.go index 28aba528..34167a7b 100644 --- a/cmd/web/restdoc/parser/body.go +++ b/cmd/web/restdoc/parser/body.go @@ -103,20 +103,20 @@ func (p *Parser) parseResponseHeader(resps map[string]*openapi3.Response, suffix // g 是否将定义为全局的对象也写入 o func (p *Parser) addResponses(o *openapi3.Operation, resps map[string]*openapi3.Response, g bool) { if l := len(resps) + len(p.resps); o.Responses == nil && l > 0 { - o.Responses = make(openapi3.Responses, l) + o.Responses = openapi3.NewResponsesWithCapacity(l) } if g { // 全局的定义在前,才会被本地定义覆盖。 for key, resp := range p.resps { - o.Responses[key] = &openapi3.ResponseRef{Ref: responsesRef + key, Value: resp} + o.Responses.Set(key, &openapi3.ResponseRef{Ref: responsesRef + key, Value: resp}) } } for status, r := range resps { - if _, found := o.Responses[status]; found { + if o.Responses.Value(status) != nil { p.l.Warning(web.Phrase("override global response %s", status)) } - o.Responses[status] = &openapi3.ResponseRef{Value: r} + o.Responses.Set(status, &openapi3.ResponseRef{Value: r}) } } diff --git a/cmd/web/restdoc/parser/callback.go b/cmd/web/restdoc/parser/callback.go index 60d7e7f6..049e6a85 100644 --- a/cmd/web/restdoc/parser/callback.go +++ b/cmd/web/restdoc/parser/callback.go @@ -87,9 +87,8 @@ func (p *Parser) parseCallback(t *openapi.OpenAPI, o *openapi3.Operation, currPa } p.addResponses(opt, resps, false) - callback := openapi3.Callback{path: pi} o.Callbacks[name] = &openapi3.CallbackRef{ - Value: &callback, + Value: openapi3.NewCallback(openapi3.WithCallback(path, pi)), } return delta diff --git a/cmd/web/restdoc/parser/parser_test.go b/cmd/web/restdoc/parser/parser_test.go index 7865c784..2db1fd2a 100644 --- a/cmd/web/restdoc/parser/parser_test.go +++ b/cmd/web/restdoc/parser/parser_test.go @@ -28,7 +28,7 @@ func TestParser_Parse(t *testing.T) { a.NotNil(d.Doc().Info). Equal(d.Doc().Info.Version, "1.0.0") - login := d.Doc().Paths["/prefix/login"].Post + login := d.Doc().Paths.Find("/prefix/login").Post a.NotNil(login). Length(login.Parameters, 6). Equal(login.Parameters[3].Value.Name, "sex"). @@ -38,11 +38,11 @@ func TestParser_Parse(t *testing.T) { Equal(login.Parameters[2].Value.Schema.Value.Type, openapi3.TypeArray). Equal(login.Parameters[2].Value.Schema.Value.Items.Value.Type, openapi3.TypeInteger). NotNil(login.RequestBody). - Length(login.Responses, 5). // 包含默认的 default + Equal(login.Responses.Len(), 5). // 包含默认的 default Length(login.Callbacks, 1). - NotNil((*login.Callbacks["onData"].Value)["{$request.query.url}"].Post) + NotNil((login.Callbacks["onData"].Value).Value("{$request.query.url}").Post) - a.NotError(d.SaveAs("./testdata/openapi.out.yaml")) + a.NotError(d.SaveAs("./testdata/openapi.out.json")) } func TestIsIgnoreTag(t *testing.T) { diff --git a/cmd/web/restdoc/parser/restdoc_test.go b/cmd/web/restdoc/parser/restdoc_test.go index 0c447480..9ea9e7bb 100644 --- a/cmd/web/restdoc/parser/restdoc_test.go +++ b/cmd/web/restdoc/parser/restdoc_test.go @@ -123,7 +123,7 @@ func TestBuildContact(t *testing.T) { Equal(c.Name, "name") } -func TestParseOpenAPI(t *testing.T) { +func TestParser_parseOpenAPI(t *testing.T) { a := assert.New(t, false) l := loggertest.New(a) @@ -132,5 +132,5 @@ func TestParseOpenAPI(t *testing.T) { p.parseOpenAPI(d, "./testdata/openapi.yaml", "test.go", 5) a.Nil(d.Doc().Info). - Length(d.Doc().Paths, 1) + Equal(d.Doc().Paths.Len(), 1) } diff --git a/cmd/web/restdoc/restdoc.go b/cmd/web/restdoc/restdoc.go index 1ef21a92..1aa375a8 100644 --- a/cmd/web/restdoc/restdoc.go +++ b/cmd/web/restdoc/restdoc.go @@ -35,7 +35,7 @@ const ( replaceUsage = web.StringPhrase("parse replace direct, only valid when d is true") ) -const defaultOutput = "./restdoc.yaml" +const defaultOutput = "./restdoc.json" func Init(opt *cmdopt.CmdOpt, p *localeutil.Printer) { opt.New("doc", title.LocaleString(p), usage.LocaleString(p), func(fs *flag.FlagSet) cmdopt.DoFunc {