mirror of
https://gitee.com/johng/gf
synced 2026-06-10 11:27:17 +08:00
Compare commits
67 Commits
v2.0.0-rc3
...
v2.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f326dcac5 | |||
| aa294ea5df | |||
| 6afc725b61 | |||
| ec01693773 | |||
| b0cf501782 | |||
| c2fb7ada0a | |||
| d0a8e60ace | |||
| ab36bb8842 | |||
| 0b3cd7b7ae | |||
| 10ed04cdb8 | |||
| e09704a408 | |||
| ade9ae3c0b | |||
| 9cf6124c4c | |||
| 6d323cc529 | |||
| aea9f6fe18 | |||
| 8a27463e44 | |||
| 47ee2cba51 | |||
| 531cc7b864 | |||
| 54bdabd94d | |||
| bb6e8fe7a8 | |||
| d5d199ebef | |||
| 158a4589d2 | |||
| 84c0f456c0 | |||
| 3fcd6ef877 | |||
| 4e2d378145 | |||
| d64898c59a | |||
| 3bff71b3fc | |||
| 8343d1cd0e | |||
| 5c23c0cecd | |||
| f580713478 | |||
| 3c58b8d7fa | |||
| 072d5f9760 | |||
| f8067f5dd5 | |||
| ea354d10cc | |||
| 1724a26957 | |||
| 46dc68dfd5 | |||
| 12fdfbf8b2 | |||
| 68bdf7deb4 | |||
| 2362c453ec | |||
| 50f6b6e0f0 | |||
| 88a9eef8a6 | |||
| 308e13a546 | |||
| a0b1fefdbb | |||
| 3edbcb7bf9 | |||
| cb78953b38 | |||
| a1ddac4e6b | |||
| 456697ea99 | |||
| 8acffd1186 | |||
| 814450fd17 | |||
| 1365c1d277 | |||
| 30be5c5e49 | |||
| 932cd9d5bb | |||
| 7b5f17c16b | |||
| b5e8e68713 | |||
| 3a803ac39f | |||
| d27db119a0 | |||
| f54d0a339c | |||
| d83b676c60 | |||
| def3dc364f | |||
| 298aa5f040 | |||
| e4d56e7ad9 | |||
| 0fce4edcd3 | |||
| a34f52ae5e | |||
| da465bb030 | |||
| d045b4d2f5 | |||
| 572e71d76a | |||
| c91b83969c |
38
.github/workflows/cli.yml
vendored
38
.github/workflows/cli.yml
vendored
@ -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_*"]'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
@ -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,
|
||||
)
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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]
|
||||
}
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -270,6 +270,6 @@ func ExampleDecodeToJson() {
|
||||
j, _ := gjson.DecodeToJson([]byte(jsonContent))
|
||||
fmt.Println(j.Map())
|
||||
|
||||
// Output:
|
||||
// May Output:
|
||||
// map[name:john score:100]
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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",
|
||||
//}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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"}`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
4
example/httpserver/swagger/config.yaml
Normal file
4
example/httpserver/swagger/config.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
server:
|
||||
address: ":8199"
|
||||
openapiPath: "/api.json"
|
||||
swaggerPath: "/swagger"
|
||||
38
example/httpserver/swagger/main.go
Normal file
38
example/httpserver/swagger/main.go
Normal 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()
|
||||
}
|
||||
@ -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)
|
||||
}()
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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" {
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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.
|
||||
// ======================================================================================================
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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++ {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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))
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
9
net/ghttp/internal/swaggerui/swaggerui-redoc.go
Normal file
9
net/ghttp/internal/swaggerui/swaggerui-redoc.go
Normal file
File diff suppressed because one or more lines are too long
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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), ".")
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================================================================
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
64
protocol/goai/goai_shemas.go
Normal file
64
protocol/goai/goai_shemas.go
Normal 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()
|
||||
}
|
||||
@ -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",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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())
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package gf
|
||||
|
||||
const VERSION = "v2.0.0-rc3"
|
||||
const VERSION = "v2.0.0"
|
||||
const AUTHORS = "john<john@goframe.org>"
|
||||
|
||||
Reference in New Issue
Block a user