diff --git a/.github/ISSUE_TEMPLATE.MD b/.github/ISSUE_TEMPLATE.MD
index 364044d16..540e46b4b 100644
--- a/.github/ISSUE_TEMPLATE.MD
+++ b/.github/ISSUE_TEMPLATE.MD
@@ -1,12 +1,11 @@
-
-
-
+
+
+
### 1. What version of `Go` and system type/arch are you using?
-
@@ -23,7 +21,6 @@ What expect to see is like: `go 1.12, linux/amd64`
### 4. What did you do?
-
\n")
return buffer.String()
@@ -224,7 +229,42 @@ func (view *View) buildInFuncNl2Br(str interface{}) string {
// buildInFuncJson implements build-in template function: json ,
// which encodes and returns `value` as JSON string.
func (view *View) buildInFuncJson(value interface{}) (string, error) {
- b, err := json.Marshal(value)
+ b, err := gjson.Marshal(value)
+ return string(b), err
+}
+
+// buildInFuncXml implements build-in template function: xml ,
+// which encodes and returns `value` as XML string.
+func (view *View) buildInFuncXml(value interface{}, rootTag ...string) (string, error) {
+ b, err := gjson.New(value).ToXml(rootTag...)
+ return string(b), err
+}
+
+// buildInFuncXml implements build-in template function: ini ,
+// which encodes and returns `value` as XML string.
+func (view *View) buildInFuncIni(value interface{}) (string, error) {
+ b, err := gjson.New(value).ToIni()
+ return string(b), err
+}
+
+// buildInFuncYaml implements build-in template function: yaml ,
+// which encodes and returns `value` as YAML string.
+func (view *View) buildInFuncYaml(value interface{}) (string, error) {
+ b, err := gjson.New(value).ToYaml()
+ return string(b), err
+}
+
+// buildInFuncYamlIndent implements build-in template function: yamli ,
+// which encodes and returns `value` as YAML string with custom indent string.
+func (view *View) buildInFuncYamlIndent(value, indent interface{}) (string, error) {
+ b, err := gjson.New(value).ToYamlIndent(gconv.String(indent))
+ return string(b), err
+}
+
+// buildInFuncToml implements build-in template function: toml ,
+// which encodes and returns `value` as TOML string.
+func (view *View) buildInFuncToml(value interface{}) (string, error) {
+ b, err := gjson.New(value).ToToml()
return string(b), err
}
diff --git a/os/gview/gview_parse.go b/os/gview/gview_parse.go
index 418bb3fdf..44ccbffca 100644
--- a/os/gview/gview_parse.go
+++ b/os/gview/gview_parse.go
@@ -59,9 +59,64 @@ var (
// Parse parses given template file `file` with given template variables `params`
// and returns the parsed template content.
func (view *View) Parse(ctx context.Context, file string, params ...Params) (result string, err error) {
- var tpl interface{}
- // It caches the file, folder and its content to enhance performance.
- r := view.fileCacheMap.GetOrSetFuncLock(file, func() interface{} {
+ var usedParams Params
+ if len(params) > 0 {
+ usedParams = params[0]
+ }
+ return view.ParseOption(ctx, Option{
+ File: file,
+ Content: "",
+ Orphan: false,
+ Params: usedParams,
+ })
+}
+
+// ParseDefault parses the default template file with params.
+func (view *View) ParseDefault(ctx context.Context, params ...Params) (result string, err error) {
+ var usedParams Params
+ if len(params) > 0 {
+ usedParams = params[0]
+ }
+ return view.ParseOption(ctx, Option{
+ File: view.config.DefaultFile,
+ Content: "",
+ Orphan: false,
+ Params: usedParams,
+ })
+}
+
+// ParseContent parses given template content `content` with template variables `params`
+// and returns the parsed content in []byte.
+func (view *View) ParseContent(ctx context.Context, content string, params ...Params) (string, error) {
+ var usedParams Params
+ if len(params) > 0 {
+ usedParams = params[0]
+ }
+ return view.ParseOption(ctx, Option{
+ Content: content,
+ Orphan: false,
+ Params: usedParams,
+ })
+}
+
+// Option for template parsing.
+type Option struct {
+ File string // Template file path in absolute or relative to searching paths.
+ Content string // Template content, it ignores `File` if `Content` is given.
+ Orphan bool // If true, the `File` is considered as a single file parsing without files recursively parsing from its folder.
+ Params Params // Template parameters map.
+}
+
+// ParseOption implements template parsing using Option.
+func (view *View) ParseOption(ctx context.Context, option Option) (result string, err error) {
+ if option.Content != "" {
+ return view.doParseContent(ctx, option.Content, option.Params)
+ }
+ if option.File == "" {
+ return "", gerror.New(`template file cannot be empty`)
+ }
+ // It caches the file, folder and content to enhance performance.
+ r := view.fileCacheMap.GetOrSetFuncLock(option.File, func() interface{} {
var (
path string
folder string
@@ -69,7 +124,7 @@ func (view *View) Parse(ctx context.Context, file string, params ...Params) (res
resource *gres.File
)
// Searching the absolute file path for `file`.
- path, folder, resource, err = view.searchFile(ctx, file)
+ path, folder, resource, err = view.searchFile(ctx, option.File)
if err != nil {
return nil
}
@@ -80,13 +135,13 @@ func (view *View) Parse(ctx context.Context, file string, params ...Params) (res
}
// Monitor template files changes using fsnotify asynchronously.
if resource == nil {
- if _, err := gfsnotify.AddOnce("gview.Parse:"+folder, folder, func(event *gfsnotify.Event) {
+ if _, err = gfsnotify.AddOnce("gview.Parse:"+folder, folder, func(event *gfsnotify.Event) {
// CLEAR THEM ALL.
view.fileCacheMap.Clear()
templates.Clear()
gfsnotify.Exit()
}); err != nil {
- intlog.Error(ctx, err)
+ intlog.Errorf(ctx, `%+v`, err)
}
}
return &fileCacheItem{
@@ -103,7 +158,12 @@ func (view *View) Parse(ctx context.Context, file string, params ...Params) (res
if item.content == "" {
return "", nil
}
+ // If it's Orphan option, it just parses the single file by ParseContent.
+ if option.Orphan {
+ return view.doParseContent(ctx, item.content, option.Params)
+ }
// Get the template object instance for `folder`.
+ var tpl interface{}
tpl, err = view.getTemplate(item.path, item.folder, fmt.Sprintf(`*%s`, gfile.Ext(item.path)))
if err != nil {
return "", err
@@ -125,7 +185,7 @@ func (view *View) Parse(ctx context.Context, file string, params ...Params) (res
// Note that the template variable assignment cannot change the value
// of the existing `params` or view.data because both variables are pointers.
// It needs to merge the values of the two maps into a new map.
- variables := gutil.MapMergeCopy(params...)
+ variables := gutil.MapMergeCopy(option.Params)
if len(view.data) > 0 {
gutil.MapMerge(variables, view.data)
}
@@ -137,11 +197,11 @@ func (view *View) Parse(ctx context.Context, file string, params ...Params) (res
if err != nil {
return "", err
}
- if err := newTpl.Execute(buffer, variables); err != nil {
+ if err = newTpl.Execute(buffer, variables); err != nil {
return "", err
}
} else {
- if err := tpl.(*texttpl.Template).Execute(buffer, variables); err != nil {
+ if err = tpl.(*texttpl.Template).Execute(buffer, variables); err != nil {
return "", err
}
}
@@ -152,14 +212,9 @@ func (view *View) Parse(ctx context.Context, file string, params ...Params) (res
return result, nil
}
-// ParseDefault parses the default template file with params.
-func (view *View) ParseDefault(ctx context.Context, params ...Params) (result string, err error) {
- return view.Parse(ctx, view.config.DefaultFile, params...)
-}
-
-// ParseContent parses given template content `content` with template variables `params`
+// doParseContent parses given template content `content` with template variables `params`
// and returns the parsed content in []byte.
-func (view *View) ParseContent(ctx context.Context, content string, params ...Params) (string, error) {
+func (view *View) doParseContent(ctx context.Context, content string, params Params) (string, error) {
// It's not necessary continuing parsing if template content is empty.
if content == "" {
return "", nil
@@ -181,7 +236,7 @@ func (view *View) ParseContent(ctx context.Context, content string, params ...Pa
})
)
// Using memory lock to ensure concurrent safety for content parsing.
- hash := strconv.FormatUint(ghash.DJBHash64([]byte(content)), 10)
+ hash := strconv.FormatUint(ghash.DJB64([]byte(content)), 10)
gmlock.LockFunc("gview.ParseContent:"+hash, func() {
if view.config.AutoEncode {
tpl, err = tpl.(*htmltpl.Template).Parse(content)
@@ -196,7 +251,7 @@ func (view *View) ParseContent(ctx context.Context, content string, params ...Pa
// Note that the template variable assignment cannot change the value
// of the existing `params` or view.data because both variables are pointers.
// It needs to merge the values of the two maps into a new map.
- variables := gutil.MapMergeCopy(params...)
+ variables := gutil.MapMergeCopy(params)
if len(view.data) > 0 {
gutil.MapMerge(variables, view.data)
}
@@ -272,10 +327,9 @@ func (view *View) getTemplate(filePath, folderPath, pattern string) (tpl interfa
}
}
- // Secondly checking the file system.
- var (
- files []string
- )
+ // Secondly checking the file system,
+ // and then automatically parsing all its sub-files recursively.
+ var files []string
files, err = gfile.ScanDir(folderPath, pattern, true)
if err != nil {
return nil
@@ -319,9 +373,7 @@ func (view *View) formatTemplateObjectCreatingError(filePath, tplName string, er
// Note that, the returned `folder` is the template folder path, but not the folder of
// the returned template file `path`.
func (view *View) searchFile(ctx context.Context, file string) (path string, folder string, resource *gres.File, err error) {
- var (
- tempPath string
- )
+ var tempPath string
// Firstly checking the resource manager.
if !gres.IsEmpty() {
// Try folders.
@@ -350,6 +402,13 @@ func (view *View) searchFile(ctx context.Context, file string) (path string, fol
// Secondly checking the file system.
if path == "" {
+ // Absolute path.
+ path = gfile.RealPath(file)
+ if path != "" {
+ folder = gfile.Dir(path)
+ return
+ }
+ // In search paths.
view.searchPaths.RLockFunc(func(array []string) {
for _, searchPath := range array {
searchPath = gstr.TrimRight(searchPath, `\/`)
@@ -359,7 +418,7 @@ func (view *View) searchFile(ctx context.Context, file string) (path string, fol
`\/`,
)
if path, _ = gspath.Search(searchPath, relativePath); path != "" {
- folder = gfile.Dir(path)
+ folder = gfile.Join(searchPath, tryFolder)
return
}
}
diff --git a/os/gview/gview_z_unit_config_test.go b/os/gview/gview_z_unit_config_test.go
index ea70d0a08..f138203c0 100644
--- a/os/gview/gview_z_unit_config_test.go
+++ b/os/gview/gview_z_unit_config_test.go
@@ -10,7 +10,6 @@ import (
"context"
"testing"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gview"
"github.com/gogf/gf/v2/test/gtest"
@@ -19,7 +18,7 @@ import (
func Test_Config(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
config := gview.Config{
- Paths: []string{gdebug.TestDataPath("config")},
+ Paths: []string{gtest.DataPath("config")},
Data: g.Map{
"name": "gf",
},
@@ -28,16 +27,16 @@ func Test_Config(t *testing.T) {
}
view := gview.New()
err := view.SetConfig(config)
- t.Assert(err, nil)
+ t.AssertNil(err)
str := `hello ${.name},version:${.version}`
view.Assigns(g.Map{"version": "1.7.0"})
result, err := view.ParseContent(context.TODO(), str, nil)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result, "hello gf,version:1.7.0")
result, err = view.ParseDefault(context.TODO())
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result, "name:gf")
})
}
@@ -46,23 +45,23 @@ func Test_ConfigWithMap(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
view := gview.New()
err := view.SetConfigWithMap(g.Map{
- "Paths": []string{gdebug.TestDataPath("config")},
+ "Paths": []string{gtest.DataPath("config")},
"DefaultFile": "test.html",
"Delimiters": []string{"${", "}"},
"Data": g.Map{
"name": "gf",
},
})
- t.Assert(err, nil)
+ t.AssertNil(err)
str := `hello ${.name},version:${.version}`
view.Assigns(g.Map{"version": "1.7.0"})
result, err := view.ParseContent(context.TODO(), str, nil)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result, "hello gf,version:1.7.0")
result, err = view.ParseDefault(context.TODO())
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result, "name:gf")
})
}
diff --git a/os/gview/gview_z_unit_feature_encode_test.go b/os/gview/gview_z_unit_feature_encode_test.go
index dc7913de5..786f98a4b 100644
--- a/os/gview/gview_z_unit_feature_encode_test.go
+++ b/os/gview/gview_z_unit_feature_encode_test.go
@@ -10,7 +10,6 @@ import (
"context"
"testing"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gview"
@@ -20,12 +19,12 @@ import (
func Test_Encode_Parse(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gview.New()
- v.SetPath(gdebug.TestDataPath("tpl"))
+ v.SetPath(gtest.DataPath("tpl"))
v.SetAutoEncode(true)
result, err := v.Parse(context.TODO(), "encode.tpl", g.Map{
"title": "my title",
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result, "
<b>my title</b>
")
})
}
@@ -33,12 +32,12 @@ func Test_Encode_Parse(t *testing.T) {
func Test_Encode_ParseContent(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gview.New()
- tplContent := gfile.GetContents(gdebug.TestDataPath("tpl", "encode.tpl"))
+ tplContent := gfile.GetContents(gtest.DataPath("tpl", "encode.tpl"))
v.SetAutoEncode(true)
result, err := v.ParseContent(context.TODO(), tplContent, g.Map{
"title": "my title",
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result, "<b>my title</b>
")
})
}
diff --git a/os/gview/gview_z_unit_i18n_test.go b/os/gview/gview_z_unit_i18n_test.go
index 58c1c27d5..dfd1e4026 100644
--- a/os/gview/gview_z_unit_i18n_test.go
+++ b/os/gview/gview_z_unit_i18n_test.go
@@ -23,27 +23,27 @@ func Test_I18n(t *testing.T) {
expect2 := `john says "こんにちは世界!"`
expect3 := `john says "{#hello}{#world}!"`
- g.I18n().SetPath(gdebug.TestDataPath("i18n"))
+ g.I18n().SetPath(gtest.DataPath("i18n"))
g.I18n().SetLanguage("zh-CN")
result1, err := g.View().ParseContent(context.TODO(), content, g.Map{
"name": "john",
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result1, expect1)
g.I18n().SetLanguage("ja")
result2, err := g.View().ParseContent(context.TODO(), content, g.Map{
"name": "john",
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result2, expect2)
g.I18n().SetLanguage("none")
result3, err := g.View().ParseContent(context.TODO(), content, g.Map{
"name": "john",
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result3, expect3)
})
gtest.C(t, func(t *gtest.T) {
@@ -58,21 +58,21 @@ func Test_I18n(t *testing.T) {
"name": "john",
"I18nLanguage": "zh-CN",
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result1, expect1)
result2, err := g.View().ParseContent(context.TODO(), content, g.Map{
"name": "john",
"I18nLanguage": "ja",
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result2, expect2)
result3, err := g.View().ParseContent(context.TODO(), content, g.Map{
"name": "john",
"I18nLanguage": "none",
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result3, expect3)
})
}
diff --git a/os/gview/gview_z_unit_test.go b/os/gview/gview_z_unit_test.go
index 680c129a6..cfead2fc9 100644
--- a/os/gview/gview_z_unit_test.go
+++ b/os/gview/gview_z_unit_test.go
@@ -23,6 +23,7 @@ import (
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
+ "github.com/gogf/gf/v2/util/guid"
)
func init() {
@@ -174,7 +175,7 @@ func Test_Func(t *testing.T) {
str = `{{concat "I" "Love" "GoFrame"}}`
result, err = gview.ParseContent(context.TODO(), str, nil)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result, `ILoveGoFrame`)
})
// eq: multiple values.
@@ -196,7 +197,7 @@ func Test_FuncNl2Br(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
str := `{{"Go\nFrame" | nl2br}}`
result, err := gview.ParseContent(context.TODO(), str, nil)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result, `Go
Frame`)
})
gtest.C(t, func(t *gtest.T) {
@@ -208,7 +209,7 @@ func Test_FuncNl2Br(t *testing.T) {
result, err := gview.ParseContent(context.TODO(), str, g.Map{
"content": s,
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result, strings.Replace(s, "\n", "
", -1))
})
}
@@ -222,7 +223,7 @@ func Test_FuncInclude(t *testing.T) {
layout = `{{include "header.html" .}}
{{include "main.html" .}}
{{include "footer.html" .}}`
- templatePath = gfile.TempDir("template")
+ templatePath = gfile.Temp(guid.S())
)
gfile.Mkdir(templatePath)
@@ -294,7 +295,7 @@ func Test_ParseContent(t *testing.T) {
func Test_HotReload(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
dirPath := gfile.Join(
- gfile.TempDir(),
+ gfile.Temp(),
"testdata",
"template-"+gconv.String(gtime.TimestampNano()),
)
@@ -303,7 +304,7 @@ func Test_HotReload(t *testing.T) {
// Initialize data.
err := gfile.PutContents(filePath, "test:{{.var}}")
- t.Assert(err, nil)
+ t.AssertNil(err)
view := gview.New(dirPath)
@@ -311,18 +312,18 @@ func Test_HotReload(t *testing.T) {
result, err := view.Parse(context.TODO(), "test.html", g.Map{
"var": "1",
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result, `test:1`)
// Update data.
err = gfile.PutContents(filePath, "test2:{{.var}}")
- t.Assert(err, nil)
+ t.AssertNil(err)
time.Sleep(100 * time.Millisecond)
result, err = view.Parse(context.TODO(), "test.html", g.Map{
"var": "2",
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(result, `test2:2`)
})
}
@@ -334,7 +335,7 @@ func Test_XSS(t *testing.T) {
r, err := v.ParseContent(context.TODO(), "{{.v}}", g.Map{
"v": s,
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(r, s)
})
gtest.C(t, func(t *gtest.T) {
@@ -344,7 +345,7 @@ func Test_XSS(t *testing.T) {
r, err := v.ParseContent(context.TODO(), "{{.v}}", g.Map{
"v": s,
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(r, ghtml.Entities(s))
})
// Tag "if".
@@ -355,7 +356,7 @@ func Test_XSS(t *testing.T) {
r, err := v.ParseContent(context.TODO(), "{{if eq 1 1}}{{.v}}{{end}}", g.Map{
"v": s,
})
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(r, ghtml.Entities(s))
})
}
@@ -374,7 +375,7 @@ func Test_BuildInFuncMap(t *testing.T) {
v := gview.New()
v.Assign("v", new(TypeForBuildInFuncMap))
r, err := v.ParseContent(context.TODO(), "{{range $k, $v := map .v.Test}} {{$k}}:{{$v}} {{end}}")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(gstr.Contains(r, "Name:john"), true)
t.Assert(gstr.Contains(r, "Score:99.9"), true)
})
@@ -397,7 +398,7 @@ func Test_BuildInFuncMaps(t *testing.T) {
v := gview.New()
v.Assign("v", new(TypeForBuildInFuncMaps))
r, err := v.ParseContent(context.TODO(), "{{range $k, $v := maps .v.Test}} {{$k}}:{{$v.Name}} {{$v.Score}} {{end}}")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(r, ` 0:john 99.9 1:smith 100 `)
})
}
@@ -410,7 +411,7 @@ func Test_BuildInFuncDump(t *testing.T) {
"score": 100,
})
r, err := v.ParseContent(context.TODO(), "{{dump .}}")
- t.Assert(err, nil)
+ t.AssertNil(err)
fmt.Println(r)
t.Assert(gstr.Contains(r, `"name": "john"`), true)
t.Assert(gstr.Contains(r, `"score": 100`), true)
@@ -424,16 +425,80 @@ func Test_BuildInFuncJson(t *testing.T) {
"name": "john",
})
r, err := v.ParseContent(context.TODO(), "{{json .v}}")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(r, `{"name":"john"}`)
})
}
+func Test_BuildInFuncXml(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ v := gview.New()
+ v.Assign("v", g.Map{
+ "name": "john",
+ })
+ r, err := v.ParseContent(context.TODO(), "{{xml .v}}")
+ t.AssertNil(err)
+ t.Assert(r, `john`)
+ })
+}
+
+func Test_BuildInFuncIni(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ v := gview.New()
+ v.Assign("v", g.Map{
+ "name": "john",
+ })
+ r, err := v.ParseContent(context.TODO(), "{{ini .v}}")
+ t.AssertNil(err)
+ t.Assert(r, `name=john
+`)
+ })
+}
+
+func Test_BuildInFuncYaml(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ v := gview.New()
+ v.Assign("v", g.Map{
+ "name": "john",
+ })
+ r, err := v.ParseContent(context.TODO(), "{{yaml .v}}")
+ t.AssertNil(err)
+ t.Assert(r, `name: john
+`)
+ })
+}
+
+func Test_BuildInFuncYamlIndent(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ v := gview.New()
+ v.Assign("v", g.Map{
+ "name": "john",
+ })
+ r, err := v.ParseContent(context.TODO(), `{{yamli .v "####"}}`)
+ t.AssertNil(err)
+ t.Assert(r, `####name: john
+`)
+ })
+}
+
+func Test_BuildInFuncToml(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ v := gview.New()
+ v.Assign("v", g.Map{
+ "name": "john",
+ })
+ r, err := v.ParseContent(context.TODO(), "{{toml .v}}")
+ t.AssertNil(err)
+ t.Assert(r, `name = "john"
+`)
+ })
+}
+
func Test_BuildInFuncPlus(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gview.New()
r, err := v.ParseContent(gctx.New(), "{{plus 1 2 3}}")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(r, `6`)
})
}
@@ -442,7 +507,7 @@ func Test_BuildInFuncMinus(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gview.New()
r, err := v.ParseContent(gctx.New(), "{{minus 1 2 3}}")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(r, `-4`)
})
}
@@ -451,7 +516,7 @@ func Test_BuildInFuncTimes(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gview.New()
r, err := v.ParseContent(gctx.New(), "{{times 1 2 3 4}}")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(r, `24`)
})
}
@@ -460,7 +525,24 @@ func Test_BuildInFuncDivide(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gview.New()
r, err := v.ParseContent(gctx.New(), "{{divide 8 2 2}}")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(r, `2`)
})
}
+
+func Test_Issue1416(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ v := gview.New()
+ err := v.SetPath(gtest.DataPath("issue1416"))
+ t.AssertNil(err)
+ r, err := v.ParseOption(context.TODO(), gview.Option{
+ File: "gview.tpl",
+ Orphan: true,
+ Params: map[string]interface{}{
+ "hello": "world",
+ },
+ })
+ t.AssertNil(err)
+ t.Assert(r, `test.tpl content, vars: world`)
+ })
+}
diff --git a/os/gview/testdata/issue1416/gview copy.tpl b/os/gview/testdata/issue1416/gview copy.tpl
new file mode 100644
index 000000000..97b8f75cc
--- /dev/null
+++ b/os/gview/testdata/issue1416/gview copy.tpl
@@ -0,0 +1 @@
+test.tpl content, vars: {{ hello }}
\ No newline at end of file
diff --git a/os/gview/testdata/issue1416/gview.tpl b/os/gview/testdata/issue1416/gview.tpl
new file mode 100644
index 000000000..c31b46da5
--- /dev/null
+++ b/os/gview/testdata/issue1416/gview.tpl
@@ -0,0 +1 @@
+test.tpl content, vars: {{.hello}}
\ No newline at end of file
diff --git a/protocol/goai/goai.go b/protocol/goai/goai.go
index c8cbb9148..aef00be0e 100644
--- a/protocol/goai/goai.go
+++ b/protocol/goai/goai.go
@@ -25,25 +25,18 @@ import (
// https://swagger.io/specification/
// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md
type OpenApiV3 struct {
- Config Config `json:"-" yaml:"-"`
- OpenAPI string `json:"openapi" yaml:"openapi"`
- Components Components `json:"components,omitempty" yaml:"components,omitempty"`
- Info Info `json:"info" yaml:"info"`
- Paths Paths `json:"paths" yaml:"paths"`
- Security *SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
- Servers *Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
- Tags *Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
- ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
-}
-
-// ExternalDocs is specified by OpenAPI/Swagger standard version 3.0.
-type ExternalDocs struct {
- URL string `json:"url,omitempty"`
- Description string `json:"description,omitempty"`
+ Config Config `json:"-"`
+ OpenAPI string `json:"openapi"`
+ Components Components `json:"components,omitempty"`
+ Info Info `json:"info"`
+ Paths Paths `json:"paths"`
+ Security *SecurityRequirements `json:"security,omitempty"`
+ Servers *Servers `json:"servers,omitempty"`
+ Tags *Tags `json:"tags,omitempty"`
+ ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
}
const (
- HttpMethodAll = `ALL`
HttpMethodGet = `GET`
HttpMethodPut = `PUT`
HttpMethodPost = `POST`
@@ -56,6 +49,7 @@ const (
)
const (
+ TypeInteger = `integer`
TypeNumber = `number`
TypeBoolean = `boolean`
TypeArray = `array`
@@ -82,22 +76,28 @@ const (
TagNamePath = `path`
TagNameMethod = `method`
TagNameMime = `mime`
+ TagNameConsumes = `consumes`
TagNameType = `type`
TagNameDomain = `domain`
- TagNameValidate = `v`
+)
+
+const (
+ patternKeyForRequired = `required`
+ patternKeyForIn = `in:`
)
var (
defaultReadContentTypes = []string{`application/json`}
defaultWriteContentTypes = []string{`application/json`}
shortTypeMapForTag = map[string]string{
- "d": "default",
- "sum": "summary",
- "sm": "summary",
- "des": "description",
- "dc": "description",
- "eg": "example",
- "egs": "examples",
+ "d": "Default",
+ "sum": "Summary",
+ "sm": "Summary",
+ "des": "Description",
+ "dc": "Description",
+ "eg": "Example",
+ "egs": "Examples",
+ "ed": "ExternalDocs",
}
)
@@ -148,7 +148,7 @@ func (oai *OpenApiV3) Add(in AddInput) error {
func (oai OpenApiV3) String() string {
b, err := json.Marshal(oai)
if err != nil {
- intlog.Error(context.TODO(), err)
+ intlog.Errorf(context.TODO(), `%+v`, err)
}
return string(b)
}
@@ -180,7 +180,10 @@ func (oai *OpenApiV3) golangTypeToOAIType(t reflect.Type) string {
case
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
- reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
+ reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ return TypeInteger
+
+ case
reflect.Float32, reflect.Float64,
reflect.Complex64, reflect.Complex128:
return TypeNumber
@@ -237,3 +240,10 @@ func (oai *OpenApiV3) fileMapWithShortTags(m map[string]string) map[string]strin
func formatRefToBytes(ref string) []byte {
return []byte(fmt.Sprintf(`{"$ref":"#/components/schemas/%s"}`, ref))
}
+
+func isValidParameterName(key string) bool {
+ if key == "-" {
+ return false
+ }
+ return true
+}
diff --git a/protocol/goai/goai_components.go b/protocol/goai/goai_components.go
index 465972c84..85a47f0b9 100644
--- a/protocol/goai/goai_components.go
+++ b/protocol/goai/goai_components.go
@@ -8,15 +8,15 @@ package goai
// Components is specified by OpenAPI/Swagger standard version 3.0.
type Components struct {
- Schemas Schemas `json:"schemas,omitempty" yaml:"schemas,omitempty"`
- Parameters ParametersMap `json:"parameters,omitempty" yaml:"parameters,omitempty"`
- Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
- RequestBodies RequestBodies `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"`
- Responses Responses `json:"responses,omitempty" yaml:"responses,omitempty"`
- SecuritySchemes SecuritySchemes `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"`
- Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
- Links Links `json:"links,omitempty" yaml:"links,omitempty"`
- Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
+ Schemas Schemas `json:"schemas,omitempty"`
+ Parameters ParametersMap `json:"parameters,omitempty"`
+ Headers Headers `json:"headers,omitempty"`
+ RequestBodies RequestBodies `json:"requestBodies,omitempty"`
+ Responses Responses `json:"responses,omitempty"`
+ SecuritySchemes SecuritySchemes `json:"securitySchemes,omitempty"`
+ Examples Examples `json:"examples,omitempty"`
+ Links Links `json:"links,omitempty"`
+ Callbacks Callbacks `json:"callbacks,omitempty"`
}
type ParametersMap map[string]*ParameterRef
diff --git a/protocol/goai/goai_example.go b/protocol/goai/goai_example.go
index 8af3005b1..61b75d4fb 100644
--- a/protocol/goai/goai_example.go
+++ b/protocol/goai/goai_example.go
@@ -12,10 +12,10 @@ import (
// Example is specified by OpenAPI/Swagger 3.0 standard.
type Example struct {
- Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
- Description string `json:"description,omitempty" yaml:"description,omitempty"`
- Value interface{} `json:"value,omitempty" yaml:"value,omitempty"`
- ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Description string `json:"description,omitempty"`
+ Value interface{} `json:"value,omitempty"`
+ ExternalValue string `json:"externalValue,omitempty"`
}
type Examples map[string]*ExampleRef
diff --git a/protocol/goai/goai_external_docs.go b/protocol/goai/goai_external_docs.go
new file mode 100644
index 000000000..2265865c0
--- /dev/null
+++ b/protocol/goai/goai_external_docs.go
@@ -0,0 +1,35 @@
+// 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/internal/json"
+ "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gconv"
+)
+
+// ExternalDocs is specified by OpenAPI/Swagger standard version 3.0.
+type ExternalDocs struct {
+ URL string `json:"url,omitempty"`
+ Description string `json:"description,omitempty"`
+}
+
+func (ed *ExternalDocs) UnmarshalValue(value interface{}) error {
+ var valueBytes = gconv.Bytes(value)
+ if json.Valid(valueBytes) {
+ return json.UnmarshalUseNumber(valueBytes, ed)
+ }
+ var (
+ valueString = string(valueBytes)
+ valueArray = gstr.Split(valueString, "|")
+ )
+ ed.URL = valueArray[0]
+ if len(valueArray) > 1 {
+ ed.Description = valueArray[1]
+ }
+ return nil
+}
diff --git a/protocol/goai/goai_info.go b/protocol/goai/goai_info.go
index 3c0738e67..91709e3d8 100644
--- a/protocol/goai/goai_info.go
+++ b/protocol/goai/goai_info.go
@@ -8,23 +8,23 @@ package goai
// Info is specified by OpenAPI/Swagger standard version 3.0.
type Info struct {
- Title string `json:"title" yaml:"title"`
- Description string `json:"description,omitempty" yaml:"description,omitempty"`
- TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"`
- Contact *Contact `json:"contact,omitempty" yaml:"contact,omitempty"`
- License *License `json:"license,omitempty" yaml:"license,omitempty"`
- Version string `json:"version" yaml:"version"`
+ Title string `json:"title"`
+ Description string `json:"description,omitempty"`
+ TermsOfService string `json:"termsOfService,omitempty"`
+ Contact *Contact `json:"contact,omitempty"`
+ License *License `json:"license,omitempty"`
+ Version string `json:"version"`
}
// Contact is specified by OpenAPI/Swagger standard version 3.0.
type Contact struct {
- Name string `json:"name,omitempty" yaml:"name,omitempty"`
- URL string `json:"url,omitempty" yaml:"url,omitempty"`
- Email string `json:"email,omitempty" yaml:"email,omitempty"`
+ Name string `json:"name,omitempty"`
+ URL string `json:"url,omitempty"`
+ Email string `json:"email,omitempty"`
}
// License is specified by OpenAPI/Swagger standard version 3.0.
type License struct {
- Name string `json:"name" yaml:"name"`
- URL string `json:"url,omitempty" yaml:"url,omitempty"`
+ Name string `json:"name"`
+ URL string `json:"url,omitempty"`
}
diff --git a/protocol/goai/goai_link.go b/protocol/goai/goai_link.go
index 261fa51cc..0968c1b71 100644
--- a/protocol/goai/goai_link.go
+++ b/protocol/goai/goai_link.go
@@ -12,12 +12,12 @@ import (
// Link is specified by OpenAPI/Swagger standard version 3.0.
type Link struct {
- OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
- OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"`
- Description string `json:"description,omitempty" yaml:"description,omitempty"`
- Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"`
- Server *Server `json:"server,omitempty" yaml:"server,omitempty"`
- RequestBody interface{} `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
+ OperationID string `json:"operationId,omitempty"`
+ OperationRef string `json:"operationRef,omitempty"`
+ Description string `json:"description,omitempty"`
+ Parameters map[string]interface{} `json:"parameters,omitempty"`
+ Server *Server `json:"server,omitempty"`
+ RequestBody interface{} `json:"requestBody,omitempty"`
}
type Links map[string]LinkRef
diff --git a/protocol/goai/goai_mediatype.go b/protocol/goai/goai_mediatype.go
index d5af61dba..4c2257df1 100644
--- a/protocol/goai/goai_mediatype.go
+++ b/protocol/goai/goai_mediatype.go
@@ -8,10 +8,10 @@ package goai
// MediaType is specified by OpenAPI/Swagger 3.0 standard.
type MediaType struct {
- Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
- Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
- Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
- Encoding map[string]*Encoding `json:"encoding,omitempty" yaml:"encoding,omitempty"`
+ Schema *SchemaRef `json:"schema,omitempty"`
+ Example interface{} `json:"example,omitempty"`
+ Examples Examples `json:"examples,omitempty"`
+ Encoding map[string]*Encoding `json:"encoding,omitempty"`
}
// Content is specified by OpenAPI/Swagger 3.0 standard.
@@ -19,9 +19,9 @@ type Content map[string]MediaType
// Encoding is specified by OpenAPI/Swagger 3.0 standard.
type Encoding struct {
- ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"`
- Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
- Style string `json:"style,omitempty" yaml:"style,omitempty"`
- Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"`
- AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
+ ContentType string `json:"contentType,omitempty"`
+ Headers Headers `json:"headers,omitempty"`
+ Style string `json:"style,omitempty"`
+ Explode *bool `json:"explode,omitempty"`
+ AllowReserved bool `json:"allowReserved,omitempty"`
}
diff --git a/protocol/goai/goai_operation.go b/protocol/goai/goai_operation.go
index 2a896f79e..db6e0b30d 100644
--- a/protocol/goai/goai_operation.go
+++ b/protocol/goai/goai_operation.go
@@ -6,18 +6,56 @@
package goai
+import (
+ "github.com/gogf/gf/v2/errors/gerror"
+ "github.com/gogf/gf/v2/internal/json"
+ "github.com/gogf/gf/v2/util/gconv"
+)
+
// Operation represents "operation" specified by OpenAPI/Swagger 3.0 standard.
type Operation struct {
- Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
- Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
- Description string `json:"description,omitempty" yaml:"description,omitempty"`
- OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
- Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
- RequestBody *RequestBodyRef `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
- Responses Responses `json:"responses" yaml:"responses"`
- Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
- Callbacks *Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
- Security *SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
- Servers *Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
- ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
+ Tags []string `json:"tags,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Description string `json:"description,omitempty"`
+ OperationID string `json:"operationId,omitempty"`
+ Parameters Parameters `json:"parameters,omitempty"`
+ RequestBody *RequestBodyRef `json:"requestBody,omitempty"`
+ Responses Responses `json:"responses"`
+ Deprecated bool `json:"deprecated,omitempty"`
+ Callbacks *Callbacks `json:"callbacks,omitempty"`
+ Security *SecurityRequirements `json:"security,omitempty"`
+ Servers *Servers `json:"servers,omitempty"`
+ ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
+ XExtensions XExtensions `json:"-"`
+}
+
+func (oai *OpenApiV3) tagMapToOperation(tagMap map[string]string, operation *Operation) error {
+ var mergedTagMap = oai.fileMapWithShortTags(tagMap)
+ if err := gconv.Struct(mergedTagMap, operation); err != nil {
+ return gerror.Wrap(err, `mapping struct tags to Operation failed`)
+ }
+ oai.tagMapToXExtensions(mergedTagMap, operation.XExtensions)
+ return nil
+}
+
+func (o Operation) MarshalJSON() ([]byte, error) {
+ var (
+ b []byte
+ m map[string]json.RawMessage
+ err error
+ )
+ type tempOperation Operation // To prevent JSON marshal recursion error.
+ if b, err = json.Marshal(tempOperation(o)); err != nil {
+ return nil, err
+ }
+ if err = json.Unmarshal(b, &m); err != nil {
+ return nil, err
+ }
+ for k, v := range o.XExtensions {
+ if b, err = json.Marshal(v); err != nil {
+ return nil, err
+ }
+ m[k] = b
+ }
+ return json.Marshal(m)
}
diff --git a/protocol/goai/goai_parameter.go b/protocol/goai/goai_parameter.go
index 082bd60e4..d20e92cdc 100644
--- a/protocol/goai/goai_parameter.go
+++ b/protocol/goai/goai_parameter.go
@@ -7,104 +7,57 @@
package goai
import (
- "fmt"
-
- "github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
- "github.com/gogf/gf/v2/os/gstructs"
- "github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
// Parameter is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#parameterObject
type Parameter struct {
- Name string `json:"name,omitempty" yaml:"name,omitempty"`
- In string `json:"in,omitempty" yaml:"in,omitempty"`
- Description string `json:"description,omitempty" yaml:"description,omitempty"`
- Style string `json:"style,omitempty" yaml:"style,omitempty"`
- Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"`
- AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
- AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"`
- Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
- Required bool `json:"required,omitempty" yaml:"required,omitempty"`
- Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
- Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
- Examples *Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
- Content *Content `json:"content,omitempty" yaml:"content,omitempty"`
+ Name string `json:"name,omitempty"`
+ In string `json:"in,omitempty"`
+ Description string `json:"description,omitempty"`
+ Style string `json:"style,omitempty"`
+ Explode *bool `json:"explode,omitempty"`
+ AllowEmptyValue bool `json:"allowEmptyValue,omitempty"`
+ AllowReserved bool `json:"allowReserved,omitempty"`
+ Deprecated bool `json:"deprecated,omitempty"`
+ Required bool `json:"required,omitempty"`
+ Schema *SchemaRef `json:"schema,omitempty"`
+ Example interface{} `json:"example,omitempty"`
+ Examples *Examples `json:"examples,omitempty"`
+ Content *Content `json:"content,omitempty"`
+ XExtensions XExtensions `json:"-"`
}
-// Parameters is specified by OpenAPI/Swagger 3.0 standard.
-type Parameters []ParameterRef
-
-type ParameterRef struct {
- Ref string
- Value *Parameter
+func (oai *OpenApiV3) tagMapToParameter(tagMap map[string]string, parameter *Parameter) error {
+ var mergedTagMap = oai.fileMapWithShortTags(tagMap)
+ if err := gconv.Struct(mergedTagMap, parameter); err != nil {
+ return gerror.Wrap(err, `mapping struct tags to Parameter failed`)
+ }
+ oai.tagMapToXExtensions(mergedTagMap, parameter.XExtensions)
+ return nil
}
-func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path, method string) (*ParameterRef, error) {
+func (p Parameter) MarshalJSON() ([]byte, error) {
var (
- tagMap = field.TagMap()
- parameter = &Parameter{
- Name: field.TagJsonName(),
- }
+ b []byte
+ m map[string]json.RawMessage
+ err error
)
- if parameter.Name == "" {
- parameter.Name = field.Name()
- }
- if len(tagMap) > 0 {
- err := gconv.Struct(oai.fileMapWithShortTags(tagMap), parameter)
- if err != nil {
- return nil, gerror.Wrap(err, `mapping struct tags to Parameter failed`)
- }
- }
- if parameter.In == "" {
- // Automatically detect its "in" attribute.
- if gstr.ContainsI(path, fmt.Sprintf(`{%s}`, parameter.Name)) {
- parameter.In = ParameterInPath
- } else {
- // Default the parameter input to "query" if method is "GET/DELETE".
- switch gstr.ToUpper(method) {
- case HttpMethodGet, HttpMethodDelete:
- parameter.In = ParameterInQuery
-
- default:
- return nil, nil
- }
- }
- }
-
- switch parameter.In {
- case ParameterInPath:
- // Required for path parameter.
- 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)
- }
- // Necessary schema or content.
- schemaRef, err := oai.newSchemaRefWithGolangType(field.Type().Type, tagMap)
- if err != nil {
+ type tempParameter Parameter // To prevent JSON marshal recursion error.
+ if b, err = json.Marshal(tempParameter(p)); err != nil {
return nil, err
}
- parameter.Schema = schemaRef
-
- return &ParameterRef{
- Ref: "",
- Value: parameter,
- }, nil
-}
-
-func (r ParameterRef) MarshalJSON() ([]byte, error) {
- if r.Ref != "" {
- return formatRefToBytes(r.Ref), nil
+ if err = json.Unmarshal(b, &m); err != nil {
+ return nil, err
}
- return json.Marshal(r.Value)
+ for k, v := range p.XExtensions {
+ if b, err = json.Marshal(v); err != nil {
+ return nil, err
+ }
+ m[k] = b
+ }
+ return json.Marshal(m)
}
diff --git a/protocol/goai/goai_parameter_ref.go b/protocol/goai/goai_parameter_ref.go
new file mode 100644
index 000000000..416ec7dcc
--- /dev/null
+++ b/protocol/goai/goai_parameter_ref.go
@@ -0,0 +1,100 @@
+// 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 (
+ "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"
+ "github.com/gogf/gf/v2/os/gstructs"
+ "github.com/gogf/gf/v2/text/gstr"
+)
+
+// Parameters is specified by OpenAPI/Swagger 3.0 standard.
+type Parameters []ParameterRef
+
+type ParameterRef struct {
+ Ref string
+ Value *Parameter
+}
+
+func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path, method string) (*ParameterRef, error) {
+ var (
+ tagMap = field.TagMap()
+ parameter = &Parameter{
+ Name: field.TagJsonName(),
+ XExtensions: make(XExtensions),
+ }
+ )
+ if parameter.Name == "" {
+ parameter.Name = field.Name()
+ }
+ if len(tagMap) > 0 {
+ if err := oai.tagMapToParameter(tagMap, parameter); err != nil {
+ return nil, err
+ }
+ }
+ if parameter.In == "" {
+ // Automatically detect its "in" attribute.
+ if gstr.ContainsI(path, fmt.Sprintf(`{%s}`, parameter.Name)) {
+ parameter.In = ParameterInPath
+ } else {
+ // Default the parameter input to "query" if method is "GET/DELETE".
+ switch gstr.ToUpper(method) {
+ case HttpMethodGet, HttpMethodDelete:
+ parameter.In = ParameterInQuery
+
+ default:
+ return nil, nil
+ }
+ }
+ }
+
+ switch parameter.In {
+ case ParameterInPath:
+ // Required for path parameter.
+ parameter.Required = true
+
+ case ParameterInCookie, ParameterInHeader, ParameterInQuery:
+
+ default:
+ return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid tag value "%s" for In`, parameter.In)
+ }
+ // Necessary schema or content.
+ schemaRef, err := oai.newSchemaRefWithGolangType(field.Type().Type, tagMap)
+ if err != nil {
+ return nil, err
+ }
+ parameter.Schema = schemaRef
+
+ // Ignore parameter.
+ if !isValidParameterName(parameter.Name) {
+ return nil, nil
+ }
+
+ // 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,
+ }, nil
+}
+
+func (r ParameterRef) MarshalJSON() ([]byte, error) {
+ if r.Ref != "" {
+ return formatRefToBytes(r.Ref), nil
+ }
+ return json.Marshal(r.Value)
+}
diff --git a/protocol/goai/goai_path.go b/protocol/goai/goai_path.go
index 4f9125f82..d5d8af66f 100644
--- a/protocol/goai/goai_path.go
+++ b/protocol/goai/goai_path.go
@@ -11,6 +11,7 @@ import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
+ "github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/os/gstructs"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
@@ -18,20 +19,21 @@ import (
)
type Path struct {
- Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
- Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
- Description string `json:"description,omitempty" yaml:"description,omitempty"`
- Connect *Operation `json:"connect,omitempty" yaml:"connect,omitempty"`
- Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
- Get *Operation `json:"get,omitempty" yaml:"get,omitempty"`
- Head *Operation `json:"head,omitempty" yaml:"head,omitempty"`
- Options *Operation `json:"options,omitempty" yaml:"options,omitempty"`
- Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
- Post *Operation `json:"post,omitempty" yaml:"post,omitempty"`
- Put *Operation `json:"put,omitempty" yaml:"put,omitempty"`
- Trace *Operation `json:"trace,omitempty" yaml:"trace,omitempty"`
- Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"`
- Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
+ Ref string `json:"$ref,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Description string `json:"description,omitempty"`
+ Connect *Operation `json:"connect,omitempty"`
+ Delete *Operation `json:"delete,omitempty"`
+ Get *Operation `json:"get,omitempty"`
+ Head *Operation `json:"head,omitempty"`
+ Options *Operation `json:"options,omitempty"`
+ Patch *Operation `json:"patch,omitempty"`
+ Post *Operation `json:"post,omitempty"`
+ Put *Operation `json:"put,omitempty"`
+ Trace *Operation `json:"trace,omitempty"`
+ Servers Servers `json:"servers,omitempty"`
+ Parameters Parameters `json:"parameters,omitempty"`
+ XExtensions XExtensions `json:"-"`
}
// Paths are specified by OpenAPI/Swagger standard version 3.0.
@@ -80,14 +82,16 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
}
var (
- path = Path{}
+ mime string
+ path = Path{XExtensions: make(XExtensions)}
inputMetaMap = gmeta.Data(inputObject.Interface())
outputMetaMap = gmeta.Data(outputObject.Interface())
isInputStructEmpty = oai.doesStructHasNoFields(inputObject.Interface())
inputStructTypeName = oai.golangTypeToSchemaName(inputObject.Type())
outputStructTypeName = oai.golangTypeToSchemaName(outputObject.Type())
operation = Operation{
- Responses: map[string]ResponseRef{},
+ Responses: map[string]ResponseRef{},
+ XExtensions: make(XExtensions),
}
)
// Path check.
@@ -126,11 +130,15 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
}
if len(inputMetaMap) > 0 {
- if err := gconv.Struct(oai.fileMapWithShortTags(inputMetaMap), &path); err != nil {
- return gerror.Wrap(err, `mapping struct tags to Path failed`)
+ if err := oai.tagMapToPath(inputMetaMap, &path); err != nil {
+ return err
}
- if err := gconv.Struct(oai.fileMapWithShortTags(inputMetaMap), &operation); err != nil {
- return gerror.Wrap(err, `mapping struct tags to Operation failed`)
+ if err := oai.tagMapToOperation(inputMetaMap, &operation); err != nil {
+ return err
+ }
+ // Allowed request mime.
+ if mime = inputMetaMap[TagNameMime]; mime == "" {
+ mime = inputMetaMap[TagNameConsumes]
}
}
@@ -200,12 +208,13 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
if _, ok := operation.Responses[responseOkKey]; !ok {
var (
response = Response{
- Content: map[string]MediaType{},
+ Content: map[string]MediaType{},
+ XExtensions: make(XExtensions),
}
)
if len(outputMetaMap) > 0 {
- if err := gconv.Struct(oai.fileMapWithShortTags(outputMetaMap), &response); err != nil {
- return gerror.Wrap(err, `mapping struct tags to Response failed`)
+ if err := oai.tagMapToResponse(outputMetaMap, &response); err != nil {
+ return err
}
}
// Supported mime types of response.
@@ -213,9 +222,9 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
contentTypes = oai.Config.ReadContentTypes
tagMimeValue = gmeta.Get(outputObject.Interface(), TagNameMime).String()
refInput = getResponseSchemaRefInput{
- BusinessStructName: outputStructTypeName,
- ResponseObject: oai.Config.CommonResponse,
- ResponseDataField: oai.Config.CommonResponseDataField,
+ BusinessStructName: outputStructTypeName,
+ CommonResponseObject: oai.Config.CommonResponse,
+ CommonResponseDataField: oai.Config.CommonResponseDataField,
}
)
if tagMimeValue != "" {
@@ -224,8 +233,8 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
for _, v := range contentTypes {
// If customized response mime type, it then ignores common response feature.
if tagMimeValue != "" {
- refInput.ResponseObject = nil
- refInput.ResponseDataField = ""
+ refInput.CommonResponseObject = nil
+ refInput.CommonResponseDataField = ""
}
schemaRef, err := oai.getResponseSchemaRef(refInput)
if err != nil {
@@ -281,3 +290,34 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
func (oai *OpenApiV3) doesStructHasNoFields(s interface{}) bool {
return reflect.TypeOf(s).NumField() == 0
}
+
+func (oai *OpenApiV3) tagMapToPath(tagMap map[string]string, path *Path) error {
+ var mergedTagMap = oai.fileMapWithShortTags(tagMap)
+ if err := gconv.Struct(mergedTagMap, path); err != nil {
+ return gerror.Wrap(err, `mapping struct tags to Path failed`)
+ }
+ oai.tagMapToXExtensions(mergedTagMap, path.XExtensions)
+ return nil
+}
+
+func (p Path) MarshalJSON() ([]byte, error) {
+ var (
+ b []byte
+ m map[string]json.RawMessage
+ err error
+ )
+ type tempPath Path // To prevent JSON marshal recursion error.
+ if b, err = json.Marshal(tempPath(p)); err != nil {
+ return nil, err
+ }
+ if err = json.Unmarshal(b, &m); err != nil {
+ return nil, err
+ }
+ for k, v := range p.XExtensions {
+ if b, err = json.Marshal(v); err != nil {
+ return nil, err
+ }
+ m[k] = b
+ }
+ return json.Marshal(m)
+}
diff --git a/protocol/goai/goai_requestbody.go b/protocol/goai/goai_requestbody.go
index e3c47aa81..353923ac0 100644
--- a/protocol/goai/goai_requestbody.go
+++ b/protocol/goai/goai_requestbody.go
@@ -16,9 +16,9 @@ import (
// RequestBody is specified by OpenAPI/Swagger 3.0 standard.
type RequestBody struct {
- Description string `json:"description,omitempty" yaml:"description,omitempty"`
- Required bool `json:"required,omitempty" yaml:"required,omitempty"`
- Content Content `json:"content,omitempty" yaml:"content,omitempty"`
+ Description string `json:"description,omitempty"`
+ Required bool `json:"required,omitempty"`
+ Content Content `json:"content,omitempty"`
}
type RequestBodyRef struct {
@@ -47,40 +47,42 @@ 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 {
- for k, v := range bizRequestStructSchemaRef.Value.Properties {
- schema.Properties[k] = v
- }
+ if in.RequestDataField == "" && bizRequestStructSchemaRef != nil {
+ // Normal request.
+ 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{
Pointer: in.RequestObject,
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
})
for _, structField := range structFields {
- var (
- fieldName = structField.Name()
- )
+ var fieldName = structField.Name()
if jsonName := structField.TagJsonName(); jsonName != "" {
fieldName = jsonName
}
switch len(dataFieldsPartsArray) {
case 1:
if structField.Name() == dataFieldsPartsArray[0] {
- schema.Properties[fieldName] = bizRequestStructSchemaRef
+ if err = oai.tagMapToSchema(structField.TagMap(), bizRequestStructSchemaRef.Value); err != nil {
+ return nil, err
+ }
+ schema.Properties.Set(fieldName, *bizRequestStructSchemaRef)
break
}
default:
if structField.Name() == dataFieldsPartsArray[0] {
- var (
- structFieldInstance = reflect.New(structField.Type().Type).Elem()
- )
+ var structFieldInstance = reflect.New(structField.Type().Type).Elem()
schemaRef, err := oai.getRequestSchemaRef(getRequestSchemaRefInput{
BusinessStructName: in.BusinessStructName,
RequestObject: structFieldInstance,
@@ -89,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
diff --git a/protocol/goai/goai_response.go b/protocol/goai/goai_response.go
index e85b04254..8961bb395 100644
--- a/protocol/goai/goai_response.go
+++ b/protocol/goai/goai_response.go
@@ -7,100 +7,47 @@
package goai
import (
- "reflect"
-
+ "github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
- "github.com/gogf/gf/v2/os/gstructs"
- "github.com/gogf/gf/v2/text/gstr"
+ "github.com/gogf/gf/v2/util/gconv"
)
// Response is specified by OpenAPI/Swagger 3.0 standard.
type Response struct {
- Description string `json:"description" yaml:"description"`
- Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
- Content Content `json:"content,omitempty" yaml:"content,omitempty"`
- Links Links `json:"links,omitempty" yaml:"links,omitempty"`
+ Description string `json:"description"`
+ Headers Headers `json:"headers,omitempty"`
+ Content Content `json:"content,omitempty"`
+ Links Links `json:"links,omitempty"`
+ XExtensions XExtensions `json:"-"`
}
-// Responses is specified by OpenAPI/Swagger 3.0 standard.
-type Responses map[string]ResponseRef
-
-type ResponseRef struct {
- Ref string
- Value *Response
-}
-
-func (r ResponseRef) MarshalJSON() ([]byte, error) {
- if r.Ref != "" {
- return formatRefToBytes(r.Ref), nil
+func (oai *OpenApiV3) tagMapToResponse(tagMap map[string]string, response *Response) error {
+ var mergedTagMap = oai.fileMapWithShortTags(tagMap)
+ if err := gconv.Struct(mergedTagMap, response); err != nil {
+ return gerror.Wrap(err, `mapping struct tags to Response failed`)
}
- return json.Marshal(r.Value)
+ oai.tagMapToXExtensions(mergedTagMap, response.XExtensions)
+ return nil
}
-type getResponseSchemaRefInput struct {
- BusinessStructName string // The business struct name.
- ResponseObject interface{} // Common response object.
- ResponseDataField string // Common response data field.
-}
-
-func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*SchemaRef, error) {
- if in.ResponseObject == nil {
- return &SchemaRef{
- Ref: in.BusinessStructName,
- }, nil
- }
-
+func (r Response) MarshalJSON() ([]byte, error) {
var (
- dataFieldsPartsArray = gstr.Split(in.ResponseDataField, ".")
- bizResponseStructSchemaRef, bizResponseStructSchemaRefExist = oai.Components.Schemas[in.BusinessStructName]
- schema, err = oai.structToSchema(in.ResponseObject)
+ b []byte
+ m map[string]json.RawMessage
+ err error
)
- if err != nil {
+ type tempResponse Response // To prevent JSON marshal recursion error.
+ if b, err = json.Marshal(tempResponse(r)); err != nil {
return nil, err
}
- if in.ResponseDataField == "" && bizResponseStructSchemaRefExist {
- for k, v := range bizResponseStructSchemaRef.Value.Properties {
- schema.Properties[k] = v
- }
- } else {
- structFields, _ := gstructs.Fields(gstructs.FieldsInput{
- Pointer: in.ResponseObject,
- RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
- })
- for _, structField := range structFields {
- var (
- fieldName = structField.Name()
- )
- if jsonName := structField.TagJsonName(); jsonName != "" {
- fieldName = jsonName
- }
- switch len(dataFieldsPartsArray) {
- case 1:
- if structField.Name() == dataFieldsPartsArray[0] {
- schema.Properties[fieldName] = bizResponseStructSchemaRef
- break
- }
- default:
- if structField.Name() == dataFieldsPartsArray[0] {
- var (
- structFieldInstance = reflect.New(structField.Type().Type).Elem()
- )
- schemaRef, err := oai.getResponseSchemaRef(getResponseSchemaRefInput{
- BusinessStructName: in.BusinessStructName,
- ResponseObject: structFieldInstance,
- ResponseDataField: gstr.Join(dataFieldsPartsArray[1:], "."),
- })
- if err != nil {
- return nil, err
- }
- schema.Properties[fieldName] = *schemaRef
- break
- }
- }
- }
+ if err = json.Unmarshal(b, &m); err != nil {
+ return nil, err
}
-
- return &SchemaRef{
- Value: schema,
- }, nil
+ for k, v := range r.XExtensions {
+ if b, err = json.Marshal(v); err != nil {
+ return nil, err
+ }
+ m[k] = b
+ }
+ return json.Marshal(m)
}
diff --git a/protocol/goai/goai_response_ref.go b/protocol/goai/goai_response_ref.go
new file mode 100644
index 000000000..495aada71
--- /dev/null
+++ b/protocol/goai/goai_response_ref.go
@@ -0,0 +1,101 @@
+// 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 (
+ "reflect"
+
+ "github.com/gogf/gf/v2/internal/json"
+ "github.com/gogf/gf/v2/os/gstructs"
+ "github.com/gogf/gf/v2/text/gstr"
+)
+
+type ResponseRef struct {
+ Ref string
+ Value *Response
+}
+
+// Responses is specified by OpenAPI/Swagger 3.0 standard.
+type Responses map[string]ResponseRef
+
+func (r ResponseRef) MarshalJSON() ([]byte, error) {
+ if r.Ref != "" {
+ return formatRefToBytes(r.Ref), nil
+ }
+ return json.Marshal(r.Value)
+}
+
+type getResponseSchemaRefInput struct {
+ BusinessStructName string // The business struct name.
+ CommonResponseObject interface{} // Common response object.
+ CommonResponseDataField string // Common response data field.
+}
+
+func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*SchemaRef, error) {
+ if in.CommonResponseObject == nil {
+ return &SchemaRef{
+ Ref: in.BusinessStructName,
+ }, nil
+ }
+
+ var (
+ 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 == "" && bizResponseStructSchemaRef != nil {
+ // Normal response.
+ 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{
+ Pointer: in.CommonResponseObject,
+ RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
+ })
+ for _, structField := range structFields {
+ var fieldName = structField.Name()
+ if jsonName := structField.TagJsonName(); jsonName != "" {
+ fieldName = jsonName
+ }
+ switch len(dataFieldsPartsArray) {
+ case 1:
+ if structField.Name() == dataFieldsPartsArray[0] {
+ if err = oai.tagMapToSchema(structField.TagMap(), bizResponseStructSchemaRef.Value); err != nil {
+ return nil, err
+ }
+ schema.Properties.Set(fieldName, *bizResponseStructSchemaRef)
+ break
+ }
+ default:
+ // Recursively creating common response object schema.
+ if structField.Name() == dataFieldsPartsArray[0] {
+ var structFieldInstance = reflect.New(structField.Type().Type).Elem()
+ schemaRef, err := oai.getResponseSchemaRef(getResponseSchemaRefInput{
+ BusinessStructName: in.BusinessStructName,
+ CommonResponseObject: structFieldInstance,
+ CommonResponseDataField: gstr.Join(dataFieldsPartsArray[1:], "."),
+ })
+ if err != nil {
+ return nil, err
+ }
+ schema.Properties.Set(fieldName, *schemaRef)
+ break
+ }
+ }
+ }
+ }
+
+ return &SchemaRef{
+ Value: schema,
+ }, nil
+}
diff --git a/protocol/goai/goai_security.go b/protocol/goai/goai_security.go
index e636b4b04..ece9b8523 100644
--- a/protocol/goai/goai_security.go
+++ b/protocol/goai/goai_security.go
@@ -11,14 +11,14 @@ import (
)
type SecurityScheme struct {
- Type string `json:"type,omitempty" yaml:"type,omitempty"`
- Description string `json:"description,omitempty" yaml:"description,omitempty"`
- Name string `json:"name,omitempty" yaml:"name,omitempty"`
- In string `json:"in,omitempty" yaml:"in,omitempty"`
- Scheme string `json:"scheme,omitempty" yaml:"scheme,omitempty"`
- BearerFormat string `json:"bearerFormat,omitempty" yaml:"bearerFormat,omitempty"`
- Flows *OAuthFlows `json:"flows,omitempty" yaml:"flows,omitempty"`
- OpenIdConnectUrl string `json:"openIdConnectUrl,omitempty" yaml:"openIdConnectUrl,omitempty"`
+ Type string `json:"type,omitempty"`
+ Description string `json:"description,omitempty"`
+ Name string `json:"name,omitempty"`
+ In string `json:"in,omitempty"`
+ Scheme string `json:"scheme,omitempty"`
+ BearerFormat string `json:"bearerFormat,omitempty"`
+ Flows *OAuthFlows `json:"flows,omitempty"`
+ OpenIdConnectUrl string `json:"openIdConnectUrl,omitempty"`
}
type SecuritySchemes map[string]SecuritySchemeRef
@@ -33,17 +33,17 @@ type SecurityRequirements []SecurityRequirement
type SecurityRequirement map[string][]string
type OAuthFlows struct {
- Implicit *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"`
- Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"`
- ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"`
- AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty" yaml:"authorizationCode,omitempty"`
+ Implicit *OAuthFlow `json:"implicit,omitempty"`
+ Password *OAuthFlow `json:"password,omitempty"`
+ ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty"`
+ AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty"`
}
type OAuthFlow struct {
- AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
- TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
- RefreshURL string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"`
- Scopes map[string]string `json:"scopes" yaml:"scopes"`
+ AuthorizationURL string `json:"authorizationUrl,omitempty"`
+ TokenURL string `json:"tokenUrl,omitempty"`
+ RefreshURL string `json:"refreshUrl,omitempty"`
+ Scopes map[string]string `json:"scopes"`
}
func (r SecuritySchemeRef) MarshalJSON() ([]byte, error) {
diff --git a/protocol/goai/goai_server.go b/protocol/goai/goai_server.go
index 3a2d0a07a..7548c8d4a 100644
--- a/protocol/goai/goai_server.go
+++ b/protocol/goai/goai_server.go
@@ -8,16 +8,16 @@ package goai
// Server is specified by OpenAPI/Swagger standard version 3.0.
type Server struct {
- URL string `json:"url" yaml:"url"`
- Description string `json:"description,omitempty" yaml:"description,omitempty"`
- Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"`
+ URL string `json:"url"`
+ Description string `json:"description,omitempty"`
+ Variables map[string]*ServerVariable `json:"variables,omitempty"`
}
// ServerVariable is specified by OpenAPI/Swagger standard version 3.0.
type ServerVariable struct {
- Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"`
- Default string `json:"default,omitempty" yaml:"default,omitempty"`
- Description string `json:"description,omitempty" yaml:"description,omitempty"`
+ Enum []string `json:"enum,omitempty"`
+ Default string `json:"default,omitempty"`
+ Description string `json:"description,omitempty"`
}
// Servers is specified by OpenAPI/Swagger standard version 3.0.
diff --git a/protocol/goai/goai_shema.go b/protocol/goai/goai_shema.go
index ada8c6e9c..5f96628f7 100644
--- a/protocol/goai/goai_shema.go
+++ b/protocol/goai/goai_shema.go
@@ -9,61 +9,89 @@ 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/json"
+ "github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gstructs"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gmeta"
+ "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"`
- AnyOf SchemaRefs `json:"anyOf,omitempty" yaml:"anyOf,omitempty"`
- AllOf SchemaRefs `json:"allOf,omitempty" yaml:"allOf,omitempty"`
- Not *SchemaRef `json:"not,omitempty" yaml:"not,omitempty"`
- Type string `json:"type,omitempty" yaml:"type,omitempty"`
- Title string `json:"title,omitempty" yaml:"title,omitempty"`
- Format string `json:"format,omitempty" yaml:"format,omitempty"`
- Description string `json:"description,omitempty" yaml:"description,omitempty"`
- Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"`
- Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
- Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
- ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
- UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
- ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
- ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
- Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"`
- ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
- WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"`
- AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
- XML interface{} `json:"xml,omitempty" yaml:"xml,omitempty"`
- Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
- Min *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
- Max *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
- MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
- MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
- MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
- Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
- MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
- MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
- Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
- Required []string `json:"required,omitempty" yaml:"required,omitempty"`
- Properties Schemas `json:"properties,omitempty" yaml:"properties,omitempty"`
- MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
- MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
- AdditionalProperties *SchemaRef `json:"additionalProperties,omitempty" yaml:"additionalProperties"`
- Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
+ OneOf SchemaRefs `json:"oneOf,omitempty"`
+ AnyOf SchemaRefs `json:"anyOf,omitempty"`
+ AllOf SchemaRefs `json:"allOf,omitempty"`
+ Not *SchemaRef `json:"not,omitempty"`
+ Type string `json:"type,omitempty"`
+ Title string `json:"title,omitempty"`
+ Format string `json:"format,omitempty"`
+ Description string `json:"description,omitempty"`
+ Enum []interface{} `json:"enum,omitempty"`
+ Default interface{} `json:"default,omitempty"`
+ Example interface{} `json:"example,omitempty"`
+ ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
+ UniqueItems bool `json:"uniqueItems,omitempty"`
+ ExclusiveMin bool `json:"exclusiveMinimum,omitempty"`
+ ExclusiveMax bool `json:"exclusiveMaximum,omitempty"`
+ Nullable bool `json:"nullable,omitempty"`
+ ReadOnly bool `json:"readOnly,omitempty"`
+ WriteOnly bool `json:"writeOnly,omitempty"`
+ AllowEmptyValue bool `json:"allowEmptyValue,omitempty"`
+ XML interface{} `json:"xml,omitempty"`
+ Deprecated bool `json:"deprecated,omitempty"`
+ Min *float64 `json:"minimum,omitempty"`
+ Max *float64 `json:"maximum,omitempty"`
+ MultipleOf *float64 `json:"multipleOf,omitempty"`
+ MinLength uint64 `json:"minLength,omitempty"`
+ MaxLength *uint64 `json:"maxLength,omitempty"`
+ Pattern string `json:"pattern,omitempty"`
+ MinItems uint64 `json:"minItems,omitempty"`
+ MaxItems *uint64 `json:"maxItems,omitempty"`
+ Items *SchemaRef `json:"items,omitempty"`
+ Required []string `json:"required,omitempty"`
+ Properties Schemas `json:"properties,omitempty"`
+ MinProps uint64 `json:"minProperties,omitempty"`
+ MaxProps *uint64 `json:"maxProperties,omitempty"`
+ AdditionalProperties *SchemaRef `json:"additionalProperties,omitempty"`
+ Discriminator *Discriminator `json:"discriminator,omitempty"`
+ XExtensions XExtensions `json:"-"`
+}
+
+func (s Schema) MarshalJSON() ([]byte, error) {
+ var (
+ b []byte
+ m map[string]json.RawMessage
+ err error
+ )
+ type tempSchema Schema // To prevent JSON marshal recursion error.
+ if b, err = json.Marshal(tempSchema(s)); err != nil {
+ return nil, err
+ }
+ if err = json.Unmarshal(b, &m); err != nil {
+ return nil, err
+ }
+ for k, v := range s.XExtensions {
+ if b, err = json.Marshal(v); err != nil {
+ return nil, err
+ }
+ m[k] = b
+ }
+ return json.Marshal(m)
}
// Discriminator is specified by OpenAPI/Swagger standard version 3.0.
type Discriminator struct {
- PropertyName string `json:"propertyName" yaml:"propertyName"`
- Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"`
+ PropertyName string `json:"propertyName"`
+ Mapping map[string]string `json:"mapping,omitempty"`
}
+// addSchema creates schemas with objects.
+// Note that the `object` can be array alias like: `type Res []Item`.
func (oai *OpenApiV3) addSchema(object ...interface{}) error {
for _, v := range object {
if err := oai.doAddSchemaSingle(v); err != nil {
@@ -74,8 +102,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 (
@@ -84,53 +112,63 @@ 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
}
// structToSchema converts and returns given struct object as Schema.
func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
- structFields, _ := gstructs.Fields(gstructs.FieldsInput{
- Pointer: object,
- RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
- })
var (
tagMap = gmeta.Data(object)
schema = &Schema{
- Properties: map[string]SchemaRef{},
+ Properties: createSchemas(),
+ XExtensions: make(XExtensions),
}
+ ignoreProperties []interface{}
)
if len(tagMap) > 0 {
- err := gconv.Struct(oai.fileMapWithShortTags(tagMap), schema)
- if err != nil {
- return nil, gerror.Wrap(err, `mapping meta data tags to Schema failed`)
+ if err := oai.tagMapToSchema(tagMap, schema); err != nil {
+ return nil, err
}
}
if schema.Type != "" && schema.Type != TypeObject {
return schema, nil
}
+ // []struct.
+ if utils.IsArray(object) {
+ schema.Type = TypeArray
+ subSchemaRef, err := oai.newSchemaRefWithGolangType(reflect.TypeOf(object).Elem(), nil)
+ if err != nil {
+ return nil, err
+ }
+ schema.Items = subSchemaRef
+ return schema, nil
+ }
+ // struct.
+ structFields, _ := gstructs.Fields(gstructs.FieldsInput{
+ Pointer: object,
+ RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
+ })
schema.Type = TypeObject
for _, structField := range structFields {
if !gstr.IsLetterUpper(structField.Name()[0]) {
continue
}
- var (
- fieldName = structField.Name()
- )
+ var fieldName = structField.Name()
if jsonName := structField.TagJsonName(); jsonName != "" {
fieldName = jsonName
}
@@ -141,7 +179,64 @@ 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)
+ }
+ }
+ if !isValidParameterName(key) {
+ ignoreProperties = append(ignoreProperties, key)
+ }
+ return true
+ })
+
+ if len(ignoreProperties) > 0 {
+ schema.Properties.Removes(ignoreProperties)
+ }
+
return schema, nil
}
+
+func (oai *OpenApiV3) tagMapToSchema(tagMap map[string]string, schema *Schema) error {
+ var mergedTagMap = oai.fileMapWithShortTags(tagMap)
+ if err := gconv.Struct(mergedTagMap, schema); err != nil {
+ return gerror.Wrap(err, `mapping struct tags to Schema failed`)
+ }
+ oai.tagMapToXExtensions(mergedTagMap, schema.XExtensions)
+ // Validation info to OpenAPI schema pattern.
+ for _, tag := range gvalid.GetTags() {
+ if validationTagValue, ok := tagMap[tag]; ok {
+ _, 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) {
+ var (
+ isAllEnumNumber = true
+ enumArray = gstr.SplitAndTrim(rule[len(patternKeyForIn):], ",")
+ )
+ for _, enum := range enumArray {
+ if !gstr.IsNumeric(enum) {
+ isAllEnumNumber = false
+ break
+ }
+ }
+ if isAllEnumNumber {
+ schema.Enum = gconv.Interfaces(gconv.Int64s(enumArray))
+ } else {
+ schema.Enum = gconv.Interfaces(enumArray)
+ }
+ }
+ }
+ }
+ break
+ }
+ }
+ return nil
+}
diff --git a/protocol/goai/goai_shemaref.go b/protocol/goai/goai_shema_ref.go
similarity index 81%
rename from protocol/goai/goai_shemaref.go
rename to protocol/goai/goai_shema_ref.go
index 14d356686..7480b8e67 100644
--- a/protocol/goai/goai_shemaref.go
+++ b/protocol/goai/goai_shema_ref.go
@@ -9,9 +9,7 @@ package goai
import (
"reflect"
- "github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
- "github.com/gogf/gf/v2/util/gconv"
)
type SchemaRefs []SchemaRef
@@ -27,18 +25,20 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap
oaiFormat = oai.golangTypeToOAIFormat(golangType)
schemaRef = &SchemaRef{}
schema = &Schema{
- Type: oaiType,
- Format: oaiFormat,
+ Type: oaiType,
+ Format: oaiFormat,
+ XExtensions: make(XExtensions),
}
)
if len(tagMap) > 0 {
- if err := gconv.Struct(oai.fileMapWithShortTags(tagMap), schema); err != nil {
- return nil, gerror.Wrap(err, `mapping struct tags to Schema failed`)
+ if err := oai.tagMapToSchema(tagMap, schema); err != nil {
+ return nil, err
}
}
schemaRef.Value = schema
switch oaiType {
case
+ TypeInteger,
TypeNumber,
TypeString,
TypeBoolean:
@@ -72,7 +72,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
}
@@ -82,10 +82,8 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap
default:
// Normal struct object.
- var (
- structTypeName = oai.golangTypeToSchemaName(golangType)
- )
- if _, ok := oai.Components.Schemas[structTypeName]; !ok {
+ var structTypeName = oai.golangTypeToSchemaName(golangType)
+ if oai.Components.Schemas.Get(structTypeName) == nil {
if err := oai.addSchema(reflect.New(golangType).Elem().Interface()); err != nil {
return nil, err
}
diff --git a/protocol/goai/goai_shemas.go b/protocol/goai/goai_shemas.go
new file mode 100644
index 000000000..c8f6c6458
--- /dev/null
+++ b/protocol/goai/goai_shemas.go
@@ -0,0 +1,69 @@
+// 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 // map[string]SchemaRef
+}
+
+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) Removes(names []interface{}) {
+ s.init()
+ s.refs.Removes(names)
+}
+
+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()
+}
diff --git a/protocol/goai/goai_tags.go b/protocol/goai/goai_tags.go
index 844d9a634..6d14ca6ed 100644
--- a/protocol/goai/goai_tags.go
+++ b/protocol/goai/goai_tags.go
@@ -11,7 +11,7 @@ type Tags []Tag
// Tag is specified by OpenAPI/Swagger 3.0 standard.
type Tag struct {
- Name string `json:"name,omitempty" yaml:"name,omitempty"`
- Description string `json:"description,omitempty" yaml:"description,omitempty"`
- ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
+ Name string `json:"name,omitempty"`
+ Description string `json:"description,omitempty"`
+ ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
}
diff --git a/protocol/goai/goai_xextensions.go b/protocol/goai/goai_xextensions.go
new file mode 100644
index 000000000..cc11ef6a6
--- /dev/null
+++ b/protocol/goai/goai_xextensions.go
@@ -0,0 +1,22 @@
+// 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/text/gstr"
+)
+
+// XExtensions stores the `x-` custom extensions.
+type XExtensions map[string]interface{}
+
+func (oai *OpenApiV3) tagMapToXExtensions(tagMap map[string]string, extensions XExtensions) {
+ for k, v := range tagMap {
+ if gstr.HasPrefix(k, "x-") || gstr.HasPrefix(k, "X-") {
+ extensions[k] = v
+ }
+ }
+}
diff --git a/protocol/goai/goai_z_unit_test.go b/protocol/goai/goai_z_unit_test.go
index 9acda5b21..e81b04715 100644
--- a/protocol/goai/goai_z_unit_test.go
+++ b/protocol/goai/goai_z_unit_test.go
@@ -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.TypeInteger)
+ 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.TypeInteger)
+ 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)
})
}
@@ -459,10 +459,16 @@ func TestOpenApiV3_CommonResponse(t *testing.T) {
Object: f,
})
t.AssertNil(err)
+
+ //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.Get("data").Value.Description,
+ `Result data for certain request according API definition`,
+ )
})
}
@@ -505,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)
})
}
@@ -545,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)
})
}
@@ -601,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)
})
}
@@ -657,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`)
})
}
@@ -692,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)
})
}
@@ -739,6 +745,133 @@ 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",
+ })
+ })
+}
+
+func Test_Properties_In_Sequence(t *testing.T) {
+ type ResourceCreateReq struct {
+ g.Meta `path:"/resource" tags:"OSS Resource" method:"put" x-sort:"1" summary:"创建实例(发货)"`
+ AppId uint64 `v:"required" dc:"应用Id"`
+ Uin string `v:"required" dc:"主用户账号,该资源隶属于的账号"`
+ CreateUin string `v:"required" dc:"创建实例的用户账号"`
+ Product string `v:"required" dc:"业务类型" eg:"tdach"`
+ Region string `v:"required" dc:"地域" eg:"ap-guangzhou"`
+ Zone string `v:"required" dc:"区域" eg:"ap-guangzhou-1"`
+ Tenant string `v:"required" dc:"业务自定义数据,透传到底层"`
+ VpcId string `dc:"业务Vpc Id, TCS场景下非必须"`
+ SubnetId string `dc:"业务Vpc子网Id"`
+ Name string `dc:"自定义实例名称,默认和ResourceId一致"`
+ ClusterPreset string `dc:"业务自定义Cluster定义,透传到底层"`
+ Engine string `dc:"引擎名称,例如:TxLightning"`
+ Version string `dc:"引擎版本,例如:10.3.213 (兼容ClickHouse 21.3.12)"`
+ SkipUpdateStatus bool `dc:"是否跳过状态更新,继续保持creating" ed:"http://goframe.org"`
+ }
+ gtest.C(t, func(t *gtest.T) {
+ var (
+ err error
+ oai = goai.New()
+ req = new(ResourceCreateReq)
+ )
+ err = oai.Add(goai.AddInput{
+ Object: req,
+ })
+ t.AssertNil(err)
+ fmt.Println(oai)
+ })
+}
+
+func TestOpenApiV3_Ignore_Parameter(t *testing.T) {
+ type CommonResponse struct {
+ Code int `json:"code" description:"Error code"`
+ Message string `json:"message" description:"Error message"`
+ Data interface{} `json:"data" description:"Result data for certain request according API definition"`
+ }
+ type ProductSearchReq struct {
+ gmeta.Meta `path:"/test" method:"get"`
+ Product string `json:"-" in:"query" v:"required|max:5" description:"Unique product key"`
+ Name string `json:"name" in:"query" v:"required" description:"Instance name"`
+ }
+ type ProductSearchRes struct {
+ ID int64 `json:"-" description:"Product ID"`
+ Product string `json:"product" v:"required" description:"Unique product key"`
+ TemplateName string `json:"templateName" v:"required" description:"Workflow template name"`
+ Version string `json:"version" v:"required" description:"Workflow template version"`
+ TxID string `json:"txID" v:"required" description:"Transaction ID for creating instance"`
+ Globals string `json:"globals" description:"Globals"`
+ }
+
+ f := func(ctx context.Context, req *ProductSearchReq) (res *ProductSearchRes, err error) {
+ return
+ }
+
+ gtest.C(t, func(t *gtest.T) {
+ var (
+ err error
+ oai = goai.New()
+ )
+
+ oai.Config.CommonResponse = CommonResponse{}
+
+ err = oai.Add(goai.AddInput{
+ Object: f,
+ })
+ t.AssertNil(err)
+ // Schema asserts.
+ // fmt.Println(oai.String())
+ t.Assert(len(oai.Components.Schemas.Map()), 3)
+ t.Assert(len(oai.Paths), 1)
+ t.Assert(len(oai.Paths["/test"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties.Map()), 8)
})
}
diff --git a/protocol/goai/testdata/example.yaml b/protocol/goai/testdata/example.yaml
index ad3972205..96ba85255 100644
--- a/protocol/goai/testdata/example.yaml
+++ b/protocol/goai/testdata/example.yaml
@@ -135,7 +135,7 @@ paths:
tags:
- pet
summary: Finds Pets by tags
- description: Muliple tags can be provided with comma separated strings. Use tag1,
+ description: Multiple tags can be provided with comma separated strings. Use tag1,
tag2, tag3 for testing.
operationId: findPetsByTags
parameters:
diff --git a/test/gtest/gtest_util.go b/test/gtest/gtest_util.go
index d8fa33d1d..ca3896d36 100644
--- a/test/gtest/gtest_util.go
+++ b/test/gtest/gtest_util.go
@@ -29,7 +29,7 @@ const (
func C(t *testing.T, f func(t *T)) {
defer func() {
if err := recover(); err != nil {
- fmt.Fprintf(os.Stderr, "%v\n%s", err, gdebug.StackWithFilter([]string{pathFilterKey}))
+ _, _ = fmt.Fprintf(os.Stderr, "%v\n%s", err, gdebug.StackWithFilter([]string{pathFilterKey}))
t.Fail()
}
}()
@@ -342,14 +342,14 @@ func AssertNil(value interface{}) {
if err, ok := value.(error); ok {
panic(fmt.Sprintf(`%+v`, err))
}
- AssertNE(value, nil)
+ Assert(value, nil)
}
-// TestDataPath retrieves and returns the testdata path of current package,
+// DataPath retrieves and returns the testdata path of current package,
// which is used for unit testing cases only.
// The optional parameter `names` specifies the sub-folders/sub-files,
// which will be joined with current system separator and returned with the path.
-func TestDataPath(names ...string) string {
+func DataPath(names ...string) string {
_, path, _ := gdebug.CallerWithFilter([]string{pathFilterKey})
path = filepath.Dir(path) + string(filepath.Separator) + "testdata"
for _, name := range names {
@@ -358,9 +358,9 @@ func TestDataPath(names ...string) string {
return path
}
-// TestDataContent retrieves and returns the file content for specified testdata path of current package
-func TestDataContent(names ...string) string {
- path := TestDataPath(names...)
+// DataContent retrieves and returns the file content for specified testdata path of current package
+func DataContent(names ...string) string {
+ path := DataPath(names...)
if path != "" {
data, err := ioutil.ReadFile(path)
if err == nil {
diff --git a/text/gregex/gregex_cache.go b/text/gregex/gregex_cache.go
index 7ceb778e4..1cc9099c0 100644
--- a/text/gregex/gregex_cache.go
+++ b/text/gregex/gregex_cache.go
@@ -38,8 +38,7 @@ func getRegexp(pattern string) (regex *regexp.Regexp, err error) {
}
// If it does not exist in the cache,
// it compiles the pattern and creates one.
- regex, err = regexp.Compile(pattern)
- if err != nil {
+ if regex, err = regexp.Compile(pattern); err != nil {
err = gerror.Wrapf(err, `regexp.Compile failed for pattern "%s"`, pattern)
return
}
diff --git a/text/gregex/gregex_z_unit_test.go b/text/gregex/gregex_z_unit_test.go
index bf9aae5ab..ca07f8407 100644
--- a/text/gregex/gregex_z_unit_test.go
+++ b/text/gregex/gregex_z_unit_test.go
@@ -70,7 +70,7 @@ func Test_Match(t *testing.T) {
wantSubs := "aaabb"
s := "acbb" + wantSubs + "dd"
subs, err := gregex.Match(re, []byte(s))
- t.Assert(err, nil)
+ t.AssertNil(err)
if string(subs[0]) != wantSubs {
t.Fatalf("regex:%s,Match(%q)[0] = %q; want %q", re, s, subs[0], wantSubs)
}
@@ -89,7 +89,7 @@ func Test_MatchString(t *testing.T) {
wantSubs := "aaabb"
s := "acbb" + wantSubs + "dd"
subs, err := gregex.MatchString(re, s)
- t.Assert(err, nil)
+ t.AssertNil(err)
if string(subs[0]) != wantSubs {
t.Fatalf("regex:%s,Match(%q)[0] = %q; want %q", re, s, subs[0], wantSubs)
}
@@ -109,7 +109,7 @@ func Test_MatchAll(t *testing.T) {
s := "acbb" + wantSubs + "dd"
s = s + `其他的` + s
subs, err := gregex.MatchAll(re, []byte(s))
- t.Assert(err, nil)
+ t.AssertNil(err)
if string(subs[0][0]) != wantSubs {
t.Fatalf("regex:%s,Match(%q)[0] = %q; want %q", re, s, subs[0][0], wantSubs)
}
@@ -135,7 +135,7 @@ func Test_MatchAllString(t *testing.T) {
wantSubs := "aaabb"
s := "acbb" + wantSubs + "dd"
subs, err := gregex.MatchAllString(re, s+`其他的`+s)
- t.Assert(err, nil)
+ t.AssertNil(err)
if string(subs[0][0]) != wantSubs {
t.Fatalf("regex:%s,Match(%q)[0] = %q; want %q", re, s, subs[0][0], wantSubs)
}
@@ -163,7 +163,7 @@ func Test_Replace(t *testing.T) {
s := "acbb" + wantSubs + "dd"
wanted := "acbb" + replace + "dd"
replacedStr, err := gregex.Replace(re, []byte(replace), []byte(s))
- t.Assert(err, nil)
+ t.AssertNil(err)
if string(replacedStr) != wanted {
t.Fatalf("regex:%s,old:%s; want %q", re, s, wanted)
}
@@ -181,7 +181,7 @@ func Test_ReplaceString(t *testing.T) {
s := "acbb" + wantSubs + "dd"
wanted := "acbb" + replace + "dd"
replacedStr, err := gregex.ReplaceString(re, replace, s)
- t.Assert(err, nil)
+ t.AssertNil(err)
if replacedStr != wanted {
t.Fatalf("regex:%s,old:%s; want %q", re, s, wanted)
}
@@ -205,7 +205,7 @@ func Test_ReplaceFun(t *testing.T) {
}
return []byte("[x" + string(s) + "y]")
})
- t.Assert(err, nil)
+ t.AssertNil(err)
if string(replacedStr) != wanted {
t.Fatalf("regex:%s,old:%s; want %q", re, s, wanted)
}
@@ -263,7 +263,7 @@ func Test_ReplaceStringFunc(t *testing.T) {
}
return "[x" + s + "y]"
})
- t.Assert(err, nil)
+ t.AssertNil(err)
if replacedStr != wanted {
t.Fatalf("regex:%s,old:%s; want %q", re, s, wanted)
}
diff --git a/text/gstr/gstr_replace.go b/text/gstr/gstr_replace.go
index 713fc02c7..1449f9be0 100644
--- a/text/gstr/gstr_replace.go
+++ b/text/gstr/gstr_replace.go
@@ -33,21 +33,17 @@ func ReplaceI(origin, search, replace string, count ...int) string {
return origin
}
var (
- searchLength = len(search)
- searchLower = strings.ToLower(search)
- originLower string
- pos int
- diff = len(replace) - searchLength
+ searchLength = len(search)
+ replaceLength = len(replace)
+ searchLower = strings.ToLower(search)
+ originLower string
+ pos int
)
for {
originLower = strings.ToLower(origin)
if pos = Pos(originLower, searchLower, pos); pos != -1 {
origin = origin[:pos] + replace + origin[pos+searchLength:]
- if diff < 0 {
- pos += -diff
- } else {
- pos += diff + 1
- }
+ pos += replaceLength
if n--; n == 0 {
break
}
diff --git a/text/gstr/gstr_z_unit_parse_test.go b/text/gstr/gstr_z_unit_parse_test.go
index 1dbed1ac5..9d4bd90cd 100644
--- a/text/gstr/gstr_z_unit_parse_test.go
+++ b/text/gstr/gstr_z_unit_parse_test.go
@@ -22,41 +22,41 @@ func Test_Parse(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := "goframe.org/index?name=john&score=100"
u, err := url.Parse(s)
- t.Assert(err, nil)
+ t.AssertNil(err)
m, err := gstr.Parse(u.RawQuery)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(m["name"], "john")
t.Assert(m["score"], "100")
// name overwrite
m, err = gstr.Parse("a=1&a=2")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(m, g.Map{
"a": 2,
})
// slice
m, err = gstr.Parse("a[]=1&a[]=2")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(m, g.Map{
"a": g.Slice{"1", "2"},
})
// map
m, err = gstr.Parse("a=1&b=2&c=3")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(m, g.Map{
"a": "1",
"b": "2",
"c": "3",
})
m, err = gstr.Parse("a=1&a=2&c=3")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(m, g.Map{
"a": "2",
"c": "3",
})
// map
m, err = gstr.Parse("m[a]=1&m[b]=2&m[c]=3")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(m, g.Map{
"m": g.Map{
"a": "1",
@@ -65,7 +65,7 @@ func Test_Parse(t *testing.T) {
},
})
m, err = gstr.Parse("m[a]=1&m[a]=2&m[b]=3")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(m, g.Map{
"m": g.Map{
"a": "2",
@@ -74,14 +74,14 @@ func Test_Parse(t *testing.T) {
})
// map - slice
m, err = gstr.Parse("m[a][]=1&m[a][]=2")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(m, g.Map{
"m": g.Map{
"a": g.Slice{"1", "2"},
},
})
m, err = gstr.Parse("m[a][b][]=1&m[a][b][]=2")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(m, g.Map{
"m": g.Map{
"a": g.Map{
@@ -91,7 +91,7 @@ func Test_Parse(t *testing.T) {
})
// map - complicated
m, err = gstr.Parse("m[a1][b1][c1][d1]=1&m[a2][b2]=2&m[a3][b3][c3]=3")
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(m, g.Map{
"m": g.Map{
"a1": g.Map{
diff --git a/text/gstr/gstr_z_unit_test.go b/text/gstr/gstr_z_unit_test.go
index 5744d3ed0..71e020ea9 100644
--- a/text/gstr/gstr_z_unit_test.go
+++ b/text/gstr/gstr_z_unit_test.go
@@ -80,6 +80,7 @@ func Test_IsNumeric(t *testing.T) {
t.Assert(gstr.IsNumeric("1a我"), false)
t.Assert(gstr.IsNumeric("0123"), true)
t.Assert(gstr.IsNumeric("我是中国人"), false)
+ t.Assert(gstr.IsNumeric("1.2.3.4"), false)
})
}
diff --git a/util/gconv/gconv.go b/util/gconv/gconv.go
index 47754a3df..b2de0d507 100644
--- a/util/gconv/gconv.go
+++ b/util/gconv/gconv.go
@@ -10,6 +10,7 @@
package gconv
import (
+ "context"
"fmt"
"math"
"reflect"
@@ -18,8 +19,9 @@ import (
"time"
"github.com/gogf/gf/v2/encoding/gbinary"
+ "github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/internal/json"
- "github.com/gogf/gf/v2/internal/utils"
+ "github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/os/gtime"
)
@@ -34,262 +36,11 @@ var (
}
// StructTagPriority defines the default priority tags for Map*/Struct* functions.
- // Note, the "gconv", "param", "params" tags are used by old version of package.
- // It is strongly recommended using short tag "c" or "p" instead in the future.
+ // Note, the `gconv/param/params` tags are used by old version of package.
+ // It is strongly recommended using short tag `c/p` instead in the future.
StructTagPriority = []string{"gconv", "param", "params", "c", "p", "json"}
)
-// Convert converts the variable `fromValue` to the type `toTypeName`, the type `toTypeName` is specified by string.
-// The optional parameter `extraParams` is used for additional necessary parameter for this conversion.
-// It supports common types conversion as its conversion based on type name string.
-func Convert(fromValue interface{}, toTypeName string, extraParams ...interface{}) interface{} {
- return doConvert(doConvertInput{
- FromValue: fromValue,
- ToTypeName: toTypeName,
- ReferValue: nil,
- Extra: extraParams,
- })
-}
-
-type doConvertInput struct {
- FromValue interface{} // Value that is converted from.
- ToTypeName string // Target value type name in string.
- ReferValue interface{} // Referred value, a value in type `ToTypeName`.
- Extra []interface{} // Extra values for implementing the converting.
-}
-
-// doConvert does commonly use types converting.
-func doConvert(in doConvertInput) interface{} {
- switch in.ToTypeName {
- case "int":
- return Int(in.FromValue)
- case "*int":
- if _, ok := in.FromValue.(*int); ok {
- return in.FromValue
- }
- v := Int(in.FromValue)
- return &v
-
- case "int8":
- return Int8(in.FromValue)
- case "*int8":
- if _, ok := in.FromValue.(*int8); ok {
- return in.FromValue
- }
- v := Int8(in.FromValue)
- return &v
-
- case "int16":
- return Int16(in.FromValue)
- case "*int16":
- if _, ok := in.FromValue.(*int16); ok {
- return in.FromValue
- }
- v := Int16(in.FromValue)
- return &v
-
- case "int32":
- return Int32(in.FromValue)
- case "*int32":
- if _, ok := in.FromValue.(*int32); ok {
- return in.FromValue
- }
- v := Int32(in.FromValue)
- return &v
-
- case "int64":
- return Int64(in.FromValue)
- case "*int64":
- if _, ok := in.FromValue.(*int64); ok {
- return in.FromValue
- }
- v := Int64(in.FromValue)
- return &v
-
- case "uint":
- return Uint(in.FromValue)
- case "*uint":
- if _, ok := in.FromValue.(*uint); ok {
- return in.FromValue
- }
- v := Uint(in.FromValue)
- return &v
-
- case "uint8":
- return Uint8(in.FromValue)
- case "*uint8":
- if _, ok := in.FromValue.(*uint8); ok {
- return in.FromValue
- }
- v := Uint8(in.FromValue)
- return &v
-
- case "uint16":
- return Uint16(in.FromValue)
- case "*uint16":
- if _, ok := in.FromValue.(*uint16); ok {
- return in.FromValue
- }
- v := Uint16(in.FromValue)
- return &v
-
- case "uint32":
- return Uint32(in.FromValue)
- case "*uint32":
- if _, ok := in.FromValue.(*uint32); ok {
- return in.FromValue
- }
- v := Uint32(in.FromValue)
- return &v
-
- case "uint64":
- return Uint64(in.FromValue)
- case "*uint64":
- if _, ok := in.FromValue.(*uint64); ok {
- return in.FromValue
- }
- v := Uint64(in.FromValue)
- return &v
-
- case "float32":
- return Float32(in.FromValue)
- case "*float32":
- if _, ok := in.FromValue.(*float32); ok {
- return in.FromValue
- }
- v := Float32(in.FromValue)
- return &v
-
- case "float64":
- return Float64(in.FromValue)
- case "*float64":
- if _, ok := in.FromValue.(*float64); ok {
- return in.FromValue
- }
- v := Float64(in.FromValue)
- return &v
-
- case "bool":
- return Bool(in.FromValue)
- case "*bool":
- if _, ok := in.FromValue.(*bool); ok {
- return in.FromValue
- }
- v := Bool(in.FromValue)
- return &v
-
- case "string":
- return String(in.FromValue)
- case "*string":
- if _, ok := in.FromValue.(*string); ok {
- return in.FromValue
- }
- v := String(in.FromValue)
- return &v
-
- case "[]byte":
- return Bytes(in.FromValue)
- case "[]int":
- return Ints(in.FromValue)
- case "[]int32":
- return Int32s(in.FromValue)
- case "[]int64":
- return Int64s(in.FromValue)
- case "[]uint":
- return Uints(in.FromValue)
- case "[]uint8":
- return Bytes(in.FromValue)
- case "[]uint32":
- return Uint32s(in.FromValue)
- case "[]uint64":
- return Uint64s(in.FromValue)
- case "[]float32":
- return Float32s(in.FromValue)
- case "[]float64":
- return Float64s(in.FromValue)
- case "[]string":
- return Strings(in.FromValue)
-
- case "Time", "time.Time":
- if len(in.Extra) > 0 {
- return Time(in.FromValue, String(in.Extra[0]))
- }
- return Time(in.FromValue)
- case "*time.Time":
- var v interface{}
- if len(in.Extra) > 0 {
- v = Time(in.FromValue, String(in.Extra[0]))
- } else {
- if _, ok := in.FromValue.(*time.Time); ok {
- return in.FromValue
- }
- v = Time(in.FromValue)
- }
- return &v
-
- case "GTime", "gtime.Time":
- if len(in.Extra) > 0 {
- if v := GTime(in.FromValue, String(in.Extra[0])); v != nil {
- return *v
- } else {
- return *gtime.New()
- }
- }
- if v := GTime(in.FromValue); v != nil {
- return *v
- } else {
- return *gtime.New()
- }
- case "*gtime.Time":
- if len(in.Extra) > 0 {
- if v := GTime(in.FromValue, String(in.Extra[0])); v != nil {
- return v
- } else {
- return gtime.New()
- }
- }
- if v := GTime(in.FromValue); v != nil {
- return v
- } else {
- return gtime.New()
- }
-
- case "Duration", "time.Duration":
- return Duration(in.FromValue)
- case "*time.Duration":
- if _, ok := in.FromValue.(*time.Duration); ok {
- return in.FromValue
- }
- v := Duration(in.FromValue)
- return &v
-
- case "map[string]string":
- return MapStrStr(in.FromValue)
-
- case "map[string]interface{}":
- return Map(in.FromValue)
-
- case "[]map[string]interface{}":
- return Maps(in.FromValue)
-
- default:
- if in.ReferValue != nil {
- var (
- referReflectValue reflect.Value
- )
- if v, ok := in.ReferValue.(reflect.Value); ok {
- referReflectValue = v
- } else {
- referReflectValue = reflect.ValueOf(in.ReferValue)
- }
- in.ToTypeName = referReflectValue.Kind().String()
- in.ReferValue = nil
- return reflect.ValueOf(doConvert(in)).Convert(referReflectValue.Type()).Interface()
- }
- return in.FromValue
- }
-}
-
// Byte converts `any` to byte.
func Byte(any interface{}) byte {
if v, ok := any.(byte); ok {
@@ -314,8 +65,15 @@ func Bytes(any interface{}) []byte {
if f, ok := value.(iBytes); ok {
return f.Bytes()
}
- originValueAndKind := utils.OriginValueAndKind(any)
+ originValueAndKind := reflection.OriginValueAndKind(any)
switch originValueAndKind.OriginKind {
+ case reflect.Map:
+ bytes, err := json.Marshal(any)
+ if err != nil {
+ intlog.Errorf(context.TODO(), `%+v`, err)
+ }
+ return bytes
+
case reflect.Array, reflect.Slice:
var (
ok = true
@@ -506,14 +264,18 @@ func checkJsonAndUnmarshalUseNumber(any interface{}, target interface{}) bool {
switch r := any.(type) {
case []byte:
if json.Valid(r) {
- _ = json.UnmarshalUseNumber(r, &target)
+ if err := json.UnmarshalUseNumber(r, &target); err != nil {
+ return false
+ }
return true
}
case string:
anyAsBytes := []byte(r)
if json.Valid(anyAsBytes) {
- _ = json.UnmarshalUseNumber(anyAsBytes, &target)
+ if err := json.UnmarshalUseNumber(anyAsBytes, &target); err != nil {
+ return false
+ }
return true
}
}
diff --git a/util/gconv/gconv_convert.go b/util/gconv/gconv_convert.go
new file mode 100644
index 000000000..baf48b48d
--- /dev/null
+++ b/util/gconv/gconv_convert.go
@@ -0,0 +1,284 @@
+// 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 gconv
+
+import (
+ "reflect"
+ "time"
+
+ "github.com/gogf/gf/v2/os/gtime"
+)
+
+// Convert converts the variable `fromValue` to the type `toTypeName`, the type `toTypeName` is specified by string.
+// The optional parameter `extraParams` is used for additional necessary parameter for this conversion.
+// It supports common types conversion as its conversion based on type name string.
+func Convert(fromValue interface{}, toTypeName string, extraParams ...interface{}) interface{} {
+ return doConvert(doConvertInput{
+ FromValue: fromValue,
+ ToTypeName: toTypeName,
+ ReferValue: nil,
+ Extra: extraParams,
+ })
+}
+
+type doConvertInput struct {
+ FromValue interface{} // Value that is converted from.
+ ToTypeName string // Target value type name in string.
+ ReferValue interface{} // Referred value, a value in type `ToTypeName`.
+ Extra []interface{} // Extra values for implementing the converting.
+ // Marks that the value is already converted and set to `ReferValue`. Caller can ignore the returned result.
+ // It is an attribute for internal usage purpose.
+ alreadySetToReferValue bool
+}
+
+// doConvert does commonly use types converting.
+func doConvert(in doConvertInput) (convertedValue interface{}) {
+ switch in.ToTypeName {
+ case "int":
+ return Int(in.FromValue)
+ case "*int":
+ if _, ok := in.FromValue.(*int); ok {
+ return in.FromValue
+ }
+ v := Int(in.FromValue)
+ return &v
+
+ case "int8":
+ return Int8(in.FromValue)
+ case "*int8":
+ if _, ok := in.FromValue.(*int8); ok {
+ return in.FromValue
+ }
+ v := Int8(in.FromValue)
+ return &v
+
+ case "int16":
+ return Int16(in.FromValue)
+ case "*int16":
+ if _, ok := in.FromValue.(*int16); ok {
+ return in.FromValue
+ }
+ v := Int16(in.FromValue)
+ return &v
+
+ case "int32":
+ return Int32(in.FromValue)
+ case "*int32":
+ if _, ok := in.FromValue.(*int32); ok {
+ return in.FromValue
+ }
+ v := Int32(in.FromValue)
+ return &v
+
+ case "int64":
+ return Int64(in.FromValue)
+ case "*int64":
+ if _, ok := in.FromValue.(*int64); ok {
+ return in.FromValue
+ }
+ v := Int64(in.FromValue)
+ return &v
+
+ case "uint":
+ return Uint(in.FromValue)
+ case "*uint":
+ if _, ok := in.FromValue.(*uint); ok {
+ return in.FromValue
+ }
+ v := Uint(in.FromValue)
+ return &v
+
+ case "uint8":
+ return Uint8(in.FromValue)
+ case "*uint8":
+ if _, ok := in.FromValue.(*uint8); ok {
+ return in.FromValue
+ }
+ v := Uint8(in.FromValue)
+ return &v
+
+ case "uint16":
+ return Uint16(in.FromValue)
+ case "*uint16":
+ if _, ok := in.FromValue.(*uint16); ok {
+ return in.FromValue
+ }
+ v := Uint16(in.FromValue)
+ return &v
+
+ case "uint32":
+ return Uint32(in.FromValue)
+ case "*uint32":
+ if _, ok := in.FromValue.(*uint32); ok {
+ return in.FromValue
+ }
+ v := Uint32(in.FromValue)
+ return &v
+
+ case "uint64":
+ return Uint64(in.FromValue)
+ case "*uint64":
+ if _, ok := in.FromValue.(*uint64); ok {
+ return in.FromValue
+ }
+ v := Uint64(in.FromValue)
+ return &v
+
+ case "float32":
+ return Float32(in.FromValue)
+ case "*float32":
+ if _, ok := in.FromValue.(*float32); ok {
+ return in.FromValue
+ }
+ v := Float32(in.FromValue)
+ return &v
+
+ case "float64":
+ return Float64(in.FromValue)
+ case "*float64":
+ if _, ok := in.FromValue.(*float64); ok {
+ return in.FromValue
+ }
+ v := Float64(in.FromValue)
+ return &v
+
+ case "bool":
+ return Bool(in.FromValue)
+ case "*bool":
+ if _, ok := in.FromValue.(*bool); ok {
+ return in.FromValue
+ }
+ v := Bool(in.FromValue)
+ return &v
+
+ case "string":
+ return String(in.FromValue)
+ case "*string":
+ if _, ok := in.FromValue.(*string); ok {
+ return in.FromValue
+ }
+ v := String(in.FromValue)
+ return &v
+
+ case "[]byte":
+ return Bytes(in.FromValue)
+ case "[]int":
+ return Ints(in.FromValue)
+ case "[]int32":
+ return Int32s(in.FromValue)
+ case "[]int64":
+ return Int64s(in.FromValue)
+ case "[]uint":
+ return Uints(in.FromValue)
+ case "[]uint8":
+ return Bytes(in.FromValue)
+ case "[]uint32":
+ return Uint32s(in.FromValue)
+ case "[]uint64":
+ return Uint64s(in.FromValue)
+ case "[]float32":
+ return Float32s(in.FromValue)
+ case "[]float64":
+ return Float64s(in.FromValue)
+ case "[]string":
+ return Strings(in.FromValue)
+
+ case "Time", "time.Time":
+ if len(in.Extra) > 0 {
+ return Time(in.FromValue, String(in.Extra[0]))
+ }
+ return Time(in.FromValue)
+ case "*time.Time":
+ var v interface{}
+ if len(in.Extra) > 0 {
+ v = Time(in.FromValue, String(in.Extra[0]))
+ } else {
+ if _, ok := in.FromValue.(*time.Time); ok {
+ return in.FromValue
+ }
+ v = Time(in.FromValue)
+ }
+ return &v
+
+ case "GTime", "gtime.Time":
+ if len(in.Extra) > 0 {
+ if v := GTime(in.FromValue, String(in.Extra[0])); v != nil {
+ return *v
+ } else {
+ return *gtime.New()
+ }
+ }
+ if v := GTime(in.FromValue); v != nil {
+ return *v
+ } else {
+ return *gtime.New()
+ }
+ case "*gtime.Time":
+ if len(in.Extra) > 0 {
+ if v := GTime(in.FromValue, String(in.Extra[0])); v != nil {
+ return v
+ } else {
+ return gtime.New()
+ }
+ }
+ if v := GTime(in.FromValue); v != nil {
+ return v
+ } else {
+ return gtime.New()
+ }
+
+ case "Duration", "time.Duration":
+ return Duration(in.FromValue)
+ case "*time.Duration":
+ if _, ok := in.FromValue.(*time.Duration); ok {
+ return in.FromValue
+ }
+ v := Duration(in.FromValue)
+ return &v
+
+ case "map[string]string":
+ return MapStrStr(in.FromValue)
+
+ case "map[string]interface{}":
+ return Map(in.FromValue)
+
+ case "[]map[string]interface{}":
+ return Maps(in.FromValue)
+
+ case "json.RawMessage":
+ return Bytes(in.FromValue)
+
+ default:
+ if in.ReferValue != nil {
+ var referReflectValue reflect.Value
+ if v, ok := in.ReferValue.(reflect.Value); ok {
+ referReflectValue = v
+ } else {
+ referReflectValue = reflect.ValueOf(in.ReferValue)
+ }
+ defer func() {
+ if recover() != nil {
+ if err := bindVarToReflectValue(referReflectValue, in.FromValue, nil); err == nil {
+ in.alreadySetToReferValue = true
+ convertedValue = referReflectValue.Interface()
+ }
+ }
+ }()
+ in.ToTypeName = referReflectValue.Kind().String()
+ in.ReferValue = nil
+ return reflect.ValueOf(doConvert(in)).Convert(referReflectValue.Type()).Interface()
+ }
+ return in.FromValue
+ }
+}
+
+func doConvertWithReflectValueSet(reflectValue reflect.Value, in doConvertInput) {
+ convertedValue := doConvert(in)
+ if !in.alreadySetToReferValue {
+ reflectValue.Set(reflect.ValueOf(convertedValue))
+ }
+}
diff --git a/util/gconv/gconv_int.go b/util/gconv/gconv_int.go
index 63bd77529..50626da78 100644
--- a/util/gconv/gconv_int.go
+++ b/util/gconv/gconv_int.go
@@ -97,8 +97,10 @@ func Int64(any interface{}) int64 {
if f, ok := value.(iInt64); ok {
return f.Int64()
}
- s := String(value)
- isMinus := false
+ var (
+ s = String(value)
+ isMinus = false
+ )
if len(s) > 0 {
if s[0] == '-' {
isMinus = true
@@ -116,15 +118,6 @@ func Int64(any interface{}) int64 {
return v
}
}
- // Octal
- if len(s) > 1 && s[0] == '0' {
- if v, e := strconv.ParseInt(s[1:], 8, 64); e == nil {
- if isMinus {
- return -v
- }
- return v
- }
- }
// Decimal
if v, e := strconv.ParseInt(s, 10, 64); e == nil {
if isMinus {
diff --git a/util/gconv/gconv_map.go b/util/gconv/gconv_map.go
index c42f8e007..3c2093283 100644
--- a/util/gconv/gconv_map.go
+++ b/util/gconv/gconv_map.go
@@ -217,23 +217,25 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
return dataMap
case reflect.Struct:
+ var dataMap = make(map[string]interface{})
// Map converting interface check.
if v, ok := value.(iMapStrAny); ok {
- m := v.MapStrAny()
- if recursive {
- for k, v := range m {
- m[k] = doMapConvertForMapOrStructValue(false, v, recursive, tags...)
+ // Value copy, in case of concurrent safety.
+ for mapK, mapV := range v.MapStrAny() {
+ if recursive {
+ dataMap[mapK] = doMapConvertForMapOrStructValue(false, mapV, recursive, tags...)
+ } else {
+ dataMap[mapK] = mapV
}
}
- return m
+ return dataMap
}
// Using reflect for converting.
var (
rtField reflect.StructField
rvField reflect.Value
- dataMap = make(map[string]interface{}) // result map.
- reflectType = reflectValue.Type() // attribute value type.
- mapKey = "" // mapKey may be the tag name or the struct attribute name.
+ reflectType = reflectValue.Type() // attribute value type.
+ mapKey = "" // mapKey may be the tag name or the struct attribute name.
)
for i := 0; i < reflectValue.NumField(); i++ {
rtField = reflectType.Field(i)
@@ -319,11 +321,26 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
break
}
array := make([]interface{}, length)
- for i := 0; i < length; i++ {
- array[i] = doMapConvertForMapOrStructValue(false, rvAttrField.Index(i), recursive, tags...)
+ for arrayIndex := 0; arrayIndex < length; arrayIndex++ {
+ array[arrayIndex] = doMapConvertForMapOrStructValue(
+ false, rvAttrField.Index(arrayIndex), recursive, tags...,
+ )
}
dataMap[mapKey] = array
-
+ case reflect.Map:
+ var (
+ mapKeys = rvAttrField.MapKeys()
+ nestedMap = make(map[string]interface{})
+ )
+ for _, k := range mapKeys {
+ nestedMap[String(k.Interface())] = doMapConvertForMapOrStructValue(
+ false,
+ rvAttrField.MapIndex(k).Interface(),
+ recursive,
+ tags...,
+ )
+ }
+ dataMap[mapKey] = nestedMap
default:
if rvField.IsValid() {
dataMap[mapKey] = reflectValue.Field(i).Interface()
diff --git a/util/gconv/gconv_slice_any.go b/util/gconv/gconv_slice_any.go
index d4f73f610..415f47634 100644
--- a/util/gconv/gconv_slice_any.go
+++ b/util/gconv/gconv_slice_any.go
@@ -9,7 +9,8 @@ package gconv
import (
"reflect"
- "github.com/gogf/gf/v2/internal/utils"
+ "github.com/gogf/gf/v2/internal/json"
+ "github.com/gogf/gf/v2/internal/reflection"
)
// SliceAny is alias of Interfaces.
@@ -22,13 +23,10 @@ func Interfaces(any interface{}) []interface{} {
if any == nil {
return nil
}
- if r, ok := any.([]interface{}); ok {
- return r
- }
- var (
- array []interface{} = nil
- )
+ var array []interface{}
switch value := any.(type) {
+ case []interface{}:
+ array = value
case []string:
array = make([]interface{}, len(value))
for k, v := range value {
@@ -65,9 +63,13 @@ func Interfaces(any interface{}) []interface{} {
array[k] = v
}
case []uint8:
- array = make([]interface{}, len(value))
- for k, v := range value {
- array[k] = v
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]interface{}, len(value))
+ for k, v := range value {
+ array[k] = v
+ }
}
case []uint16:
array = make([]interface{}, len(value))
@@ -110,7 +112,7 @@ func Interfaces(any interface{}) []interface{} {
return array
}
// Not a common type, it then uses reflection for conversion.
- originValueAndKind := utils.OriginValueAndKind(any)
+ originValueAndKind := reflection.OriginValueAndKind(any)
switch originValueAndKind.OriginKind {
case reflect.Slice, reflect.Array:
var (
diff --git a/util/gconv/gconv_slice_float.go b/util/gconv/gconv_slice_float.go
index 56c774e33..3d7b49949 100644
--- a/util/gconv/gconv_slice_float.go
+++ b/util/gconv/gconv_slice_float.go
@@ -9,7 +9,8 @@ package gconv
import (
"reflect"
- "github.com/gogf/gf/v2/internal/utils"
+ "github.com/gogf/gf/v2/internal/json"
+ "github.com/gogf/gf/v2/internal/reflection"
)
// SliceFloat is alias of Floats.
@@ -81,9 +82,13 @@ func Float32s(any interface{}) []float32 {
array = append(array, Float32(v))
}
case []uint8:
- array = make([]float32, len(value))
- for k, v := range value {
- array[k] = Float32(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]float32, len(value))
+ for k, v := range value {
+ array[k] = Float32(v)
+ }
}
case []uint16:
array = make([]float32, len(value))
@@ -132,7 +137,7 @@ func Float32s(any interface{}) []float32 {
return array
}
// Not a common type, it then uses reflection for conversion.
- originValueAndKind := utils.OriginValueAndKind(any)
+ originValueAndKind := reflection.OriginValueAndKind(any)
switch originValueAndKind.OriginKind {
case reflect.Slice, reflect.Array:
var (
@@ -201,9 +206,13 @@ func Float64s(any interface{}) []float64 {
array = append(array, Float64(v))
}
case []uint8:
- array = make([]float64, len(value))
- for k, v := range value {
- array[k] = Float64(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]float64, len(value))
+ for k, v := range value {
+ array[k] = Float64(v)
+ }
}
case []uint16:
array = make([]float64, len(value))
@@ -252,7 +261,7 @@ func Float64s(any interface{}) []float64 {
return array
}
// Not a common type, it then uses reflection for conversion.
- originValueAndKind := utils.OriginValueAndKind(any)
+ originValueAndKind := reflection.OriginValueAndKind(any)
switch originValueAndKind.OriginKind {
case reflect.Slice, reflect.Array:
var (
diff --git a/util/gconv/gconv_slice_int.go b/util/gconv/gconv_slice_int.go
index c34701bb9..f28e7fd18 100644
--- a/util/gconv/gconv_slice_int.go
+++ b/util/gconv/gconv_slice_int.go
@@ -9,7 +9,8 @@ package gconv
import (
"reflect"
- "github.com/gogf/gf/v2/internal/utils"
+ "github.com/gogf/gf/v2/internal/json"
+ "github.com/gogf/gf/v2/internal/reflection"
)
// SliceInt is alias of Ints.
@@ -69,9 +70,13 @@ func Ints(any interface{}) []int {
array[k] = int(v)
}
case []uint8:
- array = make([]int, len(value))
- for k, v := range value {
- array[k] = int(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]int, len(value))
+ for k, v := range value {
+ array[k] = int(v)
+ }
}
case []uint16:
array = make([]int, len(value))
@@ -132,7 +137,7 @@ func Ints(any interface{}) []int {
return array
}
// Not a common type, it then uses reflection for conversion.
- originValueAndKind := utils.OriginValueAndKind(any)
+ originValueAndKind := reflection.OriginValueAndKind(any)
switch originValueAndKind.OriginKind {
case reflect.Slice, reflect.Array:
var (
@@ -194,9 +199,13 @@ func Int32s(any interface{}) []int32 {
array[k] = int32(v)
}
case []uint8:
- array = make([]int32, len(value))
- for k, v := range value {
- array[k] = int32(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]int32, len(value))
+ for k, v := range value {
+ array[k] = int32(v)
+ }
}
case []uint16:
array = make([]int32, len(value))
@@ -257,7 +266,7 @@ func Int32s(any interface{}) []int32 {
return array
}
// Not a common type, it then uses reflection for conversion.
- originValueAndKind := utils.OriginValueAndKind(any)
+ originValueAndKind := reflection.OriginValueAndKind(any)
switch originValueAndKind.OriginKind {
case reflect.Slice, reflect.Array:
var (
@@ -319,9 +328,13 @@ func Int64s(any interface{}) []int64 {
array[k] = int64(v)
}
case []uint8:
- array = make([]int64, len(value))
- for k, v := range value {
- array[k] = int64(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]int64, len(value))
+ for k, v := range value {
+ array[k] = int64(v)
+ }
}
case []uint16:
array = make([]int64, len(value))
@@ -382,7 +395,7 @@ func Int64s(any interface{}) []int64 {
return array
}
// Not a common type, it then uses reflection for conversion.
- originValueAndKind := utils.OriginValueAndKind(any)
+ originValueAndKind := reflection.OriginValueAndKind(any)
switch originValueAndKind.OriginKind {
case reflect.Slice, reflect.Array:
var (
diff --git a/util/gconv/gconv_slice_str.go b/util/gconv/gconv_slice_str.go
index 9118bd522..c085d271f 100644
--- a/util/gconv/gconv_slice_str.go
+++ b/util/gconv/gconv_slice_str.go
@@ -9,7 +9,8 @@ package gconv
import (
"reflect"
- "github.com/gogf/gf/v2/internal/utils"
+ "github.com/gogf/gf/v2/internal/json"
+ "github.com/gogf/gf/v2/internal/reflection"
)
// SliceStr is alias of Strings.
@@ -57,9 +58,13 @@ func Strings(any interface{}) []string {
array[k] = String(v)
}
case []uint8:
- array = make([]string, len(value))
- for k, v := range value {
- array[k] = String(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]string, len(value))
+ for k, v := range value {
+ array[k] = String(v)
+ }
}
case []uint16:
array = make([]string, len(value))
@@ -118,7 +123,7 @@ func Strings(any interface{}) []string {
return array
}
// Not a common type, it then uses reflection for conversion.
- originValueAndKind := utils.OriginValueAndKind(any)
+ originValueAndKind := reflection.OriginValueAndKind(any)
switch originValueAndKind.OriginKind {
case reflect.Slice, reflect.Array:
var (
diff --git a/util/gconv/gconv_slice_uint.go b/util/gconv/gconv_slice_uint.go
index 01d7a1d97..a1ffa7617 100644
--- a/util/gconv/gconv_slice_uint.go
+++ b/util/gconv/gconv_slice_uint.go
@@ -10,6 +10,8 @@ import (
"reflect"
"strings"
+ "github.com/gogf/gf/v2/internal/json"
+ "github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/internal/utils"
)
@@ -75,9 +77,13 @@ func Uints(any interface{}) []uint {
case []uint:
array = value
case []uint8:
- array = make([]uint, len(value))
- for k, v := range value {
- array[k] = uint(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]uint, len(value))
+ for k, v := range value {
+ array[k] = uint(v)
+ }
}
case []uint16:
array = make([]uint, len(value))
@@ -141,7 +147,7 @@ func Uints(any interface{}) []uint {
return array
}
// Not a common type, it then uses reflection for conversion.
- originValueAndKind := utils.OriginValueAndKind(any)
+ originValueAndKind := reflection.OriginValueAndKind(any)
switch originValueAndKind.OriginKind {
case reflect.Slice, reflect.Array:
var (
@@ -209,9 +215,13 @@ func Uint32s(any interface{}) []uint32 {
array[k] = uint32(v)
}
case []uint8:
- array = make([]uint32, len(value))
- for k, v := range value {
- array[k] = uint32(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]uint32, len(value))
+ for k, v := range value {
+ array[k] = uint32(v)
+ }
}
case []uint16:
array = make([]uint32, len(value))
@@ -271,7 +281,7 @@ func Uint32s(any interface{}) []uint32 {
return array
}
// Not a common type, it then uses reflection for conversion.
- originValueAndKind := utils.OriginValueAndKind(any)
+ originValueAndKind := reflection.OriginValueAndKind(any)
switch originValueAndKind.OriginKind {
case reflect.Slice, reflect.Array:
var (
@@ -340,9 +350,13 @@ func Uint64s(any interface{}) []uint64 {
array[k] = uint64(v)
}
case []uint8:
- array = make([]uint64, len(value))
- for k, v := range value {
- array[k] = uint64(v)
+ if json.Valid(value) {
+ _ = json.UnmarshalUseNumber(value, &array)
+ } else {
+ array = make([]uint64, len(value))
+ for k, v := range value {
+ array[k] = uint64(v)
+ }
}
case []uint16:
array = make([]uint64, len(value))
@@ -401,7 +415,7 @@ func Uint64s(any interface{}) []uint64 {
return array
}
// Not a common type, it then uses reflection for conversion.
- originValueAndKind := utils.OriginValueAndKind(any)
+ originValueAndKind := reflection.OriginValueAndKind(any)
switch originValueAndKind.OriginKind {
case reflect.Slice, reflect.Array:
var (
diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go
index 03b3a5839..b9eb2f95c 100644
--- a/util/gconv/gconv_struct.go
+++ b/util/gconv/gconv_struct.go
@@ -192,11 +192,11 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
// The key of the attrMap is the attribute name of the struct,
// and the value is its replaced name for later comparison to improve performance.
var (
- tempName string
- elemFieldType reflect.StructField
- elemFieldValue reflect.Value
- elemType = pointerElemReflectValue.Type()
- attrMap = make(map[string]string)
+ tempName string
+ elemFieldType reflect.StructField
+ elemFieldValue reflect.Value
+ elemType = pointerElemReflectValue.Type()
+ attrToCheckNameMap = make(map[string]string)
)
for i := 0; i < pointerElemReflectValue.NumField(); i++ {
elemFieldType = elemType.Field(i)
@@ -219,66 +219,77 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
}
} else {
tempName = elemFieldType.Name
- attrMap[tempName] = utils.RemoveSymbols(tempName)
+ attrToCheckNameMap[tempName] = utils.RemoveSymbols(tempName)
}
}
- if len(attrMap) == 0 {
+ if len(attrToCheckNameMap) == 0 {
return nil
}
// The key of the tagMap is the attribute name of the struct,
// and the value is its replaced tag name for later comparison to improve performance.
var (
- tagMap = make(map[string]string)
- priorityTagArray []string
+ attrToTagCheckNameMap = make(map[string]string)
+ priorityTagArray []string
)
if priorityTag != "" {
priorityTagArray = append(utils.SplitAndTrim(priorityTag, ","), StructTagPriority...)
} else {
priorityTagArray = StructTagPriority
}
- tagToNameMap, err := gstructs.TagMapName(pointerElemReflectValue, priorityTagArray)
+ tagToAttrNameMap, err := gstructs.TagMapName(pointerElemReflectValue, priorityTagArray)
if err != nil {
return err
}
- for tagName, attributeName := range tagToNameMap {
+ for tagName, attributeName := range tagToAttrNameMap {
// If there's something else in the tag string,
// it uses the first part which is split using char ','.
// Eg:
// orm:"id, priority"
// orm:"name, with:uid=id"
- tagMap[attributeName] = utils.RemoveSymbols(strings.Split(tagName, ",")[0])
+ attrToTagCheckNameMap[attributeName] = utils.RemoveSymbols(strings.Split(tagName, ",")[0])
+ // If tag and attribute values both exist in `paramsMap`,
+ // it then uses the tag value overwriting the attribute value in `paramsMap`.
+ if paramsMap[tagName] != nil && paramsMap[attributeName] != nil {
+ paramsMap[attributeName] = paramsMap[tagName]
+ }
}
var (
attrName string
checkName string
)
- for mapK, mapV := range paramsMap {
+ for paramName, paramValue := range paramsMap {
attrName = ""
// It firstly checks the passed mapping rules.
if len(mapping) > 0 {
- if passedAttrKey, ok := mapping[mapK]; ok {
+ if passedAttrKey, ok := mapping[paramName]; ok {
attrName = passedAttrKey
}
}
// It secondly checks the predefined tags and matching rules.
if attrName == "" {
- checkName = utils.RemoveSymbols(mapK)
- // Loop to find the matched attribute name with or without
- // string cases and chars like '-'/'_'/'.'/' '.
+ // It firstly considers `paramName` as accurate tag name,
+ // and retrieve attribute name from `tagToAttrNameMap` .
+ attrName = tagToAttrNameMap[paramName]
+ if attrName == "" {
+ checkName = utils.RemoveSymbols(paramName)
+ // Loop to find the matched attribute name with or without
+ // string cases and chars like '-'/'_'/'.'/' '.
- // Matching the parameters to struct tag names.
- // The `tagV` is the attribute name of the struct.
- for attrKey, cmpKey := range tagMap {
- if strings.EqualFold(checkName, cmpKey) {
- attrName = attrKey
- break
+ // Matching the parameters to struct tag names.
+ // The `attrKey` is the attribute name of the struct.
+ for attrKey, cmpKey := range attrToTagCheckNameMap {
+ if strings.EqualFold(checkName, cmpKey) {
+ attrName = attrKey
+ break
+ }
}
}
+
// Matching the parameters to struct attributes.
if attrName == "" {
- for attrKey, cmpKey := range attrMap {
+ for attrKey, cmpKey := range attrToCheckNameMap {
// Eg:
// UserName eq user_name
// User-Name eq username
@@ -297,12 +308,12 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
continue
}
// If the attribute name is already checked converting, then skip it.
- if _, ok := doneMap[attrName]; ok {
+ if _, ok = doneMap[attrName]; ok {
continue
}
// Mark it done.
doneMap[attrName] = struct{}{}
- if err := bindVarToStructAttr(pointerElemReflectValue, attrName, mapV, mapping); err != nil {
+ if err = bindVarToStructAttr(pointerElemReflectValue, attrName, paramValue, mapping); err != nil {
return err
}
}
@@ -310,8 +321,8 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
}
// bindVarToStructAttr sets value to struct object attribute by name.
-func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, mapping map[string]string) (err error) {
- structFieldValue := elem.FieldByName(name)
+func bindVarToStructAttr(structReflectValue reflect.Value, attrName string, value interface{}, mapping map[string]string) (err error) {
+ structFieldValue := structReflectValue.FieldByName(attrName)
if !structFieldValue.IsValid() {
return nil
}
@@ -322,7 +333,7 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, map
defer func() {
if exception := recover(); exception != nil {
if err = bindVarToReflectValue(structFieldValue, value, mapping); err != nil {
- err = gerror.Wrapf(err, `error binding value to attribute "%s"`, name)
+ err = gerror.Wrapf(err, `error binding value to attribute "%s"`, attrName)
}
}
}()
@@ -330,13 +341,17 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, map
if empty.IsNil(value) {
structFieldValue.Set(reflect.Zero(structFieldValue.Type()))
} else {
- structFieldValue.Set(reflect.ValueOf(doConvert(
- doConvertInput{
- FromValue: value,
- ToTypeName: structFieldValue.Type().String(),
- ReferValue: structFieldValue,
- },
- )))
+ // Common interface check.
+ var ok bool
+ if err, ok = bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok {
+ return err
+ }
+ // Default converting.
+ doConvertWithReflectValueSet(structFieldValue, doConvertInput{
+ FromValue: value,
+ ToTypeName: structFieldValue.Type().String(),
+ ReferValue: structFieldValue,
+ })
}
return nil
}
@@ -412,13 +427,8 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma
return nil
}
- // Common interface check.
- if err, ok := bindVarToReflectValueWithInterfaceCheck(structFieldValue, value); ok {
- return err
- }
-
kind := structFieldValue.Kind()
- // Converting using interface, for some kinds.
+ // Converting using `Set` interface implements, for some types.
switch kind {
case reflect.Slice, reflect.Array, reflect.Ptr, reflect.Interface:
if !structFieldValue.IsNil() {
@@ -429,7 +439,7 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma
}
}
- // Converting by kind.
+ // Converting using reflection by kind.
switch kind {
case reflect.Map:
return doMapToMap(value, structFieldValue, mapping)
@@ -444,55 +454,88 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma
// Note that the slice element might be type of struct,
// so it uses Struct function doing the converting internally.
case reflect.Slice, reflect.Array:
- a := reflect.Value{}
- v := reflect.ValueOf(value)
- if v.Kind() == reflect.Slice || v.Kind() == reflect.Array {
- a = reflect.MakeSlice(structFieldValue.Type(), v.Len(), v.Len())
- if v.Len() > 0 {
- t := a.Index(0).Type()
- for i := 0; i < v.Len(); i++ {
- if t.Kind() == reflect.Ptr {
- e := reflect.New(t.Elem()).Elem()
- if err = doStruct(v.Index(i).Interface(), e, nil, ""); err != nil {
- // Note there's reflect conversion mechanism here.
- e.Set(reflect.ValueOf(v.Index(i).Interface()).Convert(t))
- }
- a.Index(i).Set(e.Addr())
- } else {
- e := reflect.New(t).Elem()
- if err = doStruct(v.Index(i).Interface(), e, nil, ""); err != nil {
- // Note there's reflect conversion mechanism here.
- e.Set(reflect.ValueOf(v.Index(i).Interface()).Convert(t))
- }
- a.Index(i).Set(e)
+ var (
+ reflectArray reflect.Value
+ reflectValue = reflect.ValueOf(value)
+ )
+ if reflectValue.Kind() == reflect.Slice || reflectValue.Kind() == reflect.Array {
+ reflectArray = reflect.MakeSlice(structFieldValue.Type(), reflectValue.Len(), reflectValue.Len())
+ if reflectValue.Len() > 0 {
+ var (
+ elemType = reflectArray.Index(0).Type()
+ elemTypeName string
+ converted bool
+ )
+ for i := 0; i < reflectValue.Len(); i++ {
+ converted = false
+ elemTypeName = elemType.Name()
+ if elemTypeName == "" {
+ elemTypeName = elemType.String()
}
+ var elem reflect.Value
+ if elemType.Kind() == reflect.Ptr {
+ elem = reflect.New(elemType.Elem()).Elem()
+ } else {
+ elem = reflect.New(elemType).Elem()
+ }
+ if elem.Kind() == reflect.Struct {
+ if err = doStruct(reflectValue.Index(i).Interface(), elem, nil, ""); err == nil {
+ converted = true
+ }
+ }
+ if !converted {
+ doConvertWithReflectValueSet(elem, doConvertInput{
+ FromValue: reflectValue.Index(i).Interface(),
+ ToTypeName: elemTypeName,
+ ReferValue: elem,
+ })
+ }
+ if elemType.Kind() == reflect.Ptr {
+ // Before it sets the `elem` to array, do pointer converting if necessary.
+ elem = elem.Addr()
+ }
+ reflectArray.Index(i).Set(elem)
}
}
} else {
- a = reflect.MakeSlice(structFieldValue.Type(), 1, 1)
- t := a.Index(0).Type()
- if t.Kind() == reflect.Ptr {
- // Pointer element.
- e := reflect.New(t.Elem()).Elem()
- if err = doStruct(value, e, nil, ""); err != nil {
- // Note there's reflect conversion mechanism here.
- e.Set(reflect.ValueOf(value).Convert(t))
- }
- a.Index(0).Set(e.Addr())
- } else {
- // Just consider it as struct element. (Although it might be other types but not basic types, eg: map)
- e := reflect.New(t).Elem()
- if err = doStruct(value, e, nil, ""); err != nil {
- return err
- }
- a.Index(0).Set(e)
+ reflectArray = reflect.MakeSlice(structFieldValue.Type(), 1, 1)
+ var (
+ elem reflect.Value
+ elemType = reflectArray.Index(0).Type()
+ elemTypeName = elemType.Name()
+ converted bool
+ )
+ if elemTypeName == "" {
+ elemTypeName = elemType.String()
}
+ if elemType.Kind() == reflect.Ptr {
+ elem = reflect.New(elemType.Elem()).Elem()
+ } else {
+ elem = reflect.New(elemType).Elem()
+ }
+ if elem.Kind() == reflect.Struct {
+ if err = doStruct(value, elem, nil, ""); err == nil {
+ converted = true
+ }
+ }
+ if !converted {
+ doConvertWithReflectValueSet(elem, doConvertInput{
+ FromValue: value,
+ ToTypeName: elemTypeName,
+ ReferValue: elem,
+ })
+ }
+ if elemType.Kind() == reflect.Ptr {
+ // Before it sets the `elem` to array, do pointer converting if necessary.
+ elem = elem.Addr()
+ }
+ reflectArray.Index(0).Set(elem)
}
- structFieldValue.Set(a)
+ structFieldValue.Set(reflectArray)
case reflect.Ptr:
item := reflect.New(structFieldValue.Type().Elem())
- if err, ok := bindVarToReflectValueWithInterfaceCheck(item, value); ok {
+ if err, ok = bindVarToReflectValueWithInterfaceCheck(item, value); ok {
structFieldValue.Set(item)
return err
}
diff --git a/util/gconv/gconv_structs.go b/util/gconv/gconv_structs.go
index bb73319fe..be1322a72 100644
--- a/util/gconv/gconv_structs.go
+++ b/util/gconv/gconv_structs.go
@@ -48,7 +48,7 @@ func doStructs(params interface{}, pointer interface{}, mapping map[string]strin
}
defer func() {
- // Catch the panic, especially the reflect operation panics.
+ // Catch the panic, especially the reflection operation panics.
if exception := recover(); exception != nil {
if v, ok := exception.(error); ok && gerror.HasStack(v) {
err = v
@@ -102,7 +102,7 @@ func doStructs(params interface{}, pointer interface{}, mapping map[string]strin
case reflect.Slice, reflect.Array:
paramsList = make([]interface{}, paramsRv.Len())
for i := 0; i < paramsRv.Len(); i++ {
- paramsList[i] = paramsRv.Index(i)
+ paramsList[i] = paramsRv.Index(i).Interface()
}
default:
var paramsMaps = Maps(params)
diff --git a/util/gconv/gconv_uint.go b/util/gconv/gconv_uint.go
index 4f8d3b63a..fd0c25854 100644
--- a/util/gconv/gconv_uint.go
+++ b/util/gconv/gconv_uint.go
@@ -104,12 +104,6 @@ func Uint64(any interface{}) uint64 {
return v
}
}
- // Octal
- if len(s) > 1 && s[0] == '0' {
- if v, e := strconv.ParseUint(s[1:], 8, 64); e == nil {
- return v
- }
- }
// Decimal
if v, e := strconv.ParseUint(s, 10, 64); e == nil {
return v
diff --git a/util/gconv/gconv_z_unit_all_test.go b/util/gconv/gconv_z_unit_all_test.go
index d42351889..87b0f430f 100644
--- a/util/gconv/gconv_z_unit_all_test.go
+++ b/util/gconv/gconv_z_unit_all_test.go
@@ -36,6 +36,103 @@ func (s1 S1) Error() string {
return "22222"
}
+// https://github.com/gogf/gf/issues/1227
+func Test_Issue1227(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ type StructFromIssue1227 struct {
+ Name string `json:"n1"`
+ }
+ tests := []struct {
+ name string
+ origin interface{}
+ want string
+ }{
+ {
+ name: "Case1",
+ origin: `{"n1":"n1"}`,
+ want: "n1",
+ },
+ {
+ name: "Case2",
+ origin: `{"name":"name"}`,
+ want: "",
+ },
+ {
+ name: "Case3",
+ origin: `{"NaMe":"NaMe"}`,
+ want: "",
+ },
+ {
+ name: "Case4",
+ origin: g.Map{"n1": "n1"},
+ want: "n1",
+ },
+ {
+ name: "Case5",
+ origin: g.Map{"NaMe": "n1"},
+ want: "n1",
+ },
+ }
+ for _, tt := range tests {
+ p := StructFromIssue1227{}
+ if err := gconv.Struct(tt.origin, &p); err != nil {
+ t.Error(err)
+ }
+ t.Assert(p.Name, tt.want)
+ }
+ })
+
+ // Chinese key.
+ gtest.C(t, func(t *gtest.T) {
+ type StructFromIssue1227 struct {
+ Name string `json:"中文Key"`
+ }
+ tests := []struct {
+ name string
+ origin interface{}
+ want string
+ }{
+ {
+ name: "Case1",
+ origin: `{"中文Key":"n1"}`,
+ want: "n1",
+ },
+ {
+ name: "Case2",
+ origin: `{"Key":"name"}`,
+ want: "",
+ },
+ {
+ name: "Case3",
+ origin: `{"NaMe":"NaMe"}`,
+ want: "",
+ },
+ {
+ name: "Case4",
+ origin: g.Map{"中文Key": "n1"},
+ want: "n1",
+ },
+ {
+ name: "Case5",
+ origin: g.Map{"中文KEY": "n1"},
+ want: "n1",
+ },
+ {
+ name: "Case5",
+ origin: g.Map{"KEY": "n1"},
+ want: "",
+ },
+ }
+ for _, tt := range tests {
+ p := StructFromIssue1227{}
+ if err := gconv.Struct(tt.origin, &p); err != nil {
+ t.Error(err)
+ }
+ t.Assert(p.Name, tt.want)
+ }
+ })
+}
+
func Test_Bool_All(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var any interface{} = nil
@@ -211,7 +308,7 @@ func Test_Int64_All(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var any interface{} = nil
t.AssertEQ(gconv.Int64("0x00e"), int64(14))
- t.Assert(gconv.Int64("022"), int64(18))
+ t.Assert(gconv.Int64("022"), int64(22))
t.Assert(gconv.Int64(any), int64(0))
t.Assert(gconv.Int64(true), 1)
@@ -405,7 +502,7 @@ func Test_Uint64_All(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var any interface{} = nil
t.AssertEQ(gconv.Uint64("0x00e"), uint64(14))
- t.Assert(gconv.Uint64("022"), uint64(18))
+ t.Assert(gconv.Uint64("022"), uint64(22))
t.AssertEQ(gconv.Uint64(any), uint64(0))
t.AssertEQ(gconv.Uint64(true), uint64(1))
@@ -1269,7 +1366,7 @@ func Test_Struct_PrivateAttribute_All(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
user := new(User)
err := gconv.Struct(g.Map{"id": 1, "name": "john"}, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Id, 1)
t.Assert(user.name, "")
})
diff --git a/util/gconv/gconv_z_unit_custom_type_test.go b/util/gconv/gconv_z_unit_custom_type_test.go
index 1bf79cd87..d6b168b63 100644
--- a/util/gconv/gconv_z_unit_custom_type_test.go
+++ b/util/gconv/gconv_z_unit_custom_type_test.go
@@ -36,6 +36,6 @@ func Test_Struct_CustomTimeDuration_Attribute(t *testing.T) {
"name": "john",
"timeout": "1s",
}, &a)
- t.Assert(err, nil)
+ t.AssertNil(err)
})
}
diff --git a/util/gconv/gconv_z_unit_map_test.go b/util/gconv/gconv_z_unit_map_test.go
index 000f439bd..634d0ebd1 100644
--- a/util/gconv/gconv_z_unit_map_test.go
+++ b/util/gconv/gconv_z_unit_map_test.go
@@ -7,7 +7,9 @@
package gconv_test
import (
+ "encoding/json"
"github.com/gogf/gf/v2/util/gutil"
+ "gopkg.in/yaml.v3"
"testing"
"github.com/gogf/gf/v2/frame/g"
@@ -407,3 +409,70 @@ func Test_MapDeepWithAttributeTag(t *testing.T) {
t.Assert(m["base"].(map[string]interface{})["create_time"], user.CreateTime)
})
}
+
+func Test_MapDeepWithNestedMapAnyAny(t *testing.T) {
+ type User struct {
+ ExtraAttributes g.Map `c:"extra_attributes"`
+ }
+
+ gtest.C(t, func(t *gtest.T) {
+ user := &User{
+ ExtraAttributes: g.Map{
+ "simple_attribute": 123,
+ "map_string_attribute": g.Map{
+ "inner_value": 456,
+ },
+ "map_interface_attribute": g.MapAnyAny{
+ "inner_value": 456,
+ 123: "integer_key_should_be_converted_to_string",
+ },
+ },
+ }
+ m := gconv.MapDeep(user)
+ t.Assert(m, g.Map{
+ "extra_attributes": g.Map{
+ "simple_attribute": 123,
+ "map_string_attribute": g.Map{
+ "inner_value": user.ExtraAttributes["map_string_attribute"].(g.Map)["inner_value"],
+ },
+ "map_interface_attribute": g.Map{
+ "inner_value": user.ExtraAttributes["map_interface_attribute"].(g.MapAnyAny)["inner_value"],
+ "123": "integer_key_should_be_converted_to_string",
+ },
+ },
+ })
+ })
+
+ type Outer struct {
+ OuterStruct map[string]interface{} `c:"outer_struct" yaml:"outer_struct"`
+ Field3 map[string]interface{} `c:"field3" yaml:"field3"`
+ }
+
+ gtest.C(t, func(t *gtest.T) {
+ problemYaml := []byte(`
+outer_struct:
+ field1: &anchor1
+ inner1: 123
+ inner2: 345
+ field2:
+ inner3: 456
+ inner4: 789
+ <<: *anchor1
+field3:
+ 123: integer_key
+`)
+ parsed := &Outer{}
+
+ err := yaml.Unmarshal(problemYaml, parsed)
+ t.AssertNil(err)
+
+ _, err = json.Marshal(parsed)
+ t.Assert(err.Error(), "json: unsupported type: map[interface {}]interface {}")
+
+ converted := gconv.MapDeep(parsed)
+ jsonData, err := json.Marshal(converted)
+ t.AssertNil(err)
+
+ t.Assert(string(jsonData), `{"field3":{"123":"integer_key"},"outer_struct":{"field1":{"inner1":123,"inner2":345},"field2":{"inner1":123,"inner2":345,"inner3":456,"inner4":789}}}`)
+ })
+}
diff --git a/util/gconv/gconv_z_unit_maptomap_test.go b/util/gconv/gconv_z_unit_maptomap_test.go
index ea0aa9db2..78500ec99 100644
--- a/util/gconv/gconv_z_unit_maptomap_test.go
+++ b/util/gconv/gconv_z_unit_maptomap_test.go
@@ -91,7 +91,7 @@ func Test_MapToMap2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := (map[string]User)(nil)
err := gconv.MapToMap(params, &m)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(len(m), 1)
t.Assert(m["key"].Id, 1)
t.Assert(m["key"].Name, "john")
@@ -99,7 +99,7 @@ func Test_MapToMap2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := make(map[string]*User)
err := gconv.MapToMap(params, &m)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(len(m), 1)
t.Assert(m["key"].Id, 1)
t.Assert(m["key"].Name, "john")
@@ -107,7 +107,7 @@ func Test_MapToMap2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := (map[string]*User)(nil)
err := gconv.MapToMap(params, &m)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(len(m), 1)
t.Assert(m["key"].Id, 1)
t.Assert(m["key"].Name, "john")
@@ -136,7 +136,7 @@ func Test_MapToMapDeep(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := (map[string]*User)(nil)
err := gconv.MapToMap(params, &m)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(len(m), 1)
t.Assert(m["key"].Id, 1)
t.Assert(m["key"].Name, "john")
diff --git a/util/gconv/gconv_z_unit_scan_test.go b/util/gconv/gconv_z_unit_scan_test.go
index e34f91d99..f94bf3e1a 100644
--- a/util/gconv/gconv_z_unit_scan_test.go
+++ b/util/gconv/gconv_z_unit_scan_test.go
@@ -8,11 +8,13 @@ package gconv_test
import (
"fmt"
+ "math/big"
+ "testing"
+
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/gconv"
- "testing"
)
func Test_Scan_StructStructs(t *testing.T) {
@@ -33,7 +35,7 @@ func Test_Scan_StructStructs(t *testing.T) {
}
)
err := gconv.Scan(params, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user, &User{
Uid: 1,
Name: "john",
@@ -91,7 +93,7 @@ func Test_Scan_StructStr(t *testing.T) {
params = `{"uid":1,"name":"john", "pass1":"123","pass2":"456"}`
)
err := gconv.Scan(params, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user, &User{
Uid: 1,
Name: "john",
@@ -108,7 +110,7 @@ func Test_Scan_StructStr(t *testing.T) {
]`
)
err := gconv.Scan(params, &users)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(users, g.Slice{
&User{
Uid: 1,
@@ -601,3 +603,30 @@ func Test_ScanList_Embedded(t *testing.T) {
t.Assert(len(entities[2].UserScores), 0)
})
}
+
+type Float64 float64
+
+func (f *Float64) UnmarshalValue(value interface{}) error {
+ if v, ok := value.(*big.Rat); ok {
+ f64, _ := v.Float64()
+ *f = Float64(f64)
+ }
+ return nil
+}
+
+func Test_Issue1607(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ type Demo struct {
+ B Float64
+ }
+ rat := &big.Rat{}
+ rat.SetFloat64(1.5)
+
+ var demos = make([]Demo, 1)
+ err := gconv.Scan([]map[string]interface{}{
+ {"A": 1, "B": rat},
+ }, &demos)
+ t.AssertNil(err)
+ t.Assert(demos[0].B, 1.5)
+ })
+}
diff --git a/util/gconv/gconv_z_unit_slice_test.go b/util/gconv/gconv_z_unit_slice_test.go
index 750711daa..7ce358a4f 100644
--- a/util/gconv/gconv_z_unit_slice_test.go
+++ b/util/gconv/gconv_z_unit_slice_test.go
@@ -103,6 +103,10 @@ func Test_Strings(t *testing.T) {
}
t.AssertEQ(gconv.Strings(array), []string{"1", "2", "3"})
})
+ // https://github.com/gogf/gf/issues/1750
+ gtest.C(t, func(t *gtest.T) {
+ t.AssertEQ(gconv.Strings("123"), []string{"123"})
+ })
}
func Test_Slice_Interfaces(t *testing.T) {
@@ -163,7 +167,7 @@ func Test_Slice_Structs(t *testing.T) {
{"id": 2, "name": "smith", "age": 20},
}
err := gconv.Structs(params, &users)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Id, params[0]["id"])
t.Assert(users[0].Name, params[0]["name"])
diff --git a/util/gconv/gconv_z_unit_struct_marshal_unmarshal_test.go b/util/gconv/gconv_z_unit_struct_marshal_unmarshal_test.go
index c6381923b..b200e732b 100644
--- a/util/gconv/gconv_z_unit_struct_marshal_unmarshal_test.go
+++ b/util/gconv/gconv_z_unit_struct_marshal_unmarshal_test.go
@@ -40,7 +40,7 @@ func Test_Struct_UnmarshalValue1(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
st := &MyTimeSt{}
err := gconv.Struct(g.Map{"ServiceDate": "2020-10-10 12:00:01"}, st)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(st.ServiceDate.Time.Format("2006-01-02 15:04:05"), "2020-10-10 12:00:01")
})
gtest.C(t, func(t *gtest.T) {
@@ -103,7 +103,7 @@ func Test_Struct_UnmarshalValue2(t *testing.T) {
var p1, p2 *Pkg
p1 = NewPkg([]byte("123"))
err := gconv.Struct(p1.Marshal(), &p2)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(p1, p2)
})
}
diff --git a/util/gconv/gconv_z_unit_struct_test.go b/util/gconv/gconv_z_unit_struct_test.go
index 75e8adbb7..ea9c29e9a 100644
--- a/util/gconv/gconv_z_unit_struct_test.go
+++ b/util/gconv/gconv_z_unit_struct_test.go
@@ -11,6 +11,7 @@ import (
"time"
"github.com/gogf/gf/v2/container/gvar"
+ "github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/os/gtime"
@@ -340,7 +341,7 @@ func Test_Struct_Attr_CustomType1(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
user := new(User)
err := gconv.Struct(g.Map{"id": 1, "name": "john"}, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Id, 1)
t.Assert(user.Name, "john")
})
@@ -355,7 +356,7 @@ func Test_Struct_Attr_CustomType2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
user := new(User)
err := gconv.Struct(g.Map{"id": g.Slice{1, 2}, "name": "john"}, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Id, g.Slice{1, 2})
t.Assert(user.Name, "john")
})
@@ -408,7 +409,7 @@ func Test_Struct_PrivateAttribute(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
user := new(User)
err := gconv.Struct(g.Map{"id": 1, "name": "john"}, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Id, 1)
t.Assert(user.name, "")
})
@@ -431,7 +432,7 @@ func Test_StructEmbedded1(t *testing.T) {
"age": 18,
}
err := gconv.Struct(params, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Id, params["id"])
t.Assert(user.Name, params["name"])
t.Assert(user.Age, 18)
@@ -459,7 +460,7 @@ func Test_StructEmbedded2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
user := new(User)
err := gconv.Struct(params, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Id, 1)
t.Assert(user.Uid, 10)
t.Assert(user.Name, "john")
@@ -492,7 +493,7 @@ func Test_StructEmbedded3(t *testing.T) {
}
user := new(User)
err := gconv.Struct(data, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Id, 100)
t.Assert(user.Uid, 101)
t.Assert(user.Nickname, "T1")
@@ -564,16 +565,16 @@ func Test_StructEmbedded5(t *testing.T) {
user1 := new(UserWithBase1)
user2 := new(UserWithBase2)
err = gconv.Struct(data, user1)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user1, &UserWithBase1{1, "john", Base{"123", "456"}})
err = gconv.Struct(data, user2)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user2, &UserWithBase2{1, "john", Base{"", ""}})
var user3 *UserWithBase1
err = gconv.Struct(user1, &user3)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user3, user1)
})
}
@@ -669,7 +670,7 @@ func Test_Struct_Create(t *testing.T) {
"Name": "john",
}
err := gconv.Struct(params, &user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Uid, 1)
t.Assert(user.Name, "john")
})
@@ -702,7 +703,7 @@ func Test_Struct_Interface(t *testing.T) {
"Name": nil,
}
err := gconv.Struct(params, &user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Uid, 1)
t.Assert(user.Name, nil)
})
@@ -731,7 +732,7 @@ func Test_Struct_NilAttribute(t *testing.T) {
"txt": "hello",
"items": nil,
}, m)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.AssertNE(m.Me, nil)
t.Assert(m.Me["day"], "20009")
t.Assert(m.Items, nil)
@@ -874,11 +875,11 @@ func Test_Struct_Complex(t *testing.T) {
}`
m := make(g.Map)
err := json.UnmarshalUseNumber([]byte(data), &m)
- t.Assert(err, nil)
+ t.AssertNil(err)
model := new(XinYanModel)
err = gconv.Struct(m, model)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(model.ErrorCode, nil)
t.Assert(model.ErrorMsg, nil)
t.Assert(model.Success, true)
@@ -933,7 +934,7 @@ func Test_Struct_Embedded(t *testing.T) {
}
v2 := g.Map{}
err := gconv.Struct(v2, &v1)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(v1.Test(), "john")
})
// Implemented interface attribute.
@@ -945,7 +946,7 @@ func Test_Struct_Embedded(t *testing.T) {
"name": "test",
}
err := gconv.Struct(v2, &v1)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(v1.Test(), "test")
})
// No implemented interface attribute.
@@ -955,7 +956,7 @@ func Test_Struct_Embedded(t *testing.T) {
"name": "test",
}
err := gconv.Struct(v2, &v1)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(v1.TestInterface, nil)
})
}
@@ -968,7 +969,7 @@ func Test_Struct_Slice(t *testing.T) {
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
@@ -978,7 +979,7 @@ func Test_Struct_Slice(t *testing.T) {
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
@@ -988,7 +989,7 @@ func Test_Struct_Slice(t *testing.T) {
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
@@ -998,7 +999,7 @@ func Test_Struct_Slice(t *testing.T) {
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
@@ -1008,7 +1009,7 @@ func Test_Struct_Slice(t *testing.T) {
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
@@ -1018,7 +1019,7 @@ func Test_Struct_Slice(t *testing.T) {
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
@@ -1028,7 +1029,7 @@ func Test_Struct_Slice(t *testing.T) {
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Scores, array)
})
gtest.C(t, func(t *gtest.T) {
@@ -1038,7 +1039,7 @@ func Test_Struct_Slice(t *testing.T) {
user := new(User)
array := g.Slice{1, 2, 3}
err := gconv.Struct(g.Map{"scores": array}, user)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(user.Scores, array)
})
}
@@ -1078,7 +1079,7 @@ func Test_Struct_WithJson(t *testing.T) {
b, _ := json.Marshal(b1)
b2 := &B{}
err := gconv.Struct(b, b2)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(b2, b1)
})
}
@@ -1102,7 +1103,7 @@ func Test_Struct_AttrStructHasTheSameTag(t *testing.T) {
}
order := new(Order)
err := gconv.Struct(data, order)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(order.Id, data["id"])
t.Assert(order.UpdatedAt, data["updated_at"])
t.Assert(order.Product.Id, 0)
@@ -1126,7 +1127,7 @@ func Test_Struct_DirectReflectSet(t *testing.T) {
b *A
)
err := gconv.Struct(a, &b)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.AssertEQ(a, b)
})
gtest.C(t, func(t *gtest.T) {
@@ -1138,7 +1139,7 @@ func Test_Struct_DirectReflectSet(t *testing.T) {
b A
)
err := gconv.Struct(a, &b)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.AssertEQ(a, b)
})
}
@@ -1160,7 +1161,7 @@ func Test_Struct_NilEmbeddedStructAttribute(t *testing.T) {
"id": 1,
"name": nil,
}, &b)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(b.Id, 1)
t.Assert(b.Name, "")
})
@@ -1175,7 +1176,7 @@ func Test_Struct_JsonParam(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var a = A{}
err := gconv.Struct([]byte(`{"id":1,"name":"john"}`), &a)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(a.Id, 1)
t.Assert(a.Name, "john")
})
@@ -1183,7 +1184,7 @@ func Test_Struct_JsonParam(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var a = &A{}
err := gconv.Struct([]byte(`{"id":1,"name":"john"}`), a)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(a.Id, 1)
t.Assert(a.Name, "john")
})
@@ -1191,7 +1192,7 @@ func Test_Struct_JsonParam(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var a *A
err := gconv.Struct([]byte(`{"id":1,"name":"john"}`), &a)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(a.Id, 1)
t.Assert(a.Name, "john")
})
@@ -1213,7 +1214,7 @@ func Test_Struct_GVarAttribute(t *testing.T) {
}
)
err := gconv.Struct(data, &a)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(a.Id, data["id"])
t.Assert(a.Name, data["name"])
t.Assert(a.Status, data["status"])
@@ -1258,3 +1259,65 @@ func Test_Struct_Empty_MapStringString(t *testing.T) {
t.AssertNil(err)
})
}
+
+// https://github.com/gogf/gf/issues/1563
+func Test_Struct_Issue1563(t *testing.T) {
+ type User struct {
+ Pass1 string `c:"password1"`
+ }
+ gtest.C(t, func(t *gtest.T) {
+ for i := 0; i < 100; i++ {
+ user := new(User)
+ params2 := g.Map{
+ "password1": "111",
+ //"PASS1": "222",
+ "Pass1": "333",
+ }
+ if err := gconv.Struct(params2, user); err == nil {
+ t.Assert(user.Pass1, `111`)
+ }
+ }
+ })
+}
+
+// https://github.com/gogf/gf/issues/1597
+func Test_Struct_Issue1597(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ type S struct {
+ A int
+ B json.RawMessage
+ }
+
+ jsonByte := []byte(`{
+ "a":1,
+ "b":{
+ "c": 3
+ }
+ }`)
+ data, err := gjson.DecodeToJson(jsonByte)
+ t.AssertNil(err)
+ s := &S{}
+ err = data.Scan(s)
+ t.AssertNil(err)
+ t.Assert(s.B, `{"c":3}`)
+ })
+}
+
+func Test_Scan_WithDoubleSliceAttribute(t *testing.T) {
+ inputData := [][]string{
+ {"aa", "bb", "cc"},
+ {"11", "22", "33"},
+ }
+ data := struct {
+ Data [][]string
+ }{
+ Data: inputData,
+ }
+ gtest.C(t, func(t *gtest.T) {
+ jv := gjson.New(gjson.MustEncodeString(data))
+ err := jv.Scan(&data)
+ t.AssertNil(err)
+ t.Assert(data.Data, inputData)
+ })
+
+}
diff --git a/util/gconv/gconv_z_unit_structs_test.go b/util/gconv/gconv_z_unit_structs_test.go
index f834fbb6b..673379b07 100644
--- a/util/gconv/gconv_z_unit_structs_test.go
+++ b/util/gconv/gconv_z_unit_structs_test.go
@@ -32,7 +32,7 @@ func Test_Structs_WithTag(t *testing.T) {
},
}
err := gconv.Structs(params, &users)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Uid, 1)
t.Assert(users[0].NickName, "name1")
@@ -52,7 +52,7 @@ func Test_Structs_WithTag(t *testing.T) {
},
}
err := gconv.Structs(params, &users)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Uid, 1)
t.Assert(users[0].NickName, "name1")
@@ -79,7 +79,7 @@ func Test_Structs_WithoutTag(t *testing.T) {
},
}
err := gconv.Structs(params, &users)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Uid, 1)
t.Assert(users[0].NickName, "name1")
@@ -99,7 +99,7 @@ func Test_Structs_WithoutTag(t *testing.T) {
},
}
err := gconv.Structs(params, &users)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(len(users), 2)
t.Assert(users[0].Uid, 1)
t.Assert(users[0].NickName, "name1")
@@ -166,7 +166,7 @@ func Test_Structs_DirectReflectSet(t *testing.T) {
b []*A
)
err := gconv.Structs(a, &b)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.AssertEQ(a, b)
})
gtest.C(t, func(t *gtest.T) {
@@ -178,7 +178,7 @@ func Test_Structs_DirectReflectSet(t *testing.T) {
b []A
)
err := gconv.Structs(a, &b)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.AssertEQ(a, b)
})
}
@@ -199,7 +199,7 @@ func Test_Structs_IntSliceAttribute(t *testing.T) {
g.Map{"id": nil, "name": "john"},
g.Map{"id": nil, "name": "smith"},
}, &array)
- t.Assert(err, nil)
+ t.AssertNil(err)
t.Assert(len(array), 2)
t.Assert(array[0].Name, "john")
t.Assert(array[1].Name, "smith")
diff --git a/util/grand/grand.go b/util/grand/grand.go
index 77478cee4..34554a2f5 100644
--- a/util/grand/grand.go
+++ b/util/grand/grand.go
@@ -60,16 +60,12 @@ func N(min, max int) int {
return min
}
if min >= 0 {
- // Because Intn dose not support negative number,
- // so we should first shift the value to left,
- // then call Intn to produce the random number,
- // and finally shift the result back to right.
- return Intn(max-(min-0)+1) + (min - 0)
+ return Intn(max-min+1) + min
}
if min < 0 {
- // Because Intn dose not support negative number,
+ // As `Intn` dose not support negative number,
// so we should first shift the value to right,
- // then call Intn to produce the random number,
+ // then call `Intn` to produce the random number,
// and finally shift the result back to left.
return Intn(max+(0-min)+1) - (0 - min)
}
diff --git a/util/gtag/gtag.go b/util/gtag/gtag.go
index 68d915806..058159386 100644
--- a/util/gtag/gtag.go
+++ b/util/gtag/gtag.go
@@ -11,8 +11,9 @@
package gtag
import (
- "fmt"
"regexp"
+
+ "github.com/gogf/gf/v2/errors/gerror"
)
var (
@@ -21,20 +22,30 @@ var (
)
// Set sets tag content for specified name.
+// Note that it panics if `name` already exists.
func Set(name, value string) {
if _, ok := data[name]; ok {
- panic(fmt.Sprintf(`value for tag "%s" already exists`, name))
+ panic(gerror.Newf(`value for tag name "%s" already exists`, name))
}
data[name] = value
}
+// SetOver performs as Set, but it overwrites the old value if `name` already exists.
+func SetOver(name, value string) {
+ data[name] = value
+}
+
// Sets sets multiple tag content by map.
func Sets(m map[string]string) {
for k, v := range m {
- if _, ok := data[k]; ok {
- panic(fmt.Sprintf(`value for tag "%s" already exists`, k))
- }
- data[k] = v
+ Set(k, v)
+ }
+}
+
+// SetsOver performs as Sets, but it overwrites the old value if `name` already exists.
+func SetsOver(m map[string]string) {
+ for k, v := range m {
+ SetOver(k, v)
}
}
@@ -46,8 +57,8 @@ func Get(name string) string {
// Parse parses and returns the content by replacing all tag name variable to
// its content for given `content`.
// Eg:
-// If "Demo:content" in tag mapping,
-// Parse(`This is {Demo}`) -> `This is content`.
+// gtag.Set("demo", "content")
+// Parse(`This is {demo}`) -> `This is content`.
func Parse(content string) string {
return regex.ReplaceAllStringFunc(content, func(s string) string {
if v, ok := data[s[1:len(s)-1]]; ok {
diff --git a/util/gtag/gtag_z_unit_test.go b/util/gtag/gtag_z_unit_test.go
index d38c9cb81..a8e8160c4 100644
--- a/util/gtag/gtag_z_unit_test.go
+++ b/util/gtag/gtag_z_unit_test.go
@@ -11,26 +11,56 @@ import (
"testing"
"github.com/gogf/gf/v2/frame/g"
- "github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/gtag"
+ "github.com/gogf/gf/v2/util/guid"
)
func Test_Set_Get(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
- k := gtime.TimestampNanoStr()
- v := gtime.TimestampNanoStr()
+ k := guid.S()
+ v := guid.S()
gtag.Set(k, v)
t.Assert(gtag.Get(k), v)
})
}
+func Test_SetOver_Get(t *testing.T) {
+ // panic by Set
+ gtest.C(t, func(t *gtest.T) {
+ var (
+ k = guid.S()
+ v1 = guid.S()
+ v2 = guid.S()
+ )
+ gtag.Set(k, v1)
+ t.Assert(gtag.Get(k), v1)
+ defer func() {
+ t.AssertNE(recover(), nil)
+ }()
+ gtag.Set(k, v2)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ var (
+ k = guid.S()
+ v1 = guid.S()
+ v2 = guid.S()
+ )
+ gtag.SetOver(k, v1)
+ t.Assert(gtag.Get(k), v1)
+ gtag.SetOver(k, v2)
+ t.Assert(gtag.Get(k), v2)
+ })
+}
+
func Test_Sets_Get(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
- k1 := gtime.TimestampNanoStr()
- k2 := gtime.TimestampNanoStr()
- v1 := gtime.TimestampNanoStr()
- v2 := gtime.TimestampNanoStr()
+ var (
+ k1 = guid.S()
+ k2 = guid.S()
+ v1 = guid.S()
+ v2 = guid.S()
+ )
gtag.Sets(g.MapStrStr{
k1: v1,
k2: v2,
@@ -40,13 +70,60 @@ func Test_Sets_Get(t *testing.T) {
})
}
+func Test_SetsOver_Get(t *testing.T) {
+ // panic by Sets
+ gtest.C(t, func(t *gtest.T) {
+ var (
+ k1 = guid.S()
+ k2 = guid.S()
+ v1 = guid.S()
+ v2 = guid.S()
+ v3 = guid.S()
+ )
+ gtag.Sets(g.MapStrStr{
+ k1: v1,
+ k2: v2,
+ })
+ t.Assert(gtag.Get(k1), v1)
+ t.Assert(gtag.Get(k2), v2)
+ defer func() {
+ t.AssertNE(recover(), nil)
+ }()
+ gtag.Sets(g.MapStrStr{
+ k1: v3,
+ k2: v3,
+ })
+ })
+ gtest.C(t, func(t *gtest.T) {
+ var (
+ k1 = guid.S()
+ k2 = guid.S()
+ v1 = guid.S()
+ v2 = guid.S()
+ v3 = guid.S()
+ )
+ gtag.SetsOver(g.MapStrStr{
+ k1: v1,
+ k2: v2,
+ })
+ t.Assert(gtag.Get(k1), v1)
+ t.Assert(gtag.Get(k2), v2)
+ gtag.SetsOver(g.MapStrStr{
+ k1: v3,
+ k2: v3,
+ })
+ t.Assert(gtag.Get(k1), v3)
+ t.Assert(gtag.Get(k2), v3)
+ })
+}
+
func Test_Parse(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
- k1 = gtime.TimestampNanoStr()
- k2 = gtime.TimestampNanoStr()
- v1 = gtime.TimestampNanoStr()
- v2 = gtime.TimestampNanoStr()
+ k1 = guid.S()
+ k2 = guid.S()
+ v1 = guid.S()
+ v2 = guid.S()
content = fmt.Sprintf(`this is {%s} and {%s}`, k1, k2)
expect = fmt.Sprintf(`this is %s and %s`, v1, v2)
)
diff --git a/util/guid/guid.go b/util/guid/guid.go
index 2259abb81..c217ed455 100644
--- a/util/guid/guid.go
+++ b/util/guid/guid.go
@@ -13,6 +13,8 @@ import (
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/encoding/ghash"
+ "github.com/gogf/gf/v2/errors/gcode"
+ "github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gipv4"
"github.com/gogf/gf/v2/util/grand"
)
@@ -38,7 +40,7 @@ func init() {
macAddrBytes = append(macAddrBytes, []byte(mac)...)
}
b := []byte{'0', '0', '0', '0', '0', '0', '0'}
- s := strconv.FormatUint(uint64(ghash.DJBHash(macAddrBytes)), 36)
+ s := strconv.FormatUint(uint64(ghash.SDBM(macAddrBytes)), 36)
copy(b, s)
macAddrStr = string(b)
}
@@ -61,13 +63,13 @@ func init() {
// be token by random string.
//
// The returned string is composed with:
-// 1. Default: MAC(7) + PID(4) + TimestampNano(12) + Sequence(3) + RandomString(6)
-// 2. CustomData: Data(7/14) + TimestampNano(12) + Sequence(3) + RandomString(3/10)
+// 1. Default: MACHash(7) + PID(4) + TimestampNano(12) + Sequence(3) + RandomString(6)
+// 2. CustomData: DataHash(7/14) + TimestampNano(12) + Sequence(3) + RandomString(3/10)
//
// Note that:
// 1. The returned length is fixed to 32 bytes for performance purpose.
// 2. The custom parameter `data` composed should have unique attribute in your
-// business situation.
+// business scenario.
func S(data ...[]byte) string {
var (
b = make([]byte, 32)
@@ -92,7 +94,10 @@ func S(data ...[]byte) string {
copy(b[n+12:], getSequence())
copy(b[n+12+3:], getRandomStr(32-n-12-3))
} else {
- panic("too many data parts, it should be no more than 2 parts")
+ panic(gerror.NewCode(
+ gcode.CodeInvalidParameter,
+ "too many data parts, it should be no more than 2 parts",
+ ))
}
return string(b)
}
@@ -124,7 +129,7 @@ func getRandomStr(n int) []byte {
// getDataHashStr creates and returns hash bytes in 7 bytes with given data bytes.
func getDataHashStr(data []byte) []byte {
b := []byte{'0', '0', '0', '0', '0', '0', '0'}
- s := strconv.FormatUint(uint64(ghash.DJBHash(data)), 36)
+ s := strconv.FormatUint(uint64(ghash.SDBM(data)), 36)
copy(b, s)
return b
}
diff --git a/util/gutil/gutil_dump.go b/util/gutil/gutil_dump.go
index f22ab31a3..148824b40 100644
--- a/util/gutil/gutil_dump.go
+++ b/util/gutil/gutil_dump.go
@@ -13,6 +13,7 @@ import (
"reflect"
"strings"
+ "github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/os/gstructs"
"github.com/gogf/gf/v2/text/gstr"
)
@@ -34,14 +35,16 @@ type iMarshalJSON interface {
// DumpOption specifies the behavior of function Export.
type DumpOption struct {
- WithType bool // WithType specifies dumping content with type information.
+ WithType bool // WithType specifies dumping content with type information.
+ ExportedOnly bool // Only dump Exported fields for structs.
}
// Dump prints variables `values` to stdout with more manually readable.
func Dump(values ...interface{}) {
for _, value := range values {
DumpWithOption(value, DumpOption{
- WithType: false,
+ WithType: false,
+ ExportedOnly: false,
})
}
}
@@ -51,7 +54,8 @@ func Dump(values ...interface{}) {
func DumpWithType(values ...interface{}) {
for _, value := range values {
DumpWithOption(value, DumpOption{
- WithType: true,
+ WithType: true,
+ ExportedOnly: false,
})
}
}
@@ -60,7 +64,8 @@ func DumpWithType(values ...interface{}) {
func DumpWithOption(value interface{}, option DumpOption) {
buffer := bytes.NewBuffer(nil)
DumpTo(buffer, value, DumpOption{
- WithType: option.WithType,
+ WithType: option.WithType,
+ ExportedOnly: option.ExportedOnly,
})
fmt.Println(buffer.String())
}
@@ -69,24 +74,43 @@ func DumpWithOption(value interface{}, option DumpOption) {
func DumpTo(writer io.Writer, value interface{}, option DumpOption) {
buffer := bytes.NewBuffer(nil)
doDump(value, "", buffer, doDumpOption{
- WithType: option.WithType,
+ WithType: option.WithType,
+ ExportedOnly: option.ExportedOnly,
})
_, _ = writer.Write(buffer.Bytes())
}
type doDumpOption struct {
- WithType bool
+ WithType bool
+ ExportedOnly bool
}
func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDumpOption) {
+ if value == nil {
+ buffer.WriteString(``)
+ return
+ }
+ var reflectValue reflect.Value
+ if v, ok := value.(reflect.Value); ok {
+ reflectValue = v
+ if v.IsValid() && v.CanInterface() {
+ value = v.Interface()
+ } else {
+ if convertedValue, ok := reflection.ValueToInterface(v); ok {
+ value = convertedValue
+ }
+ }
+ } else {
+ reflectValue = reflect.ValueOf(value)
+ }
+ // Double check nil value.
if value == nil {
buffer.WriteString(``)
return
}
var (
- reflectValue = reflect.ValueOf(value)
reflectKind = reflectValue.Kind()
- reflectTypeName = reflect.TypeOf(value).String()
+ reflectTypeName = reflectValue.Type().String()
newIndent = indent + dumpIndent
)
reflectTypeName = strings.ReplaceAll(reflectTypeName, `[]uint8`, `[]byte`)
@@ -106,6 +130,7 @@ func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDum
Option: option,
ReflectValue: reflectValue,
ReflectTypeName: reflectTypeName,
+ ExportedOnly: option.ExportedOnly,
}
)
switch reflectKind {
@@ -142,15 +167,18 @@ func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDum
doDumpNumber(exportInternalInput)
case reflect.Chan:
- buffer.WriteString(fmt.Sprintf(`<%s>`, reflect.TypeOf(value).String()))
+ buffer.WriteString(fmt.Sprintf(`<%s>`, reflectValue.Type().String()))
case reflect.Func:
if reflectValue.IsNil() || !reflectValue.IsValid() {
buffer.WriteString(``)
} else {
- buffer.WriteString(fmt.Sprintf(`<%s>`, reflect.TypeOf(value).String()))
+ buffer.WriteString(fmt.Sprintf(`<%s>`, reflectValue.Type().String()))
}
+ case reflect.Interface:
+ doDump(exportInternalInput.ReflectValue.Elem(), indent, buffer, option)
+
default:
doDumpDefault(exportInternalInput)
}
@@ -164,6 +192,7 @@ type doDumpInternalInput struct {
Option doDumpOption
ReflectValue reflect.Value
ReflectTypeName string
+ ExportedOnly bool
}
func doDumpSlice(in doDumpInternalInput) {
@@ -195,16 +224,21 @@ func doDumpSlice(in doDumpInternalInput) {
}
for i := 0; i < in.ReflectValue.Len(); i++ {
in.Buffer.WriteString(in.NewIndent)
- doDump(in.ReflectValue.Index(i).Interface(), in.NewIndent, in.Buffer, in.Option)
+ doDump(in.ReflectValue.Index(i), in.NewIndent, in.Buffer, in.Option)
in.Buffer.WriteString(",\n")
}
in.Buffer.WriteString(fmt.Sprintf("%s]", in.Indent))
}
func doDumpMap(in doDumpInternalInput) {
- var (
- mapKeys = in.ReflectValue.MapKeys()
- )
+ var mapKeys = make([]reflect.Value, 0)
+ for _, key := range in.ReflectValue.MapKeys() {
+ if !key.CanInterface() {
+ continue
+ }
+ mapKey := key
+ mapKeys = append(mapKeys, mapKey)
+ }
if len(mapKeys) == 0 {
if !in.Option.WithType {
in.Buffer.WriteString("{}")
@@ -254,7 +288,7 @@ func doDumpMap(in doDumpInternalInput) {
))
}
// Map value dump.
- doDump(in.ReflectValue.MapIndex(mapKey).Interface(), in.NewIndent, in.Buffer, in.Option)
+ doDump(in.ReflectValue.MapIndex(mapKey), in.NewIndent, in.Buffer, in.Option)
in.Buffer.WriteString(",\n")
}
in.Buffer.WriteString(fmt.Sprintf("%s}", in.Indent))
@@ -265,7 +299,17 @@ func doDumpStruct(in doDumpInternalInput) {
Pointer: in.Value,
RecursiveOption: gstructs.RecursiveOptionEmbedded,
})
- if len(structFields) == 0 {
+ var (
+ hasNoExportedFields = true
+ _, isReflectValue = in.Value.(reflect.Value)
+ )
+ for _, field := range structFields {
+ if field.IsExported() {
+ hasNoExportedFields = false
+ break
+ }
+ }
+ if !isReflectValue && (len(structFields) == 0 || hasNoExportedFields) {
var (
structContentStr = ""
attributeCountStr = "0"
@@ -277,6 +321,11 @@ func doDumpStruct(in doDumpInternalInput) {
} else if v, ok := in.Value.(iMarshalJSON); ok {
b, _ := v.MarshalJSON()
structContentStr = string(b)
+ } else {
+ // Has no common interface implements.
+ if len(structFields) != 0 {
+ goto dumpStructFields
+ }
}
if structContentStr == "" {
structContentStr = "{}"
@@ -296,11 +345,16 @@ func doDumpStruct(in doDumpInternalInput) {
}
return
}
+
+dumpStructFields:
var (
maxSpaceNum = 0
tmpSpaceNum = 0
)
for _, field := range structFields {
+ if in.ExportedOnly && !field.IsExported() {
+ continue
+ }
tmpSpaceNum = len(field.Name())
if tmpSpaceNum > maxSpaceNum {
maxSpaceNum = tmpSpaceNum
@@ -312,6 +366,9 @@ func doDumpStruct(in doDumpInternalInput) {
in.Buffer.WriteString(fmt.Sprintf("%s(%d) {\n", in.ReflectTypeName, len(structFields)))
}
for _, field := range structFields {
+ if in.ExportedOnly && !field.IsExported() {
+ continue
+ }
tmpSpaceNum = len(fmt.Sprintf(`%v`, field.Name()))
in.Buffer.WriteString(fmt.Sprintf(
"%s%s:%s",
@@ -319,7 +376,7 @@ func doDumpStruct(in doDumpInternalInput) {
field.Name(),
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
))
- doDump(field.Value.Interface(), in.NewIndent, in.Buffer, in.Option)
+ doDump(field.Value, in.NewIndent, in.Buffer, in.Option)
in.Buffer.WriteString(",\n")
}
in.Buffer.WriteString(fmt.Sprintf("%s}", in.Indent))
@@ -371,7 +428,13 @@ func doDumpBool(in doDumpInternalInput) {
}
func doDumpDefault(in doDumpInternalInput) {
- s := fmt.Sprintf("%v", in.Value)
+ var s string
+ if in.ReflectValue.IsValid() && in.ReflectValue.CanInterface() {
+ s = fmt.Sprintf("%v", in.ReflectValue.Interface())
+ }
+ if s == "" {
+ s = fmt.Sprintf("%v", in.Value)
+ }
s = gstr.Trim(s, `<>`)
if !in.Option.WithType {
in.Buffer.WriteString(s)
diff --git a/util/gutil/gutil_map.go b/util/gutil/gutil_map.go
index 8be8af5a6..92e470434 100644
--- a/util/gutil/gutil_map.go
+++ b/util/gutil/gutil_map.go
@@ -76,10 +76,7 @@ func MapPossibleItemByKey(data map[string]interface{}, key string) (foundKey str
//
// Note that this function might be of low performance.
func MapContainsPossibleKey(data map[string]interface{}, key string) bool {
- if k, _ := MapPossibleItemByKey(data, key); k != "" {
- return true
- }
- return false
+ return utils.MapContainsPossibleKey(data, key)
}
// MapOmitEmpty deletes all empty values from given map.
diff --git a/util/gutil/gutil_z_unit_dump_test.go b/util/gutil/gutil_z_unit_dump_test.go
index d90052643..336e74fb4 100755
--- a/util/gutil/gutil_z_unit_dump_test.go
+++ b/util/gutil/gutil_z_unit_dump_test.go
@@ -7,8 +7,10 @@
package gutil_test
import (
+ "bytes"
"testing"
+ "github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/test/gtest"
@@ -71,6 +73,23 @@ func Test_Dump(t *testing.T) {
})
}
+func Test_Dump_Map(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ buffer := bytes.NewBuffer(nil)
+ m := g.Map{
+ "k1": g.Map{
+ "k2": "v2",
+ },
+ }
+ gutil.DumpTo(buffer, m, gutil.DumpOption{})
+ t.Assert(buffer.String(), `{
+ "k1": {
+ "k2": "v2",
+ },
+}`)
+ })
+}
+
func TestDumpWithType(t *testing.T) {
type CommonReq struct {
AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"`
@@ -142,3 +161,109 @@ func Test_Dump_Slashes(t *testing.T) {
gutil.DumpWithType(req.Content)
})
}
+
+// https://github.com/gogf/gf/issues/1661
+func Test_Dump_Issue1661(t *testing.T) {
+ type B struct {
+ ba int
+ bb string
+ }
+ type A struct {
+ aa int
+ ab string
+ cc []B
+ }
+ gtest.C(t, func(t *gtest.T) {
+ var q1 []A
+ var q2 []A
+ q2 = make([]A, 0)
+ q1 = []A{{aa: 1, ab: "1", cc: []B{{ba: 1}, {ba: 2}, {ba: 3}}}, {aa: 2, ab: "2", cc: []B{{ba: 1}, {ba: 2}, {ba: 3}}}}
+ for _, q1v := range q1 {
+ x := []string{"11", "22"}
+ for _, iv2 := range x {
+ ls := q1v
+ for i, _ := range ls.cc {
+ sj := iv2
+ ls.cc[i].bb = sj
+ }
+ q2 = append(q2, ls)
+ }
+ }
+ buffer := bytes.NewBuffer(nil)
+ gutil.DumpTo(buffer, q2, gutil.DumpOption{})
+ t.Assert(buffer.String(), `[
+ {
+ aa: 1,
+ ab: "1",
+ cc: [
+ {
+ ba: 1,
+ bb: "22",
+ },
+ {
+ ba: 2,
+ bb: "22",
+ },
+ {
+ ba: 3,
+ bb: "22",
+ },
+ ],
+ },
+ {
+ aa: 1,
+ ab: "1",
+ cc: [
+ {
+ ba: 1,
+ bb: "22",
+ },
+ {
+ ba: 2,
+ bb: "22",
+ },
+ {
+ ba: 3,
+ bb: "22",
+ },
+ ],
+ },
+ {
+ aa: 2,
+ ab: "2",
+ cc: [
+ {
+ ba: 1,
+ bb: "22",
+ },
+ {
+ ba: 2,
+ bb: "22",
+ },
+ {
+ ba: 3,
+ bb: "22",
+ },
+ ],
+ },
+ {
+ aa: 2,
+ ab: "2",
+ cc: [
+ {
+ ba: 1,
+ bb: "22",
+ },
+ {
+ ba: 2,
+ bb: "22",
+ },
+ {
+ ba: 3,
+ bb: "22",
+ },
+ ],
+ },
+]`)
+ })
+}
diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go
index e13b1e5ac..80373104f 100644
--- a/util/gvalid/gvalid.go
+++ b/util/gvalid/gvalid.go
@@ -8,9 +8,12 @@
package gvalid
import (
+ "context"
+ "reflect"
"regexp"
"strings"
+ "github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/text/gregex"
)
@@ -20,8 +23,10 @@ type CustomMsg = map[string]interface{}
// fieldRule defined the alias name and rule string for specified field.
type fieldRule struct {
- Name string // Alias name for the field.
- Rule string // Rule string like: "max:6"
+ Name string // Alias name for the field.
+ Rule string // Rule string like: "max:6"
+ IsMeta bool // Is this rule is from gmeta.Meta, which marks it as whole struct rule.
+ FieldKind reflect.Kind // Kind of struct field, which is used for parameter type checks.
}
// iNoValidation is an interface that marks current struct not validated by package `gvalid`.
@@ -40,6 +45,8 @@ const (
noValidationTagName = "nv" // no validation tag name for struct attribute.
ruleNameBail = "bail" // the name for rule "bail"
ruleNameCi = "ci" // the name for rule "ci"
+ emptyJsonArrayStr = "[]" // Empty json string for array type.
+ emptyJsonObjectStr = "{}" // Empty json string for object type.
)
var (
@@ -197,11 +204,23 @@ var (
}
)
-// parseSequenceTag parses one sequence tag to field, rule and error message.
+// ParseTagValue parses one sequence tag to field, rule and error message.
// The sequence tag is like: [alias@]rule[...#msg...]
-func parseSequenceTag(tag string) (field, rule, msg string) {
+func ParseTagValue(tag string) (field, rule, msg string) {
// Complete sequence tag.
// Example: name@required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致
match, _ := gregex.MatchString(`\s*((\w+)\s*@){0,1}\s*([^#]+)\s*(#\s*(.*)){0,1}\s*`, tag)
- return strings.TrimSpace(match[2]), strings.TrimSpace(match[3]), strings.TrimSpace(match[5])
+ if len(match) > 5 {
+ msg = strings.TrimSpace(match[5])
+ rule = strings.TrimSpace(match[3])
+ field = strings.TrimSpace(match[2])
+ } else {
+ intlog.Errorf(context.TODO(), `invalid validation tag value: %s`, tag)
+ }
+ return
+}
+
+// GetTags returns the validation tags.
+func GetTags() []string {
+ return structTagPriority
}
diff --git a/util/gvalid/gvalid_custom_rule.go b/util/gvalid/gvalid_custom_rule.go
index 73dc0e648..2521eb4bf 100644
--- a/util/gvalid/gvalid_custom_rule.go
+++ b/util/gvalid/gvalid_custom_rule.go
@@ -8,8 +8,12 @@ package gvalid
import (
"context"
+ "fmt"
+ "reflect"
+ "runtime"
"github.com/gogf/gf/v2/container/gvar"
+ "github.com/gogf/gf/v2/internal/intlog"
)
// RuleFunc is the custom function for data validation.
@@ -39,6 +43,14 @@ var (
// RegisterRule registers custom validation rule and function for package.
func RegisterRule(rule string, f RuleFunc) {
+ if customRuleFuncMap[rule] != nil {
+ intlog.PrintFunc(context.TODO(), func() string {
+ return fmt.Sprintf(
+ `rule "%s" is overwrotten by function "%s"`,
+ rule, runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name(),
+ )
+ })
+ }
customRuleFuncMap[rule] = f
}
@@ -49,6 +61,18 @@ func RegisterRuleByMap(m map[string]RuleFunc) {
}
}
+// GetRegisteredRuleMap returns all the custom registered rules and associated functions.
+func GetRegisteredRuleMap() map[string]RuleFunc {
+ if len(customRuleFuncMap) == 0 {
+ return nil
+ }
+ ruleMap := make(map[string]RuleFunc)
+ for k, v := range customRuleFuncMap {
+ ruleMap[k] = v
+ }
+ return ruleMap
+}
+
// DeleteRule deletes custom defined validation one or more rules and associated functions from global package.
func DeleteRule(rules ...string) {
for _, rule := range rules {
diff --git a/util/gvalid/gvalid_error.go b/util/gvalid/gvalid_error.go
index ba252d8c6..c489f710e 100644
--- a/util/gvalid/gvalid_error.go
+++ b/util/gvalid/gvalid_error.go
@@ -9,6 +9,7 @@ package gvalid
import (
"strings"
+ "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/text/gstr"
@@ -32,7 +33,7 @@ type Error interface {
// validationError is the validation error for validation result.
type validationError struct {
code gcode.Code // Error code.
- rules []fieldRule // Rules by sequence, which is used for keeping error sequence.
+ rules []fieldRule // Rules by sequence, which is used for keeping error sequence only.
errors map[string]map[string]error // Error map:map[field]map[rule]message
firstKey string // The first error rule key(empty in default).
firstItem map[string]error // The first error rule value(nil in default).
@@ -52,6 +53,16 @@ func newValidationError(code gcode.Code, rules []fieldRule, fieldRuleErrorMap ma
}
fieldRuleErrorMap[field] = ruleErrorMap
}
+ // Filter repeated sequence rules.
+ var ruleNameSet = gset.NewStrSet()
+ for i := 0; i < len(rules); {
+ if !ruleNameSet.AddIfNotExist(rules[i].Name) {
+ // Delete repeated rule.
+ rules = append(rules[:i], rules[i+1:]...)
+ continue
+ }
+ i++
+ }
return &validationError{
code: code,
rules: rules,
diff --git a/util/gvalid/gvalid_validator.go b/util/gvalid/gvalid_validator.go
index 80ac39d9a..944774f3c 100644
--- a/util/gvalid/gvalid_validator.go
+++ b/util/gvalid/gvalid_validator.go
@@ -12,21 +12,22 @@ import (
"reflect"
"github.com/gogf/gf/v2/i18n/gi18n"
+ "github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/util/gconv"
)
// Validator is the validation manager for chaining operations.
type Validator struct {
- i18nManager *gi18n.Manager // I18n manager for error message translation.
- data interface{} // Validation data, which can be a map, struct or a certain value to be validated.
- assoc interface{} // Associated data, which is usually a map, for union validation.
- rules interface{} // Custom validation data.
- messages interface{} // Custom validation error messages, which can be string or type of CustomMsg.
- ruleFuncMap map[string]RuleFunc // ruleFuncMap stores custom rule functions for current Validator.
- useDataInsteadOfObjectAttributes bool // Using `data` as its validation source instead of attribute values from `Object`.
- bail bool // Stop validation after the first validation error.
- caseInsensitive bool // Case-Insensitive configuration for those rules that need value comparison.
+ i18nManager *gi18n.Manager // I18n manager for error message translation.
+ data interface{} // Validation data, which can be a map, struct or a certain value to be validated.
+ assoc interface{} // Associated data, which is usually a map, for union validation.
+ rules interface{} // Custom validation data.
+ messages interface{} // Custom validation error messages, which can be string or type of CustomMsg.
+ ruleFuncMap map[string]RuleFunc // ruleFuncMap stores custom rule functions for current Validator.
+ useAssocInsteadOfObjectAttributes bool // Using `assoc` as its validation source instead of attribute values from `Object`.
+ bail bool // Stop validation after the first validation error.
+ caseInsensitive bool // Case-Insensitive configuration for those rules that need value comparison.
}
// New creates and returns a new Validator.
@@ -46,7 +47,7 @@ func (v *Validator) Run(ctx context.Context) Error {
)
}
- originValueAndKind := utils.OriginValueAndKind(v.data)
+ originValueAndKind := reflection.OriginValueAndKind(v.data)
switch originValueAndKind.OriginKind {
case reflect.Map:
isMapValidation := false
@@ -124,14 +125,14 @@ func (v *Validator) Data(data interface{}) *Validator {
// Assoc is a chaining operation function, which sets associated validation data for current operation.
// The optional parameter `assoc` is usually type of map, which specifies the parameter map used in union validation.
-// Calling this function with `assoc` also sets `useDataInsteadOfObjectAttributes` true
+// Calling this function with `assoc` also sets `useAssocInsteadOfObjectAttributes` true
func (v *Validator) Assoc(assoc interface{}) *Validator {
if assoc == nil {
return v
}
newValidator := v.Clone()
newValidator.assoc = assoc
- newValidator.useDataInsteadOfObjectAttributes = true
+ newValidator.useAssocInsteadOfObjectAttributes = true
return newValidator
}
diff --git a/util/gvalid/gvalid_validator_check_map.go b/util/gvalid/gvalid_validator_check_map.go
index 83b7d4d17..997706751 100644
--- a/util/gvalid/gvalid_validator_check_map.go
+++ b/util/gvalid/gvalid_validator_check_map.go
@@ -13,7 +13,7 @@ import (
"strings"
"github.com/gogf/gf/v2/errors/gcode"
- "github.com/gogf/gf/v2/internal/utils"
+ "github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/util/gconv"
)
@@ -31,7 +31,7 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error {
// Sequence has order for error results.
case []string:
for _, tag := range assertValue {
- name, rule, msg := parseSequenceTag(tag)
+ name, rule, msg := ParseTagValue(tag)
if len(name) == 0 {
continue
}
@@ -97,7 +97,7 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error {
validator.rules = nil
validator.messages = nil
for _, item := range inputParamMap {
- originTypeAndKind := utils.OriginTypeAndKind(item)
+ originTypeAndKind := reflection.OriginTypeAndKind(item)
switch originTypeAndKind.OriginKind {
case reflect.Map, reflect.Struct, reflect.Slice, reflect.Array:
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go
index 6a43b2b11..42244db40 100644
--- a/util/gvalid/gvalid_validator_check_struct.go
+++ b/util/gvalid/gvalid_validator_check_struct.go
@@ -12,8 +12,10 @@ import (
"strings"
"github.com/gogf/gf/v2/errors/gcode"
+ "github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/os/gstructs"
"github.com/gogf/gf/v2/util/gconv"
+ "github.com/gogf/gf/v2/util/gmeta"
"github.com/gogf/gf/v2/util/gutil"
)
@@ -22,6 +24,8 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
errorMaps = make(map[string]map[string]error) // Returning error.
fieldToAliasNameMap = make(map[string]string) // Field names to alias name map.
resultSequenceRules = make([]fieldRule, 0)
+ isEmptyData = empty.IsEmpty(v.data)
+ isEmptyAssoc = empty.IsEmpty(v.assoc)
)
fieldMap, err := gstructs.FieldMap(gstructs.FieldMapInput{
Pointer: object,
@@ -38,7 +42,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
return newValidationErrorByStr(internalObjectErrRuleName, err)
}
// If there's no struct tag and validation rules, it does nothing and returns quickly.
- if len(tagFields) == 0 && v.messages == nil {
+ if len(tagFields) == 0 && v.messages == nil && isEmptyData && isEmptyAssoc {
return nil
}
@@ -57,7 +61,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
// Sequence has order for error results.
case []string:
for _, tag := range assertValue {
- name, rule, msg := parseSequenceTag(tag)
+ name, rule, msg := ParseTagValue(tag)
if len(name) == 0 {
continue
}
@@ -101,17 +105,17 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
}
}
// If there's no struct tag and validation rules, it does nothing and returns quickly.
- if len(tagFields) == 0 && len(checkRules) == 0 {
+ if len(tagFields) == 0 && len(checkRules) == 0 && isEmptyData && isEmptyAssoc {
return nil
}
// Input parameter map handling.
- if v.assoc == nil || !v.useDataInsteadOfObjectAttributes {
+ if v.assoc == nil || !v.useAssocInsteadOfObjectAttributes {
inputParamMap = make(map[string]interface{})
} else {
inputParamMap = gconv.Map(v.assoc)
}
// Checks and extends the parameters map with struct alias tag.
- if !v.useDataInsteadOfObjectAttributes {
+ if !v.useAssocInsteadOfObjectAttributes {
for nameOrTag, field := range fieldMap {
inputParamMap[nameOrTag] = field.Value.Interface()
if nameOrTag != field.Name() {
@@ -124,8 +128,9 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
// The custom rules has the most high priority that can overwrite the struct tag rules.
for _, field := range tagFields {
var (
- fieldName = field.Name() // Attribute name.
- name, rule, msg = parseSequenceTag(field.TagValue) // The `name` is different from `attribute alias`, which is used for validation only.
+ isMeta bool
+ fieldName = field.Name() // Attribute name.
+ name, rule, msg = ParseTagValue(field.TagValue) // The `name` is different from `attribute alias`, which is used for validation only.
)
if len(name) == 0 {
if value, ok := fieldToAliasNameMap[fieldName]; ok {
@@ -142,7 +147,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
// It here extends the params map using alias names.
// Note that the variable `name` might be alias name or attribute name.
if _, ok := inputParamMap[name]; !ok {
- if !v.useDataInsteadOfObjectAttributes {
+ if !v.useAssocInsteadOfObjectAttributes {
inputParamMap[name] = field.Value.Interface()
} else {
if name != fieldName {
@@ -168,9 +173,14 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
}
} else {
nameToRuleMap[name] = rule
+ if fieldValue := field.Value.Interface(); fieldValue != nil {
+ _, isMeta = fieldValue.(gmeta.Meta)
+ }
checkRules = append(checkRules, fieldRule{
- Name: name,
- Rule: rule,
+ Name: name,
+ Rule: rule,
+ IsMeta: isMeta,
+ FieldKind: field.OriginalKind(),
})
}
} else {
@@ -215,11 +225,9 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
}
// Temporary variable for value.
- var (
- value interface{}
- )
+ var value interface{}
- // It checks the struct recursively if its attribute is an embedded struct.
+ // It checks the struct recursively if its attribute is a struct/struct slice.
for _, field := range fieldMap {
// No validation interface implements check.
if _, ok := field.Value.Interface().(iNoValidation); ok {
@@ -263,10 +271,25 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
// The following logic is the same as some of CheckMap but with sequence support.
for _, checkRuleItem := range checkRules {
- _, value = gutil.MapPossibleItemByKey(inputParamMap, checkRuleItem.Name)
- if value == nil {
- if aliasName := fieldToAliasNameMap[checkRuleItem.Name]; aliasName != "" {
- _, value = gutil.MapPossibleItemByKey(inputParamMap, aliasName)
+ if !checkRuleItem.IsMeta {
+ _, value = gutil.MapPossibleItemByKey(inputParamMap, checkRuleItem.Name)
+ if value == nil {
+ if aliasName := fieldToAliasNameMap[checkRuleItem.Name]; aliasName != "" {
+ _, value = gutil.MapPossibleItemByKey(inputParamMap, aliasName)
+ }
+ }
+ }
+ // Empty json string checks according to mapping field kind.
+ if value != nil {
+ switch checkRuleItem.FieldKind {
+ case reflect.Struct, reflect.Map:
+ if gconv.String(value) == emptyJsonObjectStr {
+ value = ""
+ }
+ case reflect.Slice, reflect.Array:
+ if gconv.String(value) == emptyJsonArrayStr {
+ value = ""
+ }
}
}
// It checks each rule and its value in loop.
@@ -284,7 +307,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
// If value is nil or empty string and has no required* rules,
// it clears the error message.
// ============================================================
- if value == nil || gconv.String(value) == "" {
+ if !checkRuleItem.IsMeta && (value == nil || gconv.String(value) == "") {
required := false
// rule => error
for ruleKey := range errorItem {
@@ -293,6 +316,11 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
required = true
break
}
+ // All custom validation rules are required rules.
+ if _, ok := customRuleFuncMap[ruleKey]; ok {
+ required = true
+ break
+ }
}
if !required {
continue
diff --git a/util/gvalid/gvalid_validator_check_value.go b/util/gvalid/gvalid_validator_check_value.go
index 6d0d46978..bd69f9f77 100644
--- a/util/gvalid/gvalid_validator_check_value.go
+++ b/util/gvalid/gvalid_validator_check_value.go
@@ -15,10 +15,10 @@ import (
"time"
"github.com/gogf/gf/v2/container/gvar"
+ "github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
- "github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/net/gipv4"
"github.com/gogf/gf/v2/net/gipv6"
"github.com/gogf/gf/v2/os/gtime"
@@ -538,11 +538,11 @@ func (v *Validator) doCheckSingleBuildInRules(ctx context.Context, in doCheckBui
}
type doCheckValueRecursivelyInput struct {
- Value interface{}
- Type reflect.Type
- OriginKind reflect.Kind
- ErrorMaps map[string]map[string]error
- ResultSequenceRules *[]fieldRule
+ Value interface{} // Value to be validated.
+ Type reflect.Type // Struct/map/slice type which to be recursively validated.
+ OriginKind reflect.Kind // Struct/map/slice kind to be asserted in following switch case.
+ ErrorMaps map[string]map[string]error // The validated failed error map.
+ ResultSequenceRules *[]fieldRule // The validated failed rule in sequence.
}
func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValueRecursivelyInput) {
@@ -564,14 +564,15 @@ func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValue
case reflect.Map:
var (
- dataMap = gconv.Map(in.Value)
+ dataMap = gconv.Map(in.Value)
+ mapTypeElem = in.Type.Elem()
+ mapTypeKind = mapTypeElem.Kind()
)
for _, item := range dataMap {
- originTypeAndKind := utils.OriginTypeAndKind(item)
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
Value: item,
- Type: originTypeAndKind.InputType,
- OriginKind: originTypeAndKind.OriginKind,
+ Type: mapTypeElem,
+ OriginKind: mapTypeKind,
ErrorMaps: in.ErrorMaps,
ResultSequenceRules: in.ResultSequenceRules,
})
@@ -582,16 +583,20 @@ func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValue
}
case reflect.Slice, reflect.Array:
- array := gconv.Interfaces(in.Value)
+ var array []interface{}
+ if gjson.Valid(in.Value) {
+ array = gconv.Interfaces(gconv.Bytes(in.Value))
+ } else {
+ array = gconv.Interfaces(in.Value)
+ }
if len(array) == 0 {
return
}
for _, item := range array {
- originTypeAndKind := utils.OriginTypeAndKind(item)
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
Value: item,
- Type: originTypeAndKind.InputType,
- OriginKind: originTypeAndKind.OriginKind,
+ Type: in.Type.Elem(),
+ OriginKind: in.Type.Elem().Kind(),
ErrorMaps: in.ErrorMaps,
ResultSequenceRules: in.ResultSequenceRules,
})
diff --git a/util/gvalid/gvalid_z_unit_feature_checkmap_test.go b/util/gvalid/gvalid_z_unit_feature_checkmap_test.go
index 9cdb0d4ba..f3fba9a7b 100755
--- a/util/gvalid/gvalid_z_unit_feature_checkmap_test.go
+++ b/util/gvalid/gvalid_z_unit_feature_checkmap_test.go
@@ -40,7 +40,7 @@ func Test_CheckMap2(t *testing.T) {
var params interface{}
gtest.C(t, func(t *gtest.T) {
if err := g.Validator().Data(params).Run(context.TODO()); err == nil {
- t.Assert(err, nil)
+ t.AssertNil(err)
}
})
diff --git a/util/gvalid/gvalid_z_unit_feature_checkstruct_test.go b/util/gvalid/gvalid_z_unit_feature_checkstruct_test.go
index 237938d99..7e182d84c 100755
--- a/util/gvalid/gvalid_z_unit_feature_checkstruct_test.go
+++ b/util/gvalid/gvalid_z_unit_feature_checkstruct_test.go
@@ -35,7 +35,7 @@ func Test_CheckStruct(t *testing.T) {
}
obj := &Object{"john", 16}
err := g.Validator().Data(obj).Rules(rules).Messages(msgs).Run(context.TODO())
- t.Assert(err, nil)
+ t.AssertNil(err)
})
gtest.C(t, func(t *gtest.T) {
@@ -133,7 +133,7 @@ func Test_CheckStruct(t *testing.T) {
}
var login LoginRequest
err := g.Validator().Data(login).Run(context.TODO())
- t.Assert(err, nil)
+ t.AssertNil(err)
})
gtest.C(t, func(t *gtest.T) {
@@ -249,7 +249,7 @@ func Test_CheckStruct_EmbeddedObject_Attribute(t *testing.T) {
obj.Name = "john"
obj.Time = gtime.Now()
err := g.Validator().Data(obj).Rules(rules).Messages(ruleMsg).Run(context.TODO())
- t.Assert(err, nil)
+ t.AssertNil(err)
})
gtest.C(t, func(t *gtest.T) {
type Base struct {
@@ -273,7 +273,7 @@ func Test_CheckStruct_EmbeddedObject_Attribute(t *testing.T) {
obj.Type = 1
obj.Name = "john"
err := g.Validator().Data(obj).Rules(rules).Messages(ruleMsg).Run(context.TODO())
- t.Assert(err, nil)
+ t.AssertNil(err)
})
}
@@ -341,7 +341,7 @@ func Test_CheckStruct_Optional(t *testing.T) {
Size: 10,
}
err := g.Validator().Data(obj).Run(context.TODO())
- t.Assert(err, nil)
+ t.AssertNil(err)
})
gtest.C(t, func(t *gtest.T) {
type Params struct {
@@ -354,7 +354,7 @@ func Test_CheckStruct_Optional(t *testing.T) {
Size: 10,
}
err := g.Validator().Data(obj).Run(context.TODO())
- t.Assert(err, nil)
+ t.AssertNil(err)
})
gtest.C(t, func(t *gtest.T) {
type Params struct {
@@ -383,7 +383,7 @@ func Test_CheckStruct_NoTag(t *testing.T) {
Size: 10,
}
err := g.Validator().Data(obj).Run(context.TODO())
- t.Assert(err, nil)
+ t.AssertNil(err)
})
}
diff --git a/util/gvalid/gvalid_z_unit_feature_custom_rule_test.go b/util/gvalid/gvalid_z_unit_feature_custom_rule_test.go
index 360804602..82cf83125 100644
--- a/util/gvalid/gvalid_z_unit_feature_custom_rule_test.go
+++ b/util/gvalid/gvalid_z_unit_feature_custom_rule_test.go
@@ -11,8 +11,10 @@ import (
"errors"
"testing"
+ "github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/test/gtest"
+ "github.com/gogf/gf/v2/util/guid"
"github.com/gogf/gf/v2/util/gvalid"
)
@@ -37,7 +39,7 @@ func Test_CustomRule1(t *testing.T) {
err := g.Validator().Data("123456").Rules(rule).Messages("custom message").Run(ctx)
t.Assert(err.String(), "custom message")
err = g.Validator().Data("123456").Assoc(g.Map{"data": "123456"}).Rules(rule).Messages("custom message").Run(ctx)
- t.Assert(err, nil)
+ t.AssertNil(err)
})
// Error with struct validation.
gtest.C(t, func(t *gtest.T) {
@@ -63,7 +65,7 @@ func Test_CustomRule1(t *testing.T) {
Data: "123456",
}
err := g.Validator().Data(st).Run(ctx)
- t.Assert(err, nil)
+ t.AssertNil(err)
})
}
@@ -106,7 +108,7 @@ func Test_CustomRule2(t *testing.T) {
Data: "123456",
}
err := g.Validator().Data(st).Run(ctx)
- t.Assert(err, nil)
+ t.AssertNil(err)
})
}
@@ -137,7 +139,7 @@ func Test_CustomRule_AllowEmpty(t *testing.T) {
Data: "123456",
}
err := g.Validator().Data(st).Run(ctx)
- t.Assert(err, nil)
+ t.AssertNil(err)
})
// No error with struct validation.
gtest.C(t, func(t *gtest.T) {
@@ -271,3 +273,18 @@ func TestValidator_RuleFuncMap(t *testing.T) {
t.AssertNil(err)
})
}
+
+func Test_CustomRule_Overwrite(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ var rule = "custom-" + guid.S()
+ gvalid.RegisterRule(rule, func(ctx context.Context, in gvalid.RuleFuncInput) error {
+ return gerror.New("1")
+ })
+ t.Assert(g.Validator().Rules(rule).Data(1).Run(ctx), "1")
+ gvalid.RegisterRule(rule, func(ctx context.Context, in gvalid.RuleFuncInput) error {
+ return gerror.New("2")
+ })
+ t.Assert(g.Validator().Rules(rule).Data(1).Run(ctx), "2")
+ })
+ g.Dump(gvalid.GetRegisteredRuleMap())
+}
diff --git a/util/gvalid/gvalid_z_unit_feature_i18n_test.go b/util/gvalid/gvalid_z_unit_feature_i18n_test.go
index 2554f1837..d43d4c566 100644
--- a/util/gvalid/gvalid_z_unit_feature_i18n_test.go
+++ b/util/gvalid/gvalid_z_unit_feature_i18n_test.go
@@ -10,7 +10,6 @@ import (
"context"
"testing"
- "github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/i18n/gi18n"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/gvalid"
@@ -19,7 +18,7 @@ import (
func TestValidator_I18n(t *testing.T) {
var (
err gvalid.Error
- i18nManager = gi18n.New(gi18n.Options{Path: gdebug.TestDataPath("i18n")})
+ i18nManager = gi18n.New(gi18n.Options{Path: gtest.DataPath("i18n")})
ctxCn = gi18n.WithLanguage(context.TODO(), "cn")
validator = gvalid.New().I18n(i18nManager)
)
diff --git a/util/gvalid/gvalid_z_unit_feature_meta_test.go b/util/gvalid/gvalid_z_unit_feature_meta_test.go
new file mode 100644
index 000000000..7bea9e2b2
--- /dev/null
+++ b/util/gvalid/gvalid_z_unit_feature_meta_test.go
@@ -0,0 +1,47 @@
+// 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 gvalid_test
+
+import (
+ "context"
+ "testing"
+
+ "github.com/gogf/gf/v2/errors/gerror"
+ "github.com/gogf/gf/v2/frame/g"
+ "github.com/gogf/gf/v2/test/gtest"
+ "github.com/gogf/gf/v2/util/gvalid"
+)
+
+type UserCreateReq struct {
+ g.Meta `v:"UserCreateReq"`
+ Name string
+ Pass string
+}
+
+func RuleUserCreateReq(ctx context.Context, in gvalid.RuleFuncInput) error {
+ var req *UserCreateReq
+ if err := in.Data.Scan(&req); err != nil {
+ return gerror.Wrap(err, `Scan data to UserCreateReq failed`)
+ }
+ return gerror.Newf(`The name "%s" is already token by others`, req.Name)
+}
+
+func Test_Meta(t *testing.T) {
+ var user = &UserCreateReq{
+ Name: "john",
+ Pass: "123456",
+ }
+
+ gtest.C(t, func(t *gtest.T) {
+ err := g.Validator().RuleFunc("UserCreateReq", RuleUserCreateReq).
+ Data(user).
+ Assoc(g.Map{
+ "Name": "john smith",
+ }).Run(ctx)
+ t.Assert(err.String(), `The name "john smith" is already token by others`)
+ })
+}
diff --git a/util/gvalid/gvalid_z_unit_feature_recursive_test.go b/util/gvalid/gvalid_z_unit_feature_recursive_test.go
index b441f7c70..60401056c 100755
--- a/util/gvalid/gvalid_z_unit_feature_recursive_test.go
+++ b/util/gvalid/gvalid_z_unit_feature_recursive_test.go
@@ -214,3 +214,110 @@ func Test_CheckMap_Recursive_SliceStruct(t *testing.T) {
t.Assert(err.Maps()["Pass2"], g.Map{"same": "The Pass2 value `4` must be the same as field Pass1"})
})
}
+
+func Test_CheckStruct_Recursively_SliceAttribute(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ type Student struct {
+ Name string `v:"required#Student Name is required"`
+ Age int `v:"required"`
+ }
+ type Teacher struct {
+ Name string `v:"required#Teacher Name is required"`
+ Students []Student `v:"required"`
+ }
+ var (
+ teacher = Teacher{}
+ data = g.Map{
+ "name": "john",
+ "students": `[]`,
+ }
+ )
+ err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
+ t.Assert(err, `The Students field is required`)
+ })
+
+ gtest.C(t, func(t *gtest.T) {
+ type Student struct {
+ Name string `v:"required#Student Name is required"`
+ Age int `v:"required"`
+ }
+ type Teacher struct {
+ Name string `v:"required#Teacher Name is required"`
+ Students []Student
+ }
+ var (
+ teacher = Teacher{}
+ data = g.Map{
+ "name": "john",
+ }
+ )
+ err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
+ t.Assert(err, ``)
+ })
+
+ gtest.C(t, func(t *gtest.T) {
+ type Student struct {
+ Name string `v:"required#Student Name is required"`
+ Age int `v:"required"`
+ }
+ type Teacher struct {
+ Name string `v:"required#Teacher Name is required"`
+ Students []Student `v:"required"`
+ }
+ var (
+ teacher = Teacher{}
+ data = g.Map{
+ "name": "john",
+ "students": `[{"age":2}, {"name":"jack", "age":4}]`,
+ }
+ )
+ err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
+ t.Assert(err, `Student Name is required`)
+ })
+}
+
+func Test_CheckStruct_Recursively_SliceAttribute_WithTypeAlias(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ type ParamsItemBase struct {
+ Component string `v:"required" dc:"组件名称"`
+ Params string `v:"required" dc:"配置参数(一般是JSON)"`
+ Version uint64 `v:"required" dc:"参数版本"`
+ }
+ type ParamsItem = ParamsItemBase
+ type ParamsModifyReq struct {
+ Revision uint64 `v:"required"`
+ BizParams []ParamsItem `v:"required"`
+ }
+ var (
+ req = ParamsModifyReq{}
+ data = g.Map{
+ "Revision": "1",
+ "BizParams": `[{}]`,
+ }
+ )
+ err := g.Validator().Assoc(data).Data(req).Run(ctx)
+ t.Assert(err, `The Component field is required; The Params field is required; The Version field is required`)
+ })
+}
+
+func Test_CheckStruct_Recursively_MapAttribute(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ type Student struct {
+ Name string `v:"required#Student Name is required"`
+ Age int `v:"required"`
+ }
+ type Teacher struct {
+ Name string `v:"required#Teacher Name is required"`
+ Students map[string]Student `v:"required"`
+ }
+ var (
+ teacher = Teacher{}
+ data = g.Map{
+ "name": "john",
+ "students": `{"john":{"age":18}}`,
+ }
+ )
+ err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
+ t.Assert(err, `Student Name is required`)
+ })
+}
diff --git a/util/gvalid/gvalid_z_unit_feature_rule_test.go b/util/gvalid/gvalid_z_unit_feature_rule_test.go
index f249ed73b..5229ef9b4 100755
--- a/util/gvalid/gvalid_z_unit_feature_rule_test.go
+++ b/util/gvalid/gvalid_z_unit_feature_rule_test.go
@@ -670,7 +670,7 @@ func Test_Domain(t *testing.T) {
err = g.Validator().Data(k).Rules("domain").Run(ctx)
if v {
// fmt.Println(k)
- t.Assert(err, nil)
+ t.AssertNil(err)
} else {
// fmt.Println(k)
t.AssertNE(err, nil)
diff --git a/util/gvalid/gvalid_z_unit_internal_test.go b/util/gvalid/gvalid_z_unit_internal_test.go
index c1edc90ca..4a86d5053 100644
--- a/util/gvalid/gvalid_z_unit_internal_test.go
+++ b/util/gvalid/gvalid_z_unit_internal_test.go
@@ -15,30 +15,36 @@ import (
func Test_parseSequenceTag(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := "name@required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"
- field, rule, msg := parseSequenceTag(s)
+ field, rule, msg := ParseTagValue(s)
t.Assert(field, "name")
t.Assert(rule, "required|length:2,20|password3|same:password1")
t.Assert(msg, "||密码强度不足|两次密码不一致")
})
gtest.C(t, func(t *gtest.T) {
s := "required|length:2,20|password3|same:password1#||密码强度不足|两次密码不一致"
- field, rule, msg := parseSequenceTag(s)
+ field, rule, msg := ParseTagValue(s)
t.Assert(field, "")
t.Assert(rule, "required|length:2,20|password3|same:password1")
t.Assert(msg, "||密码强度不足|两次密码不一致")
})
gtest.C(t, func(t *gtest.T) {
s := "required|length:2,20|password3|same:password1"
- field, rule, msg := parseSequenceTag(s)
+ field, rule, msg := ParseTagValue(s)
t.Assert(field, "")
t.Assert(rule, "required|length:2,20|password3|same:password1")
t.Assert(msg, "")
})
gtest.C(t, func(t *gtest.T) {
s := "required"
- field, rule, msg := parseSequenceTag(s)
+ field, rule, msg := ParseTagValue(s)
t.Assert(field, "")
t.Assert(rule, "required")
t.Assert(msg, "")
})
}
+
+func Test_GetTags(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(structTagPriority, GetTags())
+ })
+}
diff --git a/version.go b/version.go
index de38fb4b1..2369cb189 100644
--- a/version.go
+++ b/version.go
@@ -1,4 +1,4 @@
package gf
-const VERSION = "v2.0.0-beta"
+const VERSION = "v2.0.6"
const AUTHORS = "john"