Compare commits

...

67 Commits

Author SHA1 Message Date
8f326dcac5 project template update 2022-03-08 22:51:56 +08:00
aa294ea5df ci updates 2022-03-08 21:27:14 +08:00
6afc725b61 ci updates 2022-03-08 21:22:01 +08:00
ec01693773 ci updates 2022-03-08 21:04:48 +08:00
b0cf501782 ci updates 2022-03-08 20:28:18 +08:00
c2fb7ada0a improve example for package gmap 2022-03-08 20:25:34 +08:00
d0a8e60ace improve example for package gmap 2022-03-08 20:14:38 +08:00
ab36bb8842 version updates 2022-03-08 20:12:48 +08:00
0b3cd7b7ae improve handler response for ghttp.Server 2022-03-08 11:50:23 +08:00
10ed04cdb8 example updates for package gmap 2022-03-08 09:49:33 +08:00
e09704a408 example updates for package gmap 2022-03-07 22:10:34 +08:00
ade9ae3c0b improve package goai 2022-03-07 21:29:37 +08:00
9cf6124c4c improve package goai 2022-03-07 21:09:02 +08:00
6d323cc529 improve package goai 2022-03-07 20:49:30 +08:00
aea9f6fe18 keep sequence in attributes definition for oai.Schemas 2022-03-07 19:46:05 +08:00
8a27463e44 ensure sequence for json.Marshal for gmap.ListMap/TreeMap 2022-03-07 17:39:41 +08:00
47ee2cba51 Merge branch 'master' of https://github.com/gogf/gf 2022-03-07 09:58:07 +08:00
531cc7b864 fix issue in package gproc 2022-03-07 09:57:51 +08:00
54bdabd94d Merge pull request #1549 from FlyingBlazer/cookie-secure-config
ghttp: add cookie security configurations
2022-03-04 17:42:44 +08:00
bb6e8fe7a8 Merge branch 'master' of https://github.com/gogf/gf 2022-03-04 11:37:30 +08:00
d5d199ebef Swagger UI updates 2022-03-04 11:36:05 +08:00
158a4589d2 Merge pull request #1637 from wangbs95/feature/fix-chinese-encode
fix(fix bug , add nexttime feature): ServeFileDownload filename doubl…
2022-03-04 09:32:01 +08:00
84c0f456c0 template pack update 2022-03-03 21:43:01 +08:00
3fcd6ef877 fix issue orphan value parsing for sructured arguments of command for package gcmd 2022-03-03 21:03:42 +08:00
4e2d378145 improve file uploading using strict route feature 2022-03-02 21:15:16 +08:00
d64898c59a improve package goai 2022-03-02 20:00:40 +08:00
3bff71b3fc merge master and update unit test 2022-03-02 15:33:58 +08:00
8343d1cd0e Merge branch 'master' into cookie-secure-config 2022-03-02 15:20:31 +08:00
5c23c0cecd fix(fix bug , add nexttime feature): ServeFileDownload filename double quotes cause underscores before and after the final file 2022-03-02 11:00:04 +08:00
f580713478 Merge branch 'master' of https://github.com/gogf/gf 2022-03-02 10:26:20 +08:00
3c58b8d7fa improve openapi 2022-03-02 10:26:09 +08:00
072d5f9760 make options public 2022-03-02 09:56:58 +08:00
f8067f5dd5 improve package ghttp 2022-03-01 22:53:19 +08:00
ea354d10cc Merge pull request #1636 from wangbs95/feature/fix-chinese-encode
revert(fix bug , add nexttime feature): ServeFileDownload File name C…
2022-03-01 22:52:34 +08:00
1724a26957 improve package gcfg 2022-03-01 22:34:57 +08:00
46dc68dfd5 CI update for gf cli 2022-03-01 21:20:17 +08:00
12fdfbf8b2 improve package gcfg 2022-03-01 21:14:45 +08:00
68bdf7deb4 revert(fix bug , add nexttime feature): ServeFileDownload File name Chinese garbled repair 2022-03-01 18:32:11 +08:00
2362c453ec improve cli command install 2022-03-01 16:39:47 +08:00
50f6b6e0f0 fix UT case for package ghttp 2022-03-01 14:08:36 +08:00
88a9eef8a6 api swagger ui update 2022-03-01 11:43:42 +08:00
308e13a546 gf cli command build update 2022-02-28 22:00:25 +08:00
a0b1fefdbb Merge branch 'master' of https://github.com/gogf/gf 2022-02-28 21:58:15 +08:00
3edbcb7bf9 gf cli update 2022-02-28 21:57:59 +08:00
cb78953b38 Merge pull request #1628 from huangqian1985/master
Improving gcmd Code Coverage
2022-02-28 20:45:01 +08:00
a1ddac4e6b Merge branch 'master' of https://github.com/gogf/gf 2022-02-28 17:49:16 +08:00
456697ea99 improve cli command install 2022-02-28 17:48:52 +08:00
8acffd1186 Improving gcmd Code Coverage 2022-02-27 21:00:23 +08:00
814450fd17 gcmd example 2022-02-27 13:22:26 +08:00
1365c1d277 Merge pull request #1623 from huangqian1985/master
Improving gjson Code Coverage
2022-02-27 08:59:19 +08:00
30be5c5e49 Improving gjson Code Coverage And Fix 2022-02-26 21:26:30 +08:00
932cd9d5bb README updates 2022-02-25 10:22:54 +08:00
7b5f17c16b gf cli pack template update 2022-02-24 22:44:48 +08:00
b5e8e68713 fix issue #1625 2022-02-24 22:07:27 +08:00
3a803ac39f fix 2022-02-24 21:41:39 +08:00
d27db119a0 fix issue #1626 2022-02-24 21:24:42 +08:00
f54d0a339c Improving gjson Code Coverage 2022-02-24 21:14:11 +08:00
d83b676c60 Merge branch 'gjson_example' 2022-02-24 20:15:55 +08:00
def3dc364f Merge branch 'master' of https://github.com/gogf/gf 2022-02-24 20:15:44 +08:00
298aa5f040 Improving gjson Code Coverage 2022-02-24 20:14:44 +08:00
e4d56e7ad9 improve package gjson 2022-02-23 16:54:15 +08:00
0fce4edcd3 Merge branch 'master' of https://github.com/gogf/gf 2022-02-23 00:46:47 +08:00
a34f52ae5e Merge branch 'gjson_example' 2022-02-23 00:46:33 +08:00
da465bb030 Improving gjson Code Coverage 2022-02-23 00:46:13 +08:00
d045b4d2f5 make unit test compatible with go 1.15 2022-01-07 18:52:21 +08:00
572e71d76a add CookieOptions
add UnitTest
2022-01-07 17:10:21 +08:00
c91b83969c WIP: add cookie security configuration 2022-01-05 14:33:20 +08:00
85 changed files with 1474 additions and 479 deletions

View File

