This commit is contained in:
huangqian 2022-03-07 22:35:39 +08:00
commit f08c18594b
32 changed files with 637 additions and 145 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,9 @@
package gmap
import (
"bytes"
"fmt"
"github.com/gogf/gf/v2/container/glist"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/internal/empty"
@ -15,6 +18,13 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)
// ListMap is a map that preserves insertion-order.
//
// It is backed by a hash table to store values and doubly-linked list to store ordering.
//
// Structure is not thread safe.
//
// Reference: http://en.wikipedia.org/wiki/Associative_array
type ListMap struct {
mu rwmutex.RWMutex
data map[interface{}]*glist.Element
@ -58,7 +68,7 @@ func (m *ListMap) IteratorAsc(f func(key interface{}, value interface{}) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
if m.list != nil {
node := (*gListMapNode)(nil)
var node *gListMapNode
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
@ -72,7 +82,7 @@ func (m *ListMap) IteratorDesc(f func(key interface{}, value interface{}) bool)
m.mu.RLock()
defer m.mu.RUnlock()
if m.list != nil {
node := (*gListMapNode)(nil)
var node *gListMapNode
m.list.IteratorDesc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
@ -146,8 +156,10 @@ func (m *ListMap) MapStrAny() map[string]interface{} {
func (m *ListMap) FilterEmpty() {
m.mu.Lock()
if m.list != nil {
keys := make([]interface{}, 0)
node := (*gListMapNode)(nil)
var (
keys = make([]interface{}, 0)
node *gListMapNode
)
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
if empty.IsEmpty(node.value) {
@ -495,7 +507,7 @@ func (m *ListMap) Merge(other *ListMap) {
other.mu.RLock()
defer other.mu.RUnlock()
}
node := (*gListMapNode)(nil)
var node *gListMapNode
other.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
if e, ok := m.data[node.key]; !ok {
@ -514,8 +526,26 @@ func (m *ListMap) String() string {
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m ListMap) MarshalJSON() ([]byte, error) {
return json.Marshal(gconv.Map(m.Map()))
func (m ListMap) MarshalJSON() (jsonBytes []byte, err error) {
if m.data == nil {
return []byte("null"), nil
}
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('{')
m.Iterator(func(key, value interface{}) bool {
valueBytes, valueJsonErr := json.Marshal(value)
if valueJsonErr != nil {
err = valueJsonErr
return false
}
if buffer.Len() > 1 {
buffer.WriteByte(',')
}
buffer.WriteString(fmt.Sprintf(`"%v":%s`, key, valueBytes))
return true
})
buffer.WriteByte('}')
return buffer.Bytes(), nil
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.

View File

@ -8,6 +8,7 @@ package gmap_test
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/internal/json"
@ -195,7 +196,7 @@ func ExampleListMap_Sets() {
m.Sets(addMap)
fmt.Println(m)
// Output:
// May Output:
// {"key1":"val1","key2":"val2","key3":"val3"}
}
@ -562,12 +563,10 @@ func ExampleListMap_String() {
func ExampleListMap_MarshalJSON() {
var m gmap.ListMap
m.Sets(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
"k3": "v3",
"k4": "v4",
})
m.Set("k1", "v1")
m.Set("k2", "v2")
m.Set("k3", "v3")
m.Set("k4", "v4")
bytes, err := json.Marshal(&m)
if err == nil {

View File

@ -190,8 +190,9 @@ func Test_ListMap_Json(t *testing.T) {
}
m1 := gmap.NewListMapFrom(data)
b1, err1 := json.Marshal(m1)
t.AssertNil(err1)
b2, err2 := json.Marshal(gconv.Map(data))
t.Assert(err1, err2)
t.AssertNil(err2)
t.Assert(b1, b2)
})
// Unmarshal
@ -226,6 +227,27 @@ func Test_ListMap_Json(t *testing.T) {
})
}
func Test_ListMap_Json_Sequence(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewListMap()
for i := 'z'; i >= 'a'; i-- {
m.Set(string(i), i)
}
b, err := json.Marshal(m)
t.AssertNil(err)
t.Assert(b, `{"z":122,"y":121,"x":120,"w":119,"v":118,"u":117,"t":116,"s":115,"r":114,"q":113,"p":112,"o":111,"n":110,"m":109,"l":108,"k":107,"j":106,"i":105,"h":104,"g":103,"f":102,"e":101,"d":100,"c":99,"b":98,"a":97}`)
})
gtest.C(t, func(t *gtest.T) {
m := gmap.NewListMap()
for i := 'a'; i <= 'z'; i++ {
m.Set(string(i), i)
}
b, err := json.Marshal(m)
t.AssertNil(err)
t.Assert(b, `{"a":97,"b":98,"c":99,"d":100,"e":101,"f":102,"g":103,"h":104,"i":105,"j":106,"k":107,"l":108,"m":109,"n":110,"o":111,"p":112,"q":113,"r":114,"s":115,"t":116,"u":117,"v":118,"w":119,"x":120,"y":121,"z":122}`)
})
}
func Test_ListMap_Pop(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewListMapFrom(g.MapAnyAny{

View File

@ -109,7 +109,7 @@ func (p *Pool) Get() (interface{}, error) {
if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() {
return f.value, nil
} else if p.ExpireFunc != nil {
// TODO: move expire function calling asynchronously from `Get` operation.
// TODO: move expire function calling asynchronously out from `Get` operation.
p.ExpireFunc(f.value)
}
} else {
@ -129,7 +129,7 @@ func (p *Pool) Size() int {
// Close closes the pool. If `p` has ExpireFunc,
// then it automatically closes all items using this function before it's closed.
// Commonly you do not need call this function manually.
// Commonly you do not need to call this function manually.
func (p *Pool) Close() {
p.closed.Set(true)
}

View File

@ -7,6 +7,7 @@
package gtree
import (
"bytes"
"fmt"
"github.com/gogf/gf/v2/container/gvar"
@ -780,8 +781,26 @@ func output(node *AVLTreeNode, prefix string, isTail bool, str *string) {
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (tree AVLTree) MarshalJSON() ([]byte, error) {
return json.Marshal(tree.MapStrAny())
func (tree AVLTree) MarshalJSON() (jsonBytes []byte, err error) {
if tree.root == nil {
return []byte("null"), nil
}
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('{')
tree.Iterator(func(key, value interface{}) bool {
valueBytes, valueJsonErr := json.Marshal(value)
if valueJsonErr != nil {
err = valueJsonErr
return false
}
if buffer.Len() > 1 {
buffer.WriteByte(',')
}
buffer.WriteString(fmt.Sprintf(`"%v":%s`, key, valueBytes))
return true
})
buffer.WriteByte('}')
return buffer.Bytes(), nil
}
// getComparator returns the comparator if it's previously set,

View File

@ -944,8 +944,26 @@ func (tree *BTree) deleteChild(node *BTreeNode, index int) {
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (tree BTree) MarshalJSON() ([]byte, error) {
return json.Marshal(tree.MapStrAny())
func (tree BTree) MarshalJSON() (jsonBytes []byte, err error) {
if tree.root == nil {
return []byte("null"), nil
}
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('{')
tree.Iterator(func(key, value interface{}) bool {
valueBytes, valueJsonErr := json.Marshal(value)
if valueJsonErr != nil {
err = valueJsonErr
return false
}
if buffer.Len() > 1 {
buffer.WriteByte(',')
}
buffer.WriteString(fmt.Sprintf(`"%v":%s`, key, valueBytes))
return true
})
buffer.WriteByte('}')
return buffer.Bytes(), nil
}
// getComparator returns the comparator if it's previously set,

View File

@ -7,6 +7,7 @@
package gtree
import (
"bytes"
"fmt"
"github.com/gogf/gf/v2/container/gvar"
@ -925,8 +926,26 @@ func (tree *RedBlackTree) nodeColor(node *RedBlackTreeNode) color {
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (tree RedBlackTree) MarshalJSON() ([]byte, error) {
return json.Marshal(gconv.Map(tree.Map()))
func (tree RedBlackTree) MarshalJSON() (jsonBytes []byte, err error) {
if tree.root == nil {
return []byte("null"), nil
}
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('{')
tree.Iterator(func(key, value interface{}) bool {
valueBytes, valueJsonErr := json.Marshal(value)
if valueJsonErr != nil {
err = valueJsonErr
return false
}
if buffer.Len() > 1 {
buffer.WriteByte(',')
}
buffer.WriteString(fmt.Sprintf(`"%v":%s`, key, valueBytes))
return true
})
buffer.WriteByte('}')
return buffer.Bytes(), nil
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.

View File

@ -23,8 +23,8 @@ import (
// UploadFile wraps the multipart uploading file with more and convenient features.
type UploadFile struct {
*multipart.FileHeader
ctx context.Context
*multipart.FileHeader `json:"-"`
ctx context.Context
}
// UploadFiles is array type for *UploadFile.
@ -32,7 +32,7 @@ type UploadFiles []*UploadFile
// Save saves the single uploading file to directory path and returns the saved file name.
//
// The parameter `dirPath` should be a directory path or it returns error.
// The parameter `dirPath` should be a directory path, or it returns error.
//
// Note that it will OVERWRITE the target file if there's already a same name file exist.
func (f *UploadFile) Save(dirPath string, randomlyRename ...bool) (filename string, err error) {

View File

@ -66,14 +66,10 @@ func (r *Request) GetRequestMap(kvMap ...map[string]interface{}) map[string]inte
var (
ok, filter bool
)
var length int
if len(kvMap) > 0 && kvMap[0] != nil {
length = len(kvMap[0])
filter = true
} else {
length = len(r.routerMap) + len(r.queryMap) + len(r.formMap) + len(r.bodyMap) + len(r.paramsMap)
}
m := make(map[string]interface{}, length)
m := make(map[string]interface{})
for k, v := range r.routerMap {
if filter {
if _, ok = kvMap[0][k]; !ok {
@ -114,6 +110,16 @@ func (r *Request) GetRequestMap(kvMap ...map[string]interface{}) map[string]inte
}
m[k] = v
}
// File uploading.
if r.MultipartForm != nil {
for name := range r.MultipartForm.File {
if uploadFiles := r.GetUploadFiles(name); len(uploadFiles) == 1 {
m[name] = uploadFiles[0]
} else {
m[name] = uploadFiles
}
}
}
// Check none exist parameters and assign it with default value.
if filter {
for k, v := range kvMap[0] {
@ -171,9 +177,11 @@ func (r *Request) doGetRequestStruct(pointer interface{}, mapping ...map[string]
if data == nil {
data = map[string]interface{}{}
}
// Default struct values.
if err = r.mergeDefaultStructValue(data, pointer); err != nil {
return data, nil
}
return data, gconv.Struct(data, pointer, mapping...)
}

View File

@ -92,7 +92,7 @@ func (r *Response) ServeFileDownload(path string, name ...string) {
}
r.Header().Set("Content-Type", "application/force-download")
r.Header().Set("Accept-Ranges", "bytes")
r.Header().Set("Content-Disposition", fmt.Sprintf(`attachment;filename="%s"`, url.QueryEscape(downloadName)))
r.Header().Set("Content-Disposition", fmt.Sprintf(`attachment;filename=%s`, url.QueryEscape(downloadName)))
r.Server.serveFile(r.Request, serveFile)
}

View File

@ -128,7 +128,7 @@ func (s *Server) Start() error {
swaggerui.Init()
s.AddStaticPath(s.config.SwaggerPath, swaggerUIPackedPath)
s.BindHookHandler(s.config.SwaggerPath+"/*", HookBeforeServe, s.swaggerUI)
s.Logger().Debugf(
s.Logger().Infof(
ctx,
`swagger ui is serving at address: %s%s/`,
s.getListenAddress(),

View File

@ -150,6 +150,18 @@ type ServerConfig struct {
// It also affects the default storage for session id.
CookieDomain string `json:"cookieDomain"`
// CookieSameSite specifies cookie SameSite property.
// It also affects the default storage for session id.
CookieSameSite string `json:"cookieSameSite"`
// CookieSameSite specifies cookie Secure property.
// It also affects the default storage for session id.
CookieSecure bool `json:"cookieSecure"`
// CookieSameSite specifies cookie HttpOnly property.
// It also affects the default storage for session id.
CookieHttpOnly bool `json:"cookieHttpOnly"`
// ======================================================================================================
// Session.
// ======================================================================================================

View File

@ -7,6 +7,7 @@
package ghttp
import (
"net/http"
"time"
)
@ -39,3 +40,25 @@ func (s *Server) GetCookiePath() string {
func (s *Server) GetCookieDomain() string {
return s.config.CookieDomain
}
// GetCookieSameSite return CookieSameSite of server.
func (s *Server) GetCookieSameSite() http.SameSite {
switch s.config.CookieSameSite {
case "lax":
return http.SameSiteLaxMode
case "none":
return http.SameSiteNoneMode
case "strict":
return http.SameSiteStrictMode
default:
return http.SameSiteDefaultMode
}
}
func (s *Server) GetCookieSecure() bool {
return s.config.CookieSecure
}
func (s *Server) GetCookieHttpOnly() bool {
return s.config.CookieHttpOnly
}

View File

@ -21,6 +21,13 @@ type Cookie struct {
response *Response // Belonged HTTP response.
}
// CookieOptions provides security config for cookies
type CookieOptions struct {
SameSite http.SameSite // cookie SameSite property
Secure bool // cookie Secure property
HttpOnly bool // cookie HttpOnly property
}
// cookieItem is the item stored in Cookie.
type cookieItem struct {
*http.Cookie // Underlying cookie items.
@ -88,24 +95,31 @@ func (c *Cookie) Set(key, value string) {
c.request.Server.GetCookieDomain(),
c.request.Server.GetCookiePath(),
c.request.Server.GetCookieMaxAge(),
CookieOptions{
SameSite: c.request.Server.GetCookieSameSite(),
Secure: c.request.Server.GetCookieSecure(),
HttpOnly: c.request.Server.GetCookieHttpOnly(),
},
)
}
// SetCookie sets cookie item with given domain, path and expiration age.
// The optional parameter `httpOnly` specifies if the cookie item is only available in HTTP,
// The optional parameter `options` specifies extra security configurations,
// which is usually empty.
func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration, httpOnly ...bool) {
func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration, options ...CookieOptions) {
c.init()
isHttpOnly := false
if len(httpOnly) > 0 {
isHttpOnly = httpOnly[0]
config := CookieOptions{}
if len(options) > 0 {
config = options[0]
}
httpCookie := &http.Cookie{
Name: key,
Value: value,
Path: path,
Domain: domain,
HttpOnly: isHttpOnly,
HttpOnly: config.HttpOnly,
SameSite: config.SameSite,
Secure: config.Secure,
}
if maxAge != 0 {
httpCookie.Expires = time.Now().Add(maxAge)
@ -136,6 +150,11 @@ func (c *Cookie) SetSessionId(id string) {
c.request.Server.GetCookieDomain(),
c.request.Server.GetCookiePath(),
c.server.GetSessionCookieMaxAge(),
CookieOptions{
SameSite: c.request.Server.GetCookieSameSite(),
Secure: c.request.Server.GetCookieSecure(),
HttpOnly: c.request.Server.GetCookieHttpOnly(),
},
)
}

View File

@ -11,8 +11,10 @@ import (
)
const (
swaggerUIDefaultURL = `https://petstore.swagger.io/v2/swagger.json`
swaggerUITemplate = `
swaggerUIDocName = `redoc.standalone.js`
swaggerUIDocNamePlaceHolder = `{SwaggerUIDocName}`
swaggerUIDocURLPlaceHolder = `{SwaggerUIDocUrl}`
swaggerUITemplate = `
<!DOCTYPE html>
<html>
<head>
@ -27,8 +29,8 @@ const (
</style>
</head>
<body>
<redoc spec-url="https://petstore.swagger.io/v2/swagger.json"></redoc>
<script src="redoc.standalone.js"> </script>
<redoc spec-url="{SwaggerUIDocUrl}"></redoc>
<script src="{SwaggerUIDocName}"> </script>
</body>
</html>
`
@ -41,11 +43,11 @@ func (s *Server) swaggerUI(r *Request) {
return
}
if r.StaticFile != nil && r.StaticFile.File != nil && r.StaticFile.IsDir {
r.Response.Write(gstr.Replace(
swaggerUITemplate,
swaggerUIDefaultURL,
s.config.OpenApiPath,
))
content := gstr.ReplaceByMap(swaggerUITemplate, map[string]string{
swaggerUIDocURLPlaceHolder: s.config.OpenApiPath,
swaggerUIDocNamePlaceHolder: gstr.TrimRight(r.GetUrl(), "/") + "/" + swaggerUIDocName,
})
r.Response.Write(content)
r.ExitAll()
}
}

View File

@ -29,6 +29,9 @@ func Test_ConfigFromMap(t *testing.T) {
"indexFiles": g.Slice{"index.php", "main.php"},
"errorLogEnabled": true,
"cookieMaxAge": "1y",
"cookieSameSite": "lax",
"cookieSecure": true,
"cookieHttpOnly": true,
}
config, err := ghttp.ConfigFromMap(m)
t.Assert(err, nil)
@ -39,6 +42,9 @@ func Test_ConfigFromMap(t *testing.T) {
t.Assert(config.CookieMaxAge, d2)
t.Assert(config.IndexFiles, m["indexFiles"])
t.Assert(config.ErrorLogEnabled, m["errorLogEnabled"])
t.Assert(config.CookieSameSite, m["cookieSameSite"])
t.Assert(config.CookieSecure, m["cookieSecure"])
t.Assert(config.CookieHttpOnly, m["cookieHttpOnly"])
})
}
@ -55,6 +61,9 @@ func Test_SetConfigWithMap(t *testing.T) {
"SessionIdName": "MySessionId",
"SessionPath": "/tmp/MySessionStoragePath",
"SessionMaxAge": 24 * time.Hour,
"cookieSameSite": "lax",
"cookieSecure": true,
"cookieHttpOnly": true,
}
s := g.Server()
err := s.SetConfigWithMap(m)

View File

@ -9,6 +9,7 @@ package ghttp_test
import (
"fmt"
"net/http"
"strings"
"testing"
"time"
@ -101,3 +102,67 @@ func Test_SetHttpCookie(t *testing.T) {
//t.Assert(client.GetContent(ctx, "/get?k=key2"), "200")
})
}
func Test_CookieOptionsDefault(t *testing.T) {
s := g.Server(guid.S())
s.BindHandler("/test", func(r *ghttp.Request) {
r.Cookie.Set(r.Get("k").String(), r.Get("v").String())
})
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := g.Client()
client.SetBrowserMode(true)
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
r1, e1 := client.Get(ctx, "/test?k=key1&v=100")
if r1 != nil {
defer r1.Close()
}
t.Assert(e1, nil)
t.Assert(r1.ReadAllString(), "")
parts := strings.Split(r1.Header.Get("Set-Cookie"), "; ")
t.AssertIN(len(parts), []int{3, 4}) // For go < 1.16 cookie always output "SameSite", see: https://github.com/golang/go/commit/542693e00529fbb4248fac614ece68b127a5ec4d
})
}
func Test_CookieOptions(t *testing.T) {
s := g.Server(guid.S())
s.SetConfigWithMap(g.Map{
"cookieSameSite": "lax",
"cookieSecure": true,
"cookieHttpOnly": true,
})
s.BindHandler("/test", func(r *ghttp.Request) {
r.Cookie.Set(r.Get("k").String(), r.Get("v").String())
})
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := g.Client()
client.SetBrowserMode(true)
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
r1, e1 := client.Get(ctx, "/test?k=key1&v=100")
if r1 != nil {
defer r1.Close()
}
t.Assert(e1, nil)
t.Assert(r1.ReadAllString(), "")
parts := strings.Split(r1.Header.Get("Set-Cookie"), "; ")
t.AssertEQ(len(parts), 6)
t.Assert(parts[3], "HttpOnly")
t.Assert(parts[4], "Secure")
t.Assert(parts[5], "SameSite=Lax")
})
}

View File

@ -7,6 +7,7 @@
package ghttp_test
import (
"context"
"fmt"
"testing"
"time"
@ -18,6 +19,7 @@ import (
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gmeta"
"github.com/gogf/gf/v2/util/guid"
)
@ -169,3 +171,49 @@ func Test_Params_File_Batch(t *testing.T) {
t.Assert(gfile.GetContents(dstPath2), gfile.GetContents(srcPath2))
})
}
func Test_Params_Strict_Route_File_Single(t *testing.T) {
type Req struct {
gmeta.Meta `method:"post" mime:"multipart/form-data"`
File *ghttp.UploadFile `type:"file"`
}
type Res struct{}
dstDirPath := gfile.Temp(gtime.TimestampNanoStr())
s := g.Server(guid.S())
s.BindHandler("/upload/single", func(ctx context.Context, req *Req) (res *Res, err error) {
var (
r = g.RequestFromCtx(ctx)
file = req.File
)
if file == nil {
r.Response.WriteExit("upload file cannot be empty")
}
name, err := file.Save(dstDirPath)
if err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit(name)
return
})
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
// normal name
gtest.C(t, func(t *gtest.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
srcPath := gdebug.TestDataPath("upload", "file1.txt")
dstPath := gfile.Join(dstDirPath, "file1.txt")
content := client.PostContent(ctx, "/upload/single", g.Map{
"file": "@file:" + srcPath,
})
t.AssertNE(content, "")
t.AssertNE(content, "upload file cannot be empty")
t.AssertNE(content, "upload failed")
t.Assert(content, "file1.txt")
t.Assert(gfile.GetContents(dstPath), gfile.GetContents(srcPath))
})
}

View File

@ -221,9 +221,7 @@ func newCommandFromMethod(object interface{}, method reflect.Value) (command *Co
return
}
var (
inputObject reflect.Value
)
var inputObject reflect.Value
if method.Type().In(1).Kind() == reflect.Ptr {
inputObject = reflect.New(method.Type().In(1).Elem()).Elem()
} else {
@ -264,8 +262,19 @@ func newCommandFromMethod(object interface{}, method reflect.Value) (command *Co
}
} else {
// Read argument from command line option name.
if arg.Orphan && parser.GetOpt(arg.Name) != nil {
data[arg.Name] = "true"
if arg.Orphan {
if orphanValue := parser.GetOpt(arg.Name); orphanValue != nil {
if orphanValue.String() == "" {
// Eg: gf -f
data[arg.Name] = "true"
} else {
// Adapter with common user habits.
// Eg:
// `gf -f=0`: which parameter `f` is parsed as false
// `gf -f=1`: which parameter `f` is parsed as true
data[arg.Name] = orphanValue.Bool()
}
}
}
}
}

View File

@ -258,7 +258,7 @@ func Test_Command_Pointer(t *testing.T) {
t.AssertNil(err)
t.Assert(value, `{"Content":"john"}`)
})
return
gtest.C(t, func(t *gtest.T) {
var (
ctx = gctx.New()
@ -272,3 +272,42 @@ func Test_Command_Pointer(t *testing.T) {
t.Assert(value, `{"Content":"john"}`)
})
}
type TestCommandOrphan struct {
g.Meta `name:"root" root:"root"`
}
type TestCommandOrphanIndexInput struct {
g.Meta `name:"index"`
Orphan1 bool `short:"n1" orphan:"true"`
Orphan2 bool `short:"n2" orphan:"true"`
Orphan3 bool `short:"n3" orphan:"true"`
}
type TestCommandOrphanIndexOutput struct {
Orphan1 bool
Orphan2 bool
Orphan3 bool
}
func (c *TestCommandOrphan) Index(ctx context.Context, in TestCommandOrphanIndexInput) (out *TestCommandOrphanIndexOutput, err error) {
out = &TestCommandOrphanIndexOutput{
Orphan1: in.Orphan1,
Orphan2: in.Orphan2,
Orphan3: in.Orphan3,
}
return
}
func Test_Command_Orphan_Parameter(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var ctx = gctx.New()
cmd, err := gcmd.NewFromObject(TestCommandOrphan{})
t.AssertNil(err)
os.Args = []string{"root", "index", "-n1", "-n2=0", "-n3=1"}
value, err := cmd.RunWithValueError(ctx)
t.AssertNil(err)
t.Assert(value.(*TestCommandOrphanIndexOutput).Orphan1, true)
t.Assert(value.(*TestCommandOrphanIndexOutput).Orphan2, false)
t.Assert(value.(*TestCommandOrphanIndexOutput).Orphan3, true)
})
}

View File

@ -107,10 +107,12 @@ func getCommPidFolderPath() (folderPath string, err error) {
break
}
}
err = gerror.Newf(
`cannot find available folder for storing pid to port mapping files in paths: %+v`,
availablePaths,
)
if commPidFolderPath == "" {
err = gerror.Newf(
`cannot find available folder for storing pid to port mapping files in paths: %+v`,
availablePaths,
)
}
})
folderPath = commPidFolderPath
return

View File

@ -43,7 +43,6 @@ type ExternalDocs struct {
}
const (
HttpMethodAll = `ALL`
HttpMethodGet = `GET`
HttpMethodPut = `PUT`
HttpMethodPost = `POST`
@ -82,9 +81,14 @@ const (
TagNamePath = `path`
TagNameMethod = `method`
TagNameMime = `mime`
TagNameConsumes = `consumes`
TagNameType = `type`
TagNameDomain = `domain`
TagNameValidate = `v`
)
const (
patternKeyForRequired = `required`
patternKeyForIn = `in:`
)
var (

View File

@ -9,6 +9,7 @@ package goai
import (
"fmt"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
@ -54,7 +55,8 @@ func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path
parameter.Name = field.Name()
}
if len(tagMap) > 0 {
if err := gconv.Struct(oai.fileMapWithShortTags(tagMap), parameter); err != nil {
err := gconv.Struct(oai.fileMapWithShortTags(tagMap), parameter)
if err != nil {
return nil, gerror.Wrap(err, `mapping struct tags to Parameter failed`)
}
}
@ -80,10 +82,6 @@ func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path
parameter.Required = true
case ParameterInCookie, ParameterInHeader, ParameterInQuery:
// Check validation tag.
if validateTagValue := field.Tag(TagNameValidate); gstr.ContainsI(validateTagValue, `required`) {
parameter.Required = true
}
default:
return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid tag value "%s" for In`, parameter.In)
@ -95,6 +93,13 @@ func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path
}
parameter.Schema = schemaRef
// Required check.
if parameter.Schema.Value != nil && parameter.Schema.Value.Pattern != "" {
if gset.NewStrSetFrom(gstr.Split(parameter.Schema.Value.Pattern, "|")).Contains(patternKeyForRequired) {
parameter.Required = true
}
}
return &ParameterRef{
Ref: "",
Value: parameter,

View File

@ -80,6 +80,7 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
}
var (
mime string
path = Path{}
inputMetaMap = gmeta.Data(inputObject.Interface())
outputMetaMap = gmeta.Data(outputObject.Interface())
@ -126,12 +127,17 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
}
if len(inputMetaMap) > 0 {
if err := gconv.Struct(oai.fileMapWithShortTags(inputMetaMap), &path); err != nil {
inputMetaMap = oai.fileMapWithShortTags(inputMetaMap)
if err := gconv.Struct(inputMetaMap, &path); err != nil {
return gerror.Wrap(err, `mapping struct tags to Path failed`)
}
if err := gconv.Struct(oai.fileMapWithShortTags(inputMetaMap), &operation); err != nil {
if err := gconv.Struct(inputMetaMap, &operation); err != nil {
return gerror.Wrap(err, `mapping struct tags to Operation failed`)
}
// Allowed request mime.
if mime = inputMetaMap[TagNameMime]; mime == "" {
mime = inputMetaMap[TagNameConsumes]
}
}
// =================================================================================================================

View File

@ -47,18 +47,19 @@ func (oai *OpenApiV3) getRequestSchemaRef(in getRequestSchemaRefInput) (*SchemaR
}
var (
dataFieldsPartsArray = gstr.Split(in.RequestDataField, ".")
bizRequestStructSchemaRef, bizRequestStructSchemaRefExist = oai.Components.Schemas[in.BusinessStructName]
schema, err = oai.structToSchema(in.RequestObject)
dataFieldsPartsArray = gstr.Split(in.RequestDataField, ".")
bizRequestStructSchemaRef = oai.Components.Schemas.Get(in.BusinessStructName)
schema, err = oai.structToSchema(in.RequestObject)
)
if err != nil {
return nil, err
}
if in.RequestDataField == "" && bizRequestStructSchemaRefExist {
if in.RequestDataField == "" && bizRequestStructSchemaRef != nil {
// Normal request.
for k, v := range bizRequestStructSchemaRef.Value.Properties {
schema.Properties[k] = v
}
bizRequestStructSchemaRef.Value.Properties.Iterator(func(key string, ref SchemaRef) bool {
schema.Properties.Set(key, ref)
return true
})
} else {
// Common request.
structFields, _ := gstructs.Fields(gstructs.FieldsInput{
@ -76,7 +77,7 @@ func (oai *OpenApiV3) getRequestSchemaRef(in getRequestSchemaRefInput) (*SchemaR
if err = oai.tagMapToSchema(structField.TagMap(), bizRequestStructSchemaRef.Value); err != nil {
return nil, err
}
schema.Properties[fieldName] = bizRequestStructSchemaRef
schema.Properties.Set(fieldName, *bizRequestStructSchemaRef)
break
}
default:
@ -90,13 +91,12 @@ func (oai *OpenApiV3) getRequestSchemaRef(in getRequestSchemaRefInput) (*SchemaR
if err != nil {
return nil, err
}
schema.Properties[fieldName] = *schemaRef
schema.Properties.Set(fieldName, *schemaRef)
break
}
}
}
}
return &SchemaRef{
Value: schema,
}, nil

View File

@ -51,18 +51,19 @@ func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*Schem
}
var (
dataFieldsPartsArray = gstr.Split(in.CommonResponseDataField, ".")
bizResponseStructSchemaRef, bizResponseStructSchemaRefExist = oai.Components.Schemas[in.BusinessStructName]
schema, err = oai.structToSchema(in.CommonResponseObject)
dataFieldsPartsArray = gstr.Split(in.CommonResponseDataField, ".")
bizResponseStructSchemaRef = oai.Components.Schemas.Get(in.BusinessStructName)
schema, err = oai.structToSchema(in.CommonResponseObject)
)
if err != nil {
return nil, err
}
if in.CommonResponseDataField == "" && bizResponseStructSchemaRefExist {
if in.CommonResponseDataField == "" && bizResponseStructSchemaRef != nil {
// Normal response.
for k, v := range bizResponseStructSchemaRef.Value.Properties {
schema.Properties[k] = v
}
bizResponseStructSchemaRef.Value.Properties.Iterator(func(key string, ref SchemaRef) bool {
schema.Properties.Set(key, ref)
return true
})
} else {
// Common response.
structFields, _ := gstructs.Fields(gstructs.FieldsInput{
@ -80,7 +81,7 @@ func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*Schem
if err = oai.tagMapToSchema(structField.TagMap(), bizResponseStructSchemaRef.Value); err != nil {
return nil, err
}
schema.Properties[fieldName] = bizResponseStructSchemaRef
schema.Properties.Set(fieldName, *bizResponseStructSchemaRef)
break
}
default:
@ -95,7 +96,7 @@ func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*Schem
if err != nil {
return nil, err
}
schema.Properties[fieldName] = *schemaRef
schema.Properties.Set(fieldName, *schemaRef)
break
}
}

View File

@ -9,6 +9,8 @@ package goai
import (
"reflect"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gstructs"
@ -18,8 +20,6 @@ import (
"github.com/gogf/gf/v2/util/gvalid"
)
type Schemas map[string]SchemaRef
// Schema is specified by OpenAPI/Swagger 3.0 standard.
type Schema struct {
OneOf SchemaRefs `json:"oneOf,omitempty" yaml:"oneOf,omitempty"`
@ -78,8 +78,8 @@ func (oai *OpenApiV3) addSchema(object ...interface{}) error {
}
func (oai *OpenApiV3) doAddSchemaSingle(object interface{}) error {
if oai.Components.Schemas == nil {
oai.Components.Schemas = map[string]SchemaRef{}
if oai.Components.Schemas.refs == nil {
oai.Components.Schemas.refs = gmap.NewListMap()
}
var (
@ -88,21 +88,21 @@ func (oai *OpenApiV3) doAddSchemaSingle(object interface{}) error {
)
// Already added.
if _, ok := oai.Components.Schemas[structTypeName]; ok {
if oai.Components.Schemas.Get(structTypeName) != nil {
return nil
}
// Take the holder first.
oai.Components.Schemas[structTypeName] = SchemaRef{}
oai.Components.Schemas.Set(structTypeName, SchemaRef{})
schema, err := oai.structToSchema(object)
if err != nil {
return err
}
oai.Components.Schemas[structTypeName] = SchemaRef{
oai.Components.Schemas.Set(structTypeName, SchemaRef{
Ref: "",
Value: schema,
}
})
return nil
}
@ -111,7 +111,7 @@ func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
var (
tagMap = gmeta.Data(object)
schema = &Schema{
Properties: map[string]SchemaRef{},
Properties: createSchemas(),
}
)
if len(tagMap) > 0 {
@ -153,8 +153,18 @@ func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
if err != nil {
return nil, err
}
schema.Properties[fieldName] = *schemaRef
schema.Properties.Set(fieldName, *schemaRef)
}
schema.Properties.Iterator(func(key string, ref SchemaRef) bool {
if ref.Value != nil && ref.Value.Pattern != "" {
validationRuleSet := gset.NewStrSetFrom(gstr.Split(ref.Value.Pattern, "|"))
if validationRuleSet.Contains(patternKeyForRequired) {
schema.Required = append(schema.Required, key)
}
}
return true
})
return schema, nil
}
@ -166,8 +176,16 @@ func (oai *OpenApiV3) tagMapToSchema(tagMap map[string]string, schema *Schema) e
// Validation info to OpenAPI schema pattern.
for _, tag := range gvalid.GetTags() {
if validationTagValue, ok := tagMap[tag]; ok {
_, validationRule, _ := gvalid.ParseTagValue(validationTagValue)
schema.Pattern = validationRule
_, validationRules, _ := gvalid.ParseTagValue(validationTagValue)
schema.Pattern = validationRules
// Enum checks.
if len(schema.Enum) == 0 {
for _, rule := range gstr.SplitAndTrim(validationRules, "|") {
if gstr.HasPrefix(rule, patternKeyForIn) {
schema.Enum = gconv.Interfaces(gstr.SplitAndTrim(rule[len(patternKeyForIn):], ","))
}
}
}
break
}
}

View File

@ -70,7 +70,7 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap
var (
structTypeName = oai.golangTypeToSchemaName(golangType)
)
if _, ok := oai.Components.Schemas[structTypeName]; !ok {
if oai.Components.Schemas.Get(structTypeName) == nil {
if err := oai.addSchema(reflect.New(golangType).Interface()); err != nil {
return nil, err
}
@ -81,7 +81,7 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap
default:
// Normal struct object.
var structTypeName = oai.golangTypeToSchemaName(golangType)
if _, ok := oai.Components.Schemas[structTypeName]; !ok {
if oai.Components.Schemas.Get(structTypeName) == nil {
if err := oai.addSchema(reflect.New(golangType).Elem().Interface()); err != nil {
return nil, err
}

View File

@ -0,0 +1,64 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package goai
import (
"github.com/gogf/gf/v2/container/gmap"
)
type Schemas struct {
refs *gmap.ListMap
}
func createSchemas() Schemas {
return Schemas{
refs: gmap.NewListMap(),
}
}
func (s *Schemas) init() {
if s.refs == nil {
s.refs = gmap.NewListMap()
}
}
func (s Schemas) Get(name string) *SchemaRef {
s.init()
value := s.refs.Get(name)
if value != nil {
ref := value.(SchemaRef)
return &ref
}
return nil
}
func (s Schemas) Set(name string, ref SchemaRef) {
s.init()
s.refs.Set(name, ref)
}
func (s Schemas) Map() map[string]SchemaRef {
s.init()
m := make(map[string]SchemaRef)
s.refs.Iterator(func(key, value interface{}) bool {
m[key.(string)] = value.(SchemaRef)
return true
})
return m
}
func (s Schemas) Iterator(f func(key string, ref SchemaRef) bool) {
s.init()
s.refs.Iterator(func(key, value interface{}) bool {
return f(key.(string), value.(SchemaRef))
})
}
func (s Schemas) MarshalJSON() ([]byte, error) {
s.init()
return s.refs.MarshalJSON()
}

View File

@ -48,14 +48,14 @@ func Test_Basic(t *testing.T) {
})
t.AssertNil(err)
// Schema asserts.
t.Assert(len(oai.Components.Schemas), 2)
t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Type, goai.TypeObject)
t.Assert(len(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Properties), 7)
t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Properties[`appId`].Value.Type, goai.TypeNumber)
t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Properties[`resourceId`].Value.Type, goai.TypeString)
t.Assert(len(oai.Components.Schemas.Map()), 2)
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Type, goai.TypeObject)
t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Map()), 7)
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Get(`appId`).Value.Type, goai.TypeNumber)
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Get(`resourceId`).Value.Type, goai.TypeString)
t.Assert(len(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`].Value.Properties), 3)
t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`].Value.Properties[`Params`].Value.Type, goai.TypeArray)
t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`).Value.Properties.Map()), 3)
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`).Value.Properties.Get(`Params`).Value.Type, goai.TypeArray)
})
}
@ -108,14 +108,14 @@ func TestOpenApiV3_Add(t *testing.T) {
t.AssertNil(err)
// fmt.Println(oai.String())
// Schema asserts.
t.Assert(len(oai.Components.Schemas), 3)
t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Type, goai.TypeObject)
t.Assert(len(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Properties), 7)
t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Properties[`appId`].Value.Type, goai.TypeNumber)
t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].Value.Properties[`resourceId`].Value.Type, goai.TypeString)
t.Assert(len(oai.Components.Schemas.Map()), 3)
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Type, goai.TypeObject)
t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Map()), 7)
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Get(`appId`).Value.Type, goai.TypeNumber)
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).Value.Properties.Get(`resourceId`).Value.Type, goai.TypeString)
t.Assert(len(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`].Value.Properties), 3)
t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`].Value.Properties[`Params`].Value.Type, goai.TypeArray)
t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`).Value.Properties.Map()), 3)
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`).Value.Properties.Get(`Params`).Value.Type, goai.TypeArray)
// Paths.
t.Assert(len(oai.Paths), 1)
@ -158,9 +158,9 @@ func TestOpenApiV3_Add_Recursive(t *testing.T) {
})
t.AssertNil(err)
// Schema asserts.
t.Assert(len(oai.Components.Schemas), 3)
t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CategoryTreeItem`].Value.Type, goai.TypeObject)
t.Assert(len(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CategoryTreeItem`].Value.Properties), 3)
t.Assert(len(oai.Components.Schemas.Map()), 3)
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CategoryTreeItem`).Value.Type, goai.TypeObject)
t.Assert(len(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CategoryTreeItem`).Value.Properties.Map()), 3)
})
}
@ -222,7 +222,7 @@ func TestOpenApiV3_Add_AutoDetectIn(t *testing.T) {
fmt.Println(oai.String())
t.Assert(len(oai.Components.Schemas), 2)
t.Assert(len(oai.Components.Schemas.Map()), 2)
t.Assert(len(oai.Paths), 1)
t.AssertNE(oai.Paths[path].Get, nil)
t.Assert(len(oai.Paths[path].Get.Parameters), 3)
@ -274,9 +274,9 @@ func TestOpenApiV3_CommonRequest(t *testing.T) {
})
t.AssertNil(err)
// Schema asserts.
t.Assert(len(oai.Components.Schemas), 3)
t.Assert(len(oai.Components.Schemas.Map()), 3)
t.Assert(len(oai.Paths), 1)
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties), 3)
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Map()), 3)
})
}
@ -319,9 +319,9 @@ func TestOpenApiV3_CommonRequest_WithoutDataField_Setting(t *testing.T) {
t.AssertNil(err)
// Schema asserts.
// fmt.Println(oai.String())
t.Assert(len(oai.Components.Schemas), 3)
t.Assert(len(oai.Components.Schemas.Map()), 3)
t.Assert(len(oai.Paths), 1)
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties), 5)
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Map()), 5)
})
}
@ -359,9 +359,9 @@ func TestOpenApiV3_CommonRequest_EmptyRequest(t *testing.T) {
t.AssertNil(err)
// Schema asserts.
// fmt.Println(oai.String())
t.Assert(len(oai.Components.Schemas), 3)
t.Assert(len(oai.Components.Schemas.Map()), 3)
t.Assert(len(oai.Paths), 1)
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties), 3)
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Map()), 3)
})
}
@ -414,10 +414,10 @@ func TestOpenApiV3_CommonRequest_SubDataField(t *testing.T) {
t.AssertNil(err)
// Schema asserts.
// fmt.Println(oai.String())
t.Assert(len(oai.Components.Schemas), 4)
t.Assert(len(oai.Components.Schemas.Map()), 4)
t.Assert(len(oai.Paths), 1)
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties), 1)
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties[`Request`].Value.Properties), 4)
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Map()), 1)
t.Assert(len(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Value.Properties.Get(`Request`).Value.Properties.Map()), 4)
})
}
@ -460,13 +460,13 @@ func TestOpenApiV3_CommonResponse(t *testing.T) {
})
t.AssertNil(err)
//g.Dump(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties)
//g.Dump(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map())
// Schema asserts.
t.Assert(len(oai.Components.Schemas), 3)
t.Assert(len(oai.Components.Schemas.Map()), 3)
t.Assert(len(oai.Paths), 1)
t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties), 3)
t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 3)
t.Assert(
oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties["data"].Value.Description,
oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Get("data").Value.Description,
`Result data for certain request according API definition`,
)
})
@ -511,9 +511,9 @@ func TestOpenApiV3_CommonResponse_WithoutDataField_Setting(t *testing.T) {
t.AssertNil(err)
// Schema asserts.
fmt.Println(oai.String())
t.Assert(len(oai.Components.Schemas), 3)
t.Assert(len(oai.Components.Schemas.Map()), 3)
t.Assert(len(oai.Paths), 1)
t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties), 8)
t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 8)
})
}
@ -551,10 +551,10 @@ func TestOpenApiV3_CommonResponse_EmptyResponse(t *testing.T) {
t.AssertNil(err)
// Schema asserts.
// fmt.Println(oai.String())
t.Assert(len(oai.Components.Schemas), 3)
t.Assert(len(oai.Components.Schemas.Map()), 3)
t.Assert(len(oai.Paths), 1)
t.Assert(oai.Paths["/index"].Put.RequestBody.Value.Content["application/json"].Schema.Ref, `github.com.gogf.gf.v2.protocol.goai_test.Req`)
t.Assert(len(oai.Paths["/index"].Put.Responses["200"].Value.Content["application/json"].Schema.Value.Properties), 3)
t.Assert(len(oai.Paths["/index"].Put.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 3)
})
}
@ -607,10 +607,10 @@ func TestOpenApiV3_CommonResponse_SubDataField(t *testing.T) {
t.AssertNil(err)
// Schema asserts.
// fmt.Println(oai.String())
t.Assert(len(oai.Components.Schemas), 4)
t.Assert(len(oai.Components.Schemas.Map()), 4)
t.Assert(len(oai.Paths), 1)
t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties), 1)
t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties[`Response`].Value.Properties), 7)
t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 1)
t.Assert(len(oai.Paths["/index"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Get(`Response`).Value.Properties.Map()), 7)
})
}
@ -663,12 +663,12 @@ func TestOpenApiV3_ShortTags(t *testing.T) {
t.AssertNil(err)
// fmt.Println(oai.String())
// Schema asserts.
t.Assert(len(oai.Components.Schemas), 3)
t.Assert(len(oai.Components.Schemas.Map()), 3)
t.Assert(oai.Paths[`/test1/{appId}`].Summary, `CreateResourceReq sum`)
t.Assert(oai.
Components.
Schemas[`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`].
Value.Properties[`resourceId`].Value.Description, `资源Id`)
Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`).
Value.Properties.Get(`resourceId`).Value.Description, `资源Id`)
})
}
@ -698,7 +698,7 @@ func TestOpenApiV3_HtmlResponse(t *testing.T) {
t.AssertNil(err)
// fmt.Println(oai.String())
t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.Res`].Value.Type, goai.TypeString)
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.Res`).Value.Type, goai.TypeString)
})
}
@ -745,6 +745,57 @@ func TestOpenApiV3_HtmlResponseWithCommonResponse(t *testing.T) {
t.AssertNil(err)
// fmt.Println(oai.String())
t.Assert(oai.Components.Schemas[`github.com.gogf.gf.v2.protocol.goai_test.Res`].Value.Type, goai.TypeString)
t.Assert(oai.Components.Schemas.Get(`github.com.gogf.gf.v2.protocol.goai_test.Res`).Value.Type, goai.TypeString)
})
}
func Test_Required_In_Schema(t *testing.T) {
type CommonReq struct {
AppId int64 `json:"appId" v:"required" in:"cookie" description:"应用Id"`
ResourceId string `json:"resourceId" in:"query" description:"资源Id"`
}
type SetSpecInfo struct {
StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" description:"StorageType"`
Shards int32 `description:"shards 分片数"`
Params []string `description:"默认参数(json 串-ClickHouseParams)"`
}
type CreateResourceReq struct {
CommonReq
gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default"`
Name string `description:"实例名称"`
Product string `description:"业务类型"`
Region string `v:"required|min:1" description:"区域"`
SetMap map[string]*SetSpecInfo `v:"required|min:1" description:"配置Map"`
SetSlice []SetSpecInfo `v:"required|min:1" description:"配置Slice"`
}
gtest.C(t, func(t *gtest.T) {
var (
err error
oai = goai.New()
req = new(CreateResourceReq)
)
err = oai.Add(goai.AddInput{
Object: req,
})
t.AssertNil(err)
var (
schemaKey1 = `github.com.gogf.gf.v2.protocol.goai_test.CreateResourceReq`
schemaKey2 = `github.com.gogf.gf.v2.protocol.goai_test.SetSpecInfo`
)
t.Assert(oai.Components.Schemas.Map()[schemaKey1].Value.Required, g.Slice{
"appId",
"Region",
"SetMap",
"SetSlice",
})
t.Assert(oai.Components.Schemas.Map()[schemaKey2].Value.Required, g.Slice{
"StorageType",
})
t.Assert(oai.Components.Schemas.Map()[schemaKey2].Value.Properties.Map()["StorageType"].Value.Enum, g.Slice{
"CLOUD_PREMIUM",
"CLOUD_SSD",
"CLOUD_HSSD",
})
})
}