@ -6,6 +6,10 @@ on:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
env:
TZ: Asia/Shanghai
jobs:
build:
name: Build And Release
@ -26,37 +30,19 @@ jobs:
GOOS=linux GOARCH=amd64 go build main.go
chmod +x main
./main install -y
# gf build
- name: Build CLI Binary For All Platform
run: |
cd cmd/gf
gf build main.go -n gf -a all -s all
# 处理gf-cli批量编译后的文件结构
- name: Move Files Before Upx
run: |
cd cmd/gf/bin
for OS in *;do for FILE in $OS/*;\
do if [[ ${OS} =~ 'windows' ]];\
then rm -rf noupx && mkdir noupx && mv $FILE noupx/gf_$OS.exe && rm -rf $OS;\
else mv $FILE gf_$OS && rm -rf $OS;\
fi;done;done
# UPX 加壳所有文件
- name: Upx All Binary
uses: gacts/upx@master
with:
dir: 'cmd/gf/bin'
upx_args: '-9'
# 移动未UPX的windows程序到上传bin目录下
- name: Move Files After Upx
- name: Move Files Before Release
run: |
cd cmd/gf/bin
mv noupx/* ./ && rm -rf noupx
ls -l
cd cmd/gf/temp
for OS in *;do for FILE in $OS/*;\
do mv $FILE gf_$OS && rm -rf $OS;\
done;done
- name: Create Github Release
id: create_release
uses: actions/create-release@v1
@ -74,4 +60,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
asset_paths: '["cmd/gf/bin/gf_*"]'
asset_paths: '["cmd/gf/temp/gf_*"]'

View File

@ -7,26 +7,17 @@
You can also install `gf` tool using pre-built binaries: https://github.com/gogf/gf/releases
1. `Mac`
1. `Mac` & `Linux`
```shell
# Intel.
wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_darwin_amd64 && chmod +x gf && ./gf install && rm ./gf
# M1.
wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_darwin_arm64 && chmod +x gf && ./gf install && rm ./gf
wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH) && chmod +x gf && ./gf install -y && rm ./gf
```
> If you're using `zsh`, you might need rename your alias by command `alias gf=gf` to resolve the conflicts between `gf` and `git fetch`.
2. `Linux`
```shell
wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_linux_amd64 && chmod +x gf && ./gf install && rm ./gf
```
3. `Windows`
2. `Windows`
Manually download, execute it and then follow the instruction.
4. Database `sqlite` and `oracle` are not support in `gf gen` command in default as it needs `cgo` and `gcc`, you can manually make some changes to the source codes and do the building.
3. Database `sqlite` and `oracle` are not support in `gf gen` command in default as it needs `cgo` and `gcc`, you can manually make some changes to the source codes and do the building.
## 2. Commands
```html

View File

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"regexp"
"runtime"
"strings"
@ -114,11 +115,12 @@ type cBuildInput struct {
Arch string `short:"a" name:"arch" brief:"output binary architecture, multiple arch separated with ','"`
System string `short:"s" name:"system" brief:"output binary system, multiple os separated with ','"`
Output string `short:"o" name:"output" brief:"output binary path, used when building single binary file"`
Path string `short:"p" name:"path" brief:"output binary directory path, default is './bin'" d:"./bin"`
Path string `short:"p" name:"path" brief:"output binary directory path, default is './temp'" d:"./temp"`
Extra string `short:"e" name:"extra" brief:"extra custom \"go build\" options"`
Mod string `short:"m" name:"mod" brief:"like \"-mod\" option of \"go build\", use \"-m none\" to disable go module"`
Cgo bool `short:"c" name:"cgo" brief:"enable or disable cgo feature, it's disabled in default" orphan:"true"`
VarMap g.Map `short:"r" name:"varMap" brief:"custom built embedded variable into binary"`
Exit bool `name:"exit" brief:"exit building when any error occurs, default is false" orphan:"true"`
Pack string `name:"pack" brief:"pack specified folder into temporary go file before building and removes it after built"`
}
type cBuildOutput struct{}
@ -254,7 +256,14 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e
cmdShow, _ := gregex.ReplaceString(`\s+(-ldflags ".+?")\s+`, " ", cmd)
mlog.Print(cmdShow)
if result, err := gproc.ShellExec(cmd); err != nil {
mlog.Printf("failed to build, os:%s, arch:%s, error:\n%s\n", system, arch, gstr.Trim(result))
mlog.Printf(
"failed to build, os:%s, arch:%s, error:\n%s\n\n%s\n",
system, arch, gstr.Trim(result),
`you may use command option "--debug" to enable debug info and check the details`,
)
if in.Exit {
os.Exit(1)
}
} else {
mlog.Debug(gstr.Trim(result))
}
@ -264,6 +273,7 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e
}
}
}
buildDone:
mlog.Print("done!")
return

View File

@ -81,6 +81,7 @@ func (c cDocker) Index(ctx context.Context, in cDockerInput) (out *cDockerOutput
}
// Binary build.
in.Build += " --exit"
if in.Main != "" {
if err = gproc.ShellRun(fmt.Sprintf(`gf build %s %s`, in.Main, in.Build)); err != nil {
return

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -32,7 +32,7 @@ type serviceInstallAvailablePath struct {
func (s serviceInstall) Run(ctx context.Context) (err error) {
// Ask where to install.
paths := s.getInstallPathsData()
paths := s.getAvailablePaths()
if len(paths) <= 0 {
mlog.Printf("no path detected, you can manually install gf by copying the binary to path folder.")
return
@ -43,16 +43,21 @@ func (s serviceInstall) Run(ctx context.Context) (err error) {
// Print all paths status and determine the default selectedID value.
var (
selectedID = -1
newPaths []serviceInstallAvailablePath
pathSet = gset.NewStrSet() // Used for repeated items filtering.
)
for id, aPath := range paths {
if !pathSet.AddIfNotExist(aPath.dirPath) {
for _, path := range paths {
if !pathSet.AddIfNotExist(path.dirPath) {
continue
}
mlog.Printf(" %2d | %8t | %9t | %s", id, aPath.writable, aPath.installed, aPath.dirPath)
newPaths = append(newPaths, path)
}
paths = newPaths
for id, path := range paths {
mlog.Printf(" %2d | %8t | %9t | %s", id, path.writable, path.installed, path.dirPath)
if selectedID == -1 {
// Use the previously installed path as the most priority choice.
if aPath.installed {
if path.installed {
selectedID = id
}
}
@ -61,6 +66,7 @@ func (s serviceInstall) Run(ctx context.Context) (err error) {
if selectedID == -1 {
// Order by choosing priority.
commonPaths := garray.NewStrArrayFrom(g.SliceStr{
s.getGoPathBin(),
`/usr/local/bin`,
`/usr/bin`,
`/usr/sbin`,
@ -121,10 +127,10 @@ func (s serviceInstall) Run(ctx context.Context) (err error) {
}
// Uninstall the old binary.
for _, aPath := range paths {
for _, path := range paths {
// Do not delete myself.
if aPath.filePath != "" && aPath.filePath != dstPath.filePath && gfile.SelfPath() != aPath.filePath {
_ = gfile.Remove(aPath.filePath)
if path.filePath != "" && path.filePath != dstPath.filePath && gfile.SelfPath() != path.filePath {
_ = gfile.Remove(path.filePath)
}
}
return
@ -132,7 +138,7 @@ func (s serviceInstall) Run(ctx context.Context) (err error) {
// IsInstalled checks and returns whether the binary is installed.
func (s serviceInstall) IsInstalled() bool {
paths := s.getInstallPathsData()
paths := s.getAvailablePaths()
for _, aPath := range paths {
if aPath.installed {
return true
@ -141,11 +147,26 @@ func (s serviceInstall) IsInstalled() bool {
return false
}
// GetInstallPathsData returns the installation paths data for the binary.
func (s serviceInstall) getInstallPathsData() []serviceInstallAvailablePath {
var folderPaths []serviceInstallAvailablePath
// Pre generate binaryFileName.
binaryFileName := "gf" + gfile.Ext(gfile.SelfPath())
// getGoPathBinFilePath retrieves ad returns the GOPATH/bin path for binary.
func (s serviceInstall) getGoPathBin() string {
if goPath := genv.Get(`GOPATH`).String(); goPath != "" {
return gfile.Join(goPath, "bin")
}
return ""
}
// getAvailablePaths returns the installation paths data for the binary.
func (s serviceInstall) getAvailablePaths() []serviceInstallAvailablePath {
var (
folderPaths []serviceInstallAvailablePath
binaryFileName = "gf" + gfile.Ext(gfile.SelfPath())
)
// $GOPATH/bin
if goPathBin := s.getGoPathBin(); goPathBin != "" {
folderPaths = s.checkAndAppendToAvailablePath(
folderPaths, goPathBin, binaryFileName,
)
}
switch runtime.GOOS {
case "darwin":
darwinInstallationCheckPaths := []string{"/usr/local/bin"}
@ -157,21 +178,24 @@ func (s serviceInstall) getInstallPathsData() []serviceInstallAvailablePath {
fallthrough
default:
// $GOPATH/bin
gopath := gfile.Join(runtime.GOROOT(), "bin")
folderPaths = s.checkAndAppendToAvailablePath(
folderPaths, gopath, binaryFileName,
)
// Search and find the writable directory path.
envPath := genv.Get("PATH", genv.Get("Path").String()).String()
if gstr.Contains(envPath, ";") {
// windows.
for _, v := range gstr.SplitAndTrim(envPath, ";") {
if v == "." {
continue
}
folderPaths = s.checkAndAppendToAvailablePath(
folderPaths, v, binaryFileName,
)
}
} else if gstr.Contains(envPath, ":") {
// *nix.
for _, v := range gstr.SplitAndTrim(envPath, ":") {
if v == "." {
continue
}
folderPaths = s.checkAndAppendToAvailablePath(
folderPaths, v, binaryFileName,
)

View File

@ -3,7 +3,6 @@ package mlog
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/genv"
"github.com/gogf/gf/v2/os/glog"
@ -36,9 +35,9 @@ func init() {
func SetHeaderPrint(enabled bool) {
logger.SetHeaderPrint(enabled)
if enabled {
genv.Set(headerPrintEnvName, "1")
_ = genv.Set(headerPrintEnvName, "1")
} else {
genv.Set(headerPrintEnvName, "0")
_ = genv.Set(headerPrintEnvName, "0")
}
}
@ -51,17 +50,17 @@ func Printf(format string, v ...interface{}) {
}
func Fatal(v ...interface{}) {
logger.Fatal(ctx, append(g.Slice{"ERROR:"}, v...)...)
logger.Fatal(ctx, v...)
}
func Fatalf(format string, v ...interface{}) {
logger.Fatalf(ctx, "ERROR: "+format, v...)
logger.Fatalf(ctx, format, v...)
}
func Debug(v ...interface{}) {
logger.Debug(ctx, append(g.Slice{"DEBUG:"}, v...)...)
logger.Debug(ctx, v...)
}
func Debugf(format string, v ...interface{}) {
logger.Debugf(ctx, "DEBUG: "+format, v...)
logger.Debugf(ctx, format, v...)
}

View File

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

View File

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

View File

@ -8,6 +8,7 @@ package gmap_test
import (
"fmt"
"github.com/gogf/gf/v2/util/gutil"
"github.com/gogf/gf/v2/container/gmap"
@ -277,7 +278,7 @@ func ExampleNewListMapFrom() {
n := gmap.NewListMapFrom(m.Map(), true)
fmt.Println(n)
// Output:
// May Output:
// {"key1":"var1","key2":"var2"}
// {"key1":"var1","key2":"var2"}
}
@ -290,7 +291,7 @@ func ExampleNewTreeMap() {
fmt.Println(m.Map())
// Output:
// May Output:
// map[key1:var1 key2:var2]
}
@ -305,7 +306,7 @@ func ExampleNewTreeMapFrom() {
n := gmap.NewListMapFrom(m.Map(), true)
fmt.Println(n.Map())
// Output:
// May Output:
// map[key1:var1 key2:var2]
// map[key1:var1 key2:var2]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -90,6 +90,9 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
// Delete item from map.
delete((*pointer).(map[string]interface{}), array[i])
} else {
if (*pointer).(map[string]interface{}) == nil {
*pointer = map[string]interface{}{}
}
(*pointer).(map[string]interface{})[array[i]] = value
}
} else {

View File

@ -60,12 +60,7 @@ func (j *Json) Get(pattern string, def ...interface{}) *gvar.Var {
return nil
}
var result *interface{}
if j.vc {
result = j.getPointerByPattern(pattern)
} else {
result = j.getPointerByPatternWithoutViolenceCheck(pattern)
}
result := j.getPointerByPattern(pattern)
if result != nil {
return gvar.New(*result)
}

View File

@ -67,38 +67,30 @@ func NewWithOptions(data interface{}, options Options) *Json {
}
}
default:
var reflectInfo = utils.OriginValueAndKind(data)
var (
pointedData interface{}
reflectInfo = utils.OriginValueAndKind(data)
)
switch reflectInfo.OriginKind {
case reflect.Slice, reflect.Array:
var i interface{} = gconv.Interfaces(data)
j = &Json{
p: &i,
c: byte(defaultSplitChar),
vc: false,
}
pointedData = gconv.Interfaces(data)
case reflect.Map:
var i interface{} = gconv.MapDeep(data, options.Tags)
j = &Json{
p: &i,
c: byte(defaultSplitChar),
vc: false,
}
pointedData = gconv.MapDeep(data, options.Tags)
case reflect.Struct:
if v, ok := data.(iVal); ok {
return NewWithOptions(v.Val(), options)
}
var i interface{} = gconv.MapDeep(data, options.Tags)
j = &Json{
p: &i,
c: byte(defaultSplitChar),
vc: false,
}
pointedData = gconv.MapDeep(data, options.Tags)
default:
j = &Json{
p: &data,
c: byte(defaultSplitChar),
vc: false,
}
pointedData = data
}
j = &Json{
p: &pointedData,
c: byte(defaultSplitChar),
vc: false,
}
}
j.mu = rwmutex.New(options.Safe)

View File

@ -270,6 +270,6 @@ func ExampleDecodeToJson() {
j, _ := gjson.DecodeToJson([]byte(jsonContent))
fmt.Println(j.Map())
// Output:
// May Output:
// map[name:john score:100]
}

View File

@ -19,6 +19,10 @@ func ExampleLoad() {
fmt.Println(j.Get("name"))
fmt.Println(j.Get("score"))
notExistFilePath := gdebug.TestDataPath("json", "data2.json")
j2, _ := gjson.Load(notExistFilePath)
fmt.Println(j2.Get("name"))
// Output:
// john
// 100
@ -97,25 +101,51 @@ func ExampleLoadToml() {
func ExampleLoadContent() {
jsonContent := `{"name":"john", "score":"100"}`
j, _ := gjson.LoadContent(jsonContent)
fmt.Println(j.Get("name"))
fmt.Println(j.Get("score"))
// Output:
// john
// 100
}
func ExampleLoadContent_UTF8BOM() {
jsonContent := `{"name":"john", "score":"100"}`
content := make([]byte, 3, len(jsonContent)+3)
content[0] = 0xEF
content[1] = 0xBB
content[2] = 0xBF
content = append(content, jsonContent...)
j, _ := gjson.LoadContent(content)
fmt.Println(j.Get("name"))
fmt.Println(j.Get("score"))
// Output:
// john
// 100
}
func ExampleLoadContent_Xml() {
xmlContent := `<?xml version="1.0" encoding="UTF-8"?>
<base>
<name>john</name>
<score>100</score>
</base>`
j, _ := gjson.LoadContent(jsonContent)
x, _ := gjson.LoadContent(xmlContent)
fmt.Println(j.Get("name"))
fmt.Println(j.Get("score"))
fmt.Println(x.Get("base.name"))
fmt.Println(x.Get("base.score"))
// Output:
// john
// 100
// john
// 100
}
func ExampleLoadContentType() {
@ -128,11 +158,13 @@ func ExampleLoadContentType() {
j, _ := gjson.LoadContentType("json", jsonContent)
x, _ := gjson.LoadContentType("xml", xmlContent)
j1, _ := gjson.LoadContentType("json", "")
fmt.Println(j.Get("name"))
fmt.Println(j.Get("score"))
fmt.Println(x.Get("base.name"))
fmt.Println(x.Get("base.score"))
fmt.Println(j1.Get(""))
// Output:
// john
@ -148,6 +180,8 @@ func ExampleIsValidDataType() {
fmt.Println(gjson.IsValidDataType("mp4"))
fmt.Println(gjson.IsValidDataType("xsl"))
fmt.Println(gjson.IsValidDataType("txt"))
fmt.Println(gjson.IsValidDataType(""))
fmt.Println(gjson.IsValidDataType(".json"))
// Output:
// true
@ -156,6 +190,8 @@ func ExampleIsValidDataType() {
// false
// false
// false
// false
// true
}
func ExampleLoad_Xml() {

View File

@ -34,7 +34,7 @@ func ExampleNewWithTag() {
Score: 100,
Title: "engineer",
}
j := gjson.NewWithTag(me, "tag")
j := gjson.NewWithTag(me, "tag", true)
fmt.Println(j.Get("name"))
fmt.Println(j.Get("score"))
fmt.Println(j.Get("Title"))
@ -70,6 +70,26 @@ func ExampleNewWithOptions() {
// engineer
}
func ExampleNewWithOptions_UTF8BOM() {
jsonContent := `{"name":"john", "score":"100"}`
content := make([]byte, 3, len(jsonContent)+3)
content[0] = 0xEF
content[1] = 0xBB
content[2] = 0xBF
content = append(content, jsonContent...)
j := gjson.NewWithOptions(content, gjson.Options{
Tags: "tag",
})
fmt.Println(j.Get("name"))
fmt.Println(j.Get("score"))
// Output:
// john
// 100
}
func ExampleNew_Xml() {
jsonContent := `<?xml version="1.0" encoding="UTF-8"?><doc><name>john</name><score>100</score></doc>`
j := gjson.New(jsonContent)

View File

@ -590,61 +590,52 @@ func ExampleJson_ToIni() {
func ExampleJson_ToIniString() {
type BaseInfo struct {
Name string
Age int
}
info := BaseInfo{
Name: "John",
Age: 18,
}
j := gjson.New(info)
IniStr, _ := j.ToIniString()
fmt.Println(string(IniStr))
// May Output:
// Output:
//Name=John
//Age=18
}
func ExampleJson_MustToIni() {
type BaseInfo struct {
Name string
Age int
}
info := BaseInfo{
Name: "John",
Age: 18,
}
j := gjson.New(info)
IniBytes := j.MustToIni()
fmt.Println(string(IniBytes))
// May Output:
// Output:
//Name=John
//Age=18
}
func ExampleJson_MustToIniString() {
type BaseInfo struct {
Name string
Age int
}
info := BaseInfo{
Name: "John",
Age: 18,
}
j := gjson.New(info)
IniStr := j.MustToIniString()
fmt.Println(string(IniStr))
// May Output:
// Output:
//Name=John
//Age=18
}
func ExampleJson_MarshalJSON() {
@ -758,8 +749,12 @@ func ExampleJson_Interface() {
j := gjson.New(info)
fmt.Println(j.Interface())
var nilJ *gjson.Json = nil
fmt.Println(nilJ.Interface())
// Output:
// map[Age:18 Name:John]
// <nil>
}
func ExampleJson_Var() {
@ -807,11 +802,16 @@ func ExampleJson_Get() {
}`
j, _ := gjson.LoadContent(data)
fmt.Println(j.Get("."))
fmt.Println(j.Get("users"))
fmt.Println(j.Get("users.count"))
fmt.Println(j.Get("users.array"))
var nilJ *gjson.Json = nil
fmt.Println(nilJ.Get("."))
// Output:
// {"users":{"array":["John","Ming"],"count":1}}
// {"array":["John","Ming"],"count":1}
// 1
// ["John","Ming"]
@ -893,10 +893,11 @@ func ExampleJson_Set() {
j := gjson.New(info)
j.Set("Addr", "ChengDu")
j.Set("Friends.0", "Tom")
fmt.Println(j.Var().String())
// Output:
// {"Addr":"ChengDu","Age":18,"Name":"John"}
// {"Addr":"ChengDu","Age":18,"Friends":["Tom"],"Name":"John"}
}
func ExampleJson_MustSet() {
@ -1091,7 +1092,7 @@ func ExampleJson_Scan() {
fmt.Println(info)
// Output:
// May Output:
// {john 18}
}
@ -1099,12 +1100,11 @@ func ExampleJson_Dump() {
data := `{"name":"john","age":"18"}`
j, _ := gjson.LoadContent(data)
j.Dump()
// May Output:
//{
// "age": "18",
// "name": "john",
// "age": "18",
//}
}

View File

@ -67,6 +67,11 @@ func Test_MapAttributeConvert(t *testing.T) {
t.Assert(tx.Title, g.Map{
"l1": "标签1", "l2": "标签2",
})
j.Dump()
var nilJ *gjson.Json = nil
nilJ.Dump()
})
gtest.C(t, func(t *gtest.T) {

View File

@ -28,11 +28,17 @@ func Test_Load_JSON1(t *testing.T) {
t.Assert(j.Get("a.1").Int(), 2)
})
// JSON
gtest.C(t, func(t *gtest.T) {
errData := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]`)
_, err := gjson.LoadContentType("json", errData, true)
t.AssertNE(err, nil)
})
// JSON
gtest.C(t, func(t *gtest.T) {
path := "test.json"
gfile.PutBytes(path, data)
defer gfile.Remove(path)
j, err := gjson.Load(path)
j, err := gjson.Load(path, true)
t.Assert(err, nil)
t.Assert(j.Get("n").String(), "123456789")
t.Assert(j.Get("m").Map(), g.Map{"k": "v"})
@ -68,6 +74,22 @@ func Test_Load_XML(t *testing.T) {
t.Assert(j.Get("doc.a.1").Int(), 2)
})
// XML
gtest.C(t, func(t *gtest.T) {
j, err := gjson.LoadXml(data, true)
t.Assert(err, nil)
t.Assert(j.Get("doc.n").String(), "123456789")
t.Assert(j.Get("doc.m").Map(), g.Map{"k": "v"})
t.Assert(j.Get("doc.m.k").String(), "v")
t.Assert(j.Get("doc.a").Slice(), g.Slice{"1", "2", "3"})
t.Assert(j.Get("doc.a.1").Int(), 2)
})
// XML
gtest.C(t, func(t *gtest.T) {
errData := []byte(`<doc><a>1</a><a>2</a><a>3</a><m><k>v</k></m><n>123456789</n><doc>`)
_, err := gjson.LoadContentType("xml", errData, true)
t.AssertNE(err, nil)
})
// XML
gtest.C(t, func(t *gtest.T) {
path := "test.xml"
gfile.PutBytes(path, data)
@ -121,6 +143,16 @@ m:
t.Assert(j.Get("a.1").Int(), 2)
})
// YAML
gtest.C(t, func(t *gtest.T) {
j, err := gjson.LoadYaml(data, true)
t.Assert(err, nil)
t.Assert(j.Get("n").String(), "123456789")
t.Assert(j.Get("m").Map(), g.Map{"k": "v"})
t.Assert(j.Get("m.k").String(), "v")
t.Assert(j.Get("a").Slice(), g.Slice{1, 2, 3})
t.Assert(j.Get("a.1").Int(), 2)
})
// YAML
gtest.C(t, func(t *gtest.T) {
path := "test.yaml"
gfile.PutBytes(path, data)
@ -142,6 +174,11 @@ func Test_Load_YAML2(t *testing.T) {
t.Assert(err, nil)
t.Assert(j.Get("i"), "123456789")
})
gtest.C(t, func(t *gtest.T) {
errData := []byte("i # 123456789")
_, err := gjson.LoadContentType("yaml", errData, true)
t.AssertNE(err, nil)
})
}
func Test_Load_TOML1(t *testing.T) {
@ -163,6 +200,16 @@ n = 123456789
t.Assert(j.Get("a.1").Int(), 2)
})
// TOML
gtest.C(t, func(t *gtest.T) {
j, err := gjson.LoadToml(data, true)
t.Assert(err, nil)
t.Assert(j.Get("n").String(), "123456789")
t.Assert(j.Get("m").Map(), g.Map{"k": "v"})
t.Assert(j.Get("m.k").String(), "v")
t.Assert(j.Get("a").Slice(), g.Slice{"1", "2", "3"})
t.Assert(j.Get("a.1").Int(), 2)
})
// TOML
gtest.C(t, func(t *gtest.T) {
path := "test.toml"
gfile.PutBytes(path, data)
@ -184,6 +231,11 @@ func Test_Load_TOML2(t *testing.T) {
t.Assert(err, nil)
t.Assert(j.Get("i"), "123456789")
})
gtest.C(t, func(t *gtest.T) {
errData := []byte("i : 123456789")
_, err := gjson.LoadContentType("toml", errData, true)
t.AssertNE(err, nil)
})
}
func Test_Load_Basic(t *testing.T) {
@ -245,6 +297,26 @@ enable=true
gtest.Fatal(err)
}
})
gtest.C(t, func(t *gtest.T) {
j, err := gjson.LoadIni(data, true)
if err != nil {
gtest.Fatal(err)
}
t.Assert(j.Get("addr.ip").String(), "127.0.0.1")
t.Assert(j.Get("addr.port").String(), "9001")
t.Assert(j.Get("addr.enable").String(), "true")
t.Assert(j.Get("DBINFO.type").String(), "mysql")
t.Assert(j.Get("DBINFO.user").String(), "root")
t.Assert(j.Get("DBINFO.password").String(), "password")
})
gtest.C(t, func(t *gtest.T) {
errData := []byte("i : 123456789")
_, err := gjson.LoadContentType("ini", errData, true)
t.AssertNE(err, nil)
})
}
func Test_Load_YamlWithV3(t *testing.T) {

View File

@ -339,3 +339,11 @@ func Test_Set_GArray(t *testing.T) {
t.Assert(j.Get("arr").Array(), g.Slice{"test"})
})
}
func Test_Set_WithEmptyStruct(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
j := gjson.New(&struct{}{})
t.AssertNil(j.Set("aa", "123"))
t.Assert(j.MustToJsonString(), `{"aa":"123"}`)
})
}

View File

@ -197,7 +197,7 @@ func Test_Struct1(t *testing.T) {
}]
}`
data := new(UserCollectionAddReq)
j, err := gjson.LoadJson(jsonContent)
j, err := gjson.LoadJson(jsonContent, true)
t.Assert(err, nil)
err = j.Scan(data)
t.Assert(err, nil)

View File

@ -0,0 +1,4 @@
server:
address: ":8199"
openapiPath: "/api.json"
swaggerPath: "/swagger"

View File

@ -0,0 +1,38 @@
package main
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
type HelloReq struct {
g.Meta `path:"/hello" method:"get"`
Name string `v:"required" dc:"Your name"`
}
type HelloRes struct {
Reply string `dc:"Reply content"`
}
type Hello struct{}
func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) {
g.Log().Debugf(ctx, `receive say: %+v`, req)
res = &HelloRes{
Reply: fmt.Sprintf(`Hi %s`, req.Name),
}
return
}
func main() {
s := g.Server()
s.Use(ghttp.MiddlewareHandlerResponse)
s.Group("/", func(group *ghttp.RouterGroup) {
group.Bind(
new(Hello),
)
})
s.Run()
}

View File

@ -65,16 +65,15 @@ func Test_Config2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var err error
dirPath := gfile.Temp(gtime.TimestampNanoStr())
err = gfile.Mkdir(dirPath)
t.Assert(err, nil)
t.AssertNil(gfile.Mkdir(dirPath))
defer gfile.Remove(dirPath)
name := "config/config.toml"
err = gfile.PutContents(gfile.Join(dirPath, name), configContent)
t.Assert(err, nil)
t.AssertNil(err)
err = gins.Config().GetAdapter().(*gcfg.AdapterFile).AddPath(dirPath)
t.Assert(err, nil)
t.AssertNil(err)
defer gins.Config().GetAdapter().(*gcfg.AdapterFile).Clear()
@ -200,9 +199,11 @@ func Test_Config4(t *testing.T) {
func Test_Basic2(t *testing.T) {
config := `log-path = "logs"`
gtest.C(t, func(t *gtest.T) {
path := gcfg.DefaultConfigFile
err := gfile.PutContents(path, config)
t.Assert(err, nil)
var (
path = gcfg.DefaultConfigFileName
err = gfile.PutContents(path, config)
)
t.AssertNil(err)
defer func() {
_ = gfile.Remove(path)
}()

View File

@ -123,20 +123,21 @@ const (
)
const (
supportedHttpMethods = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
defaultMethod = "ALL"
exceptionExit = "exit"
exceptionExitAll = "exit_all"
exceptionExitHook = "exit_hook"
routeCacheDuration = time.Hour
methodNameInit = "Init"
methodNameShut = "Shut"
ctxKeyForRequest = "gHttpRequestObject"
contentTypeXml = "text/xml"
contentTypeHtml = "text/html"
contentTypeJson = "application/json"
swaggerUIPackedPath = "/goframe/swaggerui"
responseTraceIDHeader = "Trace-ID"
supportedHttpMethods = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
defaultMethod = "ALL"
exceptionExit = "exit"
exceptionExitAll = "exit_all"
exceptionExitHook = "exit_hook"
routeCacheDuration = time.Hour
ctxKeyForRequest = "gHttpRequestObject"
contentTypeXml = "text/xml"
contentTypeHtml = "text/html"
contentTypeJson = "application/json"
swaggerUIPackedPath = "/goframe/swaggerui"
responseTraceIDHeader = "Trace-ID"
specialMethodNameInit = "Init"
specialMethodNameShut = "Shut"
specialMethodNameIndex = "Index"
)
var (

View File

@ -7,6 +7,8 @@
package ghttp
import (
"net/http"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
@ -28,30 +30,33 @@ func MiddlewareHandlerResponse(r *Request) {
}
var (
err error
res interface{}
ctx = r.Context()
internalErr error
msg string
ctx = r.Context()
err = r.GetError()
res = r.GetHandlerResponse()
code = gerror.Code(err)
)
res, err = r.GetHandlerResponse()
if err != nil {
code := gerror.Code(err)
if code == gcode.CodeNil {
code = gcode.CodeInternalError
}
internalErr = r.Response.WriteJson(DefaultHandlerResponse{
Code: code.Code(),
Message: err.Error(),
Data: nil,
})
if internalErr != nil {
intlog.Errorf(ctx, `%+v`, internalErr)
msg = err.Error()
} else if r.Response.Status > 0 && r.Response.Status != http.StatusOK {
msg = http.StatusText(r.Response.Status)
switch r.Response.Status {
case http.StatusNotFound:
code = gcode.CodeNotFound
case http.StatusForbidden:
code = gcode.CodeNotAuthorized
default:
code = gcode.CodeUnknown
}
return
} else {
code = gcode.CodeOK
}
internalErr = r.Response.WriteJson(DefaultHandlerResponse{
Code: gcode.CodeOK.Code(),
Message: "",
internalErr := r.Response.WriteJson(DefaultHandlerResponse{
Code: code.Code(),
Message: msg,
Data: res,
})
if internalErr != nil {

View File

@ -41,7 +41,7 @@ type Request struct {
context context.Context // Custom context for internal usage purpose.
handlers []*handlerParsedItem // All matched handlers containing handler, hook and middleware for this request.
handlerResponse handlerResponse // Handler response object and its error value for Request/Response handler.
handlerResponse interface{} // Handler response object for Request/Response handler.
hasHookHandler bool // A bool marking whether there's hook handler in the handlers for performance purpose.
hasServeHandler bool // A bool marking whether there's serving handler in the handlers for performance purpose.
parsedQuery bool // A bool marking whether the GET parameters parsed.
@ -267,6 +267,6 @@ func (r *Request) ReloadParam() {
}
// GetHandlerResponse retrieves and returns the handler response object and its error.
func (r *Request) GetHandlerResponse() (res interface{}, err error) {
return r.handlerResponse.Object, r.handlerResponse.Error
func (r *Request) GetHandlerResponse() interface{} {
return r.handlerResponse
}

View File

@ -138,12 +138,12 @@ func (m *middleware) callHandlerFunc(funcInfo handlerFuncInfo) {
)
if funcInfo.Type.In(1).Kind() == reflect.Ptr {
inputObject = reflect.New(funcInfo.Type.In(1).Elem())
m.request.handlerResponse.Error = m.request.Parse(inputObject.Interface())
m.request.error = m.request.Parse(inputObject.Interface())
} else {
inputObject = reflect.New(funcInfo.Type.In(1).Elem()).Elem()
m.request.handlerResponse.Error = m.request.Parse(inputObject.Addr().Interface())
m.request.error = m.request.Parse(inputObject.Addr().Interface())
}
if m.request.handlerResponse.Error != nil {
if m.request.error != nil {
return
}
inputValues = append(inputValues, inputObject)
@ -155,15 +155,15 @@ func (m *middleware) callHandlerFunc(funcInfo handlerFuncInfo) {
case 1:
if !results[0].IsNil() {
if err, ok := results[0].Interface().(error); ok {
m.request.handlerResponse.Error = err
m.request.error = err
}
}
case 2:
m.request.handlerResponse.Object = results[0].Interface()
m.request.handlerResponse = results[0].Interface()
if !results[1].IsNil() {
if err, ok := results[1].Interface().(error); ok {
m.request.handlerResponse.Error = err
m.request.error = err
}
}
}

View File

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

View File

@ -25,8 +25,8 @@ func (r *Request) SetForm(key string, value interface{}) {
func (r *Request) GetForm(key string, def ...interface{}) *gvar.Var {
r.parseForm()
if len(r.formMap) > 0 {
if v, ok := r.formMap[key]; ok {
return gvar.New(v)
if value, ok := r.formMap[key]; ok {
return gvar.New(value)
}
}
if len(def) > 0 {

View File

@ -30,8 +30,10 @@ func (r *Request) SetParamMap(data map[string]interface{}) {
// It returns `def` if `key` does not exist.
// It returns nil if `def` is not passed.
func (r *Request) GetParam(key string, def ...interface{}) *gvar.Var {
if r.paramsMap != nil {
return gvar.New(r.paramsMap[key])
if len(r.paramsMap) > 0 {
if value, ok := r.paramsMap[key]; ok {
return gvar.New(value)
}
}
if len(def) > 0 {
return gvar.New(def[0])

View File

@ -29,8 +29,8 @@ func (r *Request) SetQuery(key string, value interface{}) {
func (r *Request) GetQuery(key string, def ...interface{}) *gvar.Var {
r.parseQuery()
if len(r.queryMap) > 0 {
if v, ok := r.queryMap[key]; ok {
return gvar.New(v)
if value, ok := r.queryMap[key]; ok {
return gvar.New(value)
}
}
if r.Method == "GET" {

View File

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

View File

@ -11,6 +11,7 @@ import (
"bytes"
"fmt"
"net/http"
"net/url"
"github.com/gogf/gf/v2/net/gtrace"
"github.com/gogf/gf/v2/os/gfile"
@ -91,7 +92,7 @@ func (r *Response) ServeFileDownload(path string, name ...string) {
}
r.Header().Set("Content-Type", "application/force-download")
r.Header().Set("Accept-Ranges", "bytes")
r.Header().Set("Content-Disposition", fmt.Sprintf(`attachment;filename="%s"`, downloadName))
r.Header().Set("Content-Disposition", fmt.Sprintf(`attachment;filename=%s`, url.QueryEscape(downloadName)))
r.Server.serveFile(r.Request, serveFile)
}

View File

@ -16,6 +16,7 @@ import (
"strings"
"time"
"github.com/gogf/gf/v2/net/ghttp/internal/swaggerui"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/v2/container/garray"
@ -24,7 +25,6 @@ import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/net/ghttp/internal/swaggerui"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/genv"
"github.com/gogf/gf/v2/os/gfile"
@ -128,7 +128,7 @@ func (s *Server) Start() error {
swaggerui.Init()
s.AddStaticPath(s.config.SwaggerPath, swaggerUIPackedPath)
s.BindHookHandler(s.config.SwaggerPath+"/*", HookBeforeServe, s.swaggerUI)
s.Logger().Debugf(
s.Logger().Infof(
ctx,
`swagger ui is serving at address: %s%s/`,
s.getListenAddress(),

View File

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

View File

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

View File

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

View File

@ -99,9 +99,7 @@ func (s *Server) setHandler(ctx context.Context, in setHandlerInput) {
// Change the registered route according meta info from its request structure.
if handler.Info.Type != nil && handler.Info.Type.NumIn() == 2 {
var (
objectReq = reflect.New(handler.Info.Type.In(1))
)
var objectReq = reflect.New(handler.Info.Type.In(1))
if v := gmeta.Get(objectReq, goai.TagNamePath); !v.IsEmpty() {
uri = v.String()
}
@ -132,12 +130,22 @@ func (s *Server) setHandler(ctx context.Context, in setHandlerInput) {
if !s.config.RouteOverWrite {
switch handler.Type {
case HandlerTypeHandler, HandlerTypeObject:
if item, ok := s.routesMap[routerKey]; ok {
s.Logger().Fatalf(
ctx,
`duplicated route registry "%s" at %s , already registered at %s`,
pattern, handler.Source, item[0].Source,
)
if items, ok := s.routesMap[routerKey]; ok {
var duplicatedHandler *handlerItem
for _, item := range items {
switch item.Handler.Type {
case HandlerTypeHandler, HandlerTypeObject:
duplicatedHandler = item.Handler
break
}
}
if duplicatedHandler != nil {
s.Logger().Fatalf(
ctx,
`duplicated route registry "%s" at %s , already registered at %s`,
pattern, handler.Source, duplicatedHandler.Source,
)
}
return
}
}

View File

@ -23,9 +23,7 @@ import (
// 1. func(*ghttp.Request)
// 2. func(context.Context, BizRequest)(BizResponse, error)
func (s *Server) BindHandler(pattern string, handler interface{}) {
var (
ctx = context.TODO()
)
var ctx = context.TODO()
funcInfo, err := s.checkAndCreateFuncInfo(handler, "", "", "")
if err != nil {
s.Logger().Fatalf(ctx, `%+v`, err)

View File

@ -76,9 +76,7 @@ type doBindObjectInput struct {
func (s *Server) doBindObject(ctx context.Context, in doBindObjectInput) {
// Convert input method to map for convenience and high performance searching purpose.
var (
methodMap map[string]bool
)
var methodMap map[string]bool
if len(in.Method) > 0 {
methodMap = make(map[string]bool)
for _, v := range strings.Split(in.Method, ",") {
@ -112,11 +110,11 @@ func (s *Server) doBindObject(ctx context.Context, in doBindObjectInput) {
reflectType = reflectValue.Type()
}
structName := reflectType.Elem().Name()
if reflectValue.MethodByName("Init").IsValid() {
initFunc = reflectValue.MethodByName("Init").Interface().(func(*Request))
if reflectValue.MethodByName(specialMethodNameInit).IsValid() {
initFunc = reflectValue.MethodByName(specialMethodNameInit).Interface().(func(*Request))
}
if reflectValue.MethodByName("Shut").IsValid() {
shutFunc = reflectValue.MethodByName("Shut").Interface().(func(*Request))
if reflectValue.MethodByName(specialMethodNameShut).IsValid() {
shutFunc = reflectValue.MethodByName(specialMethodNameShut).Interface().(func(*Request))
}
pkgPath := reflectType.Elem().PkgPath()
pkgName := gfile.Basename(pkgPath)
@ -125,7 +123,7 @@ func (s *Server) doBindObject(ctx context.Context, in doBindObjectInput) {
if methodMap != nil && !methodMap[methodName] {
continue
}
if methodName == "Init" || methodName == "Shut" {
if methodName == specialMethodNameInit || methodName == specialMethodNameShut {
continue
}
objName := gstr.Replace(reflectType.String(), fmt.Sprintf(`%s.`, pkgName), "")
@ -155,7 +153,12 @@ func (s *Server) doBindObject(ctx context.Context, in doBindObjectInput) {
//
// Note that if there's built-in variables in pattern, this route will not be added
// automatically.
if strings.EqualFold(methodName, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, in.Pattern) {
var (
isIndexMethod = strings.EqualFold(methodName, specialMethodNameIndex)
hasBuildInVar = gregex.IsMatchString(`\{\.\w+\}`, in.Pattern)
hashTwoParams = funcInfo.Type.NumIn() == 2
)
if isIndexMethod && !hasBuildInVar && !hashTwoParams {
p := gstr.PosRI(key, "/index")
k := key[0:p] + key[p+6:]
if len(k) == 0 || k[0] == '@' {
@ -209,11 +212,11 @@ func (s *Server) doBindObjectMethod(ctx context.Context, in doBindObjectMethodIn
s.Logger().Fatalf(ctx, "invalid method name: %s", methodName)
return
}
if reflectValue.MethodByName("Init").IsValid() {
initFunc = reflectValue.MethodByName("Init").Interface().(func(*Request))
if reflectValue.MethodByName(specialMethodNameInit).IsValid() {
initFunc = reflectValue.MethodByName(specialMethodNameInit).Interface().(func(*Request))
}
if reflectValue.MethodByName("Shut").IsValid() {
shutFunc = reflectValue.MethodByName("Shut").Interface().(func(*Request))
if reflectValue.MethodByName(specialMethodNameShut).IsValid() {
shutFunc = reflectValue.MethodByName(specialMethodNameShut).Interface().(func(*Request))
}
var (
pkgPath = reflectType.Elem().PkgPath()
@ -260,11 +263,11 @@ func (s *Server) doBindObjectRest(ctx context.Context, in doBindObjectInput) {
reflectType = reflectValue.Type()
}
structName := reflectType.Elem().Name()
if reflectValue.MethodByName(methodNameInit).IsValid() {
initFunc = reflectValue.MethodByName(methodNameInit).Interface().(func(*Request))
if reflectValue.MethodByName(specialMethodNameInit).IsValid() {
initFunc = reflectValue.MethodByName(specialMethodNameInit).Interface().(func(*Request))
}
if reflectValue.MethodByName(methodNameShut).IsValid() {
shutFunc = reflectValue.MethodByName(methodNameShut).Interface().(func(*Request))
if reflectValue.MethodByName(specialMethodNameShut).IsValid() {
shutFunc = reflectValue.MethodByName(specialMethodNameShut).Interface().(func(*Request))
}
pkgPath := reflectType.Elem().PkgPath()
for i := 0; i < reflectValue.NumMethod(); i++ {

View File

@ -7,14 +7,33 @@
package ghttp
import (
"net/http"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
)
const (
swaggerUIDefaultURL = `https://petstore.swagger.io/v2/swagger.json`
swaggerUIDocName = `redoc.standalone.js`
swaggerUIDocNamePlaceHolder = `{SwaggerUIDocName}`
swaggerUIDocURLPlaceHolder = `{SwaggerUIDocUrl}`
swaggerUITemplate = `
<!DOCTYPE html>
<html>
<head>
<title>API Reference</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url="{SwaggerUIDocUrl}"></redoc>
<script src="{SwaggerUIDocName}"> </script>
</body>
</html>
`
)
// swaggerUI is a build-in hook handler for replace default swagger json URL to local openapi json file path.
@ -23,20 +42,12 @@ func (s *Server) swaggerUI(r *Request) {
if s.config.OpenApiPath == "" {
return
}
var (
indexFileName = `index.html`
)
if r.StaticFile != nil && r.StaticFile.File != nil && gfile.Basename(r.StaticFile.File.Name()) == indexFileName {
if gfile.Basename(r.URL.Path) != indexFileName && r.originUrlPath[len(r.originUrlPath)-1] != '/' {
r.Response.Header().Set("Location", r.originUrlPath+"/")
r.Response.WriteHeader(http.StatusMovedPermanently)
r.ExitAll()
}
r.Response.Write(gstr.Replace(
string(r.StaticFile.File.Content()),
swaggerUIDefaultURL,
s.config.OpenApiPath,
))
if r.StaticFile != nil && r.StaticFile.File != nil && r.StaticFile.IsDir {
content := gstr.ReplaceByMap(swaggerUITemplate, map[string]string{
swaggerUIDocURLPlaceHolder: s.config.OpenApiPath,
swaggerUIDocNamePlaceHolder: gstr.TrimRight(r.GetUrl(), "/") + "/" + swaggerUIDocName,
})
r.Response.Write(content)
r.ExitAll()
}
}

View File

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

View File

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

View File

@ -60,9 +60,9 @@ func Test_OpenApi_Swagger(t *testing.T) {
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
t.Assert(c.GetContent(ctx, "/test?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18,"Name":"john"}}`)
t.Assert(c.GetContent(ctx, "/test/error"), `{"code":50,"message":"error","data":null}`)
t.Assert(c.GetContent(ctx, "/test/error"), `{"code":50,"message":"error","data":{"Id":1,"Age":0,"Name":""}}`)
t.Assert(gstr.Contains(c.GetContent(ctx, "/swagger/"), `SwaggerUIBundle`), true)
t.Assert(gstr.Contains(c.GetContent(ctx, "/swagger/"), `API Reference`), true)
t.Assert(gstr.Contains(c.GetContent(ctx, "/api.json"), `/test/error`), true)
})
}

View File

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

View File

@ -16,6 +16,7 @@ import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/guid"
)
@ -55,7 +56,7 @@ func Test_Router_Handler_Extended_Handler_WithObject(t *testing.T) {
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
t.Assert(client.GetContent(ctx, "/test?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18,"Name":"john"}}`)
t.Assert(client.GetContent(ctx, "/test/error"), `{"code":50,"message":"error","data":null}`)
t.Assert(client.GetContent(ctx, "/test/error"), `{"code":50,"message":"error","data":{"Id":1,"Age":0,"Name":""}}`)
})
}
@ -81,7 +82,7 @@ type TestForHandlerWithObjectAndMeta2Res struct {
type ControllerForHandlerWithObjectAndMeta1 struct{}
func (ControllerForHandlerWithObjectAndMeta1) Test1(ctx context.Context, req *TestForHandlerWithObjectAndMeta1Req) (res *TestForHandlerWithObjectAndMeta1Res, err error) {
func (ControllerForHandlerWithObjectAndMeta1) Index(ctx context.Context, req *TestForHandlerWithObjectAndMeta1Req) (res *TestForHandlerWithObjectAndMeta1Res, err error) {
return &TestForHandlerWithObjectAndMeta1Res{
Id: 1,
Age: req.Age,
@ -145,10 +146,10 @@ func Test_Router_Handler_Extended_Handler_WithObjectAndMeta(t *testing.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
t.Assert(client.GetContent(ctx, "/"), `{"code":0,"message":"","data":null}`)
t.Assert(client.GetContent(ctx, "/"), `{"code":65,"message":"Not Found","data":null}`)
t.Assert(client.GetContent(ctx, "/custom-test1?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18}}`)
t.Assert(client.GetContent(ctx, "/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`)
t.Assert(client.PostContent(ctx, "/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":null}`)
t.Assert(client.PostContent(ctx, "/custom-test2?age=18&name=john"), `{"code":65,"message":"Not Found","data":null}`)
})
}
@ -176,10 +177,10 @@ func Test_Router_Handler_Extended_Handler_Group_Bind(t *testing.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
t.Assert(client.GetContent(ctx, "/"), `{"code":0,"message":"","data":null}`)
t.Assert(client.GetContent(ctx, "/"), `{"code":65,"message":"Not Found","data":null}`)
t.Assert(client.GetContent(ctx, "/api/v1/custom-test1?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18}}`)
t.Assert(client.GetContent(ctx, "/api/v1/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`)
t.Assert(client.PostContent(ctx, "/api/v1/custom-test2?age=18&name=john"), `{"code":0,"message":"","data":null}`)
t.Assert(client.PostContent(ctx, "/api/v1/custom-test2?age=18&name=john"), `{"code":65,"message":"Not Found","data":null}`)
t.Assert(client.GetContent(ctx, "/api/v1/custom-test3?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Age":18}}`)
t.Assert(client.GetContent(ctx, "/api/v1/custom-test4?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`)
@ -190,3 +191,42 @@ func Test_Router_Handler_Extended_Handler_Group_Bind(t *testing.T) {
t.Assert(client.GetContent(ctx, "/api/v2/custom-test4?age=18&name=john"), `{"code":0,"message":"","data":{"Id":1,"Name":"john"}}`)
})
}
// https://github.com/gogf/gf/issues/1626
func Test_Issue1626(t *testing.T) {
type TestReq struct {
Name string `v:"required"`
}
type TestRes struct {
Name string
}
s := g.Server(guid.S())
s.Use(
ghttp.MiddlewareHandlerResponse,
func(r *ghttp.Request) {
r.Middleware.Next()
if err := r.GetError(); err != nil {
r.Response.ClearBuffer()
r.Response.Write(err.Error())
}
},
)
s.BindHandler("/test", func(ctx context.Context, req *TestReq) (res *TestRes, err error) {
return &TestRes{Name: req.Name}, nil
})
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
c := g.Client()
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
t.Assert(c.GetContent(ctx, "/test"), `The Name field is required`)
t.Assert(
gstr.Contains(c.GetContent(ctx, "/test?name=john"), `{"Name":"john"}`),
true,
)
})
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,10 @@
// Package swaggerui provides packed swagger ui static files using resource manager.
//
// Files from: https://github.com/swagger-api/swagger-ui
// Pack command: gf pack swagger-ui swaggerui-packed.go -n=swaggerui -p=/goframe/swaggerui
// Files from:
// https://github.com/Redocly/redoc
// https://www.jsdelivr.com/package/npm/redoc
//
// Pack command:
// gf pack redoc.standalone.js swaggerui-redoc.go -n=swaggerui -p=/goframe/swaggerui
package swaggerui

View File

@ -9,7 +9,6 @@ package gcfg
import (
"context"
"fmt"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/errors/gcode"
@ -26,7 +25,8 @@ type Config struct {
}
const (
DefaultName = "config" // DefaultName is the default group name for instance usage.
DefaultInstanceName = "config" // DefaultName is the default instance name for instance usage.
DefaultConfigFileName = "config" // DefaultConfigFile is the default configuration file name.
)
// New creates and returns a Config object with default adapter of AdapterFile.
@ -52,31 +52,20 @@ func NewWithAdapter(adapter Adapter) *Config {
// exists in the configuration directory, it then sets it as the default configuration file. The
// toml file type is the default configuration file type.
func Instance(name ...string) *Config {
var (
ctx = context.TODO()
key = DefaultName
)
var instanceName = DefaultInstanceName
if len(name) > 0 && name[0] != "" {
key = name[0]
instanceName = name[0]
}
return localInstances.GetOrSetFuncLock(key, func() interface{} {
adapter, err := NewAdapterFile()
return localInstances.GetOrSetFuncLock(instanceName, func() interface{} {
adapterFile, err := NewAdapterFile()
if err != nil {
intlog.Errorf(context.Background(), `%+v`, err)
return nil
}
// If it's not using default configuration or its configuration file is not available,
// it searches the possible configuration file according to the name and all supported
// file types.
if key != DefaultName || !adapter.Available(ctx) {
for _, fileType := range supportedFileTypes {
if file := fmt.Sprintf(`%s.%s`, key, fileType); adapter.Available(ctx, file) {
adapter.SetFileName(file)
break
}
}
if instanceName != DefaultInstanceName {
adapterFile.SetFileName(instanceName)
}
return NewWithAdapter(adapter)
return NewWithAdapter(adapterFile)
}).(*Config)
}

View File

@ -30,7 +30,6 @@ type AdapterFile struct {
}
const (
DefaultConfigFile = "config.toml" // DefaultConfigFile is the default configuration file name.
commandEnvKeyForFile = "gf.gcfg.file" // commandEnvKeyForFile is the configuration key for command argument or environment configuring file name.
commandEnvKeyForPath = "gf.gcfg.path" // commandEnvKeyForPath is the configuration key for command argument or environment configuring directory path.
)
@ -55,7 +54,7 @@ var (
func NewAdapterFile(file ...string) (*AdapterFile, error) {
var (
err error
name = DefaultConfigFile
name = DefaultConfigFileName
)
if len(file) > 0 {
name = file[0]
@ -224,6 +223,7 @@ func (c *AdapterFile) getJson(fileName ...string) (configJson *gjson.Json, err e
} else {
usedFileName = c.defaultName
}
// It uses json map to cache specified configuration file content.
result := c.jsonMap.GetOrSetFuncLock(usedFileName, func() interface{} {
var (
content string

View File

@ -15,7 +15,7 @@ import (
// SetContent sets customized configuration content for specified `file`.
// The `file` is unnecessary param, default is DefaultConfigFile.
func (c *AdapterFile) SetContent(content string, file ...string) {
name := DefaultConfigFile
name := DefaultConfigFileName
if len(file) > 0 {
name = file[0]
}
@ -37,7 +37,7 @@ func (c *AdapterFile) SetContent(content string, file ...string) {
// GetContent returns customized configuration content for specified `file`.
// The `file` is unnecessary param, default is DefaultConfigFile.
func (c *AdapterFile) GetContent(file ...string) string {
name := DefaultConfigFile
name := DefaultConfigFileName
if len(file) > 0 {
name = file[0]
}
@ -47,7 +47,7 @@ func (c *AdapterFile) GetContent(file ...string) string {
// RemoveContent removes the global configuration with specified `file`.
// If `name` is not passed, it removes configuration of the default group name.
func (c *AdapterFile) RemoveContent(file ...string) {
name := DefaultConfigFile
name := DefaultConfigFileName
if len(file) > 0 {
name = file[0]
}

View File

@ -10,6 +10,7 @@ import (
"bytes"
"context"
"fmt"
"os"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
@ -138,33 +139,37 @@ func (c *AdapterFile) AddPath(path string) (err error) {
return nil
}
// GetFilePath returns the absolute configuration file path for the given filename by `file`.
// doGetFilePath returns the absolute configuration file path for the given filename by `file`.
// If `file` is not passed, it returns the configuration file path of the default name.
// It returns an empty `path` string and an error if the given `file` does not exist.
func (c *AdapterFile) GetFilePath(fileName ...string) (path string, err error) {
func (c *AdapterFile) doGetFilePath(fileName string) (path string) {
var (
tempPath string
usedFileName = c.defaultName
tempPath string
resFile *gres.File
fileInfo os.FileInfo
)
if len(fileName) > 0 {
usedFileName = fileName[0]
}
// Searching resource manager.
if !gres.IsEmpty() {
for _, tryFolder := range resourceTryFolders {
tempPath = tryFolder + usedFileName
if file := gres.Get(tempPath); file != nil {
path = file.Name()
return
tempPath = tryFolder + fileName
if resFile = gres.Get(tempPath); resFile != nil {
fileInfo, _ = resFile.Stat()
if fileInfo != nil && !fileInfo.IsDir() {
path = resFile.Name()
return
}
}
}
c.searchPaths.RLockFunc(func(array []string) {
for _, searchPath := range array {
for _, tryFolder := range resourceTryFolders {
tempPath = searchPath + tryFolder + usedFileName
if file := gres.Get(tempPath); file != nil {
path = file.Name()
return
tempPath = searchPath + tryFolder + fileName
if resFile = gres.Get(tempPath); resFile != nil {
fileInfo, _ = resFile.Stat()
if fileInfo != nil && !fileInfo.IsDir() {
path = resFile.Name()
return
}
}
}
}
@ -176,7 +181,7 @@ func (c *AdapterFile) GetFilePath(fileName ...string) (path string, err error) {
// Searching local file system.
if path == "" {
// Absolute path.
if path = gfile.RealPath(usedFileName); path != "" {
if path = gfile.RealPath(fileName); path != "" && !gfile.IsDir(path) {
return
}
c.searchPaths.RLockFunc(func(array []string) {
@ -184,25 +189,58 @@ func (c *AdapterFile) GetFilePath(fileName ...string) (path string, err error) {
searchPath = gstr.TrimRight(searchPath, `\/`)
for _, tryFolder := range localSystemTryFolders {
relativePath := gstr.TrimRight(
gfile.Join(tryFolder, usedFileName),
gfile.Join(tryFolder, fileName),
`\/`,
)
if path, _ = gspath.Search(searchPath, relativePath); path != "" {
if path, _ = gspath.Search(searchPath, relativePath); path != "" && !gfile.IsDir(path) {
return
}
}
}
})
}
return
}
// GetFilePath returns the absolute configuration file path for the given filename by `file`.
// If `file` is not passed, it returns the configuration file path of the default name.
// It returns an empty `path` string and an error if the given `file` does not exist.
func (c *AdapterFile) GetFilePath(fileName ...string) (path string, err error) {
var (
fileExtName string
tempFileName string
usedFileName = c.defaultName
)
if len(fileName) > 0 {
usedFileName = fileName[0]
}
fileExtName = gfile.ExtName(usedFileName)
if path = c.doGetFilePath(usedFileName); (path == "" || gfile.IsDir(path)) && !gstr.InArray(supportedFileTypes, fileExtName) {
// If it's not using default configuration or its configuration file is not available,
// it searches the possible configuration file according to the name and all supported
// file types.
for _, fileType := range supportedFileTypes {
tempFileName = fmt.Sprintf(`%s.%s`, usedFileName, fileType)
if path = c.doGetFilePath(tempFileName); path != "" {
break
}
}
}
// If it cannot find the path of `file`, it formats and returns a detailed error.
if path == "" {
var buffer = bytes.NewBuffer(nil)
if c.searchPaths.Len() > 0 {
buffer.WriteString(fmt.Sprintf(
`config file "%s" not found in resource manager or the following system searching paths:`,
usedFileName,
))
if !gstr.InArray(supportedFileTypes, fileExtName) {
buffer.WriteString(fmt.Sprintf(
`possible config files "%s" or "%s" not found in resource manager or following system searching paths:`,
usedFileName, fmt.Sprintf(`%s.%s`, usedFileName, gstr.Join(supportedFileTypes, "/")),
))
} else {
buffer.WriteString(fmt.Sprintf(
`specified config file "%s" not found in resource manager or following system searching paths:`,
usedFileName,
))
}
c.searchPaths.RLockFunc(func(array []string) {
index := 1
for _, searchPath := range array {

View File

@ -31,9 +31,11 @@ array = [1,2,3]
cache = "127.0.0.1:6379,1"
`
gtest.C(t, func(t *gtest.T) {
path := gcfg.DefaultConfigFile
err := gfile.PutContents(path, config)
t.Assert(err, nil)
var (
path = gcfg.DefaultConfigFileName
err = gfile.PutContents(path, config)
)
t.AssertNil(err)
defer gfile.Remove(path)
c, err := gcfg.New()
@ -47,9 +49,11 @@ array = [1,2,3]
func Test_Basic2(t *testing.T) {
config := `log-path = "logs"`
gtest.C(t, func(t *gtest.T) {
path := gcfg.DefaultConfigFile
err := gfile.PutContents(path, config)
t.Assert(err, nil)
var (
path = gcfg.DefaultConfigFileName
err = gfile.PutContents(path, config)
)
t.AssertNil(err)
defer func() {
_ = gfile.Remove(path)
}()

View File

@ -37,11 +37,13 @@ v4 = "1.234"
`
gtest.C(t, func(t *gtest.T) {
path := DefaultConfigFile
err := gfile.PutContents(path, config)
t.Assert(err, nil)
var (
path = DefaultConfigFileName
err = gfile.PutContents(path, config)
)
t.AssertNil(err)
defer func() {
t.Assert(gfile.Remove(path), nil)
t.AssertNil(gfile.Remove(path))
}()
c := Instance()

View File

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

View File

@ -200,7 +200,7 @@ func (p *Parser) GetOptAll() map[string]string {
// GetArg returns the argument at `index` as gvar.Var.
func (p *Parser) GetArg(index int, def ...string) *gvar.Var {
if index < len(p.parsedArgs) {
if index >= 0 && index < len(p.parsedArgs) {
return gvar.New(p.parsedArgs[index])
}
if len(def) > 0 {

View File

@ -7,7 +7,9 @@
package gcmd_test
import (
"context"
"fmt"
"github.com/gogf/gf/v2/os/gctx"
"os"
"github.com/gogf/gf/v2/frame/g"
@ -53,6 +55,15 @@ func ExampleGetOpt() {
// Opt["o"]: "gf.exe", Opt["y"]: "", Opt["d"]: "default value"
}
func ExampleGetOpt_Def() {
gcmd.Init("gf", "build", "main.go", "-o=gf.exe", "-y")
fmt.Println(gcmd.GetOpt("s", "Def").String())
// Output:
// Def
}
func ExampleGetOptAll() {
gcmd.Init("gf", "build", "main.go", "-o=gf.exe", "-y")
fmt.Printf(`%#v`, gcmd.GetOptAll())
@ -85,6 +96,7 @@ func ExampleParse() {
fmt.Println(p.GetOpt("y") != nil)
fmt.Println(p.GetOpt("yes") != nil)
fmt.Println(p.GetOpt("none") != nil)
fmt.Println(p.GetOpt("none", "Def"))
// Output:
// gf.exe
@ -92,4 +104,168 @@ func ExampleParse() {
// true
// true
// false
// Def
}
func ExampleCommandFromCtx() {
var (
command = gcmd.Command{
Name: "start",
}
)
ctx := context.WithValue(gctx.New(), gcmd.CtxKeyCommand, &command)
unAddCtx := context.WithValue(gctx.New(), gcmd.CtxKeyCommand, &gcmd.Command{})
nonKeyCtx := context.WithValue(gctx.New(), "Testkey", &gcmd.Command{})
fmt.Println(gcmd.CommandFromCtx(ctx).Name)
fmt.Println(gcmd.CommandFromCtx(unAddCtx).Name)
fmt.Println(gcmd.CommandFromCtx(nonKeyCtx) == nil)
// Output:
// start
//
// true
}
func ExampleCommand_AddCommand() {
commandRoot := &gcmd.Command{
Name: "gf",
}
commandRoot.AddCommand(&gcmd.Command{
Name: "start",
}, &gcmd.Command{})
commandRoot.Print()
// Output:
//USAGE
// gf COMMAND [OPTION]
//
//COMMAND
// start
}
func ExampleCommand_AddCommand_Repeat() {
commandRoot := &gcmd.Command{
Name: "gf",
}
err := commandRoot.AddCommand(&gcmd.Command{
Name: "start",
}, &gcmd.Command{
Name: "stop",
}, &gcmd.Command{
Name: "start",
})
fmt.Println(err)
// Output:
// command "start" is already added to command "gf"
}
func ExampleCommand_AddObject() {
var (
command = gcmd.Command{
Name: "start",
}
)
command.AddObject(&TestCmdObject{})
command.Print()
// Output:
//USAGE
// start COMMAND [OPTION]
//
//COMMAND
// root root env command
}
func ExampleCommand_AddObject_Error() {
var (
command = gcmd.Command{
Name: "start",
}
)
err := command.AddObject(&[]string{"Test"})
fmt.Println(err)
// Output:
// input object should be type of struct, but got "*[]string"
}
func ExampleCommand_Print() {
commandRoot := &gcmd.Command{
Name: "gf",
}
commandRoot.AddCommand(&gcmd.Command{
Name: "start",
}, &gcmd.Command{})
commandRoot.Print()
// Output:
//USAGE
// gf COMMAND [OPTION]
//
//COMMAND
// start
}
func ExampleScan() {
fmt.Println(gcmd.Scan("gf scan"))
// Output:
// gf scan
}
func ExampleScanf() {
fmt.Println(gcmd.Scanf("gf %s", "scanf"))
// Output:
// gf scanf
}
func ExampleParserFromCtx() {
parser, _ := gcmd.Parse(nil)
ctx := context.WithValue(gctx.New(), gcmd.CtxKeyParser, parser)
nilCtx := context.WithValue(gctx.New(), "NilCtxKeyParser", parser)
fmt.Println(gcmd.ParserFromCtx(ctx).GetArgAll())
fmt.Println(gcmd.ParserFromCtx(nilCtx) == nil)
// Output:
// [gf build main.go]
// true
}
func ExampleParseArgs() {
p, _ := gcmd.ParseArgs([]string{
"gf", "--force", "remove", "-fq", "-p=www", "path", "-n", "root",
}, nil)
fmt.Println(p.GetArgAll())
fmt.Println(p.GetOptAll())
// Output:
// [gf path]
// map[force:remove fq: n:root p:www]
}
func ExampleParser_GetArg() {
p, _ := gcmd.ParseArgs([]string{
"gf", "--force", "remove", "-fq", "-p=www", "path", "-n", "root",
}, nil)
fmt.Println(p.GetArg(-1, "Def").String())
fmt.Println(p.GetArg(-1) == nil)
// Output:
// Def
// true
}

View File

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

View File

@ -48,6 +48,9 @@ func Test_Parse(t *testing.T) {
t.Assert(p.GetOpt("q") != nil, true)
t.Assert(p.GetOpt("quiet") != nil, true)
t.Assert(p.GetOpt("none") != nil, false)
_, err = p.MarshalJSON()
t.AssertNil(err)
})
}

View File

@ -417,8 +417,10 @@ func IsEmpty(path string) bool {
// The extension is the suffix beginning at the final dot
// in the final element of path; it is empty if there is
// no dot.
//
// Note: the result contains symbol '.'.
// Eg:
// main.go => .go
// api.json => .json
func Ext(path string) string {
ext := filepath.Ext(path)
if p := strings.IndexByte(ext, '?'); p != -1 {
@ -429,6 +431,9 @@ func Ext(path string) string {
// ExtName is like function Ext, which returns the file name extension used by path,
// but the result does not contain symbol '.'.
// Eg:
// main.go => go
// api.json => json
func ExtName(path string) string {
return strings.TrimLeft(Ext(path), ".")
}

View File

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

View File

@ -76,7 +76,11 @@ func doZipPathWriter(path string, exclude string, zipWriter *zip.Writer, prefix
intlog.Printf(context.TODO(), `exclude file path: %s`, file)
continue
}
if err = zipFile(file, headerPrefix+gfile.Dir(file[len(path):]), zipWriter); err != nil {
subFilePath := file[len(path):]
if subFilePath != "" {
subFilePath = gfile.Dir(subFilePath)
}
if err = zipFile(file, headerPrefix+subFilePath, zipWriter); err != nil {
return err
}
}

View File

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

View File

@ -9,6 +9,7 @@ package goai
import (
"fmt"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
@ -81,10 +82,6 @@ func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path
parameter.Required = true
case ParameterInCookie, ParameterInHeader, ParameterInQuery:
// Check validation tag.
if validateTagValue := field.Tag(TagNameValidate); gstr.ContainsI(validateTagValue, `required`) {
parameter.Required = true
}
default:
return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid tag value "%s" for In`, parameter.In)
@ -96,6 +93,13 @@ func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path
}
parameter.Schema = schemaRef
// Required check.
if parameter.Schema.Value != nil && parameter.Schema.Value.Pattern != "" {
if gset.NewStrSetFrom(gstr.Split(parameter.Schema.Value.Pattern, "|")).Contains(patternKeyForRequired) {
parameter.Required = true
}
}
return &ParameterRef{
Ref: "",
Value: parameter,

View File

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

View File

@ -47,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

View File

@ -51,40 +51,43 @@ func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*Schem
}
var (
dataFieldsPartsArray = gstr.Split(in.CommonResponseDataField, ".")
bizResponseStructSchemaRef, bizResponseStructSchemaRefExist = oai.Components.Schemas[in.BusinessStructName]
schema, err = oai.structToSchema(in.CommonResponseObject)
dataFieldsPartsArray = gstr.Split(in.CommonResponseDataField, ".")
bizResponseStructSchemaRef = oai.Components.Schemas.Get(in.BusinessStructName)
schema, err = oai.structToSchema(in.CommonResponseObject)
)
if err != nil {
return nil, err
}
if in.CommonResponseDataField == "" && bizResponseStructSchemaRefExist {
for k, v := range bizResponseStructSchemaRef.Value.Properties {
schema.Properties[k] = v
}
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()
)
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
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()
)
var structFieldInstance = reflect.New(structField.Type().Type).Elem()
schemaRef, err := oai.getResponseSchemaRef(getResponseSchemaRefInput{
BusinessStructName: in.BusinessStructName,
CommonResponseObject: structFieldInstance,
@ -93,7 +96,7 @@ func (oai *OpenApiV3) getResponseSchemaRef(in getResponseSchemaRefInput) (*Schem
if err != nil {
return nil, err
}
schema.Properties[fieldName] = *schemaRef
schema.Properties.Set(fieldName, *schemaRef)
break
}
}

View File

@ -9,16 +9,17 @@ package goai
import (
"reflect"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/utils"
"github.com/gogf/gf/v2/os/gstructs"
"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"`
@ -77,8 +78,8 @@ func (oai *OpenApiV3) addSchema(object ...interface{}) error {
}
func (oai *OpenApiV3) doAddSchemaSingle(object interface{}) error {
if oai.Components.Schemas == nil {
oai.Components.Schemas = map[string]SchemaRef{}
if oai.Components.Schemas.refs == nil {
oai.Components.Schemas.refs = gmap.NewListMap()
}
var (
@ -87,21 +88,21 @@ func (oai *OpenApiV3) doAddSchemaSingle(object interface{}) error {
)
// Already added.
if _, ok := oai.Components.Schemas[structTypeName]; ok {
if oai.Components.Schemas.Get(structTypeName) != nil {
return nil
}
// Take the holder first.
oai.Components.Schemas[structTypeName] = SchemaRef{}
oai.Components.Schemas.Set(structTypeName, SchemaRef{})
schema, err := oai.structToSchema(object)
if err != nil {
return err
}
oai.Components.Schemas[structTypeName] = SchemaRef{
oai.Components.Schemas.Set(structTypeName, SchemaRef{
Ref: "",
Value: schema,
}
})
return nil
}
@ -110,13 +111,12 @@ func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
var (
tagMap = gmeta.Data(object)
schema = &Schema{
Properties: map[string]SchemaRef{},
Properties: createSchemas(),
}
)
if len(tagMap) > 0 {
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 {
@ -142,9 +142,7 @@ func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
if !gstr.IsLetterUpper(structField.Name()[0]) {
continue
}
var (
fieldName = structField.Name()
)
var fieldName = structField.Name()
if jsonName := structField.TagJsonName(); jsonName != "" {
fieldName = jsonName
}
@ -155,7 +153,41 @@ func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
if err != nil {
return nil, err
}
schema.Properties[fieldName] = *schemaRef
schema.Properties.Set(fieldName, *schemaRef)
}
schema.Properties.Iterator(func(key string, ref SchemaRef) bool {
if ref.Value != nil && ref.Value.Pattern != "" {
validationRuleSet := gset.NewStrSetFrom(gstr.Split(ref.Value.Pattern, "|"))
if validationRuleSet.Contains(patternKeyForRequired) {
schema.Required = append(schema.Required, key)
}
}
return true
})
return schema, nil
}
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`)
}
// 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) {
schema.Enum = gconv.Interfaces(gstr.SplitAndTrim(rule[len(patternKeyForIn):], ","))
}
}
}
break
}
}
return nil
}

View File

@ -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
@ -32,8 +30,8 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap
}
)
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
@ -72,7 +70,7 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap
var (
structTypeName = oai.golangTypeToSchemaName(golangType)
)
if _, ok := oai.Components.Schemas[structTypeName]; !ok {
if oai.Components.Schemas.Get(structTypeName) == nil {
if err := oai.addSchema(reflect.New(golangType).Interface()); err != nil {
return nil, err
}
@ -82,10 +80,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
}

View File

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

View File

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

View File

@ -22,13 +22,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 {

View File

@ -198,11 +198,16 @@ 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])
}
// GetTags returns the validation tags.
func GetTags() []string {
return structTagPriority
}

View File

@ -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
}

View File

@ -58,7 +58,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
}
@ -126,8 +126,8 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error
for _, field := range tagFields {
var (
isMeta bool
fieldName = field.Name() // Attribute name.
name, rule, msg = parseSequenceTag(field.TagValue) // The `name` is different from `attribute alias`, which is used for validation only.
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 {

View File

@ -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())
})
}

View File

@ -1,4 +1,4 @@
package gf
const VERSION = "v2.0.0-rc3"
const VERSION = "v2.0.0"
const AUTHORS = "john<john@goframe.org>"