mirror of
https://gitee.com/johng/gf
synced 2026-07-02 19:31:07 +08:00
Compare commits
37 Commits
v2.1.2
...
contrib/dr
| Author | SHA1 | Date | |
|---|---|---|---|
| a0619f7ff0 | |||
| 37aee19bfa | |||
| 27609d8da8 | |||
| c083b333d8 | |||
| ee376883d1 | |||
| 98169784b1 | |||
| 9d1c6f2daa | |||
| 25d4ba320a | |||
| 3988a7ff6b | |||
| 26e3c7aeb8 | |||
| eff46bd1db | |||
| 7a3176ea77 | |||
| a656ad0941 | |||
| 299573dd19 | |||
| 43b84f4044 | |||
| 897d6d9ad0 | |||
| e4c8cfc16b | |||
| 95888e0b77 | |||
| 4ded89d453 | |||
| 82a3391937 | |||
| f580b7a488 | |||
| 9df0a9da0a | |||
| 6172862061 | |||
| 1ae037f515 | |||
| 6f7cd96a7f | |||
| e00d3ff7ff | |||
| 390b936153 | |||
| 863bea1ad1 | |||
| b7794a8783 | |||
| bb3c51c6cc | |||
| c3c82cebd5 | |||
| 5d51e9fa2c | |||
| 2c70bb6a00 | |||
| 98b2e8ab18 | |||
| 675ae9bade | |||
| 3e7e8ba6f2 | |||
| f1766bdbdc |
2
.github/workflows/cli.yml
vendored
2
.github/workflows/cli.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ github.ref }}
|
tag_name: ${{ github.ref }}
|
||||||
release_name: GoFrame CLI Release ${{ github.ref }}
|
release_name: GoFrame Release ${{ github.ref }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
|
|||||||
40
.github/workflows/gf.yml
vendored
40
.github/workflows/gf.yml
vendored
@ -8,6 +8,7 @@ on:
|
|||||||
- develop
|
- develop
|
||||||
- personal/**
|
- personal/**
|
||||||
- feature/**
|
- feature/**
|
||||||
|
- enhance/**
|
||||||
- fix/**
|
- fix/**
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
@ -16,8 +17,14 @@ on:
|
|||||||
- develop
|
- develop
|
||||||
- personal/**
|
- personal/**
|
||||||
- feature/**
|
- feature/**
|
||||||
|
- enhance/**
|
||||||
- fix/**
|
- fix/**
|
||||||
|
|
||||||
|
# This allows a subsequently queued workflow run to interrupt previous runs
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
TZ: "Asia/Shanghai"
|
TZ: "Asia/Shanghai"
|
||||||
|
|
||||||
@ -111,15 +118,13 @@ jobs:
|
|||||||
- 1521:1521
|
- 1521:1521
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go: [ "1.15", "1.16", "1.17" ]
|
go-version: [ "1.15", "1.16", "1.17", "1.18" ]
|
||||||
goarch: [ "386", "amd64" ]
|
goarch: [ "386", "amd64" ]
|
||||||
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set Up Timezone
|
- name: Setup Timezone
|
||||||
uses: szenius/set-timezone@v1.0
|
uses: szenius/set-timezone@v1.0
|
||||||
with:
|
with:
|
||||||
timezoneLinux: "Asia/Shanghai"
|
timezoneLinux: "Asia/Shanghai"
|
||||||
@ -127,13 +132,30 @@ jobs:
|
|||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set Up Go
|
- name: Setup Golang ${{ matrix.go-version }}
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
|
- name: Setup Golang caches
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
# In order:
|
||||||
|
# * Module download cache
|
||||||
|
# * Build cache (Linux)
|
||||||
|
# * Build cache (Mac)
|
||||||
|
# * Build cache (Windows)
|
||||||
|
path: |
|
||||||
|
~/go/pkg/mod
|
||||||
|
~/.cache/go-build
|
||||||
|
~/Library/Caches/go-build
|
||||||
|
~\AppData\Local\go-build
|
||||||
|
key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-${{ matrix.go-version }}-
|
||||||
|
|
||||||
- name: Start containers
|
- name: Start containers
|
||||||
run: docker-compose -f ".github/workflows/docker-compose.yml" up -d --build
|
run: docker-compose -f ".github/workflows/docker/docker-compose.yml" up -d --build
|
||||||
|
|
||||||
- name: Before Script
|
- name: Before Script
|
||||||
run: |
|
run: |
|
||||||
@ -165,10 +187,10 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Stop containers
|
- name: Stop containers
|
||||||
run: docker-compose -f ".github/workflows/docker-compose.yml" down
|
run: docker-compose -f ".github/workflows/docker/docker-compose.yml" down
|
||||||
|
|
||||||
- name: Report Coverage
|
- name: Report Coverage
|
||||||
uses: codecov/codecov-action@v2
|
uses: codecov/codecov-action@v2
|
||||||
with:
|
with:
|
||||||
flags: go-${{ matrix.go }}-${{ matrix.goarch }}
|
flags: go-${{ matrix.go-version }}-${{ matrix.goarch }}
|
||||||
|
|
||||||
|
|||||||
28
.github/workflows/issue-check-inactive.yml
vendored
Normal file
28
.github/workflows/issue-check-inactive.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# 规则描述:每天0点(GMT+8)执行一次,将最近7天没有活跃且非BUG的ISSUE设置标签:inactive
|
||||||
|
name: Issue Check Inactive
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 3 * * *"
|
||||||
|
|
||||||
|
env: # 设置环境变量
|
||||||
|
TZ: Asia/Shanghai #时区(设置时区可使页面中的`最近更新时间`使用时区时间)
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
issue-check-inactive:
|
||||||
|
permissions:
|
||||||
|
issues: write # for actions-cool/issues-helper to update issues
|
||||||
|
# pull-requests: write # for actions-cool/issues-helper to update PRs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: check-inactive
|
||||||
|
uses: actions-cool/issues-helper@v3
|
||||||
|
with:
|
||||||
|
actions: 'check-inactive'
|
||||||
|
inactive-label: 'inactive'
|
||||||
|
inactive-day: 7
|
||||||
|
issue-state: open
|
||||||
|
exclude-labels: 'bug'
|
||||||
22
.github/workflows/issue-close-inactive.yml
vendored
Normal file
22
.github/workflows/issue-close-inactive.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# 规则描述:每天0点(GMT+8)执行一次,将最近30天没有活跃且非BUG的ISSUE关闭
|
||||||
|
name: Issue Close Inactive
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 3 * * *"
|
||||||
|
|
||||||
|
env: # 设置环境变量
|
||||||
|
TZ: Asia/Shanghai #时区(设置时区可使页面中的`最近更新时间`使用时区时间)
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
close-issues:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: need close
|
||||||
|
uses: actions-cool/issues-helper@v3
|
||||||
|
with:
|
||||||
|
actions: "close-issues"
|
||||||
|
# token: ${{ secrets.GF_TOKEN }}
|
||||||
|
labels: 'inactive'
|
||||||
|
inactive-day: 30
|
||||||
|
exclude-labels: 'bug'
|
||||||
25
.github/workflows/issue-labeled.yml
vendored
Normal file
25
.github/workflows/issue-labeled.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
## 规则描述:当issue被标记为help wanted 时,增加评论
|
||||||
|
|
||||||
|
name: Issue Labeled
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [labeled]
|
||||||
|
|
||||||
|
env: # 设置环境变量
|
||||||
|
TZ: Asia/Shanghai # 时区(设置时区可使页面中的`最近更新时间`使用时区时间)
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
reply-labeled:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: contribution welcome
|
||||||
|
if: github.event.label.name == 'help wanted'
|
||||||
|
uses: actions-cool/issues-helper@v3
|
||||||
|
with:
|
||||||
|
actions: "create-comment, remove-labels"
|
||||||
|
# token: ${{ secrets.GF_TOKEN }}
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
body: |
|
||||||
|
Hello @${{ github.event.issue.user.login }}. We like your proposal/feedback and would appreciate a contribution via a Pull Request by you or another community member. We thank you in advance for your contribution and are looking forward to reviewing it!
|
||||||
|
你好 @${{ github.event.issue.user.login }}。我们喜欢您的提案/反馈,并希望您或其他社区成员通过拉取请求做出贡献。我们提前感谢您的贡献,并期待对其进行审查。
|
||||||
29
.github/workflows/issue-remove-inactive.yml
vendored
Normal file
29
.github/workflows/issue-remove-inactive.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# 规则描述:在issue没有活跃且尚未被关闭期间,若issue作者更新或评论该ISSUE,则移除其inactive标签
|
||||||
|
name: Issue Remove Inactive
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [edited]
|
||||||
|
issue_comment:
|
||||||
|
types: [created, edited]
|
||||||
|
|
||||||
|
env: # 设置环境变量
|
||||||
|
TZ: Asia/Shanghai #时区(设置时区可使页面中的`最近更新时间`使用时区时间)
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
issue-remove-inactive:
|
||||||
|
permissions:
|
||||||
|
issues: write # for actions-cool/issues-helper to update issues
|
||||||
|
# pull-requests: write # for actions-cool/issues-helper to update PRs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: remove inactive
|
||||||
|
if: github.event.issue.state == 'open' && github.actor == github.event.issue.user.login
|
||||||
|
uses: actions-cool/issues-helper@v3
|
||||||
|
with:
|
||||||
|
actions: 'remove-labels'
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
labels: 'inactive'
|
||||||
29
.github/workflows/issue-remove-need-more-details.yml
vendored
Normal file
29
.github/workflows/issue-remove-need-more-details.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# 规则描述:将需要提供更多细节且暂未关闭的issue,在issue作者评论后,移除 need more details 标签
|
||||||
|
name: Issue Remove Need More Details
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [edited]
|
||||||
|
issue_comment:
|
||||||
|
types: [created, edited]
|
||||||
|
|
||||||
|
env: # 设置环境变量
|
||||||
|
TZ: Asia/Shanghai #时区(设置时区可使页面中的`最近更新时间`使用时区时间)
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
issue-remove-need-more-details:
|
||||||
|
permissions:
|
||||||
|
issues: write # for actions-cool/issues-helper to update issues
|
||||||
|
# pull-requests: write # for actions-cool/issues-helper to update PRs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: remove need more details
|
||||||
|
if: github.event.issue.state == 'open' && github.actor == github.event.issue.user.login
|
||||||
|
uses: actions-cool/issues-helper@v3
|
||||||
|
with:
|
||||||
|
actions: 'remove-labels'
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
labels: 'need more details'
|
||||||
@ -331,6 +331,10 @@ func (c cGenService) generateServiceFiles(
|
|||||||
mlog.Printf(`not overwrite, ignore generating service go file: %s`, filePath)
|
mlog.Printf(`not overwrite, ignore generating service go file: %s`, filePath)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if !utils.IsFileDoNotEdit(filePath) {
|
||||||
|
mlog.Printf(`ignore file as it is manually maintained: %s`, filePath)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !c.isToGenerateServiceGoFile(filePath, funcArray) {
|
if !c.isToGenerateServiceGoFile(filePath, funcArray) {
|
||||||
mlog.Printf(`not dirty, ignore generating service go file: %s`, filePath)
|
mlog.Printf(`not dirty, ignore generating service go file: %s`, filePath)
|
||||||
continue
|
continue
|
||||||
@ -347,10 +351,6 @@ func (c cGenService) generateServiceFiles(
|
|||||||
|
|
||||||
// isToGenerateServiceGoFile checks and returns whether the service content dirty.
|
// isToGenerateServiceGoFile checks and returns whether the service content dirty.
|
||||||
func (c cGenService) isToGenerateServiceGoFile(filePath string, funcArray *garray.StrArray) bool {
|
func (c cGenService) isToGenerateServiceGoFile(filePath string, funcArray *garray.StrArray) bool {
|
||||||
if !utils.IsFileDoNotEdit(filePath) {
|
|
||||||
mlog.Debugf(`ignore file as it is manually maintained: %s`, filePath)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
var (
|
var (
|
||||||
fileContent = gfile.GetContents(filePath)
|
fileContent = gfile.GetContents(filePath)
|
||||||
generatedFuncArray = garray.NewSortedStrArrayFrom(funcArray.Slice())
|
generatedFuncArray = garray.NewSortedStrArrayFrom(funcArray.Slice())
|
||||||
|
|||||||
@ -94,6 +94,7 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
|
|||||||
// Update the GoFrame version.
|
// Update the GoFrame version.
|
||||||
if in.Update {
|
if in.Update {
|
||||||
mlog.Print("update goframe...")
|
mlog.Print("update goframe...")
|
||||||
|
// go get -u github.com/gogf/gf/v2@latest
|
||||||
updateCommand := `go get -u github.com/gogf/gf/v2@latest`
|
updateCommand := `go get -u github.com/gogf/gf/v2@latest`
|
||||||
if in.Name != "." {
|
if in.Name != "." {
|
||||||
updateCommand = fmt.Sprintf(`cd %s && %s`, in.Name, updateCommand)
|
updateCommand = fmt.Sprintf(`cd %s && %s`, in.Name, updateCommand)
|
||||||
@ -101,6 +102,14 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
|
|||||||
if err = gproc.ShellRun(ctx, updateCommand); err != nil {
|
if err = gproc.ShellRun(ctx, updateCommand); err != nil {
|
||||||
mlog.Fatal(err)
|
mlog.Fatal(err)
|
||||||
}
|
}
|
||||||
|
// go mod tidy
|
||||||
|
gomModTidyCommand := `go mod tidy`
|
||||||
|
if in.Name != "." {
|
||||||
|
gomModTidyCommand = fmt.Sprintf(`cd %s && %s`, in.Name, gomModTidyCommand)
|
||||||
|
}
|
||||||
|
if err = gproc.ShellRun(ctx, gomModTidyCommand); err != nil {
|
||||||
|
mlog.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mlog.Print("initialization done! ")
|
mlog.Print("initialization done! ")
|
||||||
|
|||||||
@ -78,6 +78,10 @@ generated json tag case for model struct, cases are as follows:
|
|||||||
| Kebab | any-kind-of-string |
|
| Kebab | any-kind-of-string |
|
||||||
| KebabScreaming | ANY-KIND-OF-STRING |
|
| KebabScreaming | ANY-KIND-OF-STRING |
|
||||||
`
|
`
|
||||||
|
CGenDaoBriefTplDaoIndexPath = `template file path for dao index file`
|
||||||
|
CGenDaoBriefTplDaoInternalPath = `template file path for dao internal file`
|
||||||
|
CGenDaoBriefTplDaoDoPathPath = `template file path for dao do file`
|
||||||
|
CGenDaoBriefTplDaoEntityPath = `template file path for dao entity file`
|
||||||
|
|
||||||
tplVarTableName = `{TplTableName}`
|
tplVarTableName = `{TplTableName}`
|
||||||
tplVarTableNameCamelCase = `{TplTableNameCamelCase}`
|
tplVarTableNameCamelCase = `{TplTableNameCamelCase}`
|
||||||
@ -89,6 +93,7 @@ generated json tag case for model struct, cases are as follows:
|
|||||||
tplVarColumnNames = `{TplColumnNames}`
|
tplVarColumnNames = `{TplColumnNames}`
|
||||||
tplVarGroupName = `{TplGroupName}`
|
tplVarGroupName = `{TplGroupName}`
|
||||||
tplVarDatetimeStr = `{TplDatetimeStr}`
|
tplVarDatetimeStr = `{TplDatetimeStr}`
|
||||||
|
tplVarCreatedAtDatetimeStr = `{TplCreatedAtDatetimeStr}`
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -97,58 +102,66 @@ var (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
gtag.Sets(g.MapStrStr{
|
gtag.Sets(g.MapStrStr{
|
||||||
`CGenDaoConfig`: CGenDaoConfig,
|
`CGenDaoConfig`: CGenDaoConfig,
|
||||||
`CGenDaoUsage`: CGenDaoUsage,
|
`CGenDaoUsage`: CGenDaoUsage,
|
||||||
`CGenDaoBrief`: CGenDaoBrief,
|
`CGenDaoBrief`: CGenDaoBrief,
|
||||||
`CGenDaoEg`: CGenDaoEg,
|
`CGenDaoEg`: CGenDaoEg,
|
||||||
`CGenDaoAd`: CGenDaoAd,
|
`CGenDaoAd`: CGenDaoAd,
|
||||||
`CGenDaoBriefPath`: CGenDaoBriefPath,
|
`CGenDaoBriefPath`: CGenDaoBriefPath,
|
||||||
`CGenDaoBriefLink`: CGenDaoBriefLink,
|
`CGenDaoBriefLink`: CGenDaoBriefLink,
|
||||||
`CGenDaoBriefTables`: CGenDaoBriefTables,
|
`CGenDaoBriefTables`: CGenDaoBriefTables,
|
||||||
`CGenDaoBriefTablesEx`: CGenDaoBriefTablesEx,
|
`CGenDaoBriefTablesEx`: CGenDaoBriefTablesEx,
|
||||||
`CGenDaoBriefPrefix`: CGenDaoBriefPrefix,
|
`CGenDaoBriefPrefix`: CGenDaoBriefPrefix,
|
||||||
`CGenDaoBriefRemovePrefix`: CGenDaoBriefRemovePrefix,
|
`CGenDaoBriefRemovePrefix`: CGenDaoBriefRemovePrefix,
|
||||||
`CGenDaoBriefStdTime`: CGenDaoBriefStdTime,
|
`CGenDaoBriefStdTime`: CGenDaoBriefStdTime,
|
||||||
`CGenDaoBriefWithTime`: CGenDaoBriefWithTime,
|
`CGenDaoBriefWithTime`: CGenDaoBriefWithTime,
|
||||||
`CGenDaoBriefDaoPath`: CGenDaoBriefDaoPath,
|
`CGenDaoBriefDaoPath`: CGenDaoBriefDaoPath,
|
||||||
`CGenDaoBriefDoPath`: CGenDaoBriefDoPath,
|
`CGenDaoBriefDoPath`: CGenDaoBriefDoPath,
|
||||||
`CGenDaoBriefEntityPath`: CGenDaoBriefEntityPath,
|
`CGenDaoBriefEntityPath`: CGenDaoBriefEntityPath,
|
||||||
`CGenDaoBriefGJsonSupport`: CGenDaoBriefGJsonSupport,
|
`CGenDaoBriefGJsonSupport`: CGenDaoBriefGJsonSupport,
|
||||||
`CGenDaoBriefImportPrefix`: CGenDaoBriefImportPrefix,
|
`CGenDaoBriefImportPrefix`: CGenDaoBriefImportPrefix,
|
||||||
`CGenDaoBriefOverwriteDao`: CGenDaoBriefOverwriteDao,
|
`CGenDaoBriefOverwriteDao`: CGenDaoBriefOverwriteDao,
|
||||||
`CGenDaoBriefModelFile`: CGenDaoBriefModelFile,
|
`CGenDaoBriefModelFile`: CGenDaoBriefModelFile,
|
||||||
`CGenDaoBriefModelFileForDao`: CGenDaoBriefModelFileForDao,
|
`CGenDaoBriefModelFileForDao`: CGenDaoBriefModelFileForDao,
|
||||||
`CGenDaoBriefDescriptionTag`: CGenDaoBriefDescriptionTag,
|
`CGenDaoBriefDescriptionTag`: CGenDaoBriefDescriptionTag,
|
||||||
`CGenDaoBriefNoJsonTag`: CGenDaoBriefNoJsonTag,
|
`CGenDaoBriefNoJsonTag`: CGenDaoBriefNoJsonTag,
|
||||||
`CGenDaoBriefNoModelComment`: CGenDaoBriefNoModelComment,
|
`CGenDaoBriefNoModelComment`: CGenDaoBriefNoModelComment,
|
||||||
`CGenDaoBriefGroup`: CGenDaoBriefGroup,
|
`CGenDaoBriefGroup`: CGenDaoBriefGroup,
|
||||||
`CGenDaoBriefJsonCase`: CGenDaoBriefJsonCase,
|
`CGenDaoBriefJsonCase`: CGenDaoBriefJsonCase,
|
||||||
|
`CGenDaoBriefTplDaoIndexPath`: CGenDaoBriefTplDaoIndexPath,
|
||||||
|
`CGenDaoBriefTplDaoInternalPath`: CGenDaoBriefTplDaoInternalPath,
|
||||||
|
`CGenDaoBriefTplDaoDoPathPath`: CGenDaoBriefTplDaoDoPathPath,
|
||||||
|
`CGenDaoBriefTplDaoEntityPath`: CGenDaoBriefTplDaoEntityPath,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
CGenDao struct{}
|
CGenDao struct{}
|
||||||
CGenDaoInput struct {
|
CGenDaoInput struct {
|
||||||
g.Meta `name:"dao" config:"{CGenDaoConfig}" usage:"{CGenDaoUsage}" brief:"{CGenDaoBrief}" eg:"{CGenDaoEg}" ad:"{CGenDaoAd}"`
|
g.Meta `name:"dao" config:"{CGenDaoConfig}" usage:"{CGenDaoUsage}" brief:"{CGenDaoBrief}" eg:"{CGenDaoEg}" ad:"{CGenDaoAd}"`
|
||||||
Path string `name:"path" short:"p" brief:"{CGenDaoBriefPath}" d:"internal"`
|
Path string `name:"path" short:"p" brief:"{CGenDaoBriefPath}" d:"internal"`
|
||||||
Link string `name:"link" short:"l" brief:"{CGenDaoBriefLink}"`
|
Link string `name:"link" short:"l" brief:"{CGenDaoBriefLink}"`
|
||||||
Tables string `name:"tables" short:"t" brief:"{CGenDaoBriefTables}"`
|
Tables string `name:"tables" short:"t" brief:"{CGenDaoBriefTables}"`
|
||||||
TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"`
|
TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"`
|
||||||
Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"`
|
Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"`
|
||||||
Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"`
|
Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"`
|
||||||
RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"`
|
RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"`
|
||||||
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"`
|
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"`
|
||||||
ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"`
|
ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"`
|
||||||
DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"`
|
DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"`
|
||||||
DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"`
|
DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"`
|
||||||
EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"`
|
EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"`
|
||||||
StdTime bool `name:"stdTime" short:"s" brief:"{CGenDaoBriefStdTime}" orphan:"true"`
|
TplDaoIndexPath string `name:"tplDaoIndexPath" short:"t1" brief:"{CGenDaoBriefTplDaoIndexPath}"`
|
||||||
WithTime bool `name:"withTime" short:"w" brief:"{CGenDaoBriefWithTime}" orphan:"true"`
|
TplDaoInternalPath string `name:"tplDaoInternalPath" short:"t2" brief:"{CGenDaoBriefTplDaoInternalPath}"`
|
||||||
GJsonSupport bool `name:"gJsonSupport" short:"n" brief:"{CGenDaoBriefGJsonSupport}" orphan:"true"`
|
TplDaoDoPath string `name:"tplDaoDoPath" short:"t3" brief:"{CGenDaoBriefTplDaoDoPathPath}"`
|
||||||
OverwriteDao bool `name:"overwriteDao" short:"v" brief:"{CGenDaoBriefOverwriteDao}" orphan:"true"`
|
TplDaoEntitylPath string `name:"tplDaoEntityPath" short:"t4" brief:"{CGenDaoBriefTplDaoEntityPath}"`
|
||||||
DescriptionTag bool `name:"descriptionTag" short:"c" brief:"{CGenDaoBriefDescriptionTag}" orphan:"true"`
|
StdTime bool `name:"stdTime" short:"s" brief:"{CGenDaoBriefStdTime}" orphan:"true"`
|
||||||
NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag" orphan:"true"`
|
WithTime bool `name:"withTime" short:"w" brief:"{CGenDaoBriefWithTime}" orphan:"true"`
|
||||||
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
|
GJsonSupport bool `name:"gJsonSupport" short:"n" brief:"{CGenDaoBriefGJsonSupport}" orphan:"true"`
|
||||||
|
OverwriteDao bool `name:"overwriteDao" short:"v" brief:"{CGenDaoBriefOverwriteDao}" orphan:"true"`
|
||||||
|
DescriptionTag bool `name:"descriptionTag" short:"c" brief:"{CGenDaoBriefDescriptionTag}" orphan:"true"`
|
||||||
|
NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"`
|
||||||
|
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
|
||||||
}
|
}
|
||||||
CGenDaoOutput struct{}
|
CGenDaoOutput struct{}
|
||||||
|
|
||||||
@ -305,12 +318,14 @@ func getImportPartContent(source string, isDo bool) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func replaceDefaultVar(in CGenDaoInternalInput, origin string) string {
|
func replaceDefaultVar(in CGenDaoInternalInput, origin string) string {
|
||||||
var tplDatetimeStr string
|
var tplCreatedAtDatetimeStr string
|
||||||
|
var tplDatetimeStr string = createdAt.String()
|
||||||
if in.WithTime {
|
if in.WithTime {
|
||||||
tplDatetimeStr = fmt.Sprintf(`Created at %s`, createdAt.String())
|
tplCreatedAtDatetimeStr = fmt.Sprintf(`Created at %s`, tplDatetimeStr)
|
||||||
}
|
}
|
||||||
return gstr.ReplaceByMap(origin, g.MapStrStr{
|
return gstr.ReplaceByMap(origin, g.MapStrStr{
|
||||||
tplVarDatetimeStr: tplDatetimeStr,
|
tplVarDatetimeStr: tplDatetimeStr,
|
||||||
|
tplVarCreatedAtDatetimeStr: tplCreatedAtDatetimeStr,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,3 +352,12 @@ func sortFieldKeyForDao(fieldMap map[string]*gdb.TableField) []string {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTemplateFromPathOrDefault(filePath string, def string) string {
|
||||||
|
if filePath != "" {
|
||||||
|
if contents := gfile.GetContents(filePath); contents != "" {
|
||||||
|
return contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|||||||
@ -64,12 +64,14 @@ func generateDao(ctx context.Context, db gdb.DB, in CGenDaoInternalInput) {
|
|||||||
func generateDaoIndex(in CGenDaoInternalInput, tableNameCamelCase, tableNameCamelLowerCase, importPrefix, dirPathDao, fileName string) {
|
func generateDaoIndex(in CGenDaoInternalInput, tableNameCamelCase, tableNameCamelLowerCase, importPrefix, dirPathDao, fileName string) {
|
||||||
path := gfile.Join(dirPathDao, fileName+".go")
|
path := gfile.Join(dirPathDao, fileName+".go")
|
||||||
if in.OverwriteDao || !gfile.Exists(path) {
|
if in.OverwriteDao || !gfile.Exists(path) {
|
||||||
indexContent := gstr.ReplaceByMap(getTplDaoIndexContent(""), g.MapStrStr{
|
indexContent := gstr.ReplaceByMap(
|
||||||
tplVarImportPrefix: importPrefix,
|
getTemplateFromPathOrDefault(in.TplDaoIndexPath, consts.TemplateGenDaoIndexContent),
|
||||||
tplVarTableName: in.TableName,
|
g.MapStrStr{
|
||||||
tplVarTableNameCamelCase: tableNameCamelCase,
|
tplVarImportPrefix: importPrefix,
|
||||||
tplVarTableNameCamelLowerCase: tableNameCamelLowerCase,
|
tplVarTableName: in.TableName,
|
||||||
})
|
tplVarTableNameCamelCase: tableNameCamelCase,
|
||||||
|
tplVarTableNameCamelLowerCase: tableNameCamelLowerCase,
|
||||||
|
})
|
||||||
indexContent = replaceDefaultVar(in, indexContent)
|
indexContent = replaceDefaultVar(in, indexContent)
|
||||||
if err := gfile.PutContents(path, strings.TrimSpace(indexContent)); err != nil {
|
if err := gfile.PutContents(path, strings.TrimSpace(indexContent)); err != nil {
|
||||||
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
|
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
|
||||||
@ -87,15 +89,17 @@ func generateDaoInternal(
|
|||||||
fieldMap map[string]*gdb.TableField,
|
fieldMap map[string]*gdb.TableField,
|
||||||
) {
|
) {
|
||||||
path := gfile.Join(dirPathDao, "internal", fileName+".go")
|
path := gfile.Join(dirPathDao, "internal", fileName+".go")
|
||||||
modelContent := gstr.ReplaceByMap(getTplDaoInternalContent(""), g.MapStrStr{
|
modelContent := gstr.ReplaceByMap(
|
||||||
tplVarImportPrefix: importPrefix,
|
getTemplateFromPathOrDefault(in.TplDaoInternalPath, consts.TemplateGenDaoInternalContent),
|
||||||
tplVarTableName: in.TableName,
|
g.MapStrStr{
|
||||||
tplVarGroupName: in.Group,
|
tplVarImportPrefix: importPrefix,
|
||||||
tplVarTableNameCamelCase: tableNameCamelCase,
|
tplVarTableName: in.TableName,
|
||||||
tplVarTableNameCamelLowerCase: tableNameCamelLowerCase,
|
tplVarGroupName: in.Group,
|
||||||
tplVarColumnDefine: gstr.Trim(generateColumnDefinitionForDao(fieldMap)),
|
tplVarTableNameCamelCase: tableNameCamelCase,
|
||||||
tplVarColumnNames: gstr.Trim(generateColumnNamesForDao(fieldMap)),
|
tplVarTableNameCamelLowerCase: tableNameCamelLowerCase,
|
||||||
})
|
tplVarColumnDefine: gstr.Trim(generateColumnDefinitionForDao(fieldMap)),
|
||||||
|
tplVarColumnNames: gstr.Trim(generateColumnNamesForDao(fieldMap)),
|
||||||
|
})
|
||||||
modelContent = replaceDefaultVar(in, modelContent)
|
modelContent = replaceDefaultVar(in, modelContent)
|
||||||
if err := gfile.PutContents(path, strings.TrimSpace(modelContent)); err != nil {
|
if err := gfile.PutContents(path, strings.TrimSpace(modelContent)); err != nil {
|
||||||
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
|
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
|
||||||
@ -105,20 +109,6 @@ func generateDaoInternal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTplDaoIndexContent(tplDaoIndexPath string) string {
|
|
||||||
if tplDaoIndexPath != "" {
|
|
||||||
return gfile.GetContents(tplDaoIndexPath)
|
|
||||||
}
|
|
||||||
return consts.TemplateDaoDaoIndexContent
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTplDaoInternalContent(tplDaoInternalPath string) string {
|
|
||||||
if tplDaoInternalPath != "" {
|
|
||||||
return gfile.GetContents(tplDaoInternalPath)
|
|
||||||
}
|
|
||||||
return consts.TemplateDaoDaoInternalContent
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateColumnNamesForDao generates and returns the column names assignment content of column struct
|
// generateColumnNamesForDao generates and returns the column names assignment content of column struct
|
||||||
// for specified table.
|
// for specified table.
|
||||||
func generateColumnNamesForDao(fieldMap map[string]*gdb.TableField) string {
|
func generateColumnNamesForDao(fieldMap map[string]*gdb.TableField) string {
|
||||||
|
|||||||
@ -32,8 +32,9 @@ func generateDo(ctx context.Context, db gdb.DB, tableNames, newTableNames []stri
|
|||||||
var (
|
var (
|
||||||
newTableName = newTableNames[i]
|
newTableName = newTableNames[i]
|
||||||
doFilePath = gfile.Join(doDirPath, gstr.CaseSnake(newTableName)+".go")
|
doFilePath = gfile.Join(doDirPath, gstr.CaseSnake(newTableName)+".go")
|
||||||
structDefinition = generateStructDefinition(generateStructDefinitionInput{
|
structDefinition = generateStructDefinition(ctx, generateStructDefinitionInput{
|
||||||
CGenDaoInternalInput: in,
|
CGenDaoInternalInput: in,
|
||||||
|
DB: db,
|
||||||
StructName: gstr.CaseCamel(newTableName),
|
StructName: gstr.CaseCamel(newTableName),
|
||||||
FieldMap: fieldMap,
|
FieldMap: fieldMap,
|
||||||
IsDo: true,
|
IsDo: true,
|
||||||
@ -68,12 +69,14 @@ func generateDo(ctx context.Context, db gdb.DB, tableNames, newTableNames []stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generateDoContent(in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string) string {
|
func generateDoContent(in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string) string {
|
||||||
doContent := gstr.ReplaceByMap(consts.TemplateGenDaoDoContent, g.MapStrStr{
|
doContent := gstr.ReplaceByMap(
|
||||||
tplVarTableName: tableName,
|
getTemplateFromPathOrDefault(in.TplDaoDoPath, consts.TemplateGenDaoDoContent),
|
||||||
tplVarPackageImports: getImportPartContent(structDefine, true),
|
g.MapStrStr{
|
||||||
tplVarTableNameCamelCase: tableNameCamelCase,
|
tplVarTableName: tableName,
|
||||||
tplVarStructDefine: structDefine,
|
tplVarPackageImports: getImportPartContent(structDefine, true),
|
||||||
})
|
tplVarTableNameCamelCase: tableNameCamelCase,
|
||||||
|
tplVarStructDefine: structDefine,
|
||||||
|
})
|
||||||
doContent = replaceDefaultVar(in, doContent)
|
doContent = replaceDefaultVar(in, doContent)
|
||||||
return doContent
|
return doContent
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,8 +28,9 @@ func generateEntity(ctx context.Context, db gdb.DB, tableNames, newTableNames []
|
|||||||
in,
|
in,
|
||||||
newTableName,
|
newTableName,
|
||||||
gstr.CaseCamel(newTableName),
|
gstr.CaseCamel(newTableName),
|
||||||
generateStructDefinition(generateStructDefinitionInput{
|
generateStructDefinition(ctx, generateStructDefinitionInput{
|
||||||
CGenDaoInternalInput: in,
|
CGenDaoInternalInput: in,
|
||||||
|
DB: db,
|
||||||
StructName: gstr.CaseCamel(newTableName),
|
StructName: gstr.CaseCamel(newTableName),
|
||||||
FieldMap: fieldMap,
|
FieldMap: fieldMap,
|
||||||
IsDo: false,
|
IsDo: false,
|
||||||
@ -47,12 +48,14 @@ func generateEntity(ctx context.Context, db gdb.DB, tableNames, newTableNames []
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generateEntityContent(in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string) string {
|
func generateEntityContent(in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string) string {
|
||||||
entityContent := gstr.ReplaceByMap(consts.TemplateGenDaoEntityContent, g.MapStrStr{
|
entityContent := gstr.ReplaceByMap(
|
||||||
tplVarTableName: tableName,
|
getTemplateFromPathOrDefault(in.TplDaoEntitylPath, consts.TemplateGenDaoEntityContent),
|
||||||
tplVarPackageImports: getImportPartContent(structDefine, false),
|
g.MapStrStr{
|
||||||
tplVarTableNameCamelCase: tableNameCamelCase,
|
tplVarTableName: tableName,
|
||||||
tplVarStructDefine: structDefine,
|
tplVarPackageImports: getImportPartContent(structDefine, false),
|
||||||
})
|
tplVarTableNameCamelCase: tableNameCamelCase,
|
||||||
|
tplVarStructDefine: structDefine,
|
||||||
|
})
|
||||||
entityContent = replaceDefaultVar(in, entityContent)
|
entityContent = replaceDefaultVar(in, entityContent)
|
||||||
return entityContent
|
return entityContent
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@ package gendao
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/database/gdb"
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
@ -14,18 +14,19 @@ import (
|
|||||||
|
|
||||||
type generateStructDefinitionInput struct {
|
type generateStructDefinitionInput struct {
|
||||||
CGenDaoInternalInput
|
CGenDaoInternalInput
|
||||||
|
DB gdb.DB // Current DB.
|
||||||
StructName string // Struct name.
|
StructName string // Struct name.
|
||||||
FieldMap map[string]*gdb.TableField // Table field map.
|
FieldMap map[string]*gdb.TableField // Table field map.
|
||||||
IsDo bool // Is generating DTO struct.
|
IsDo bool // Is generating DTO struct.
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateStructDefinition(in generateStructDefinitionInput) string {
|
func generateStructDefinition(ctx context.Context, in generateStructDefinitionInput) string {
|
||||||
buffer := bytes.NewBuffer(nil)
|
buffer := bytes.NewBuffer(nil)
|
||||||
array := make([][]string, len(in.FieldMap))
|
array := make([][]string, len(in.FieldMap))
|
||||||
names := sortFieldKeyForDao(in.FieldMap)
|
names := sortFieldKeyForDao(in.FieldMap)
|
||||||
for index, name := range names {
|
for index, name := range names {
|
||||||
field := in.FieldMap[name]
|
field := in.FieldMap[name]
|
||||||
array[index] = generateStructFieldDefinition(field, in)
|
array[index] = generateStructFieldDefinition(ctx, field, in)
|
||||||
}
|
}
|
||||||
tw := tablewriter.NewWriter(buffer)
|
tw := tablewriter.NewWriter(buffer)
|
||||||
tw.SetBorder(false)
|
tw.SetBorder(false)
|
||||||
@ -50,92 +51,39 @@ func generateStructDefinition(in generateStructDefinitionInput) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generateStructFieldForModel generates and returns the attribute definition for specified field.
|
// generateStructFieldForModel generates and returns the attribute definition for specified field.
|
||||||
func generateStructFieldDefinition(field *gdb.TableField, in generateStructDefinitionInput) []string {
|
func generateStructFieldDefinition(
|
||||||
|
ctx context.Context, field *gdb.TableField, in generateStructDefinitionInput,
|
||||||
|
) []string {
|
||||||
var (
|
var (
|
||||||
|
err error
|
||||||
typeName string
|
typeName string
|
||||||
jsonTag = getJsonTagFromCase(field.Name, in.JsonCase)
|
jsonTag = getJsonTagFromCase(field.Name, in.JsonCase)
|
||||||
)
|
)
|
||||||
t, _ := gregex.ReplaceString(`\(.+\)`, "", field.Type)
|
typeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
|
||||||
t = gstr.Split(gstr.Trim(t), " ")[0]
|
if err != nil {
|
||||||
t = gstr.ToLower(t)
|
panic(err)
|
||||||
|
}
|
||||||
switch t {
|
switch typeName {
|
||||||
case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob":
|
case gdb.LocalTypeDate, gdb.LocalTypeDatetime:
|
||||||
typeName = "[]byte"
|
|
||||||
|
|
||||||
case "bit", "int", "int2", "tinyint", "small_int", "smallint", "medium_int", "mediumint", "serial":
|
|
||||||
if gstr.ContainsI(field.Type, "unsigned") {
|
|
||||||
typeName = "uint"
|
|
||||||
} else {
|
|
||||||
typeName = "int"
|
|
||||||
}
|
|
||||||
|
|
||||||
case "int4", "int8", "big_int", "bigint", "bigserial":
|
|
||||||
if gstr.ContainsI(field.Type, "unsigned") {
|
|
||||||
typeName = "uint64"
|
|
||||||
} else {
|
|
||||||
typeName = "int64"
|
|
||||||
}
|
|
||||||
|
|
||||||
// pgsql int32 slice.
|
|
||||||
case "_int2":
|
|
||||||
if gstr.ContainsI(field.Type, "unsigned") {
|
|
||||||
typeName = "[]uint"
|
|
||||||
} else {
|
|
||||||
typeName = "[]int"
|
|
||||||
}
|
|
||||||
|
|
||||||
// pgsql int64 slice.
|
|
||||||
case "_int4", "_int8":
|
|
||||||
if gstr.ContainsI(field.Type, "unsigned") {
|
|
||||||
typeName = "[]uint64"
|
|
||||||
} else {
|
|
||||||
typeName = "[]int64"
|
|
||||||
}
|
|
||||||
|
|
||||||
case "real":
|
|
||||||
typeName = "float32"
|
|
||||||
|
|
||||||
case "float", "double", "decimal", "smallmoney", "numeric":
|
|
||||||
typeName = "float64"
|
|
||||||
|
|
||||||
case "bool":
|
|
||||||
typeName = "bool"
|
|
||||||
|
|
||||||
case "datetime", "timestamp", "date", "time":
|
|
||||||
if in.StdTime {
|
if in.StdTime {
|
||||||
typeName = "time.Time"
|
typeName = "time.Time"
|
||||||
} else {
|
} else {
|
||||||
typeName = "*gtime.Time"
|
typeName = "*gtime.Time"
|
||||||
}
|
}
|
||||||
case "json", "jsonb":
|
|
||||||
|
case gdb.LocalTypeInt64Bytes:
|
||||||
|
typeName = "int64"
|
||||||
|
|
||||||
|
case gdb.LocalTypeUint64Bytes:
|
||||||
|
typeName = "uint64"
|
||||||
|
|
||||||
|
// Special type handle.
|
||||||
|
case gdb.LocalTypeJson, gdb.LocalTypeJsonb:
|
||||||
if in.GJsonSupport {
|
if in.GJsonSupport {
|
||||||
typeName = "*gjson.Json"
|
typeName = "*gjson.Json"
|
||||||
} else {
|
} else {
|
||||||
typeName = "string"
|
typeName = "string"
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
// Automatically detect its data type.
|
|
||||||
switch {
|
|
||||||
case strings.Contains(t, "int"):
|
|
||||||
typeName = "int"
|
|
||||||
case strings.Contains(t, "text") || strings.Contains(t, "char"):
|
|
||||||
typeName = "string"
|
|
||||||
case strings.Contains(t, "float") || strings.Contains(t, "double"):
|
|
||||||
typeName = "float64"
|
|
||||||
case strings.Contains(t, "bool"):
|
|
||||||
typeName = "bool"
|
|
||||||
case strings.Contains(t, "binary") || strings.Contains(t, "blob"):
|
|
||||||
typeName = "[]byte"
|
|
||||||
case strings.Contains(t, "date") || strings.Contains(t, "time"):
|
|
||||||
if in.StdTime {
|
|
||||||
typeName = "time.Time"
|
|
||||||
} else {
|
|
||||||
typeName = "*gtime.Time"
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
typeName = "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package consts
|
package consts
|
||||||
|
|
||||||
const TemplateDaoDaoIndexContent = `
|
const TemplateGenDaoIndexContent = `
|
||||||
// =================================================================================
|
// =================================================================================
|
||||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||||
// =================================================================================
|
// =================================================================================
|
||||||
@ -31,9 +31,9 @@ var (
|
|||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const TemplateDaoDaoInternalContent = `
|
const TemplateGenDaoInternalContent = `
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Code generated by GoFrame CLI tool. DO NOT EDIT. {TplDatetimeStr}
|
// Code generated by GoFrame CLI tool. DO NOT EDIT. {TplCreatedAtDatetimeStr}
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
package internal
|
package internal
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package consts
|
|||||||
|
|
||||||
const TemplateGenDaoDoContent = `
|
const TemplateGenDaoDoContent = `
|
||||||
// =================================================================================
|
// =================================================================================
|
||||||
// Code generated by GoFrame CLI tool. DO NOT EDIT. {TplDatetimeStr}
|
// Code generated by GoFrame CLI tool. DO NOT EDIT. {TplCreatedAtDatetimeStr}
|
||||||
// =================================================================================
|
// =================================================================================
|
||||||
|
|
||||||
package do
|
package do
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package consts
|
|||||||
|
|
||||||
const TemplateGenDaoEntityContent = `
|
const TemplateGenDaoEntityContent = `
|
||||||
// =================================================================================
|
// =================================================================================
|
||||||
// Code generated by GoFrame CLI tool. DO NOT EDIT. {TplDatetimeStr}
|
// Code generated by GoFrame CLI tool. DO NOT EDIT. {TplCreatedAtDatetimeStr}
|
||||||
// =================================================================================
|
// =================================================================================
|
||||||
|
|
||||||
package entity
|
package entity
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
package consts
|
package consts
|
||||||
|
|
||||||
const TemplateGenServiceContent = `
|
const TemplateGenServiceContent = `
|
||||||
// ==========================================================================
|
// ================================================================================
|
||||||
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
||||||
// ==========================================================================
|
// You can delete these comments if you wish manually maintain this interface file.
|
||||||
|
// ================================================================================
|
||||||
|
|
||||||
package {PackageName}
|
package {PackageName}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,10 @@ import (
|
|||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ClickHouse/clickhouse-go/v2"
|
"github.com/ClickHouse/clickhouse-go/v2"
|
||||||
"github.com/gogf/gf/v2/container/gmap"
|
"github.com/gogf/gf/v2/container/gmap"
|
||||||
"github.com/gogf/gf/v2/database/gdb"
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
@ -24,9 +28,6 @@ import (
|
|||||||
"github.com/gogf/gf/v2/text/gstr"
|
"github.com/gogf/gf/v2/text/gstr"
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Driver is the driver for postgresql database.
|
// Driver is the driver for postgresql database.
|
||||||
@ -148,7 +149,13 @@ func (d *Driver) TableFields(
|
|||||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
if link, err = d.SlaveLink(useSchema); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
getColumnsSql := fmt.Sprintf("select name,position,default_expression,comment,type,is_in_partition_key,is_in_sorting_key,is_in_primary_key,is_in_sampling_key from `system`.columns c where database = '%s' and `table` = '%s'", d.GetConfig().Name, table)
|
var (
|
||||||
|
columns = "name,position,default_expression,comment,type,is_in_partition_key,is_in_sorting_key,is_in_primary_key,is_in_sampling_key"
|
||||||
|
getColumnsSql = fmt.Sprintf(
|
||||||
|
"select %s from `system`.columns c where `table` = '%s'",
|
||||||
|
columns, table,
|
||||||
|
)
|
||||||
|
)
|
||||||
result, err = d.DoSelect(ctx, link, getColumnsSql)
|
result, err = d.DoSelect(ctx, link, getColumnsSql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -127,6 +127,16 @@ func clickhouseConfigDB() gdb.DB {
|
|||||||
return connect
|
return connect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clickhouseLink() gdb.DB {
|
||||||
|
connect, err := gdb.New(gdb.ConfigNode{
|
||||||
|
Link: "clickhouse://default@127.0.0.1:9000,127.0.0.1:9000/default?dial_timeout=200ms&max_execution_time=60",
|
||||||
|
Type: "clickhouse",
|
||||||
|
})
|
||||||
|
gtest.AssertNil(err)
|
||||||
|
gtest.AssertNE(connect, nil)
|
||||||
|
return connect
|
||||||
|
}
|
||||||
|
|
||||||
func createClickhouseTableVisits(connect gdb.DB) error {
|
func createClickhouseTableVisits(connect gdb.DB) error {
|
||||||
_, err := connect.Exec(context.Background(), sqlVisitsDDL)
|
_, err := connect.Exec(context.Background(), sqlVisitsDDL)
|
||||||
return err
|
return err
|
||||||
@ -204,7 +214,7 @@ func TestDriverClickhouse_TableFields_Use_Config(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDriverClickhouse_TableFields_Use_Link(t *testing.T) {
|
func TestDriverClickhouse_TableFields_Use_Link(t *testing.T) {
|
||||||
connect := clickhouseConfigDB()
|
connect := clickhouseLink()
|
||||||
gtest.AssertNil(createClickhouseTableVisits(connect))
|
gtest.AssertNil(createClickhouseTableVisits(connect))
|
||||||
defer dropClickhouseTableVisits(connect)
|
defer dropClickhouseTableVisits(connect)
|
||||||
field, err := connect.TableFields(context.Background(), "visits")
|
field, err := connect.TableFields(context.Background(), "visits")
|
||||||
|
|||||||
@ -1190,24 +1190,26 @@ func Test_DB_TableField(t *testing.T) {
|
|||||||
"field_varchar": "abc",
|
"field_varchar": "abc",
|
||||||
"field_varbinary": "aaa",
|
"field_varbinary": "aaa",
|
||||||
}
|
}
|
||||||
res, err := db.Model(name).Data(data).Insert()
|
gtest.C(t, func(t *gtest.T) {
|
||||||
if err != nil {
|
res, err := db.Model(name).Data(data).Insert()
|
||||||
gtest.Fatal(err)
|
if err != nil {
|
||||||
}
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
n, err := res.RowsAffected()
|
n, err := res.RowsAffected()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gtest.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
gtest.Assert(n, 1)
|
t.Assert(n, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := db.Model(name).Fields("*").Where("field_int = ?", 2).All()
|
result, err := db.Model(name).Fields("*").Where("field_int = ?", 2).All()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gtest.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
t.Assert(result[0], data)
|
||||||
|
})
|
||||||
|
|
||||||
gtest.Assert(result[0], data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_DB_Prefix(t *testing.T) {
|
func Test_DB_Prefix(t *testing.T) {
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
// Note:
|
// Note:
|
||||||
// 1. It needs manually import: _ "github.com/lib/pq"
|
// 1. It needs manually import: _ "github.com/lib/pq"
|
||||||
// 2. It does not support Save/Replace features.
|
// 2. It does not support Save/Replace features.
|
||||||
// 3. It does not support LastInsertId.
|
|
||||||
|
|
||||||
// Package pgsql implements gdb.Driver, which supports operations for PostgreSql.
|
// Package pgsql implements gdb.Driver, which supports operations for PostgreSql.
|
||||||
package pgsql
|
package pgsql
|
||||||
@ -18,15 +17,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
|
||||||
_ "github.com/lib/pq"
|
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/container/gmap"
|
"github.com/gogf/gf/v2/container/gmap"
|
||||||
"github.com/gogf/gf/v2/database/gdb"
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
"github.com/gogf/gf/v2/errors/gcode"
|
"github.com/gogf/gf/v2/errors/gcode"
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
|
"github.com/gogf/gf/v2/os/gctx"
|
||||||
"github.com/gogf/gf/v2/text/gregex"
|
"github.com/gogf/gf/v2/text/gregex"
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
"github.com/gogf/gf/v2/text/gstr"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Driver is the driver for postgresql database.
|
// Driver is the driver for postgresql database.
|
||||||
@ -39,6 +38,10 @@ var (
|
|||||||
tableFieldsMap = gmap.New(true)
|
tableFieldsMap = gmap.New(true)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
internalPrimaryKeyInCtx gctx.StrKey = "primary_key"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := gdb.Register(`pgsql`, New()); err != nil {
|
if err := gdb.Register(`pgsql`, New()); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -118,6 +121,43 @@ func (d *Driver) GetChars() (charLeft string, charRight string) {
|
|||||||
return `"`, `"`
|
return `"`, `"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckLocalTypeForValue checks and returns corresponding local golang type for given db type.
|
||||||
|
func (d *Driver) CheckLocalTypeForValue(ctx context.Context, fieldType string, fieldValue interface{}) (string, error) {
|
||||||
|
var typeName string
|
||||||
|
match, _ := gregex.MatchString(`(.+?)\((.+)\)`, fieldType)
|
||||||
|
if len(match) == 3 {
|
||||||
|
typeName = gstr.Trim(match[1])
|
||||||
|
} else {
|
||||||
|
typeName = fieldType
|
||||||
|
}
|
||||||
|
typeName = strings.ToLower(typeName)
|
||||||
|
switch typeName {
|
||||||
|
case
|
||||||
|
// For pgsql, int2 = smallint.
|
||||||
|
"int2",
|
||||||
|
// For pgsql, int4 = integer
|
||||||
|
"int4":
|
||||||
|
return gdb.LocalTypeInt, nil
|
||||||
|
|
||||||
|
case
|
||||||
|
// For pgsql, int8 = bigint
|
||||||
|
"int8":
|
||||||
|
return gdb.LocalTypeInt64, nil
|
||||||
|
|
||||||
|
case
|
||||||
|
"_int2",
|
||||||
|
"_int4":
|
||||||
|
return gdb.LocalTypeIntSlice, nil
|
||||||
|
|
||||||
|
case
|
||||||
|
"_int8":
|
||||||
|
return gdb.LocalTypeInt64Slice, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return d.Core.CheckLocalTypeForField(ctx, fieldType, fieldValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ConvertValueForLocal converts value to local Golang type of value according field type name from database.
|
// ConvertValueForLocal converts value to local Golang type of value according field type name from database.
|
||||||
// The parameter `fieldType` is in lower case, like:
|
// The parameter `fieldType` is in lower case, like:
|
||||||
// `float(5,2)`, `unsigned double(5,2)`, `decimal(10,2)`, `char(45)`, `varchar(100)`, etc.
|
// `float(5,2)`, `unsigned double(5,2)`, `decimal(10,2)`, `char(45)`, `varchar(100)`, etc.
|
||||||
@ -125,19 +165,17 @@ func (d *Driver) ConvertValueForLocal(ctx context.Context, fieldType string, fie
|
|||||||
typeName, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
|
typeName, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
|
||||||
typeName = strings.ToLower(typeName)
|
typeName = strings.ToLower(typeName)
|
||||||
switch typeName {
|
switch typeName {
|
||||||
|
// For pgsql, int2 = smallint and int4 = integer.
|
||||||
|
case "int2", "int4":
|
||||||
|
return gconv.Int(gconv.String(fieldValue)), nil
|
||||||
|
|
||||||
// For pgsql, int8 = bigint.
|
// For pgsql, int8 = bigint.
|
||||||
case "int8":
|
case "int8":
|
||||||
if gstr.ContainsI(fieldType, "unsigned") {
|
|
||||||
return gconv.Uint64(gconv.String(fieldValue)), nil
|
|
||||||
}
|
|
||||||
return gconv.Int64(gconv.String(fieldValue)), nil
|
return gconv.Int64(gconv.String(fieldValue)), nil
|
||||||
|
|
||||||
// Int32 slice.
|
// Int32 slice.
|
||||||
case
|
case
|
||||||
"_int2":
|
"_int2", "_int4":
|
||||||
if gstr.ContainsI(fieldType, "unsigned") {
|
|
||||||
gconv.Uints(gconv.String(fieldValue))
|
|
||||||
}
|
|
||||||
return gconv.Ints(
|
return gconv.Ints(
|
||||||
gstr.ReplaceByMap(gconv.String(fieldValue),
|
gstr.ReplaceByMap(gconv.String(fieldValue),
|
||||||
map[string]string{
|
map[string]string{
|
||||||
@ -149,10 +187,7 @@ func (d *Driver) ConvertValueForLocal(ctx context.Context, fieldType string, fie
|
|||||||
|
|
||||||
// Int64 slice.
|
// Int64 slice.
|
||||||
case
|
case
|
||||||
"_int4", "_int8":
|
"_int8":
|
||||||
if gstr.ContainsI(fieldType, "unsigned") {
|
|
||||||
gconv.Uint64(gconv.String(fieldValue))
|
|
||||||
}
|
|
||||||
return gconv.Int64s(
|
return gconv.Int64s(
|
||||||
gstr.ReplaceByMap(gconv.String(fieldValue),
|
gstr.ReplaceByMap(gconv.String(fieldValue),
|
||||||
map[string]string{
|
map[string]string{
|
||||||
@ -218,6 +253,7 @@ ORDER BY
|
|||||||
c.relname`,
|
c.relname`,
|
||||||
querySchema,
|
querySchema,
|
||||||
)
|
)
|
||||||
|
query, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(query))
|
||||||
result, err = d.DoSelect(ctx, link, query)
|
result, err = d.DoSelect(ctx, link, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -314,7 +350,111 @@ func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list
|
|||||||
`Replace operation is not supported by pgsql driver`,
|
`Replace operation is not supported by pgsql driver`,
|
||||||
)
|
)
|
||||||
|
|
||||||
default:
|
case gdb.InsertOptionIgnore:
|
||||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
return nil, gerror.NewCode(
|
||||||
|
gcode.CodeNotSupported,
|
||||||
|
`Insert ignore operation is not supported by pgsql driver`,
|
||||||
|
)
|
||||||
|
|
||||||
|
case gdb.InsertOptionDefault:
|
||||||
|
tableFields, err := d.TableFields(ctx, table)
|
||||||
|
if err == nil {
|
||||||
|
for _, field := range tableFields {
|
||||||
|
if field.Key == "pri" {
|
||||||
|
pkField := *field
|
||||||
|
ctx = context.WithValue(ctx, internalPrimaryKeyInCtx, pkField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) DoExec(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) {
|
||||||
|
var (
|
||||||
|
isUseCoreDoExec bool = false // Check whether the default method needs to be used
|
||||||
|
primaryKey string = ""
|
||||||
|
pkField gdb.TableField
|
||||||
|
)
|
||||||
|
|
||||||
|
// Transaction checks.
|
||||||
|
if link == nil {
|
||||||
|
if tx := gdb.TXFromCtx(ctx, d.GetGroup()); tx != nil {
|
||||||
|
isUseCoreDoExec = true
|
||||||
|
}
|
||||||
|
} else if link.IsTransaction() {
|
||||||
|
isUseCoreDoExec = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if value := ctx.Value(internalPrimaryKeyInCtx); value != nil {
|
||||||
|
var ok bool
|
||||||
|
pkField, ok = value.(gdb.TableField)
|
||||||
|
if !ok {
|
||||||
|
isUseCoreDoExec = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isUseCoreDoExec = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if it is a insert operation.
|
||||||
|
if !isUseCoreDoExec && pkField.Name != "" && strings.Contains(sql, "INSERT INTO") {
|
||||||
|
primaryKey = pkField.Name
|
||||||
|
sql += " RETURNING " + primaryKey
|
||||||
|
} else {
|
||||||
|
// use default DoExec
|
||||||
|
return d.Core.DoExec(ctx, link, sql, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the insert operation with primary key can execute the following code
|
||||||
|
|
||||||
|
if d.GetConfig().ExecTimeout > 0 {
|
||||||
|
var cancelFunc context.CancelFunc
|
||||||
|
ctx, cancelFunc = context.WithTimeout(ctx, d.GetConfig().ExecTimeout)
|
||||||
|
defer cancelFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sql filtering.
|
||||||
|
// TODO: internal function formatSql
|
||||||
|
// sql, args = formatSql(sql, args)
|
||||||
|
sql, args, err = d.DoFilter(ctx, link, sql, args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link execution.
|
||||||
|
var out gdb.DoCommitOutput
|
||||||
|
out, err = d.DoCommit(ctx, gdb.DoCommitInput{
|
||||||
|
Link: link,
|
||||||
|
Sql: sql,
|
||||||
|
Args: args,
|
||||||
|
Stmt: nil,
|
||||||
|
Type: gdb.SqlTypeQueryContext,
|
||||||
|
IsTransaction: link.IsTransaction(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
affected := len(out.Records)
|
||||||
|
if affected > 0 {
|
||||||
|
if !strings.Contains(pkField.Type, "int") {
|
||||||
|
return Result{
|
||||||
|
affected: int64(affected),
|
||||||
|
lastInsertId: 0,
|
||||||
|
lastInsertIdError: gerror.NewCodef(
|
||||||
|
gcode.CodeNotSupported,
|
||||||
|
"LastInsertId is not supported by primary key type: %s", pkField.Type),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.Records[affected-1][primaryKey] != nil {
|
||||||
|
lastInsertId := out.Records[affected-1][primaryKey].Int()
|
||||||
|
return Result{
|
||||||
|
affected: int64(affected),
|
||||||
|
lastInsertId: int64(lastInsertId),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
18
contrib/drivers/pgsql/pgsql_result.go
Normal file
18
contrib/drivers/pgsql/pgsql_result.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package pgsql
|
||||||
|
|
||||||
|
import "database/sql"
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
sql.Result
|
||||||
|
affected int64
|
||||||
|
lastInsertId int64
|
||||||
|
lastInsertIdError error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pgr Result) RowsAffected() (int64, error) {
|
||||||
|
return pgr.affected, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pgr Result) LastInsertId() (int64, error) {
|
||||||
|
return pgr.lastInsertId, pgr.lastInsertIdError
|
||||||
|
}
|
||||||
@ -15,6 +15,36 @@ import (
|
|||||||
"github.com/gogf/gf/v2/test/gtest"
|
"github.com/gogf/gf/v2/test/gtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Test_LastInsertId(t *testing.T) {
|
||||||
|
|
||||||
|
// err not nil
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
_, err := db.Model("notexist").Insert(g.List{
|
||||||
|
{"name": "user1"},
|
||||||
|
{"name": "user2"},
|
||||||
|
{"name": "user3"},
|
||||||
|
})
|
||||||
|
t.AssertNE(err, nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
tableName := createTable()
|
||||||
|
defer dropTable(tableName)
|
||||||
|
res, err := db.Model(tableName).Insert(g.List{
|
||||||
|
{"passport": "user1", "password": "pwd", "nickname": "nickname", "create_time": CreateTime},
|
||||||
|
{"passport": "user2", "password": "pwd", "nickname": "nickname", "create_time": CreateTime},
|
||||||
|
{"passport": "user3", "password": "pwd", "nickname": "nickname", "create_time": CreateTime},
|
||||||
|
})
|
||||||
|
t.Assert(err, nil)
|
||||||
|
lastInsertId, err := res.LastInsertId()
|
||||||
|
t.Assert(err, nil)
|
||||||
|
t.Assert(lastInsertId, int64(3))
|
||||||
|
rowsAffected, err := res.RowsAffected()
|
||||||
|
t.Assert(err, nil)
|
||||||
|
t.Assert(rowsAffected, int64(3))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func Test_Driver_DoFilter(t *testing.T) {
|
func Test_Driver_DoFilter(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
ctx = gctx.New()
|
ctx = gctx.New()
|
||||||
|
|||||||
@ -36,8 +36,7 @@ type Driver struct {
|
|||||||
var (
|
var (
|
||||||
// tableFieldsMap caches the table information retrieved from database.
|
// tableFieldsMap caches the table information retrieved from database.
|
||||||
tableFieldsMap = gmap.New(true)
|
tableFieldsMap = gmap.New(true)
|
||||||
// Error
|
ErrorSave = gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by sqlite driver`)
|
||||||
ErrorSave = gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by sqlite driver`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@ -172,6 +172,7 @@ type DB interface {
|
|||||||
TableFields(ctx context.Context, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields.
|
TableFields(ctx context.Context, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields.
|
||||||
ConvertDataForRecord(ctx context.Context, data interface{}) (map[string]interface{}, error) // See Core.ConvertDataForRecord
|
ConvertDataForRecord(ctx context.Context, data interface{}) (map[string]interface{}, error) // See Core.ConvertDataForRecord
|
||||||
ConvertValueForLocal(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) // See Core.ConvertValueForLocal
|
ConvertValueForLocal(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) // See Core.ConvertValueForLocal
|
||||||
|
CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (string, error) // See Core.CheckLocalTypeForField
|
||||||
FilteredLink() string // FilteredLink is used for filtering sensitive information in `Link` configuration before output it to tracing server.
|
FilteredLink() string // FilteredLink is used for filtering sensitive information in `Link` configuration before output it to tracing server.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,6 +313,27 @@ const (
|
|||||||
SqlTypeStmtQueryRowContext = "DB.Statement.QueryRowContext"
|
SqlTypeStmtQueryRowContext = "DB.Statement.QueryRowContext"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LocalTypeString = "string"
|
||||||
|
LocalTypeDate = "date"
|
||||||
|
LocalTypeDatetime = "datetime"
|
||||||
|
LocalTypeInt = "int"
|
||||||
|
LocalTypeUint = "uint"
|
||||||
|
LocalTypeInt64 = "int64"
|
||||||
|
LocalTypeUint64 = "uint64"
|
||||||
|
LocalTypeIntSlice = "[]int"
|
||||||
|
LocalTypeInt64Slice = "[]int64"
|
||||||
|
LocalTypeUint64Slice = "[]uint64"
|
||||||
|
LocalTypeInt64Bytes = "int64-bytes"
|
||||||
|
LocalTypeUint64Bytes = "uint64-bytes"
|
||||||
|
LocalTypeFloat32 = "float32"
|
||||||
|
LocalTypeFloat64 = "float64"
|
||||||
|
LocalTypeBytes = "[]byte"
|
||||||
|
LocalTypeBool = "bool"
|
||||||
|
LocalTypeJson = "json"
|
||||||
|
LocalTypeJsonb = "jsonb"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// instances is the management map for instances.
|
// instances is the management map for instances.
|
||||||
instances = gmap.NewStrAnyMap(true)
|
instances = gmap.NewStrAnyMap(true)
|
||||||
@ -374,16 +396,14 @@ func NewByGroup(group ...string) (db DB, err error) {
|
|||||||
var node *ConfigNode
|
var node *ConfigNode
|
||||||
if node, err = getConfigNodeByGroup(groupName, true); err == nil {
|
if node, err = getConfigNodeByGroup(groupName, true); err == nil {
|
||||||
return doNewByNode(*node, groupName)
|
return doNewByNode(*node, groupName)
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
} else {
|
return nil, err
|
||||||
return nil, gerror.NewCodef(
|
|
||||||
gcode.CodeInvalidConfiguration,
|
|
||||||
`database configuration node "%s" is not found, did you misspell group name "%s" or miss the database configuration?`,
|
|
||||||
groupName, groupName,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
return nil, gerror.NewCodef(
|
||||||
|
gcode.CodeInvalidConfiguration,
|
||||||
|
`database configuration node "%s" is not found, did you misspell group name "%s" or miss the database configuration?`,
|
||||||
|
groupName, groupName,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// doNewByNode creates and returns an ORM object with given configuration node and group name.
|
// doNewByNode creates and returns an ORM object with given configuration node and group name.
|
||||||
@ -459,13 +479,12 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) {
|
|||||||
} else {
|
} else {
|
||||||
return getConfigNodeByWeight(slaveList), nil
|
return getConfigNodeByWeight(slaveList), nil
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return nil, gerror.NewCodef(
|
|
||||||
gcode.CodeInvalidConfiguration,
|
|
||||||
"empty database configuration for item name '%s'",
|
|
||||||
group,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
return nil, gerror.NewCodef(
|
||||||
|
gcode.CodeInvalidConfiguration,
|
||||||
|
"empty database configuration for item name '%s'",
|
||||||
|
group,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getConfigNodeByWeight calculates the configuration weights and randomly returns a node.
|
// getConfigNodeByWeight calculates the configuration weights and randomly returns a node.
|
||||||
@ -498,12 +517,13 @@ func getConfigNodeByWeight(cg ConfigGroup) *ConfigNode {
|
|||||||
)
|
)
|
||||||
for i := 0; i < len(cg); i++ {
|
for i := 0; i < len(cg); i++ {
|
||||||
max = min + cg[i].Weight*100
|
max = min + cg[i].Weight*100
|
||||||
// fmt.Printf("r: %d, min: %d, max: %d\n", r, min, max)
|
|
||||||
if random >= min && random < max {
|
if random >= min && random < max {
|
||||||
return &cg[i]
|
// Return a copy of the ConfigNode.
|
||||||
} else {
|
node := ConfigNode{}
|
||||||
min = max
|
node = cg[i]
|
||||||
|
return &node
|
||||||
}
|
}
|
||||||
|
min = max
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -518,6 +538,8 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
|||||||
)
|
)
|
||||||
// Load balance.
|
// Load balance.
|
||||||
if c.group != "" {
|
if c.group != "" {
|
||||||
|
configs.RLock()
|
||||||
|
defer configs.RUnlock()
|
||||||
node, err = getConfigNodeByGroup(c.group, master)
|
node, err = getConfigNodeByGroup(c.group, master)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -121,16 +121,19 @@ func (c *Core) ConvertDataForRecordValue(ctx context.Context, value interface{})
|
|||||||
return convertedValue, nil
|
return convertedValue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertValueForLocal converts value to local Golang type of value according field type name from database.
|
// CheckLocalTypeForField checks and returns corresponding type for given db type.
|
||||||
// The parameter `fieldType` is in lower case, like:
|
func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (string, error) {
|
||||||
// `float(5,2)`, `unsigned double(5,2)`, `decimal(10,2)`, `char(45)`, `varchar(100)`, etc.
|
var (
|
||||||
func (c *Core) ConvertValueForLocal(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) {
|
typeName string
|
||||||
// If there's no type retrieved, it returns the `fieldValue` directly
|
typePattern string
|
||||||
// to use its original data type, as `fieldValue` is type of interface{}.
|
)
|
||||||
if fieldType == "" {
|
match, _ := gregex.MatchString(`(.+?)\((.+)\)`, fieldType)
|
||||||
return fieldValue, nil
|
if len(match) == 3 {
|
||||||
|
typeName = gstr.Trim(match[1])
|
||||||
|
typePattern = gstr.Trim(match[2])
|
||||||
|
} else {
|
||||||
|
typeName = gstr.Split(fieldType, " ")[0]
|
||||||
}
|
}
|
||||||
typeName, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
|
|
||||||
typeName = strings.ToLower(typeName)
|
typeName = strings.ToLower(typeName)
|
||||||
switch typeName {
|
switch typeName {
|
||||||
case
|
case
|
||||||
@ -140,7 +143,7 @@ func (c *Core) ConvertValueForLocal(ctx context.Context, fieldType string, field
|
|||||||
"tinyblob",
|
"tinyblob",
|
||||||
"mediumblob",
|
"mediumblob",
|
||||||
"longblob":
|
"longblob":
|
||||||
return gconv.Bytes(fieldValue), nil
|
return LocalTypeBytes, nil
|
||||||
|
|
||||||
case
|
case
|
||||||
"int",
|
"int",
|
||||||
@ -151,21 +154,22 @@ func (c *Core) ConvertValueForLocal(ctx context.Context, fieldType string, field
|
|||||||
"mediumint",
|
"mediumint",
|
||||||
"serial":
|
"serial":
|
||||||
if gstr.ContainsI(fieldType, "unsigned") {
|
if gstr.ContainsI(fieldType, "unsigned") {
|
||||||
return gconv.Uint(gconv.String(fieldValue)), nil
|
return LocalTypeUint, nil
|
||||||
}
|
}
|
||||||
return gconv.Int(gconv.String(fieldValue)), nil
|
return LocalTypeInt, nil
|
||||||
|
|
||||||
case
|
case
|
||||||
"big_int",
|
"big_int",
|
||||||
"bigint",
|
"bigint",
|
||||||
"bigserial":
|
"bigserial":
|
||||||
if gstr.ContainsI(fieldType, "unsigned") {
|
if gstr.ContainsI(fieldType, "unsigned") {
|
||||||
return gconv.Uint64(gconv.String(fieldValue)), nil
|
return LocalTypeUint64, nil
|
||||||
}
|
}
|
||||||
return gconv.Int64(gconv.String(fieldValue)), nil
|
return LocalTypeInt64, nil
|
||||||
|
|
||||||
case "real":
|
case
|
||||||
return gconv.Float32(gconv.String(fieldValue)), nil
|
"real":
|
||||||
|
return LocalTypeFloat32, nil
|
||||||
|
|
||||||
case
|
case
|
||||||
"float",
|
"float",
|
||||||
@ -174,9 +178,124 @@ func (c *Core) ConvertValueForLocal(ctx context.Context, fieldType string, field
|
|||||||
"money",
|
"money",
|
||||||
"numeric",
|
"numeric",
|
||||||
"smallmoney":
|
"smallmoney":
|
||||||
|
return LocalTypeFloat64, nil
|
||||||
|
|
||||||
|
case
|
||||||
|
"bit":
|
||||||
|
// It is suggested using bit(1) as boolean.
|
||||||
|
if typePattern == "1" {
|
||||||
|
return LocalTypeBool, nil
|
||||||
|
}
|
||||||
|
s := gconv.String(fieldValue)
|
||||||
|
// mssql is true|false string.
|
||||||
|
if strings.EqualFold(s, "true") || strings.EqualFold(s, "false") {
|
||||||
|
return LocalTypeBool, nil
|
||||||
|
}
|
||||||
|
if gstr.ContainsI(fieldType, "unsigned") {
|
||||||
|
return LocalTypeUint64Bytes, nil
|
||||||
|
}
|
||||||
|
return LocalTypeInt64Bytes, nil
|
||||||
|
|
||||||
|
case
|
||||||
|
"bool":
|
||||||
|
return LocalTypeBool, nil
|
||||||
|
|
||||||
|
case
|
||||||
|
"date":
|
||||||
|
return LocalTypeDate, nil
|
||||||
|
|
||||||
|
case
|
||||||
|
"datetime",
|
||||||
|
"timestamp",
|
||||||
|
"timestamptz":
|
||||||
|
return LocalTypeDatetime, nil
|
||||||
|
|
||||||
|
case
|
||||||
|
"json":
|
||||||
|
return LocalTypeJson, nil
|
||||||
|
|
||||||
|
case
|
||||||
|
"jsonb":
|
||||||
|
return LocalTypeJsonb, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Auto-detect field type, using key match.
|
||||||
|
switch {
|
||||||
|
case strings.Contains(typeName, "text") || strings.Contains(typeName, "char") || strings.Contains(typeName, "character"):
|
||||||
|
return LocalTypeString, nil
|
||||||
|
|
||||||
|
case strings.Contains(typeName, "float") || strings.Contains(typeName, "double") || strings.Contains(typeName, "numeric"):
|
||||||
|
return LocalTypeFloat64, nil
|
||||||
|
|
||||||
|
case strings.Contains(typeName, "bool"):
|
||||||
|
return LocalTypeBool, nil
|
||||||
|
|
||||||
|
case strings.Contains(typeName, "binary") || strings.Contains(typeName, "blob"):
|
||||||
|
return LocalTypeBytes, nil
|
||||||
|
|
||||||
|
case strings.Contains(typeName, "int"):
|
||||||
|
if gstr.ContainsI(fieldType, "unsigned") {
|
||||||
|
return LocalTypeUint, nil
|
||||||
|
}
|
||||||
|
return LocalTypeInt, nil
|
||||||
|
|
||||||
|
case strings.Contains(typeName, "time"):
|
||||||
|
return LocalTypeDatetime, nil
|
||||||
|
|
||||||
|
case strings.Contains(typeName, "date"):
|
||||||
|
return LocalTypeDatetime, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return LocalTypeString, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertValueForLocal converts value to local Golang type of value according field type name from database.
|
||||||
|
// The parameter `fieldType` is in lower case, like:
|
||||||
|
// `float(5,2)`, `unsigned double(5,2)`, `decimal(10,2)`, `char(45)`, `varchar(100)`, etc.
|
||||||
|
func (c *Core) ConvertValueForLocal(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) {
|
||||||
|
// If there's no type retrieved, it returns the `fieldValue` directly
|
||||||
|
// to use its original data type, as `fieldValue` is type of interface{}.
|
||||||
|
if fieldType == "" {
|
||||||
|
return fieldValue, nil
|
||||||
|
}
|
||||||
|
typeName, err := c.db.CheckLocalTypeForField(ctx, fieldType, fieldValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch typeName {
|
||||||
|
case LocalTypeBytes:
|
||||||
|
if strings.Contains(typeName, "binary") || strings.Contains(typeName, "blob") {
|
||||||
|
return fieldValue, nil
|
||||||
|
}
|
||||||
|
return gconv.Bytes(fieldValue), nil
|
||||||
|
|
||||||
|
case LocalTypeInt:
|
||||||
|
return gconv.Int(gconv.String(fieldValue)), nil
|
||||||
|
|
||||||
|
case LocalTypeUint:
|
||||||
|
return gconv.Uint(gconv.String(fieldValue)), nil
|
||||||
|
|
||||||
|
case LocalTypeInt64:
|
||||||
|
return gconv.Int64(gconv.String(fieldValue)), nil
|
||||||
|
|
||||||
|
case LocalTypeUint64:
|
||||||
|
return gconv.Uint64(gconv.String(fieldValue)), nil
|
||||||
|
|
||||||
|
case LocalTypeInt64Bytes:
|
||||||
|
return gbinary.BeDecodeToInt64(gconv.Bytes(fieldValue)), nil
|
||||||
|
|
||||||
|
case LocalTypeUint64Bytes:
|
||||||
|
return gbinary.BeDecodeToUint64(gconv.Bytes(fieldValue)), nil
|
||||||
|
|
||||||
|
case LocalTypeFloat32:
|
||||||
|
return gconv.Float32(gconv.String(fieldValue)), nil
|
||||||
|
|
||||||
|
case LocalTypeFloat64:
|
||||||
return gconv.Float64(gconv.String(fieldValue)), nil
|
return gconv.Float64(gconv.String(fieldValue)), nil
|
||||||
|
|
||||||
case "bit":
|
case LocalTypeBool:
|
||||||
s := gconv.String(fieldValue)
|
s := gconv.String(fieldValue)
|
||||||
// mssql is true|false string.
|
// mssql is true|false string.
|
||||||
if strings.EqualFold(s, "true") {
|
if strings.EqualFold(s, "true") {
|
||||||
@ -185,12 +304,9 @@ func (c *Core) ConvertValueForLocal(ctx context.Context, fieldType string, field
|
|||||||
if strings.EqualFold(s, "false") {
|
if strings.EqualFold(s, "false") {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
return gbinary.BeDecodeToInt64(gconv.Bytes(fieldValue)), nil
|
|
||||||
|
|
||||||
case "bool":
|
|
||||||
return gconv.Bool(fieldValue), nil
|
return gconv.Bool(fieldValue), nil
|
||||||
|
|
||||||
case "date":
|
case LocalTypeDate:
|
||||||
// Date without time.
|
// Date without time.
|
||||||
if t, ok := fieldValue.(time.Time); ok {
|
if t, ok := fieldValue.(time.Time); ok {
|
||||||
return gtime.NewFromTime(t).Format("Y-m-d"), nil
|
return gtime.NewFromTime(t).Format("Y-m-d"), nil
|
||||||
@ -198,10 +314,7 @@ func (c *Core) ConvertValueForLocal(ctx context.Context, fieldType string, field
|
|||||||
t, _ := gtime.StrToTime(gconv.String(fieldValue))
|
t, _ := gtime.StrToTime(gconv.String(fieldValue))
|
||||||
return t.Format("Y-m-d"), nil
|
return t.Format("Y-m-d"), nil
|
||||||
|
|
||||||
case
|
case LocalTypeDatetime:
|
||||||
"datetime",
|
|
||||||
"timestamp",
|
|
||||||
"timestamptz":
|
|
||||||
if t, ok := fieldValue.(time.Time); ok {
|
if t, ok := fieldValue.(time.Time); ok {
|
||||||
return gtime.NewFromTime(t), nil
|
return gtime.NewFromTime(t), nil
|
||||||
}
|
}
|
||||||
@ -209,42 +322,7 @@ func (c *Core) ConvertValueForLocal(ctx context.Context, fieldType string, field
|
|||||||
return t, nil
|
return t, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Auto-detect field type, using key match.
|
return gconv.String(fieldValue), nil
|
||||||
switch {
|
|
||||||
case strings.Contains(typeName, "text") || strings.Contains(typeName, "char") || strings.Contains(typeName, "character"):
|
|
||||||
return gconv.String(fieldValue), nil
|
|
||||||
|
|
||||||
case strings.Contains(typeName, "float") || strings.Contains(typeName, "double") || strings.Contains(typeName, "numeric"):
|
|
||||||
return gconv.Float64(gconv.String(fieldValue)), nil
|
|
||||||
|
|
||||||
case strings.Contains(typeName, "bool"):
|
|
||||||
return gconv.Bool(gconv.String(fieldValue)), nil
|
|
||||||
|
|
||||||
case strings.Contains(typeName, "binary") || strings.Contains(typeName, "blob"):
|
|
||||||
return fieldValue, nil
|
|
||||||
|
|
||||||
case strings.Contains(typeName, "int"):
|
|
||||||
return gconv.Int(gconv.String(fieldValue)), nil
|
|
||||||
|
|
||||||
case strings.Contains(typeName, "time"):
|
|
||||||
s := gconv.String(fieldValue)
|
|
||||||
t, err := gtime.StrToTime(s)
|
|
||||||
if err != nil {
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
|
|
||||||
case strings.Contains(typeName, "date"):
|
|
||||||
s := gconv.String(fieldValue)
|
|
||||||
t, err := gtime.StrToTime(s)
|
|
||||||
if err != nil {
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return gconv.String(fieldValue), nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -124,7 +124,7 @@ func (c *Core) QuoteString(s string) string {
|
|||||||
// if true it does nothing to the table name, or else adds the prefix to the table name.
|
// if true it does nothing to the table name, or else adds the prefix to the table name.
|
||||||
func (c *Core) QuotePrefixTableName(table string) string {
|
func (c *Core) QuotePrefixTableName(table string) string {
|
||||||
charLeft, charRight := c.db.GetChars()
|
charLeft, charRight := c.db.GetChars()
|
||||||
return doHandleTableName(table, c.db.GetPrefix(), charLeft, charRight)
|
return doQuoteTableName(table, c.db.GetPrefix(), charLeft, charRight)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChars returns the security char for current database.
|
// GetChars returns the security char for current database.
|
||||||
|
|||||||
@ -42,11 +42,6 @@ type iInterfaces interface {
|
|||||||
Interfaces() []interface{}
|
Interfaces() []interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// iMapStrAny is the interface support for converting struct parameter to map.
|
|
||||||
type iMapStrAny interface {
|
|
||||||
MapStrAny() map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// iTableName is the interface for retrieving table name fro struct.
|
// iTableName is the interface for retrieving table name fro struct.
|
||||||
type iTableName interface {
|
type iTableName interface {
|
||||||
TableName() string
|
TableName() string
|
||||||
@ -187,7 +182,7 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
|
|||||||
//
|
//
|
||||||
// Note that, this will automatically check the table prefix whether already added, if true it does
|
// Note that, this will automatically check the table prefix whether already added, if true it does
|
||||||
// nothing to the table name, or else adds the prefix to the table name and returns new table name with prefix.
|
// nothing to the table name, or else adds the prefix to the table name and returns new table name with prefix.
|
||||||
func doHandleTableName(table, prefix, charLeft, charRight string) string {
|
func doQuoteTableName(table, prefix, charLeft, charRight string) string {
|
||||||
var (
|
var (
|
||||||
index = 0
|
index = 0
|
||||||
chars = charLeft + charRight
|
chars = charLeft + charRight
|
||||||
|
|||||||
@ -72,7 +72,7 @@ func Test_Func_addTablePrefix(t *testing.T) {
|
|||||||
"UserCenter..user as u, user_detail as ut": "`UserCenter`..`user` as u,`user_detail` as ut",
|
"UserCenter..user as u, user_detail as ut": "`UserCenter`..`user` as u,`user_detail` as ut",
|
||||||
}
|
}
|
||||||
for k, v := range array {
|
for k, v := range array {
|
||||||
t.Assert(doHandleTableName(k, prefix, "`", "`"), v)
|
t.Assert(doQuoteTableName(k, prefix, "`", "`"), v)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
@ -91,7 +91,7 @@ func Test_Func_addTablePrefix(t *testing.T) {
|
|||||||
"UserCenter..user as u, user_detail as ut": "`UserCenter`..`gf_user` as u,`gf_user_detail` as ut",
|
"UserCenter..user as u, user_detail as ut": "`UserCenter`..`gf_user` as u,`gf_user_detail` as ut",
|
||||||
}
|
}
|
||||||
for k, v := range array {
|
for k, v := range array {
|
||||||
t.Assert(doHandleTableName(k, prefix, "`", "`"), v)
|
t.Assert(doQuoteTableName(k, prefix, "`", "`"), v)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -121,7 +121,11 @@ func filterFileByFilters(file string, filters []string) (filtered bool) {
|
|||||||
}
|
}
|
||||||
// GOROOT filter.
|
// GOROOT filter.
|
||||||
if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
|
if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
|
||||||
return true
|
// https://github.com/gogf/gf/issues/2047
|
||||||
|
fileSeparator := file[len(goRootForFilter)]
|
||||||
|
if fileSeparator == filepath.Separator || fileSeparator == '\\' || fileSeparator == '/' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -210,7 +210,8 @@ func LoadContentType(dataType string, data interface{}, safe ...bool) (*Json, er
|
|||||||
content = content[3:]
|
content = content[3:]
|
||||||
}
|
}
|
||||||
options := Options{
|
options := Options{
|
||||||
Type: dataType,
|
Type: dataType,
|
||||||
|
StrNumber: true,
|
||||||
}
|
}
|
||||||
if len(safe) > 0 && safe[0] {
|
if len(safe) > 0 && safe[0] {
|
||||||
options.Safe = true
|
options.Safe = true
|
||||||
|
|||||||
@ -110,3 +110,13 @@ func Test_NewWithOptions(t *testing.T) {
|
|||||||
t.Assert(array, []uint64{9223372036854775807, 9223372036854775806})
|
t.Assert(array, []uint64{9223372036854775807, 9223372036854775806})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_LoadContentType(t *testing.T) {
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
data := []byte("value = 79937385836643329")
|
||||||
|
j, err := gjson.LoadContentType("toml", data)
|
||||||
|
t.AssertNil(err)
|
||||||
|
value := j.Get("value").Int64()
|
||||||
|
t.Assert(value, 79937385836643329)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -4,316 +4,55 @@
|
|||||||
// If a copy of the MIT was not distributed with this file,
|
// If a copy of the MIT was not distributed with this file,
|
||||||
// You can obtain one at https://github.com/gogf/gf.
|
// You can obtain one at https://github.com/gogf/gf.
|
||||||
|
|
||||||
// Package gerror provides simple functions to manipulate errors.
|
// Package gerror provides rich functionalities to manipulate errors.
|
||||||
//
|
//
|
||||||
// Very note that, this package is quite a basic package, which SHOULD NOT import extra packages
|
// For maintainers, please very note that,
|
||||||
|
// this package is quite a basic package, which SHOULD NOT import extra packages
|
||||||
// except standard packages and internal packages, to avoid cycle imports.
|
// except standard packages and internal packages, to avoid cycle imports.
|
||||||
package gerror
|
package gerror
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/errors/gcode"
|
"github.com/gogf/gf/v2/errors/gcode"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creates and returns an error which is formatted from given text.
|
// IIs is the interface for Is feature.
|
||||||
func New(text string) error {
|
type IIs interface {
|
||||||
return &Error{
|
Error() string
|
||||||
stack: callers(),
|
Is(target error) bool
|
||||||
text: text,
|
|
||||||
code: gcode.CodeNil,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Newf returns an error that formats as the given format and args.
|
// IEqual is the interface for Equal feature.
|
||||||
func Newf(format string, args ...interface{}) error {
|
type IEqual interface {
|
||||||
return &Error{
|
Error() string
|
||||||
stack: callers(),
|
Equal(target error) bool
|
||||||
text: fmt.Sprintf(format, args...),
|
|
||||||
code: gcode.CodeNil,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSkip creates and returns an error which is formatted from given text.
|
// ICode is the interface for Code feature.
|
||||||
// The parameter `skip` specifies the stack callers skipped amount.
|
type ICode interface {
|
||||||
func NewSkip(skip int, text string) error {
|
Error() string
|
||||||
return &Error{
|
Code() gcode.Code
|
||||||
stack: callers(skip),
|
|
||||||
text: text,
|
|
||||||
code: gcode.CodeNil,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSkipf returns an error that formats as the given format and args.
|
// IStack is the interface for Stack feature.
|
||||||
// The parameter `skip` specifies the stack callers skipped amount.
|
type IStack interface {
|
||||||
func NewSkipf(skip int, format string, args ...interface{}) error {
|
Error() string
|
||||||
return &Error{
|
Stack() string
|
||||||
stack: callers(skip),
|
|
||||||
text: fmt.Sprintf(format, args...),
|
|
||||||
code: gcode.CodeNil,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap wraps error with text. It returns nil if given err is nil.
|
// ICause is the interface for Cause feature.
|
||||||
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
|
type ICause interface {
|
||||||
func Wrap(err error, text string) error {
|
Error() string
|
||||||
if err == nil {
|
Cause() error
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &Error{
|
|
||||||
error: err,
|
|
||||||
stack: callers(),
|
|
||||||
text: text,
|
|
||||||
code: Code(err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapf returns an error annotating err with a stack trace at the point Wrapf is called, and the format specifier.
|
// ICurrent is the interface for Current feature.
|
||||||
// It returns nil if given `err` is nil.
|
type ICurrent interface {
|
||||||
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
|
Error() string
|
||||||
func Wrapf(err error, format string, args ...interface{}) error {
|
Current() error
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &Error{
|
|
||||||
error: err,
|
|
||||||
stack: callers(),
|
|
||||||
text: fmt.Sprintf(format, args...),
|
|
||||||
code: Code(err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WrapSkip wraps error with text. It returns nil if given err is nil.
|
// IUnwrap is the interface for Unwrap feature.
|
||||||
// The parameter `skip` specifies the stack callers skipped amount.
|
type IUnwrap interface {
|
||||||
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
|
Error() string
|
||||||
func WrapSkip(skip int, err error, text string) error {
|
Unwrap() error
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &Error{
|
|
||||||
error: err,
|
|
||||||
stack: callers(skip),
|
|
||||||
text: text,
|
|
||||||
code: Code(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapSkipf wraps error with text that is formatted with given format and args. It returns nil if given err is nil.
|
|
||||||
// The parameter `skip` specifies the stack callers skipped amount.
|
|
||||||
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
|
|
||||||
func WrapSkipf(skip int, err error, format string, args ...interface{}) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &Error{
|
|
||||||
error: err,
|
|
||||||
stack: callers(skip),
|
|
||||||
text: fmt.Sprintf(format, args...),
|
|
||||||
code: Code(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCode creates and returns an error that has error code and given text.
|
|
||||||
func NewCode(code gcode.Code, text ...string) error {
|
|
||||||
return &Error{
|
|
||||||
stack: callers(),
|
|
||||||
text: strings.Join(text, ", "),
|
|
||||||
code: code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCodef returns an error that has error code and formats as the given format and args.
|
|
||||||
func NewCodef(code gcode.Code, format string, args ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
stack: callers(),
|
|
||||||
text: fmt.Sprintf(format, args...),
|
|
||||||
code: code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCodeSkip creates and returns an error which has error code and is formatted from given text.
|
|
||||||
// The parameter `skip` specifies the stack callers skipped amount.
|
|
||||||
func NewCodeSkip(code gcode.Code, skip int, text ...string) error {
|
|
||||||
return &Error{
|
|
||||||
stack: callers(skip),
|
|
||||||
text: strings.Join(text, ", "),
|
|
||||||
code: code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCodeSkipf returns an error that has error code and formats as the given format and args.
|
|
||||||
// The parameter `skip` specifies the stack callers skipped amount.
|
|
||||||
func NewCodeSkipf(code gcode.Code, skip int, format string, args ...interface{}) error {
|
|
||||||
return &Error{
|
|
||||||
stack: callers(skip),
|
|
||||||
text: fmt.Sprintf(format, args...),
|
|
||||||
code: code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapCode wraps error with code and text.
|
|
||||||
// It returns nil if given err is nil.
|
|
||||||
func WrapCode(code gcode.Code, err error, text ...string) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &Error{
|
|
||||||
error: err,
|
|
||||||
stack: callers(),
|
|
||||||
text: strings.Join(text, ", "),
|
|
||||||
code: code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapCodef wraps error with code and format specifier.
|
|
||||||
// It returns nil if given `err` is nil.
|
|
||||||
func WrapCodef(code gcode.Code, err error, format string, args ...interface{}) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &Error{
|
|
||||||
error: err,
|
|
||||||
stack: callers(),
|
|
||||||
text: fmt.Sprintf(format, args...),
|
|
||||||
code: code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapCodeSkip wraps error with code and text.
|
|
||||||
// It returns nil if given err is nil.
|
|
||||||
// The parameter `skip` specifies the stack callers skipped amount.
|
|
||||||
func WrapCodeSkip(code gcode.Code, skip int, err error, text ...string) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &Error{
|
|
||||||
error: err,
|
|
||||||
stack: callers(skip),
|
|
||||||
text: strings.Join(text, ", "),
|
|
||||||
code: code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapCodeSkipf wraps error with code and text that is formatted with given format and args.
|
|
||||||
// It returns nil if given err is nil.
|
|
||||||
// The parameter `skip` specifies the stack callers skipped amount.
|
|
||||||
func WrapCodeSkipf(code gcode.Code, skip int, err error, format string, args ...interface{}) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &Error{
|
|
||||||
error: err,
|
|
||||||
stack: callers(skip),
|
|
||||||
text: fmt.Sprintf(format, args...),
|
|
||||||
code: code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code returns the error code of current error.
|
|
||||||
// It returns CodeNil if it has no error code neither it does not implement interface Code.
|
|
||||||
func Code(err error) gcode.Code {
|
|
||||||
if err == nil {
|
|
||||||
return gcode.CodeNil
|
|
||||||
}
|
|
||||||
if e, ok := err.(iCode); ok {
|
|
||||||
return e.Code()
|
|
||||||
}
|
|
||||||
if e, ok := err.(iNext); ok {
|
|
||||||
return Code(e.Next())
|
|
||||||
}
|
|
||||||
if e, ok := err.(iUnwrap); ok {
|
|
||||||
return Code(e.Unwrap())
|
|
||||||
}
|
|
||||||
return gcode.CodeNil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cause returns the root cause error of `err`.
|
|
||||||
func Cause(err error) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if e, ok := err.(iCause); ok {
|
|
||||||
return e.Cause()
|
|
||||||
}
|
|
||||||
if e, ok := err.(iNext); ok {
|
|
||||||
return Cause(e.Next())
|
|
||||||
}
|
|
||||||
if e, ok := err.(iUnwrap); ok {
|
|
||||||
return Cause(e.Unwrap())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stack returns the stack callers as string.
|
|
||||||
// It returns the error string directly if the `err` does not support stacks.
|
|
||||||
func Stack(err error) string {
|
|
||||||
if err == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if e, ok := err.(iStack); ok {
|
|
||||||
return e.Stack()
|
|
||||||
}
|
|
||||||
return err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current creates and returns the current level error.
|
|
||||||
// It returns nil if current level error is nil.
|
|
||||||
func Current(err error) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if e, ok := err.(iCurrent); ok {
|
|
||||||
return e.Current()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next returns the next level error.
|
|
||||||
// It returns nil if current level error or the next level error is nil.
|
|
||||||
func Next(err error) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if e, ok := err.(iNext); ok {
|
|
||||||
return e.Next()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unwrap is alias of function `Next`.
|
|
||||||
// It is just for implements for stdlib errors.Unwrap from Go version 1.17.
|
|
||||||
func Unwrap(err error) error {
|
|
||||||
return Next(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasStack checks and returns whether `err` implemented interface `iStack`.
|
|
||||||
func HasStack(err error) bool {
|
|
||||||
_, ok := err.(iStack)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal reports whether current error `err` equals to error `target`.
|
|
||||||
// Please note that, in default comparison for `Error`,
|
|
||||||
// the errors are considered the same if both the `code` and `text` of them are the same.
|
|
||||||
func Equal(err, target error) bool {
|
|
||||||
if err == target {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if e, ok := err.(iEqual); ok {
|
|
||||||
return e.Equal(target)
|
|
||||||
}
|
|
||||||
if e, ok := target.(iEqual); ok {
|
|
||||||
return e.Equal(err)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is reports whether current error `err` has error `target` in its chaining errors.
|
|
||||||
// It is just for implements for stdlib errors.Unwrap from Go version 1.17.
|
|
||||||
func Is(err, target error) bool {
|
|
||||||
if e, ok := err.(iIs); ok {
|
|
||||||
return e.Is(target)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
110
errors/gerror/gerror_api.go
Normal file
110
errors/gerror/gerror_api.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// 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 gerror
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/errors/gcode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates and returns an error which is formatted from given text.
|
||||||
|
func New(text string) error {
|
||||||
|
return &Error{
|
||||||
|
stack: callers(),
|
||||||
|
text: text,
|
||||||
|
code: gcode.CodeNil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Newf returns an error that formats as the given format and args.
|
||||||
|
func Newf(format string, args ...interface{}) error {
|
||||||
|
return &Error{
|
||||||
|
stack: callers(),
|
||||||
|
text: fmt.Sprintf(format, args...),
|
||||||
|
code: gcode.CodeNil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSkip creates and returns an error which is formatted from given text.
|
||||||
|
// The parameter `skip` specifies the stack callers skipped amount.
|
||||||
|
func NewSkip(skip int, text string) error {
|
||||||
|
return &Error{
|
||||||
|
stack: callers(skip),
|
||||||
|
text: text,
|
||||||
|
code: gcode.CodeNil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSkipf returns an error that formats as the given format and args.
|
||||||
|
// The parameter `skip` specifies the stack callers skipped amount.
|
||||||
|
func NewSkipf(skip int, format string, args ...interface{}) error {
|
||||||
|
return &Error{
|
||||||
|
stack: callers(skip),
|
||||||
|
text: fmt.Sprintf(format, args...),
|
||||||
|
code: gcode.CodeNil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap wraps error with text. It returns nil if given err is nil.
|
||||||
|
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
|
||||||
|
func Wrap(err error, text string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Error{
|
||||||
|
error: err,
|
||||||
|
stack: callers(),
|
||||||
|
text: text,
|
||||||
|
code: Code(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapf returns an error annotating err with a stack trace at the point Wrapf is called, and the format specifier.
|
||||||
|
// It returns nil if given `err` is nil.
|
||||||
|
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
|
||||||
|
func Wrapf(err error, format string, args ...interface{}) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Error{
|
||||||
|
error: err,
|
||||||
|
stack: callers(),
|
||||||
|
text: fmt.Sprintf(format, args...),
|
||||||
|
code: Code(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapSkip wraps error with text. It returns nil if given err is nil.
|
||||||
|
// The parameter `skip` specifies the stack callers skipped amount.
|
||||||
|
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
|
||||||
|
func WrapSkip(skip int, err error, text string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Error{
|
||||||
|
error: err,
|
||||||
|
stack: callers(skip),
|
||||||
|
text: text,
|
||||||
|
code: Code(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapSkipf wraps error with text that is formatted with given format and args. It returns nil if given err is nil.
|
||||||
|
// The parameter `skip` specifies the stack callers skipped amount.
|
||||||
|
// Note that it does not lose the error code of wrapped error, as it inherits the error code from it.
|
||||||
|
func WrapSkipf(skip int, err error, format string, args ...interface{}) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Error{
|
||||||
|
error: err,
|
||||||
|
stack: callers(skip),
|
||||||
|
text: fmt.Sprintf(format, args...),
|
||||||
|
code: Code(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
139
errors/gerror/gerror_api_code.go
Normal file
139
errors/gerror/gerror_api_code.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// 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 gerror
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/errors/gcode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewCode creates and returns an error that has error code and given text.
|
||||||
|
func NewCode(code gcode.Code, text ...string) error {
|
||||||
|
return &Error{
|
||||||
|
stack: callers(),
|
||||||
|
text: strings.Join(text, ", "),
|
||||||
|
code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCodef returns an error that has error code and formats as the given format and args.
|
||||||
|
func NewCodef(code gcode.Code, format string, args ...interface{}) error {
|
||||||
|
return &Error{
|
||||||
|
stack: callers(),
|
||||||
|
text: fmt.Sprintf(format, args...),
|
||||||
|
code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCodeSkip creates and returns an error which has error code and is formatted from given text.
|
||||||
|
// The parameter `skip` specifies the stack callers skipped amount.
|
||||||
|
func NewCodeSkip(code gcode.Code, skip int, text ...string) error {
|
||||||
|
return &Error{
|
||||||
|
stack: callers(skip),
|
||||||
|
text: strings.Join(text, ", "),
|
||||||
|
code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCodeSkipf returns an error that has error code and formats as the given format and args.
|
||||||
|
// The parameter `skip` specifies the stack callers skipped amount.
|
||||||
|
func NewCodeSkipf(code gcode.Code, skip int, format string, args ...interface{}) error {
|
||||||
|
return &Error{
|
||||||
|
stack: callers(skip),
|
||||||
|
text: fmt.Sprintf(format, args...),
|
||||||
|
code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapCode wraps error with code and text.
|
||||||
|
// It returns nil if given err is nil.
|
||||||
|
func WrapCode(code gcode.Code, err error, text ...string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Error{
|
||||||
|
error: err,
|
||||||
|
stack: callers(),
|
||||||
|
text: strings.Join(text, ", "),
|
||||||
|
code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapCodef wraps error with code and format specifier.
|
||||||
|
// It returns nil if given `err` is nil.
|
||||||
|
func WrapCodef(code gcode.Code, err error, format string, args ...interface{}) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Error{
|
||||||
|
error: err,
|
||||||
|
stack: callers(),
|
||||||
|
text: fmt.Sprintf(format, args...),
|
||||||
|
code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapCodeSkip wraps error with code and text.
|
||||||
|
// It returns nil if given err is nil.
|
||||||
|
// The parameter `skip` specifies the stack callers skipped amount.
|
||||||
|
func WrapCodeSkip(code gcode.Code, skip int, err error, text ...string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Error{
|
||||||
|
error: err,
|
||||||
|
stack: callers(skip),
|
||||||
|
text: strings.Join(text, ", "),
|
||||||
|
code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapCodeSkipf wraps error with code and text that is formatted with given format and args.
|
||||||
|
// It returns nil if given err is nil.
|
||||||
|
// The parameter `skip` specifies the stack callers skipped amount.
|
||||||
|
func WrapCodeSkipf(code gcode.Code, skip int, err error, format string, args ...interface{}) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Error{
|
||||||
|
error: err,
|
||||||
|
stack: callers(skip),
|
||||||
|
text: fmt.Sprintf(format, args...),
|
||||||
|
code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code returns the error code of current error.
|
||||||
|
// It returns `CodeNil` if it has no error code neither it does not implement interface Code.
|
||||||
|
func Code(err error) gcode.Code {
|
||||||
|
if err == nil {
|
||||||
|
return gcode.CodeNil
|
||||||
|
}
|
||||||
|
if e, ok := err.(ICode); ok {
|
||||||
|
return e.Code()
|
||||||
|
}
|
||||||
|
if e, ok := err.(IUnwrap); ok {
|
||||||
|
return Code(e.Unwrap())
|
||||||
|
}
|
||||||
|
return gcode.CodeNil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasCode checks and reports whether `err` has `code` in its chaining errors.
|
||||||
|
func HasCode(err error, code gcode.Code) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e, ok := err.(ICode); ok {
|
||||||
|
return code == e.Code()
|
||||||
|
}
|
||||||
|
if e, ok := err.(IUnwrap); ok {
|
||||||
|
return HasCode(e.Unwrap(), code)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
118
errors/gerror/gerror_api_stack.go
Normal file
118
errors/gerror/gerror_api_stack.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
// 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 gerror
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// stack represents a stack of program counters.
|
||||||
|
type stack []uintptr
|
||||||
|
|
||||||
|
const (
|
||||||
|
// maxStackDepth marks the max stack depth for error back traces.
|
||||||
|
maxStackDepth = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cause returns the root cause error of `err`.
|
||||||
|
func Cause(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if e, ok := err.(ICause); ok {
|
||||||
|
return e.Cause()
|
||||||
|
}
|
||||||
|
if e, ok := err.(IUnwrap); ok {
|
||||||
|
return Cause(e.Unwrap())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack returns the stack callers as string.
|
||||||
|
// It returns the error string directly if the `err` does not support stacks.
|
||||||
|
func Stack(err error) string {
|
||||||
|
if err == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if e, ok := err.(IStack); ok {
|
||||||
|
return e.Stack()
|
||||||
|
}
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current creates and returns the current level error.
|
||||||
|
// It returns nil if current level error is nil.
|
||||||
|
func Current(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if e, ok := err.(ICurrent); ok {
|
||||||
|
return e.Current()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the next level error.
|
||||||
|
// It returns nil if current level error or the next level error is nil.
|
||||||
|
func Unwrap(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if e, ok := err.(IUnwrap); ok {
|
||||||
|
return e.Unwrap()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasStack checks and reports whether `err` implemented interface `gerror.IStack`.
|
||||||
|
func HasStack(err error) bool {
|
||||||
|
_, ok := err.(IStack)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal reports whether current error `err` equals to error `target`.
|
||||||
|
// Please note that, in default comparison logic for `Error`,
|
||||||
|
// the errors are considered the same if both the `code` and `text` of them are the same.
|
||||||
|
func Equal(err, target error) bool {
|
||||||
|
if err == target {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if e, ok := err.(IEqual); ok {
|
||||||
|
return e.Equal(target)
|
||||||
|
}
|
||||||
|
if e, ok := target.(IEqual); ok {
|
||||||
|
return e.Equal(err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is reports whether current error `err` has error `target` in its chaining errors.
|
||||||
|
// It is just for implements for stdlib errors.Is from Go version 1.17.
|
||||||
|
func Is(err, target error) bool {
|
||||||
|
if e, ok := err.(IIs); ok {
|
||||||
|
return e.Is(target)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasError is alias of Is, which more easily understanding semantics.
|
||||||
|
func HasError(err, target error) bool {
|
||||||
|
return Is(err, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// callers returns the stack callers.
|
||||||
|
// Note that it here just retrieves the caller memory address array not the caller information.
|
||||||
|
func callers(skip ...int) stack {
|
||||||
|
var (
|
||||||
|
pcs [maxStackDepth]uintptr
|
||||||
|
n = 3
|
||||||
|
)
|
||||||
|
if len(skip) > 0 {
|
||||||
|
n += skip[0]
|
||||||
|
}
|
||||||
|
return pcs[:runtime.Callers(n, pcs[:])]
|
||||||
|
}
|
||||||
@ -68,7 +68,7 @@ func (err *Error) Cause() error {
|
|||||||
if e, ok := loop.error.(*Error); ok {
|
if e, ok := loop.error.(*Error); ok {
|
||||||
// Internal Error struct.
|
// Internal Error struct.
|
||||||
loop = e
|
loop = e
|
||||||
} else if e, ok := loop.error.(iCause); ok {
|
} else if e, ok := loop.error.(ICause); ok {
|
||||||
// Other Error that implements ApiCause interface.
|
// Other Error that implements ApiCause interface.
|
||||||
return e.Cause()
|
return e.Cause()
|
||||||
} else {
|
} else {
|
||||||
@ -76,6 +76,7 @@ func (err *Error) Cause() error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// return loop
|
// return loop
|
||||||
|
//
|
||||||
// To be compatible with Case of https://github.com/pkg/errors.
|
// To be compatible with Case of https://github.com/pkg/errors.
|
||||||
return errors.New(loop.text)
|
return errors.New(loop.text)
|
||||||
}
|
}
|
||||||
@ -97,21 +98,15 @@ func (err *Error) Current() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next returns the next level error.
|
// Unwrap is alias of function `Next`.
|
||||||
// It returns nil if current level error or the next level error is nil.
|
// It is just for implements for stdlib errors.Unwrap from Go version 1.17.
|
||||||
func (err *Error) Next() error {
|
func (err *Error) Unwrap() error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err.error
|
return err.error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap is alias of function `Next`.
|
|
||||||
// It is just for implements for stdlib errors.Unwrap from Go version 1.17.
|
|
||||||
func (err *Error) Unwrap() error {
|
|
||||||
return err.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal reports whether current error `err` equals to error `target`.
|
// Equal reports whether current error `err` equals to error `target`.
|
||||||
// Please note that, in default comparison for `Error`,
|
// Please note that, in default comparison for `Error`,
|
||||||
// the errors are considered the same if both the `code` and `text` of them are the same.
|
// the errors are considered the same if both the `code` and `text` of them are the same.
|
||||||
@ -132,19 +127,19 @@ func (err *Error) Equal(target error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Is reports whether current error `err` has error `target` in its chaining errors.
|
// Is reports whether current error `err` has error `target` in its chaining errors.
|
||||||
// It is just for implements for stdlib errors.Unwrap from Go version 1.17.
|
// It is just for implements for stdlib errors.Is from Go version 1.17.
|
||||||
func (err *Error) Is(target error) bool {
|
func (err *Error) Is(target error) bool {
|
||||||
if Equal(err, target) {
|
if Equal(err, target) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
nextErr := err.Next()
|
nextErr := err.Unwrap()
|
||||||
if nextErr == nil {
|
if nextErr == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if Equal(nextErr, target) {
|
if Equal(nextErr, target) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if e, ok := nextErr.(iIs); ok {
|
if e, ok := nextErr.(IIs); ok {
|
||||||
return e.Is(target)
|
return e.Is(target)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -17,7 +17,7 @@ func (err *Error) Code() gcode.Code {
|
|||||||
return gcode.CodeNil
|
return gcode.CodeNil
|
||||||
}
|
}
|
||||||
if err.code == gcode.CodeNil {
|
if err.code == gcode.CodeNil {
|
||||||
return Code(err.Next())
|
return Code(err.Unwrap())
|
||||||
}
|
}
|
||||||
return err.code
|
return err.code
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,57 +0,0 @@
|
|||||||
// 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 gerror
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gogf/gf/v2/errors/gcode"
|
|
||||||
)
|
|
||||||
|
|
||||||
// iIs is the interface for Is feature.
|
|
||||||
type iIs interface {
|
|
||||||
Is(target error) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// iEqual is the interface for Equal feature.
|
|
||||||
type iEqual interface {
|
|
||||||
Equal(target error) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// iCode is the interface for Code feature.
|
|
||||||
type iCode interface {
|
|
||||||
Error() string
|
|
||||||
Code() gcode.Code
|
|
||||||
}
|
|
||||||
|
|
||||||
// iStack is the interface for Stack feature.
|
|
||||||
type iStack interface {
|
|
||||||
Error() string
|
|
||||||
Stack() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// iCause is the interface for Cause feature.
|
|
||||||
type iCause interface {
|
|
||||||
Error() string
|
|
||||||
Cause() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// iCurrent is the interface for Current feature.
|
|
||||||
type iCurrent interface {
|
|
||||||
Error() string
|
|
||||||
Current() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// iNext is the interface for Next feature.
|
|
||||||
type iNext interface {
|
|
||||||
Error() string
|
|
||||||
Next() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// iUnwrap is the interface for Unwrap feature.
|
|
||||||
type iUnwrap interface {
|
|
||||||
Error() string
|
|
||||||
Unwrap() error
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
// 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 gerror
|
|
||||||
|
|
||||||
import "runtime"
|
|
||||||
|
|
||||||
// stack represents a stack of program counters.
|
|
||||||
type stack []uintptr
|
|
||||||
|
|
||||||
const (
|
|
||||||
// maxStackDepth marks the max stack depth for error back traces.
|
|
||||||
maxStackDepth = 32
|
|
||||||
)
|
|
||||||
|
|
||||||
// callers returns the stack callers.
|
|
||||||
// Note that it here just retrieves the caller memory address array not the caller information.
|
|
||||||
func callers(skip ...int) stack {
|
|
||||||
var (
|
|
||||||
pcs [maxStackDepth]uintptr
|
|
||||||
n = 3
|
|
||||||
)
|
|
||||||
if len(skip) > 0 {
|
|
||||||
n += skip[0]
|
|
||||||
}
|
|
||||||
return pcs[:runtime.Callers(n, pcs[:])]
|
|
||||||
}
|
|
||||||
@ -237,24 +237,6 @@ func Test_Current(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_Next(t *testing.T) {
|
|
||||||
gtest.C(t, func(t *gtest.T) {
|
|
||||||
err := errors.New("1")
|
|
||||||
err = gerror.Wrap(err, "2")
|
|
||||||
err = gerror.Wrap(err, "3")
|
|
||||||
t.Assert(err.Error(), "3: 2: 1")
|
|
||||||
|
|
||||||
err = gerror.Next(err)
|
|
||||||
t.Assert(err.Error(), "2: 1")
|
|
||||||
|
|
||||||
err = gerror.Next(err)
|
|
||||||
t.Assert(err.Error(), "1")
|
|
||||||
|
|
||||||
err = gerror.Next(err)
|
|
||||||
t.AssertNil(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Unwrap(t *testing.T) {
|
func Test_Unwrap(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
err := errors.New("1")
|
err := errors.New("1")
|
||||||
@ -381,3 +363,25 @@ func Test_Is(t *testing.T) {
|
|||||||
t.Assert(gerror.Is(err2, err1), true)
|
t.Assert(gerror.Is(err2, err1), true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_HashError(t *testing.T) {
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
err1 := errors.New("1")
|
||||||
|
err2 := gerror.Wrap(err1, "2")
|
||||||
|
err2 = gerror.Wrap(err2, "3")
|
||||||
|
t.Assert(gerror.HasError(err2, err1), true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HashCode(t *testing.T) {
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
err1 := errors.New("1")
|
||||||
|
err2 := gerror.WrapCode(gcode.CodeNotAuthorized, err1, "2")
|
||||||
|
err3 := gerror.Wrap(err2, "3")
|
||||||
|
err4 := gerror.Wrap(err3, "4")
|
||||||
|
t.Assert(gerror.HasCode(err1, gcode.CodeNotAuthorized), false)
|
||||||
|
t.Assert(gerror.HasCode(err2, gcode.CodeNotAuthorized), true)
|
||||||
|
t.Assert(gerror.HasCode(err3, gcode.CodeNotAuthorized), true)
|
||||||
|
t.Assert(gerror.HasCode(err4, gcode.CodeNotAuthorized), true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -100,6 +100,7 @@ func doPrint(ctx context.Context, content string, stack bool) {
|
|||||||
buffer.WriteString(content)
|
buffer.WriteString(content)
|
||||||
buffer.WriteString("\n")
|
buffer.WriteString("\n")
|
||||||
if stack {
|
if stack {
|
||||||
|
buffer.WriteString("Caller Stack:\n")
|
||||||
buffer.WriteString(gdebug.StackWithFilter([]string{stackFilterKey}))
|
buffer.WriteString(gdebug.StackWithFilter([]string{stackFilterKey}))
|
||||||
}
|
}
|
||||||
fmt.Print(buffer.String())
|
fmt.Print(buffer.String())
|
||||||
|
|||||||
@ -12,6 +12,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/errors/gcode"
|
||||||
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/container/gmap"
|
"github.com/gogf/gf/v2/container/gmap"
|
||||||
@ -98,6 +100,9 @@ type (
|
|||||||
// Listening file descriptor mapping.
|
// Listening file descriptor mapping.
|
||||||
// The key is either "http" or "https" and the value is its FD.
|
// The key is either "http" or "https" and the value is its FD.
|
||||||
listenerFdMap = map[string]string
|
listenerFdMap = map[string]string
|
||||||
|
|
||||||
|
// internalPanic is the custom panic for internal usage.
|
||||||
|
internalPanic string
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -119,9 +124,6 @@ const (
|
|||||||
const (
|
const (
|
||||||
supportedHttpMethods = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
|
supportedHttpMethods = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
|
||||||
defaultMethod = "ALL"
|
defaultMethod = "ALL"
|
||||||
exceptionExit = "exit"
|
|
||||||
exceptionExitAll = "exit_all"
|
|
||||||
exceptionExitHook = "exit_hook"
|
|
||||||
routeCacheDuration = time.Hour
|
routeCacheDuration = time.Hour
|
||||||
ctxKeyForRequest = "gHttpRequestObject"
|
ctxKeyForRequest = "gHttpRequestObject"
|
||||||
contentTypeXml = "text/xml"
|
contentTypeXml = "text/xml"
|
||||||
@ -135,6 +137,12 @@ const (
|
|||||||
gracefulShutdownTimeout = 5 * time.Second
|
gracefulShutdownTimeout = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
exceptionExit internalPanic = "exit"
|
||||||
|
exceptionExitAll internalPanic = "exit_all"
|
||||||
|
exceptionExitHook internalPanic = "exit_hook"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// methodsMap stores all supported HTTP method.
|
// methodsMap stores all supported HTTP method.
|
||||||
// It is used for quick HTTP method searching using map.
|
// It is used for quick HTTP method searching using map.
|
||||||
@ -169,3 +177,10 @@ var (
|
|||||||
// defaultValueTags are the struct tag names for default value storing.
|
// defaultValueTags are the struct tag names for default value storing.
|
||||||
defaultValueTags = []string{"d", "default"}
|
defaultValueTags = []string{"d", "default"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNeedJsonBody = gerror.NewOption(gerror.Option{
|
||||||
|
Text: "the request body content should be JSON format",
|
||||||
|
Code: gcode.CodeInvalidRequest,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|||||||
23
net/ghttp/ghttp_middleware_json_body.go
Normal file
23
net/ghttp/ghttp_middleware_json_body.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// 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 ghttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gogf/gf/v2/internal/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MiddlewareJsonBody validates and returns request body whether JSON format.
|
||||||
|
func MiddlewareJsonBody(r *Request) {
|
||||||
|
requestBody := r.GetBody()
|
||||||
|
if len(requestBody) > 0 {
|
||||||
|
if !json.Valid(requestBody) {
|
||||||
|
r.SetError(ErrNeedJsonBody)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.Middleware.Next()
|
||||||
|
}
|
||||||
@ -263,7 +263,7 @@ func (r *Request) SetError(err error) {
|
|||||||
|
|
||||||
// ReloadParam is used for modifying request parameter.
|
// ReloadParam is used for modifying request parameter.
|
||||||
// Sometimes, we want to modify request parameters through middleware, but directly modifying Request.Body
|
// Sometimes, we want to modify request parameters through middleware, but directly modifying Request.Body
|
||||||
// is invalid, so it clears the parsed* marks to make the parameters re-parsed.
|
// is invalid, so it clears the parsed* marks of Request to make the parameters reparsed.
|
||||||
func (r *Request) ReloadParam() {
|
func (r *Request) ReloadParam() {
|
||||||
r.parsedBody = false
|
r.parsedBody = false
|
||||||
r.parsedForm = false
|
r.parsedForm = false
|
||||||
|
|||||||
@ -148,6 +148,11 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
if request.Response.Status == 0 {
|
if request.Response.Status == 0 {
|
||||||
if request.StaticFile != nil || request.Middleware.served || request.Response.buffer.Len() > 0 {
|
if request.StaticFile != nil || request.Middleware.served || request.Response.buffer.Len() > 0 {
|
||||||
request.Response.WriteHeader(http.StatusOK)
|
request.Response.WriteHeader(http.StatusOK)
|
||||||
|
} else if err := request.GetError(); err != nil {
|
||||||
|
if request.Response.BufferLength() == 0 {
|
||||||
|
request.Response.Write(err.Error())
|
||||||
|
}
|
||||||
|
request.Response.WriteHeader(http.StatusInternalServerError)
|
||||||
} else {
|
} else {
|
||||||
request.Response.WriteHeader(http.StatusNotFound)
|
request.Response.WriteHeader(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -674,3 +674,26 @@ func Test_Middleware_Panic(t *testing.T) {
|
|||||||
t.Assert(client.GetContent(ctx, "/"), "exception recovered: error")
|
t.Assert(client.GetContent(ctx, "/"), "exception recovered: error")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_Middleware_JsonBody(t *testing.T) {
|
||||||
|
s := g.Server(guid.S())
|
||||||
|
s.Group("/", func(group *ghttp.RouterGroup) {
|
||||||
|
group.Middleware(ghttp.MiddlewareJsonBody)
|
||||||
|
group.ALL("/", func(r *ghttp.Request) {
|
||||||
|
r.Response.Write("hello")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
s.SetDumpRouterMap(false)
|
||||||
|
s.Start()
|
||||||
|
defer s.Shutdown()
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
client := g.Client()
|
||||||
|
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
|
||||||
|
|
||||||
|
t.Assert(client.GetContent(ctx, "/"), "hello")
|
||||||
|
t.Assert(client.PutContent(ctx, "/"), "hello")
|
||||||
|
t.Assert(client.PutContent(ctx, "/", `{"name":"john"}`), "hello")
|
||||||
|
t.Assert(client.PutContent(ctx, "/", `{"name":}`), "the request body content should be JSON format")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -202,6 +202,9 @@ func (oai *OpenApiV3) golangTypeToOAIFormat(t reflect.Type) string {
|
|||||||
return FormatBinary
|
return FormatBinary
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
if oai.isEmbeddedStructDefinition(t) {
|
||||||
|
return `EmbeddedStructDefinition`
|
||||||
|
}
|
||||||
return format
|
return format
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -311,6 +311,12 @@ func (oai *OpenApiV3) removeOperationDuplicatedProperties(operation Operation) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, requestBodyContent := range operation.RequestBody.Value.Content {
|
for _, requestBodyContent := range operation.RequestBody.Value.Content {
|
||||||
|
|
||||||
|
// Check request body schema
|
||||||
|
if requestBodyContent.Schema == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Check request body schema ref.
|
// Check request body schema ref.
|
||||||
if schema := oai.Components.Schemas.Get(requestBodyContent.Schema.Ref); schema != nil {
|
if schema := oai.Components.Schemas.Get(requestBodyContent.Schema.Ref); schema != nil {
|
||||||
schema.Value.Required = oai.removeItemsFromArray(schema.Value.Required, duplicatedParameterNames)
|
schema.Value.Required = oai.removeItemsFromArray(schema.Value.Required, duplicatedParameterNames)
|
||||||
|
|||||||
@ -174,8 +174,11 @@ func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var fieldName = structField.Name()
|
var fieldName = structField.Name()
|
||||||
if jsonName := structField.TagJsonName(); jsonName != "" {
|
for _, tagName := range gconv.StructTagPriority {
|
||||||
fieldName = jsonName
|
if tagValue := structField.Tag(tagName); tagValue != "" {
|
||||||
|
fieldName = tagValue
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
schemaRef, err := oai.newSchemaRefWithGolangType(
|
schemaRef, err := oai.newSchemaRefWithGolangType(
|
||||||
structField.Type().Type,
|
structField.Type().Type,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/internal/json"
|
"github.com/gogf/gf/v2/internal/json"
|
||||||
|
"github.com/gogf/gf/v2/text/gstr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SchemaRefs []SchemaRef
|
type SchemaRefs []SchemaRef
|
||||||
@ -19,8 +20,25 @@ type SchemaRef struct {
|
|||||||
Value *Schema
|
Value *Schema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isEmbeddedStructDefine checks and returns whether given golang type is embedded struct definition, like:
|
||||||
|
// struct A struct{
|
||||||
|
// B struct{
|
||||||
|
// // ...
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// The `B` in `A` is called `embedded struct definition`.
|
||||||
|
func (oai *OpenApiV3) isEmbeddedStructDefinition(golangType reflect.Type) bool {
|
||||||
|
s := golangType.String()
|
||||||
|
if gstr.Contains(s, `struct {`) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSchemaRefWithGolangType creates a new Schema and returns its SchemaRef.
|
||||||
func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap map[string]string) (*SchemaRef, error) {
|
func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap map[string]string) (*SchemaRef, error) {
|
||||||
var (
|
var (
|
||||||
|
err error
|
||||||
oaiType = oai.golangTypeToOAIType(golangType)
|
oaiType = oai.golangTypeToOAIType(golangType)
|
||||||
oaiFormat = oai.golangTypeToOAIFormat(golangType)
|
oaiFormat = oai.golangTypeToOAIFormat(golangType)
|
||||||
schemaRef = &SchemaRef{}
|
schemaRef = &SchemaRef{}
|
||||||
@ -85,15 +103,24 @@ func (oai *OpenApiV3) newSchemaRefWithGolangType(golangType reflect.Type, tagMap
|
|||||||
schemaRef.Value = nil
|
schemaRef.Value = nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Normal struct object.
|
golangTypeInstance := reflect.New(golangType).Elem().Interface()
|
||||||
var structTypeName = oai.golangTypeToSchemaName(golangType)
|
if oai.isEmbeddedStructDefinition(golangType) {
|
||||||
if oai.Components.Schemas.Get(structTypeName) == nil {
|
schema, err = oai.structToSchema(golangTypeInstance)
|
||||||
if err := oai.addSchema(reflect.New(golangType).Elem().Interface()); err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
schemaRef.Ref = ""
|
||||||
|
schemaRef.Value = schema
|
||||||
|
} else {
|
||||||
|
var structTypeName = oai.golangTypeToSchemaName(golangType)
|
||||||
|
if oai.Components.Schemas.Get(structTypeName) == nil {
|
||||||
|
if err := oai.addSchema(golangTypeInstance); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schemaRef.Ref = structTypeName
|
||||||
|
schemaRef.Value = nil
|
||||||
}
|
}
|
||||||
schemaRef.Ref = structTypeName
|
|
||||||
schemaRef.Value = nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return schemaRef, nil
|
return schemaRef, nil
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/internal/json"
|
||||||
"github.com/gogf/gf/v2/net/goai"
|
"github.com/gogf/gf/v2/net/goai"
|
||||||
"github.com/gogf/gf/v2/test/gtest"
|
"github.com/gogf/gf/v2/test/gtest"
|
||||||
"github.com/gogf/gf/v2/util/gmeta"
|
"github.com/gogf/gf/v2/util/gmeta"
|
||||||
@ -936,3 +937,66 @@ func Test_EnumOfSchemaItems(t *testing.T) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_AliasNameOfAttribute(t *testing.T) {
|
||||||
|
type CreateResourceReq struct {
|
||||||
|
gmeta.Meta `path:"/CreateResourceReq" method:"POST"`
|
||||||
|
Name string `p:"n"`
|
||||||
|
Age string `json:"a"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
t.Assert(
|
||||||
|
oai.Components.Schemas.Get(`github.com.gogf.gf.v2.net.goai_test.CreateResourceReq`).Value.
|
||||||
|
Properties.Get(`Name`), nil,
|
||||||
|
)
|
||||||
|
t.Assert(
|
||||||
|
oai.Components.Schemas.Get(`github.com.gogf.gf.v2.net.goai_test.CreateResourceReq`).Value.
|
||||||
|
Properties.Get(`Age`), nil,
|
||||||
|
)
|
||||||
|
t.AssertNE(
|
||||||
|
oai.Components.Schemas.Get(`github.com.gogf.gf.v2.net.goai_test.CreateResourceReq`).Value.
|
||||||
|
Properties.Get(`n`), nil,
|
||||||
|
)
|
||||||
|
t.AssertNE(
|
||||||
|
oai.Components.Schemas.Get(`github.com.gogf.gf.v2.net.goai_test.CreateResourceReq`).Value.
|
||||||
|
Properties.Get(`a`), nil,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_EmbeddedStructAttribute(t *testing.T) {
|
||||||
|
type CreateResourceReq struct {
|
||||||
|
gmeta.Meta `path:"/CreateResourceReq" method:"POST"`
|
||||||
|
Name string `dc:"This is name."`
|
||||||
|
Embedded struct {
|
||||||
|
Age uint `dc:"This is embedded age."`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
b, err := json.Marshal(oai)
|
||||||
|
t.AssertNil(err)
|
||||||
|
t.Assert(b, `{"openapi":"3.0.0","components":{"schemas":{"github.com.gogf.gf.v2.net.goai_test.CreateResourceReq":{"properties":{"Name":{"description":"This is name.","format":"string","properties":{},"type":"string"},"Embedded":{"properties":{"Age":{"description":"This is embedded age.","format":"uint","properties":{},"type":"integer"}},"type":"object"}},"type":"object"}}},"info":{"title":"","version":""},"paths":null}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -71,14 +71,6 @@ func (lru *adapterMemoryLru) Pop() interface{} {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print is used for test only.
|
|
||||||
// func (lru *adapterMemoryLru) Print() {
|
|
||||||
// for _, v := range lru.list.FrontAll() {
|
|
||||||
// fmt.Printf("%v ", v)
|
|
||||||
// }
|
|
||||||
// fmt.Println()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// SyncAndClear synchronizes the keys from `rawList` to `list` and `data`
|
// SyncAndClear synchronizes the keys from `rawList` to `list` and `data`
|
||||||
// using Least Recently Used algorithm.
|
// using Least Recently Used algorithm.
|
||||||
func (lru *adapterMemoryLru) SyncAndClear(ctx context.Context) {
|
func (lru *adapterMemoryLru) SyncAndClear(ctx context.Context) {
|
||||||
@ -87,9 +79,7 @@ func (lru *adapterMemoryLru) SyncAndClear(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Data synchronization.
|
// Data synchronization.
|
||||||
var (
|
var alreadyExistItem interface{}
|
||||||
alreadyExistItem interface{}
|
|
||||||
)
|
|
||||||
for {
|
for {
|
||||||
if rawListItem := lru.rawList.PopFront(); rawListItem != nil {
|
if rawListItem := lru.rawList.PopFront(); rawListItem != nil {
|
||||||
// Deleting the key from list.
|
// Deleting the key from list.
|
||||||
@ -104,9 +94,9 @@ func (lru *adapterMemoryLru) SyncAndClear(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Data cleaning up.
|
// Data cleaning up.
|
||||||
for i := lru.Size() - lru.cache.cap; i > 0; i-- {
|
for clearLength := lru.Size() - lru.cache.cap; clearLength > 0; clearLength-- {
|
||||||
if s := lru.Pop(); s != nil {
|
if topKey := lru.Pop(); topKey != nil {
|
||||||
lru.cache.clearByKey(s, true)
|
lru.cache.clearByKey(topKey, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,9 +28,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tagNameDc = `dc`
|
tagNameDc = `dc` // description.
|
||||||
tagNameAd = `ad`
|
tagNameAd = `ad` // additional
|
||||||
tagNameEg = `eg`
|
tagNameEg = `eg` // examples.
|
||||||
tagNameArg = `arg`
|
tagNameArg = `arg`
|
||||||
tagNameRoot = `root`
|
tagNameRoot = `root`
|
||||||
)
|
)
|
||||||
@ -61,7 +61,7 @@ func NewFromObject(object interface{}) (rootCmd *Command, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Root command creating.
|
// Root command creating.
|
||||||
rootCmd, err = newCommandFromObjectMeta(object)
|
rootCmd, err = newCommandFromObjectMeta(object, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -76,17 +76,19 @@ func NewFromObject(object interface{}) (rootCmd *Command, err error) {
|
|||||||
}
|
}
|
||||||
for i := 0; i < reflectValue.NumMethod(); i++ {
|
for i := 0; i < reflectValue.NumMethod(); i++ {
|
||||||
var (
|
var (
|
||||||
method = reflectValue.Method(i)
|
method = reflectValue.Type().Method(i)
|
||||||
methodCmd *Command
|
methodValue = reflectValue.Method(i)
|
||||||
|
methodType = methodValue.Type()
|
||||||
|
methodCmd *Command
|
||||||
)
|
)
|
||||||
methodCmd, err = newCommandFromMethod(object, method)
|
methodCmd, err = newCommandFromMethod(object, method, methodValue, methodType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if nameSet.Contains(methodCmd.Name) {
|
if nameSet.Contains(methodCmd.Name) {
|
||||||
err = gerror.Newf(
|
err = gerror.Newf(
|
||||||
`command name should be unique, found duplicated command name in method "%s"`,
|
`command name should be unique, found duplicated command name in method "%s"`,
|
||||||
method.Type().String(),
|
methodType.String(),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -135,27 +137,23 @@ func methodToRootCmdWhenNameEqual(rootCmd *Command, methodCmd *Command) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCommandFromObjectMeta(object interface{}) (command *Command, err error) {
|
// The `object` is the Meta attribute from business object, and the `name` is the command name,
|
||||||
var (
|
// commonly from method name, which is used when no name tag is defined in Meta.
|
||||||
metaData = gmeta.Data(object)
|
func newCommandFromObjectMeta(object interface{}, name string) (command *Command, err error) {
|
||||||
)
|
var metaData = gmeta.Data(object)
|
||||||
if len(metaData) == 0 {
|
|
||||||
err = gerror.Newf(
|
|
||||||
`no meta data found in struct "%s"`,
|
|
||||||
reflect.TypeOf(object).String(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = gconv.Scan(metaData, &command); err != nil {
|
if err = gconv.Scan(metaData, &command); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Name filed is necessary.
|
// Name filed is necessary.
|
||||||
if command.Name == "" {
|
if command.Name == "" {
|
||||||
err = gerror.Newf(
|
if name == "" {
|
||||||
`command name cannot be empty, "name" tag not found in meta of struct "%s"`,
|
err = gerror.Newf(
|
||||||
reflect.TypeOf(object).String(),
|
`command name cannot be empty, "name" tag not found in meta of struct "%s"`,
|
||||||
)
|
reflect.TypeOf(object).String(),
|
||||||
return
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
command.Name = name
|
||||||
}
|
}
|
||||||
if command.Description == "" {
|
if command.Description == "" {
|
||||||
command.Description = metaData[tagNameDc]
|
command.Description = metaData[tagNameDc]
|
||||||
@ -169,71 +167,70 @@ func newCommandFromObjectMeta(object interface{}) (command *Command, err error)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCommandFromMethod(object interface{}, method reflect.Value) (command *Command, err error) {
|
func newCommandFromMethod(
|
||||||
var (
|
object interface{}, method reflect.Method, methodValue reflect.Value, methodType reflect.Type,
|
||||||
reflectType = method.Type()
|
) (command *Command, err error) {
|
||||||
)
|
|
||||||
// Necessary validation for input/output parameters and naming.
|
// Necessary validation for input/output parameters and naming.
|
||||||
if reflectType.NumIn() != 2 || reflectType.NumOut() != 2 {
|
if methodType.NumIn() != 2 || methodType.NumOut() != 2 {
|
||||||
if reflectType.PkgPath() != "" {
|
if methodType.PkgPath() != "" {
|
||||||
err = gerror.NewCodef(
|
err = gerror.NewCodef(
|
||||||
gcode.CodeInvalidParameter,
|
gcode.CodeInvalidParameter,
|
||||||
`invalid command: %s.%s.%s defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
|
`invalid command: %s.%s.%s defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
|
||||||
reflectType.PkgPath(), reflect.TypeOf(object).Name(), reflectType.Name(), reflectType.String(),
|
methodType.PkgPath(), reflect.TypeOf(object).Name(), methodType.Name(), methodType.String(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
err = gerror.NewCodef(
|
err = gerror.NewCodef(
|
||||||
gcode.CodeInvalidParameter,
|
gcode.CodeInvalidParameter,
|
||||||
`invalid command: defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
|
`invalid command: defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
|
||||||
reflectType.String(),
|
methodType.String(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if reflectType.In(0).String() != "context.Context" {
|
if methodType.In(0).String() != "context.Context" {
|
||||||
err = gerror.NewCodef(
|
err = gerror.NewCodef(
|
||||||
gcode.CodeInvalidParameter,
|
gcode.CodeInvalidParameter,
|
||||||
`invalid command: defined as "%s", but the first input parameter should be type of "context.Context"`,
|
`invalid command: defined as "%s", but the first input parameter should be type of "context.Context"`,
|
||||||
reflectType.String(),
|
methodType.String(),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if reflectType.Out(1).String() != "error" {
|
if methodType.Out(1).String() != "error" {
|
||||||
err = gerror.NewCodef(
|
err = gerror.NewCodef(
|
||||||
gcode.CodeInvalidParameter,
|
gcode.CodeInvalidParameter,
|
||||||
`invalid command: defined as "%s", but the last output parameter should be type of "error"`,
|
`invalid command: defined as "%s", but the last output parameter should be type of "error"`,
|
||||||
reflectType.String(),
|
methodType.String(),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// The input struct should be named as `xxxInput`.
|
// The input struct should be named as `xxxInput`.
|
||||||
if !gstr.HasSuffix(reflectType.In(1).String(), `Input`) {
|
if !gstr.HasSuffix(methodType.In(1).String(), `Input`) {
|
||||||
err = gerror.NewCodef(
|
err = gerror.NewCodef(
|
||||||
gcode.CodeInvalidParameter,
|
gcode.CodeInvalidParameter,
|
||||||
`invalid struct naming for input: defined as "%s", but it should be named with "Input" suffix like "xxxInput"`,
|
`invalid struct naming for input: defined as "%s", but it should be named with "Input" suffix like "xxxInput"`,
|
||||||
reflectType.In(1).String(),
|
methodType.In(1).String(),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// The output struct should be named as `xxxOutput`.
|
// The output struct should be named as `xxxOutput`.
|
||||||
if !gstr.HasSuffix(reflectType.Out(0).String(), `Output`) {
|
if !gstr.HasSuffix(methodType.Out(0).String(), `Output`) {
|
||||||
err = gerror.NewCodef(
|
err = gerror.NewCodef(
|
||||||
gcode.CodeInvalidParameter,
|
gcode.CodeInvalidParameter,
|
||||||
`invalid struct naming for output: defined as "%s", but it should be named with "Output" suffix like "xxxOutput"`,
|
`invalid struct naming for output: defined as "%s", but it should be named with "Output" suffix like "xxxOutput"`,
|
||||||
reflectType.Out(0).String(),
|
methodType.Out(0).String(),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var inputObject reflect.Value
|
var inputObject reflect.Value
|
||||||
if method.Type().In(1).Kind() == reflect.Ptr {
|
if methodType.In(1).Kind() == reflect.Ptr {
|
||||||
inputObject = reflect.New(method.Type().In(1).Elem()).Elem()
|
inputObject = reflect.New(methodType.In(1).Elem()).Elem()
|
||||||
} else {
|
} else {
|
||||||
inputObject = reflect.New(method.Type().In(1)).Elem()
|
inputObject = reflect.New(methodType.In(1)).Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command creating.
|
// Command creating.
|
||||||
if command, err = newCommandFromObjectMeta(inputObject.Interface()); err != nil {
|
if command, err = newCommandFromObjectMeta(inputObject.Interface(), method.Name); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,7 +309,7 @@ func newCommandFromMethod(object interface{}, method reflect.Value) (command *Co
|
|||||||
inputValues = append(inputValues, inputObject)
|
inputValues = append(inputValues, inputObject)
|
||||||
|
|
||||||
// Call handler with dynamic created parameter values.
|
// Call handler with dynamic created parameter values.
|
||||||
results := method.Call(inputValues)
|
results := methodValue.Call(inputValues)
|
||||||
out = results[0].Interface()
|
out = results[0].Interface()
|
||||||
if !results[1].IsNil() {
|
if !results[1].IsNil() {
|
||||||
if v, ok := results[1].Interface().(error); ok {
|
if v, ok := results[1].Interface().(error); ok {
|
||||||
|
|||||||
49
os/gcmd/gcmd_z_unit_feature_object4_test.go
Normal file
49
os/gcmd/gcmd_z_unit_feature_object4_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 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 gcmd_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/os/gcmd"
|
||||||
|
"github.com/gogf/gf/v2/os/gctx"
|
||||||
|
"github.com/gogf/gf/v2/test/gtest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestNoNameTagCase struct {
|
||||||
|
g.Meta `name:"root"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestNoNameTagCaseRootInput struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
type TestNoNameTagCaseRootOutput struct {
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TestNoNameTagCase) TEST(ctx context.Context, in TestNoNameTagCaseRootInput) (out *TestNoNameTagCaseRootOutput, err error) {
|
||||||
|
out = &TestNoNameTagCaseRootOutput{
|
||||||
|
Content: in.Name,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Command_NoNameTagCase(t *testing.T) {
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
var ctx = gctx.New()
|
||||||
|
cmd, err := gcmd.NewFromObject(TestNoNameTagCase{})
|
||||||
|
t.AssertNil(err)
|
||||||
|
|
||||||
|
os.Args = []string{"root", "TEST", "-name=john"}
|
||||||
|
value, err := cmd.RunWithValueError(ctx)
|
||||||
|
t.AssertNil(err)
|
||||||
|
t.Assert(value, `{"Content":"john"}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -137,7 +137,7 @@ func (entry *Entry) Close() {
|
|||||||
// gcron.Entry relies on gtimer to implement a scheduled task check for gcron.Entry per second.
|
// gcron.Entry relies on gtimer to implement a scheduled task check for gcron.Entry per second.
|
||||||
func (entry *Entry) checkAndRun(ctx context.Context) {
|
func (entry *Entry) checkAndRun(ctx context.Context) {
|
||||||
currentTime := time.Now()
|
currentTime := time.Now()
|
||||||
if !entry.schedule.checkMeetAndUpdateLastSeconds(currentTime) {
|
if !entry.schedule.checkMeetAndUpdateLastSeconds(ctx, currentTime) {
|
||||||
intlog.Printf(
|
intlog.Printf(
|
||||||
ctx,
|
ctx,
|
||||||
`timely check, current time does not meet cron job "%s"`,
|
`timely check, current time does not meet cron job "%s"`,
|
||||||
|
|||||||
@ -15,23 +15,22 @@ import (
|
|||||||
"github.com/gogf/gf/v2/container/gtype"
|
"github.com/gogf/gf/v2/container/gtype"
|
||||||
"github.com/gogf/gf/v2/errors/gcode"
|
"github.com/gogf/gf/v2/errors/gcode"
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
"github.com/gogf/gf/v2/internal/intlog"
|
|
||||||
"github.com/gogf/gf/v2/os/gtime"
|
"github.com/gogf/gf/v2/os/gtime"
|
||||||
"github.com/gogf/gf/v2/text/gregex"
|
"github.com/gogf/gf/v2/text/gregex"
|
||||||
)
|
)
|
||||||
|
|
||||||
// cronSchedule is the schedule for cron job.
|
// cronSchedule is the schedule for cron job.
|
||||||
type cronSchedule struct {
|
type cronSchedule struct {
|
||||||
create int64 // Created timestamp.
|
createTimestamp int64 // Created timestamp in seconds.
|
||||||
every int64 // Running interval in seconds.
|
everySeconds int64 // Running interval in seconds.
|
||||||
pattern string // The raw cron pattern string.
|
pattern string // The raw cron pattern string.
|
||||||
second map[int]struct{} // Job can run in these second numbers.
|
secondMap map[int]struct{} // Job can run in these second numbers.
|
||||||
minute map[int]struct{} // Job can run in these minute numbers.
|
minuteMap map[int]struct{} // Job can run in these minute numbers.
|
||||||
hour map[int]struct{} // Job can run in these hour numbers.
|
hourMap map[int]struct{} // Job can run in these hour numbers.
|
||||||
day map[int]struct{} // Job can run in these day numbers.
|
dayMap map[int]struct{} // Job can run in these day numbers.
|
||||||
week map[int]struct{} // Job can run in these week numbers.
|
weekMap map[int]struct{} // Job can run in these week numbers.
|
||||||
month map[int]struct{} // Job can run in these moth numbers.
|
monthMap map[int]struct{} // Job can run in these moth numbers.
|
||||||
lastTimestamp *gtype.Int64 // Last timestamp number, for seconds fix.
|
lastTimestamp *gtype.Int64 // Last timestamp number, for timestamp fix in some delay.
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -107,6 +106,7 @@ var (
|
|||||||
|
|
||||||
// newSchedule creates and returns a schedule object for given cron pattern.
|
// newSchedule creates and returns a schedule object for given cron pattern.
|
||||||
func newSchedule(pattern string) (*cronSchedule, error) {
|
func newSchedule(pattern string) (*cronSchedule, error) {
|
||||||
|
var currentTimestamp = time.Now().Unix()
|
||||||
// Check if the predefined patterns.
|
// Check if the predefined patterns.
|
||||||
if match, _ := gregex.MatchString(`(@\w+)\s*(\w*)\s*`, pattern); len(match) > 0 {
|
if match, _ := gregex.MatchString(`(@\w+)\s*(\w*)\s*`, pattern); len(match) > 0 {
|
||||||
key := strings.ToLower(match[1])
|
key := strings.ToLower(match[1])
|
||||||
@ -118,10 +118,10 @@ func newSchedule(pattern string) (*cronSchedule, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &cronSchedule{
|
return &cronSchedule{
|
||||||
create: time.Now().Unix(),
|
createTimestamp: currentTimestamp,
|
||||||
every: int64(d.Seconds()),
|
everySeconds: int64(d.Seconds()),
|
||||||
pattern: pattern,
|
pattern: pattern,
|
||||||
lastTimestamp: gtype.NewInt64(),
|
lastTimestamp: gtype.NewInt64(currentTimestamp),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern: "%s"`, pattern)
|
return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern: "%s"`, pattern)
|
||||||
@ -130,46 +130,46 @@ func newSchedule(pattern string) (*cronSchedule, error) {
|
|||||||
// 0 0 0 1 1 2
|
// 0 0 0 1 1 2
|
||||||
if match, _ := gregex.MatchString(regexForCron, pattern); len(match) == 7 {
|
if match, _ := gregex.MatchString(regexForCron, pattern); len(match) == 7 {
|
||||||
schedule := &cronSchedule{
|
schedule := &cronSchedule{
|
||||||
create: time.Now().Unix(),
|
createTimestamp: currentTimestamp,
|
||||||
every: 0,
|
everySeconds: 0,
|
||||||
pattern: pattern,
|
pattern: pattern,
|
||||||
lastTimestamp: gtype.NewInt64(),
|
lastTimestamp: gtype.NewInt64(currentTimestamp),
|
||||||
}
|
}
|
||||||
// Second.
|
// Second.
|
||||||
if m, err := parsePatternItem(match[1], 0, 59, false); err != nil {
|
if m, err := parsePatternItem(match[1], 0, 59, false); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
schedule.second = m
|
schedule.secondMap = m
|
||||||
}
|
}
|
||||||
// Minute.
|
// Minute.
|
||||||
if m, err := parsePatternItem(match[2], 0, 59, false); err != nil {
|
if m, err := parsePatternItem(match[2], 0, 59, false); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
schedule.minute = m
|
schedule.minuteMap = m
|
||||||
}
|
}
|
||||||
// Hour.
|
// Hour.
|
||||||
if m, err := parsePatternItem(match[3], 0, 23, false); err != nil {
|
if m, err := parsePatternItem(match[3], 0, 23, false); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
schedule.hour = m
|
schedule.hourMap = m
|
||||||
}
|
}
|
||||||
// Day.
|
// Day.
|
||||||
if m, err := parsePatternItem(match[4], 1, 31, true); err != nil {
|
if m, err := parsePatternItem(match[4], 1, 31, true); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
schedule.day = m
|
schedule.dayMap = m
|
||||||
}
|
}
|
||||||
// Month.
|
// Month.
|
||||||
if m, err := parsePatternItem(match[5], 1, 12, false); err != nil {
|
if m, err := parsePatternItem(match[5], 1, 12, false); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
schedule.month = m
|
schedule.monthMap = m
|
||||||
}
|
}
|
||||||
// Week.
|
// Week.
|
||||||
if m, err := parsePatternItem(match[6], 0, 6, true); err != nil {
|
if m, err := parsePatternItem(match[6], 0, 6, true); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
schedule.week = m
|
schedule.weekMap = m
|
||||||
}
|
}
|
||||||
return schedule, nil
|
return schedule, nil
|
||||||
}
|
}
|
||||||
@ -208,6 +208,7 @@ func parsePatternItem(item string, min int, max int, allowQuestionMark bool) (ma
|
|||||||
case 6:
|
case 6:
|
||||||
// It's checking week field.
|
// It's checking week field.
|
||||||
itemType = patternItemTypeWeek
|
itemType = patternItemTypeWeek
|
||||||
|
|
||||||
case 12:
|
case 12:
|
||||||
// It's checking month field.
|
// It's checking month field.
|
||||||
itemType = patternItemTypeMonth
|
itemType = patternItemTypeMonth
|
||||||
@ -268,78 +269,48 @@ func parsePatternItemValue(value string, itemType int) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkMeetAndUpdateLastSeconds checks if the given time `t` meets the runnable point for the job.
|
// checkMeetAndUpdateLastSeconds checks if the given time `t` meets the runnable point for the job.
|
||||||
func (s *cronSchedule) checkMeetAndUpdateLastSeconds(t time.Time) bool {
|
func (s *cronSchedule) checkMeetAndUpdateLastSeconds(ctx context.Context, t time.Time) bool {
|
||||||
if s.every != 0 {
|
if s.everySeconds != 0 {
|
||||||
// It checks using interval.
|
// It checks using interval.
|
||||||
if diff := t.Unix() - s.create; diff > 0 {
|
secondsAfterCreated := t.Unix() - s.createTimestamp
|
||||||
return diff%s.every == 0
|
secondsAfterCreated += int64(s.getFixedTimestampDelta(ctx, t))
|
||||||
|
if secondsAfterCreated > 0 {
|
||||||
|
return secondsAfterCreated%s.everySeconds == 0
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// It checks using normal cron pattern.
|
// It checks using normal cron pattern.
|
||||||
if _, ok := s.second[s.getFixedSecond(t)]; !ok {
|
if _, ok := s.secondMap[s.getFixedSecond(ctx, t)]; !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if _, ok := s.minute[t.Minute()]; !ok {
|
if _, ok := s.minuteMap[t.Minute()]; !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if _, ok := s.hour[t.Hour()]; !ok {
|
if _, ok := s.hourMap[t.Hour()]; !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if _, ok := s.day[t.Day()]; !ok {
|
if _, ok := s.dayMap[t.Day()]; !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if _, ok := s.month[int(t.Month())]; !ok {
|
if _, ok := s.monthMap[int(t.Month())]; !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if _, ok := s.week[int(t.Weekday())]; !ok {
|
if _, ok := s.weekMap[int(t.Weekday())]; !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFixedSecond checks, fixes and returns the seconds that have delay in some seconds.
|
|
||||||
// Reference: https://github.com/golang/go/issues/14410
|
|
||||||
func (s *cronSchedule) getFixedSecond(t time.Time) int {
|
|
||||||
var (
|
|
||||||
second = t.Second()
|
|
||||||
currentTimestamp = t.Unix()
|
|
||||||
lastTimestamp = s.lastTimestamp.Val()
|
|
||||||
)
|
|
||||||
switch {
|
|
||||||
case
|
|
||||||
lastTimestamp == 0,
|
|
||||||
lastTimestamp == currentTimestamp-1:
|
|
||||||
lastTimestamp = currentTimestamp
|
|
||||||
|
|
||||||
case
|
|
||||||
lastTimestamp == currentTimestamp-2,
|
|
||||||
lastTimestamp == currentTimestamp-3,
|
|
||||||
lastTimestamp == currentTimestamp:
|
|
||||||
lastTimestamp += 1
|
|
||||||
second += 1
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Too much delay, let's update the last timestamp to current one.
|
|
||||||
lastTimestamp = currentTimestamp
|
|
||||||
intlog.Printf(
|
|
||||||
context.Background(),
|
|
||||||
`too much delay, last "%d", current "%d"`,
|
|
||||||
lastTimestamp, currentTimestamp,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
second %= 60
|
|
||||||
s.lastTimestamp.Set(lastTimestamp)
|
|
||||||
return second
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next returns the next time this schedule is activated, greater than the given
|
// Next returns the next time this schedule is activated, greater than the given
|
||||||
// time. If no time can be found to satisfy the schedule, return the zero time.
|
// time. If no time can be found to satisfy the schedule, return the zero time.
|
||||||
func (s *cronSchedule) Next(t time.Time) time.Time {
|
func (s *cronSchedule) Next(t time.Time) time.Time {
|
||||||
if s.every != 0 {
|
if s.everySeconds != 0 {
|
||||||
diff := t.Unix() - s.create
|
var (
|
||||||
cnt := diff/s.every + 1
|
diff = t.Unix() - s.createTimestamp
|
||||||
return t.Add(time.Duration(cnt*s.every) * time.Second)
|
count = diff/s.everySeconds + 1
|
||||||
|
)
|
||||||
|
return t.Add(time.Duration(count*s.everySeconds) * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start at the earliest possible time (the upcoming second).
|
// Start at the earliest possible time (the upcoming second).
|
||||||
@ -355,7 +326,7 @@ WRAP:
|
|||||||
return t // who will care the job that run in five years later
|
return t // who will care the job that run in five years later
|
||||||
}
|
}
|
||||||
|
|
||||||
for !s.match(s.month, int(t.Month())) {
|
for !s.match(s.monthMap, int(t.Month())) {
|
||||||
if !added {
|
if !added {
|
||||||
added = true
|
added = true
|
||||||
t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, loc)
|
t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, loc)
|
||||||
@ -387,7 +358,7 @@ WRAP:
|
|||||||
goto WRAP
|
goto WRAP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for !s.match(s.hour, t.Hour()) {
|
for !s.match(s.hourMap, t.Hour()) {
|
||||||
if !added {
|
if !added {
|
||||||
added = true
|
added = true
|
||||||
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, loc)
|
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, loc)
|
||||||
@ -398,7 +369,7 @@ WRAP:
|
|||||||
goto WRAP
|
goto WRAP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for !s.match(s.minute, t.Minute()) {
|
for !s.match(s.minuteMap, t.Minute()) {
|
||||||
if !added {
|
if !added {
|
||||||
added = true
|
added = true
|
||||||
t = t.Truncate(time.Minute)
|
t = t.Truncate(time.Minute)
|
||||||
@ -409,7 +380,7 @@ WRAP:
|
|||||||
goto WRAP
|
goto WRAP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for !s.match(s.second, t.Second()) {
|
for !s.match(s.secondMap, t.Second()) {
|
||||||
if !added {
|
if !added {
|
||||||
added = true
|
added = true
|
||||||
t = t.Truncate(time.Second)
|
t = t.Truncate(time.Second)
|
||||||
@ -425,8 +396,8 @@ WRAP:
|
|||||||
// dayMatches returns true if the schedule's day-of-week and day-of-month
|
// dayMatches returns true if the schedule's day-of-week and day-of-month
|
||||||
// restrictions are satisfied by the given time.
|
// restrictions are satisfied by the given time.
|
||||||
func (s *cronSchedule) dayMatches(t time.Time) bool {
|
func (s *cronSchedule) dayMatches(t time.Time) bool {
|
||||||
_, ok1 := s.day[t.Day()]
|
_, ok1 := s.dayMap[t.Day()]
|
||||||
_, ok2 := s.week[int(t.Weekday())]
|
_, ok2 := s.weekMap[int(t.Weekday())]
|
||||||
return ok1 && ok2
|
return ok1 && ok2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
53
os/gcron/gcron_schedule_fix.go
Normal file
53
os/gcron/gcron_schedule_fix.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// 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 gcron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/internal/intlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getFixedSecond checks, fixes and returns the seconds that have delay fix in some seconds.
|
||||||
|
// Reference: https://github.com/golang/go/issues/14410
|
||||||
|
func (s *cronSchedule) getFixedSecond(ctx context.Context, t time.Time) int {
|
||||||
|
return (t.Second() + s.getFixedTimestampDelta(ctx, t)) % 60
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFixedTimestampDelta checks, fixes and returns the timestamp delta that have delay fix in some seconds.
|
||||||
|
// The tolerated timestamp delay is `3` seconds in default.
|
||||||
|
func (s *cronSchedule) getFixedTimestampDelta(ctx context.Context, t time.Time) int {
|
||||||
|
var (
|
||||||
|
currentTimestamp = t.Unix()
|
||||||
|
lastTimestamp = s.lastTimestamp.Val()
|
||||||
|
delta int
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
case
|
||||||
|
lastTimestamp == currentTimestamp-1:
|
||||||
|
lastTimestamp = currentTimestamp
|
||||||
|
|
||||||
|
case
|
||||||
|
lastTimestamp == currentTimestamp-2,
|
||||||
|
lastTimestamp == currentTimestamp-3,
|
||||||
|
lastTimestamp == currentTimestamp:
|
||||||
|
lastTimestamp += 1
|
||||||
|
delta = 1
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Too much delay, let's update the last timestamp to current one.
|
||||||
|
intlog.Printf(
|
||||||
|
ctx,
|
||||||
|
`too much delay, last timestamp "%d", current "%d"`,
|
||||||
|
lastTimestamp, currentTimestamp,
|
||||||
|
)
|
||||||
|
lastTimestamp = currentTimestamp
|
||||||
|
}
|
||||||
|
s.lastTimestamp.Set(lastTimestamp)
|
||||||
|
return delta
|
||||||
|
}
|
||||||
@ -29,7 +29,7 @@ func TestSlash(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
||||||
}
|
}
|
||||||
t.AssertEQ(sched.week, c.expected)
|
t.AssertEQ(sched.weekMap, c.expected)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -88,6 +88,17 @@ func TestCron_Remove(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCron_Add_FixedPattern(t *testing.T) {
|
func TestCron_Add_FixedPattern(t *testing.T) {
|
||||||
|
//debug := utils.IsDebugEnabled()
|
||||||
|
//utils.SetDebugEnabled(true)
|
||||||
|
//defer func() {
|
||||||
|
// utils.SetDebugEnabled(debug)
|
||||||
|
//}()
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
doTestCronAddFixedPattern(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestCronAddFixedPattern(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
var (
|
var (
|
||||||
now = time.Now()
|
now = time.Now()
|
||||||
@ -96,6 +107,8 @@ func TestCron_Add_FixedPattern(t *testing.T) {
|
|||||||
minutes = now.Minute()
|
minutes = now.Minute()
|
||||||
seconds = now.Second() + 2
|
seconds = now.Second() + 2
|
||||||
)
|
)
|
||||||
|
defer cron.Close()
|
||||||
|
|
||||||
if seconds >= 60 {
|
if seconds >= 60 {
|
||||||
seconds %= 60
|
seconds %= 60
|
||||||
minutes++
|
minutes++
|
||||||
|
|||||||
@ -23,8 +23,8 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// processCtx is the context initialized from process environment.
|
processCtx context.Context // processCtx is the context initialized from process environment.
|
||||||
processCtx context.Context
|
initCtx context.Context // initCtx is the context for init function of packages.
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -33,6 +33,9 @@ func init() {
|
|||||||
i := 0
|
i := 0
|
||||||
for _, s := range os.Environ() {
|
for _, s := range os.Environ() {
|
||||||
i = strings.IndexByte(s, '=')
|
i = strings.IndexByte(s, '=')
|
||||||
|
if i == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
m[s[0:i]] = s[i+1:]
|
m[s[0:i]] = s[i+1:]
|
||||||
}
|
}
|
||||||
// OpenTelemetry from environments.
|
// OpenTelemetry from environments.
|
||||||
@ -40,6 +43,8 @@ func init() {
|
|||||||
context.Background(),
|
context.Background(),
|
||||||
propagation.MapCarrier(m),
|
propagation.MapCarrier(m),
|
||||||
)
|
)
|
||||||
|
// Initialize initialization context.
|
||||||
|
initCtx = New()
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates and returns a context which contains context id.
|
// New creates and returns a context which contains context id.
|
||||||
@ -64,3 +69,14 @@ func WithCtx(ctx context.Context) context.Context {
|
|||||||
func CtxId(ctx context.Context) string {
|
func CtxId(ctx context.Context) string {
|
||||||
return gtrace.GetTraceID(ctx)
|
return gtrace.GetTraceID(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetInitCtx sets custom initialization context.
|
||||||
|
// Note that this function cannot be called in multiple goroutines.
|
||||||
|
func SetInitCtx(ctx context.Context) {
|
||||||
|
initCtx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInitCtx returns the initialization context.
|
||||||
|
func GetInitCtx() context.Context {
|
||||||
|
return initCtx
|
||||||
|
}
|
||||||
|
|||||||
@ -30,3 +30,12 @@ func Test_WithCtx(t *testing.T) {
|
|||||||
t.Assert(ctx.Value("TEST"), 1)
|
t.Assert(ctx.Value("TEST"), 1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_SetInitCtx(t *testing.T) {
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
ctx := context.WithValue(context.TODO(), "TEST", 1)
|
||||||
|
gctx.SetInitCtx(ctx)
|
||||||
|
t.AssertNE(gctx.GetInitCtx(), "")
|
||||||
|
t.Assert(gctx.GetInitCtx().Value("TEST"), 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -56,6 +56,9 @@ type Event struct {
|
|||||||
// Op is the bits union for file operations.
|
// Op is the bits union for file operations.
|
||||||
type Op uint32
|
type Op uint32
|
||||||
|
|
||||||
|
// internalPanic is the custom panic for internal usage.
|
||||||
|
type internalPanic string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CREATE Op = 1 << iota
|
CREATE Op = 1 << iota
|
||||||
WRITE
|
WRITE
|
||||||
@ -65,8 +68,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
repeatEventFilterDuration = time.Millisecond // Duration for repeated event filter.
|
repeatEventFilterDuration = time.Millisecond // Duration for repeated event filter.
|
||||||
callbackExitEventPanicStr = "exit" // Custom exit event for internal usage.
|
callbackExitEventPanicStr internalPanic = "exit" // Custom exit event for internal usage.
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@ -136,56 +136,55 @@ func (l *Logger) print(ctx context.Context, level int, stack string, values ...i
|
|||||||
}
|
}
|
||||||
input.handlers = append(input.handlers, defaultPrintHandler)
|
input.handlers = append(input.handlers, defaultPrintHandler)
|
||||||
|
|
||||||
if l.config.HeaderPrint {
|
// Time.
|
||||||
// Time.
|
timeFormat := ""
|
||||||
timeFormat := ""
|
if l.config.Flags&F_TIME_DATE > 0 {
|
||||||
if l.config.Flags&F_TIME_DATE > 0 {
|
timeFormat += "2006-01-02"
|
||||||
timeFormat += "2006-01-02"
|
}
|
||||||
|
if l.config.Flags&F_TIME_TIME > 0 {
|
||||||
|
if timeFormat != "" {
|
||||||
|
timeFormat += " "
|
||||||
}
|
}
|
||||||
if l.config.Flags&F_TIME_TIME > 0 {
|
timeFormat += "15:04:05"
|
||||||
if timeFormat != "" {
|
}
|
||||||
timeFormat += " "
|
if l.config.Flags&F_TIME_MILLI > 0 {
|
||||||
}
|
if timeFormat != "" {
|
||||||
timeFormat += "15:04:05"
|
timeFormat += " "
|
||||||
}
|
|
||||||
if l.config.Flags&F_TIME_MILLI > 0 {
|
|
||||||
if timeFormat != "" {
|
|
||||||
timeFormat += " "
|
|
||||||
}
|
|
||||||
timeFormat += "15:04:05.000"
|
|
||||||
}
|
|
||||||
if len(timeFormat) > 0 {
|
|
||||||
input.TimeFormat = now.Format(timeFormat)
|
|
||||||
}
|
}
|
||||||
|
timeFormat += "15:04:05.000"
|
||||||
|
}
|
||||||
|
if len(timeFormat) > 0 {
|
||||||
|
input.TimeFormat = now.Format(timeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
// Level string.
|
// Level string.
|
||||||
input.LevelFormat = l.GetLevelPrefix(level)
|
input.LevelFormat = l.GetLevelPrefix(level)
|
||||||
|
|
||||||
// Caller path and Fn name.
|
// Caller path and Fn name.
|
||||||
if l.config.Flags&(F_FILE_LONG|F_FILE_SHORT|F_CALLER_FN) > 0 {
|
if l.config.Flags&(F_FILE_LONG|F_FILE_SHORT|F_CALLER_FN) > 0 {
|
||||||
callerFnName, path, line := gdebug.CallerWithFilter(
|
callerFnName, path, line := gdebug.CallerWithFilter(
|
||||||
[]string{utils.StackFilterKeyForGoFrame},
|
[]string{utils.StackFilterKeyForGoFrame},
|
||||||
l.config.StSkip,
|
l.config.StSkip,
|
||||||
)
|
)
|
||||||
if l.config.Flags&F_CALLER_FN > 0 {
|
if l.config.Flags&F_CALLER_FN > 0 {
|
||||||
if len(callerFnName) > 2 {
|
if len(callerFnName) > 2 {
|
||||||
input.CallerFunc = fmt.Sprintf(`[%s]`, callerFnName)
|
input.CallerFunc = fmt.Sprintf(`[%s]`, callerFnName)
|
||||||
}
|
|
||||||
}
|
|
||||||
if line >= 0 && len(path) > 1 {
|
|
||||||
if l.config.Flags&F_FILE_LONG > 0 {
|
|
||||||
input.CallerPath = fmt.Sprintf(`%s:%d:`, path, line)
|
|
||||||
}
|
|
||||||
if l.config.Flags&F_FILE_SHORT > 0 {
|
|
||||||
input.CallerPath = fmt.Sprintf(`%s:%d:`, gfile.Basename(path), line)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Prefix.
|
if line >= 0 && len(path) > 1 {
|
||||||
if len(l.config.Prefix) > 0 {
|
if l.config.Flags&F_FILE_LONG > 0 {
|
||||||
input.Prefix = l.config.Prefix
|
input.CallerPath = fmt.Sprintf(`%s:%d:`, path, line)
|
||||||
|
}
|
||||||
|
if l.config.Flags&F_FILE_SHORT > 0 {
|
||||||
|
input.CallerPath = fmt.Sprintf(`%s:%d:`, gfile.Basename(path), line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Prefix.
|
||||||
|
if len(l.config.Prefix) > 0 {
|
||||||
|
input.Prefix = l.config.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
// Convert value to string.
|
// Convert value to string.
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
// Tracing values.
|
// Tracing values.
|
||||||
|
|||||||
@ -81,17 +81,19 @@ func (in *HandlerInput) String(withColor ...bool) string {
|
|||||||
|
|
||||||
func (in *HandlerInput) getDefaultBuffer(withColor bool) *bytes.Buffer {
|
func (in *HandlerInput) getDefaultBuffer(withColor bool) *bytes.Buffer {
|
||||||
buffer := bytes.NewBuffer(nil)
|
buffer := bytes.NewBuffer(nil)
|
||||||
if in.TimeFormat != "" {
|
if in.Logger.config.HeaderPrint {
|
||||||
buffer.WriteString(in.TimeFormat)
|
if in.TimeFormat != "" {
|
||||||
}
|
buffer.WriteString(in.TimeFormat)
|
||||||
if in.LevelFormat != "" {
|
}
|
||||||
var levelStr = "[" + in.LevelFormat + "]"
|
if in.LevelFormat != "" {
|
||||||
if withColor {
|
var levelStr = "[" + in.LevelFormat + "]"
|
||||||
in.addStringToBuffer(buffer, in.Logger.getColoredStr(
|
if withColor {
|
||||||
in.Logger.getColorByLevel(in.Level), levelStr,
|
in.addStringToBuffer(buffer, in.Logger.getColoredStr(
|
||||||
))
|
in.Logger.getColorByLevel(in.Level), levelStr,
|
||||||
} else {
|
))
|
||||||
in.addStringToBuffer(buffer, levelStr)
|
} else {
|
||||||
|
in.addStringToBuffer(buffer, levelStr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if in.TraceId != "" {
|
if in.TraceId != "" {
|
||||||
@ -100,14 +102,16 @@ func (in *HandlerInput) getDefaultBuffer(withColor bool) *bytes.Buffer {
|
|||||||
if in.CtxStr != "" {
|
if in.CtxStr != "" {
|
||||||
in.addStringToBuffer(buffer, "{"+in.CtxStr+"}")
|
in.addStringToBuffer(buffer, "{"+in.CtxStr+"}")
|
||||||
}
|
}
|
||||||
if in.Prefix != "" {
|
if in.Logger.config.HeaderPrint {
|
||||||
in.addStringToBuffer(buffer, in.Prefix)
|
if in.Prefix != "" {
|
||||||
}
|
in.addStringToBuffer(buffer, in.Prefix)
|
||||||
if in.CallerFunc != "" {
|
}
|
||||||
in.addStringToBuffer(buffer, in.CallerFunc)
|
if in.CallerFunc != "" {
|
||||||
}
|
in.addStringToBuffer(buffer, in.CallerFunc)
|
||||||
if in.CallerPath != "" {
|
}
|
||||||
in.addStringToBuffer(buffer, in.CallerPath)
|
if in.CallerPath != "" {
|
||||||
|
in.addStringToBuffer(buffer, in.CallerPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if in.Content != "" {
|
if in.Content != "" {
|
||||||
if in.Stack != "" {
|
if in.Stack != "" {
|
||||||
|
|||||||
@ -44,14 +44,17 @@ type TimerOptions struct {
|
|||||||
Interval time.Duration // Interval is the interval escaped of the timer.
|
Interval time.Duration // Interval is the interval escaped of the timer.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// internalPanic is the custom panic for internal usage.
|
||||||
|
type internalPanic string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StatusReady = 0 // Job or Timer is ready for running.
|
StatusReady = 0 // Job or Timer is ready for running.
|
||||||
StatusRunning = 1 // Job or Timer is already running.
|
StatusRunning = 1 // Job or Timer is already running.
|
||||||
StatusStopped = 2 // Job or Timer is stopped.
|
StatusStopped = 2 // Job or Timer is stopped.
|
||||||
StatusClosed = -1 // Job or Timer is closed and waiting to be deleted.
|
StatusClosed = -1 // Job or Timer is closed and waiting to be deleted.
|
||||||
panicExit = "exit" // panicExit is used for custom job exit with panic.
|
panicExit internalPanic = "exit" // panicExit is used for custom job exit with panic.
|
||||||
defaultTimerInterval = "100" // defaultTimerInterval is the default timer interval in milliseconds.
|
defaultTimerInterval = "100" // defaultTimerInterval is the default timer interval in milliseconds.
|
||||||
commandEnvKeyForInterval = "gf.gtimer.interval" // commandEnvKeyForInterval is the key for command argument or environment configuring default interval duration for timer.
|
commandEnvKeyForInterval = "gf.gtimer.interval" // commandEnvKeyForInterval is the key for command argument or environment configuring default interval duration for timer.
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -154,11 +157,3 @@ func DelayAddOnce(ctx context.Context, delay time.Duration, interval time.Durati
|
|||||||
func DelayAddTimes(ctx context.Context, delay time.Duration, interval time.Duration, times int, job JobFunc) {
|
func DelayAddTimes(ctx context.Context, delay time.Duration, interval time.Duration, times int, job JobFunc) {
|
||||||
defaultTimer.DelayAddTimes(ctx, delay, interval, times, job)
|
defaultTimer.DelayAddTimes(ctx, delay, interval, times, job)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit is used in timing job internally, which exits and marks it closed from timer.
|
|
||||||
// The timing job will be automatically removed from timer later. It uses "panic-recover"
|
|
||||||
// mechanism internally implementing this feature, which is designed for simplification
|
|
||||||
// and convenience.
|
|
||||||
func Exit() {
|
|
||||||
panic(panicExit)
|
|
||||||
}
|
|
||||||
|
|||||||
15
os/gtimer/gtimer_exit.go
Normal file
15
os/gtimer/gtimer_exit.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// 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 gtimer
|
||||||
|
|
||||||
|
// Exit is used in timing job internally, which exits and marks it closed from timer.
|
||||||
|
// The timing job will be automatically removed from timer later. It uses "panic-recover"
|
||||||
|
// mechanism internally implementing this feature, which is designed for simplification
|
||||||
|
// and convenience.
|
||||||
|
func Exit() {
|
||||||
|
panic(panicExit)
|
||||||
|
}
|
||||||
@ -20,7 +20,7 @@ import (
|
|||||||
|
|
||||||
func TestJob_Start_Stop_Close(t *testing.T) {
|
func TestJob_Start_Stop_Close(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
job := timer.Add(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
job := timer.Add(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
@ -43,7 +43,7 @@ func TestJob_Start_Stop_Close(t *testing.T) {
|
|||||||
|
|
||||||
func TestJob_Singleton(t *testing.T) {
|
func TestJob_Singleton(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
job := timer.Add(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
job := timer.Add(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
@ -62,7 +62,7 @@ func TestJob_Singleton(t *testing.T) {
|
|||||||
|
|
||||||
func TestJob_SetTimes(t *testing.T) {
|
func TestJob_SetTimes(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
job := timer.Add(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
job := timer.Add(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
@ -76,7 +76,7 @@ func TestJob_SetTimes(t *testing.T) {
|
|||||||
|
|
||||||
func TestJob_Run(t *testing.T) {
|
func TestJob_Run(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
job := timer.Add(ctx, 1000*time.Millisecond, func(ctx context.Context) {
|
job := timer.Add(ctx, 1000*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
|
|||||||
@ -94,12 +94,12 @@ func TestDelayAdd(t *testing.T) {
|
|||||||
func TestDelayAddEntry(t *testing.T) {
|
func TestDelayAddEntry(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
gtimer.DelayAddEntry(ctx, 200*time.Millisecond, 200*time.Millisecond, func(ctx context.Context) {
|
gtimer.DelayAddEntry(ctx, 500*time.Millisecond, 500*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
}, false, 2, gtimer.StatusReady)
|
}, false, 2, gtimer.StatusReady)
|
||||||
time.Sleep(300 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
t.Assert(array.Len(), 0)
|
t.Assert(array.Len(), 0)
|
||||||
time.Sleep(1000 * time.Millisecond)
|
time.Sleep(2000 * time.Millisecond)
|
||||||
t.Assert(array.Len(), 2)
|
t.Assert(array.Len(), 2)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,13 +18,9 @@ import (
|
|||||||
"github.com/gogf/gf/v2/test/gtest"
|
"github.com/gogf/gf/v2/test/gtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New() *gtimer.Timer {
|
|
||||||
return gtimer.New()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimer_Add_Close(t *testing.T) {
|
func TestTimer_Add_Close(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
//fmt.Println("start", time.Now())
|
//fmt.Println("start", time.Now())
|
||||||
timer.Add(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
timer.Add(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
||||||
@ -53,7 +49,7 @@ func TestTimer_Add_Close(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimer_Start_Stop_Close(t *testing.T) {
|
func TestTimer_Start_Stop_Close(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
timer.Add(ctx, 1000*time.Millisecond, func(ctx context.Context) {
|
timer.Add(ctx, 1000*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
@ -75,7 +71,7 @@ func TestTimer_Start_Stop_Close(t *testing.T) {
|
|||||||
|
|
||||||
func TestJob_Reset(t *testing.T) {
|
func TestJob_Reset(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
job := timer.AddSingleton(ctx, 500*time.Millisecond, func(ctx context.Context) {
|
job := timer.AddSingleton(ctx, 500*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
@ -93,7 +89,7 @@ func TestJob_Reset(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimer_AddSingleton(t *testing.T) {
|
func TestTimer_AddSingleton(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
timer.AddSingleton(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
timer.AddSingleton(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
@ -109,7 +105,7 @@ func TestTimer_AddSingleton(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimer_AddOnce(t *testing.T) {
|
func TestTimer_AddOnce(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
timer.AddOnce(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
timer.AddOnce(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
@ -131,7 +127,7 @@ func TestTimer_AddOnce(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimer_AddTimes(t *testing.T) {
|
func TestTimer_AddTimes(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
timer.AddTimes(ctx, 200*time.Millisecond, 2, func(ctx context.Context) {
|
timer.AddTimes(ctx, 200*time.Millisecond, 2, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
@ -143,7 +139,7 @@ func TestTimer_AddTimes(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimer_DelayAdd(t *testing.T) {
|
func TestTimer_DelayAdd(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
timer.DelayAdd(ctx, 200*time.Millisecond, 200*time.Millisecond, func(ctx context.Context) {
|
timer.DelayAdd(ctx, 200*time.Millisecond, 200*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
@ -157,7 +153,7 @@ func TestTimer_DelayAdd(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimer_DelayAddJob(t *testing.T) {
|
func TestTimer_DelayAddJob(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
timer.DelayAddEntry(ctx, 200*time.Millisecond, 200*time.Millisecond, func(ctx context.Context) {
|
timer.DelayAddEntry(ctx, 200*time.Millisecond, 200*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
@ -171,7 +167,7 @@ func TestTimer_DelayAddJob(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimer_DelayAddSingleton(t *testing.T) {
|
func TestTimer_DelayAddSingleton(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
timer.DelayAddSingleton(ctx, 200*time.Millisecond, 200*time.Millisecond, func(ctx context.Context) {
|
timer.DelayAddSingleton(ctx, 200*time.Millisecond, 200*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
@ -187,7 +183,7 @@ func TestTimer_DelayAddSingleton(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimer_DelayAddOnce(t *testing.T) {
|
func TestTimer_DelayAddOnce(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
timer.DelayAddOnce(ctx, 200*time.Millisecond, 200*time.Millisecond, func(ctx context.Context) {
|
timer.DelayAddOnce(ctx, 200*time.Millisecond, 200*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
@ -205,7 +201,7 @@ func TestTimer_DelayAddOnce(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimer_DelayAddTimes(t *testing.T) {
|
func TestTimer_DelayAddTimes(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
timer.DelayAddTimes(ctx, 200*time.Millisecond, 500*time.Millisecond, 2, func(ctx context.Context) {
|
timer.DelayAddTimes(ctx, 200*time.Millisecond, 500*time.Millisecond, 2, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
@ -246,24 +242,21 @@ func TestTimer_AddLessThanInterval(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimer_AddLeveledJob1(t *testing.T) {
|
func TestTimer_AddLeveledJob1(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
//glog.Print("start")
|
|
||||||
timer.DelayAdd(ctx, 1000*time.Millisecond, 1000*time.Millisecond, func(ctx context.Context) {
|
timer.DelayAdd(ctx, 1000*time.Millisecond, 1000*time.Millisecond, func(ctx context.Context) {
|
||||||
//glog.Print("add")
|
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
})
|
})
|
||||||
time.Sleep(1500 * time.Millisecond)
|
time.Sleep(1500 * time.Millisecond)
|
||||||
t.Assert(array.Len(), 0)
|
t.Assert(array.Len(), 0)
|
||||||
time.Sleep(1300 * time.Millisecond)
|
time.Sleep(1300 * time.Millisecond)
|
||||||
//glog.Print("check")
|
|
||||||
t.Assert(array.Len(), 1)
|
t.Assert(array.Len(), 1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimer_Exit(t *testing.T) {
|
func TestTimer_Exit(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
timer := New()
|
timer := gtimer.New()
|
||||||
array := garray.New(true)
|
array := garray.New(true)
|
||||||
timer.Add(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
timer.Add(ctx, 200*time.Millisecond, func(ctx context.Context) {
|
||||||
array.Append(1)
|
array.Append(1)
|
||||||
|
|||||||
@ -501,6 +501,12 @@ func Test_BuildInFuncPlus(t *testing.T) {
|
|||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
t.Assert(r, `6`)
|
t.Assert(r, `6`)
|
||||||
})
|
})
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
v := gview.New()
|
||||||
|
r, err := v.ParseContent(gctx.New(), "{{1| plus 2}}")
|
||||||
|
t.AssertNil(err)
|
||||||
|
t.Assert(r, `3`)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_BuildInFuncMinus(t *testing.T) {
|
func Test_BuildInFuncMinus(t *testing.T) {
|
||||||
@ -510,6 +516,12 @@ func Test_BuildInFuncMinus(t *testing.T) {
|
|||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
t.Assert(r, `-4`)
|
t.Assert(r, `-4`)
|
||||||
})
|
})
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
v := gview.New()
|
||||||
|
r, err := v.ParseContent(gctx.New(), "{{2 | minus 3}}")
|
||||||
|
t.AssertNil(err)
|
||||||
|
t.Assert(r, `1`)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_BuildInFuncTimes(t *testing.T) {
|
func Test_BuildInFuncTimes(t *testing.T) {
|
||||||
@ -519,6 +531,12 @@ func Test_BuildInFuncTimes(t *testing.T) {
|
|||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
t.Assert(r, `24`)
|
t.Assert(r, `24`)
|
||||||
})
|
})
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
v := gview.New()
|
||||||
|
r, err := v.ParseContent(gctx.New(), "{{2 | times 3}}")
|
||||||
|
t.AssertNil(err)
|
||||||
|
t.Assert(r, `6`)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_BuildInFuncDivide(t *testing.T) {
|
func Test_BuildInFuncDivide(t *testing.T) {
|
||||||
@ -528,6 +546,12 @@ func Test_BuildInFuncDivide(t *testing.T) {
|
|||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
t.Assert(r, `2`)
|
t.Assert(r, `2`)
|
||||||
})
|
})
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
v := gview.New()
|
||||||
|
r, err := v.ParseContent(gctx.New(), "{{2 | divide 4}}")
|
||||||
|
t.AssertNil(err)
|
||||||
|
t.Assert(r, `2`)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_Issue1416(t *testing.T) {
|
func Test_Issue1416(t *testing.T) {
|
||||||
|
|||||||
@ -162,7 +162,7 @@ func Nl2Br(str string, isXhtml ...bool) string {
|
|||||||
}
|
}
|
||||||
switch v {
|
switch v {
|
||||||
case n, r:
|
case n, r:
|
||||||
if (i+1 < length) && (v == r && runes[i+1] == n) || (v == n && runes[i+1] == r) {
|
if (i+1 < length) && ((v == r && runes[i+1] == n) || (v == n && runes[i+1] == r)) {
|
||||||
buf.Write(br)
|
buf.Write(br)
|
||||||
skip = true
|
skip = true
|
||||||
continue
|
continue
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
package gconv
|
package gconv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/encoding/gbinary"
|
"github.com/gogf/gf/v2/encoding/gbinary"
|
||||||
@ -126,6 +127,10 @@ func Int64(any interface{}) int64 {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
// Float64
|
// Float64
|
||||||
return int64(Float64(value))
|
if valueInt64 := Float64(value); math.IsNaN(valueInt64) {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return int64(valueInt64)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
package gconv
|
package gconv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/encoding/gbinary"
|
"github.com/gogf/gf/v2/encoding/gbinary"
|
||||||
@ -109,6 +110,10 @@ func Uint64(any interface{}) uint64 {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
// Float64
|
// Float64
|
||||||
return uint64(Float64(value))
|
if valueFloat64 := Float64(value); math.IsNaN(valueFloat64) {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return uint64(valueFloat64)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
package gconv_test
|
package gconv_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -199,6 +200,7 @@ func Test_Int_All(t *testing.T) {
|
|||||||
t.AssertEQ(gconv.Int(123.456), 123)
|
t.AssertEQ(gconv.Int(123.456), 123)
|
||||||
t.AssertEQ(gconv.Int(boolStruct{}), 0)
|
t.AssertEQ(gconv.Int(boolStruct{}), 0)
|
||||||
t.AssertEQ(gconv.Int(&boolStruct{}), 0)
|
t.AssertEQ(gconv.Int(&boolStruct{}), 0)
|
||||||
|
t.AssertEQ(gconv.Int("NaN"), 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,6 +235,8 @@ func Test_Int8_All(t *testing.T) {
|
|||||||
t.AssertEQ(gconv.Int8(123.456), int8(123))
|
t.AssertEQ(gconv.Int8(123.456), int8(123))
|
||||||
t.AssertEQ(gconv.Int8(boolStruct{}), int8(0))
|
t.AssertEQ(gconv.Int8(boolStruct{}), int8(0))
|
||||||
t.AssertEQ(gconv.Int8(&boolStruct{}), int8(0))
|
t.AssertEQ(gconv.Int8(&boolStruct{}), int8(0))
|
||||||
|
t.AssertEQ(gconv.Int8("NaN"), int8(0))
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,6 +271,7 @@ func Test_Int16_All(t *testing.T) {
|
|||||||
t.AssertEQ(gconv.Int16(123.456), int16(123))
|
t.AssertEQ(gconv.Int16(123.456), int16(123))
|
||||||
t.AssertEQ(gconv.Int16(boolStruct{}), int16(0))
|
t.AssertEQ(gconv.Int16(boolStruct{}), int16(0))
|
||||||
t.AssertEQ(gconv.Int16(&boolStruct{}), int16(0))
|
t.AssertEQ(gconv.Int16(&boolStruct{}), int16(0))
|
||||||
|
t.AssertEQ(gconv.Int16("NaN"), int16(0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,6 +306,7 @@ func Test_Int32_All(t *testing.T) {
|
|||||||
t.AssertEQ(gconv.Int32(123.456), int32(123))
|
t.AssertEQ(gconv.Int32(123.456), int32(123))
|
||||||
t.AssertEQ(gconv.Int32(boolStruct{}), int32(0))
|
t.AssertEQ(gconv.Int32(boolStruct{}), int32(0))
|
||||||
t.AssertEQ(gconv.Int32(&boolStruct{}), int32(0))
|
t.AssertEQ(gconv.Int32(&boolStruct{}), int32(0))
|
||||||
|
t.AssertEQ(gconv.Int32("NaN"), int32(0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,6 +361,7 @@ func Test_Int64_All(t *testing.T) {
|
|||||||
t.AssertEQ(gconv.Int64(123.456), int64(123))
|
t.AssertEQ(gconv.Int64(123.456), int64(123))
|
||||||
t.AssertEQ(gconv.Int64(boolStruct{}), int64(0))
|
t.AssertEQ(gconv.Int64(boolStruct{}), int64(0))
|
||||||
t.AssertEQ(gconv.Int64(&boolStruct{}), int64(0))
|
t.AssertEQ(gconv.Int64(&boolStruct{}), int64(0))
|
||||||
|
t.AssertEQ(gconv.Int64("NaN"), int64(0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,6 +397,7 @@ func Test_Uint_All(t *testing.T) {
|
|||||||
t.AssertEQ(gconv.Uint(123.456), uint(123))
|
t.AssertEQ(gconv.Uint(123.456), uint(123))
|
||||||
t.AssertEQ(gconv.Uint(boolStruct{}), uint(0))
|
t.AssertEQ(gconv.Uint(boolStruct{}), uint(0))
|
||||||
t.AssertEQ(gconv.Uint(&boolStruct{}), uint(0))
|
t.AssertEQ(gconv.Uint(&boolStruct{}), uint(0))
|
||||||
|
t.AssertEQ(gconv.Uint("NaN"), uint(0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,6 +433,7 @@ func Test_Uint8_All(t *testing.T) {
|
|||||||
t.AssertEQ(gconv.Uint8(123.456), uint8(123))
|
t.AssertEQ(gconv.Uint8(123.456), uint8(123))
|
||||||
t.AssertEQ(gconv.Uint8(boolStruct{}), uint8(0))
|
t.AssertEQ(gconv.Uint8(boolStruct{}), uint8(0))
|
||||||
t.AssertEQ(gconv.Uint8(&boolStruct{}), uint8(0))
|
t.AssertEQ(gconv.Uint8(&boolStruct{}), uint8(0))
|
||||||
|
t.AssertEQ(gconv.Uint8("NaN"), uint8(0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -460,6 +469,7 @@ func Test_Uint16_All(t *testing.T) {
|
|||||||
t.AssertEQ(gconv.Uint16(123.456), uint16(123))
|
t.AssertEQ(gconv.Uint16(123.456), uint16(123))
|
||||||
t.AssertEQ(gconv.Uint16(boolStruct{}), uint16(0))
|
t.AssertEQ(gconv.Uint16(boolStruct{}), uint16(0))
|
||||||
t.AssertEQ(gconv.Uint16(&boolStruct{}), uint16(0))
|
t.AssertEQ(gconv.Uint16(&boolStruct{}), uint16(0))
|
||||||
|
t.AssertEQ(gconv.Uint16("NaN"), uint16(0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,6 +505,7 @@ func Test_Uint32_All(t *testing.T) {
|
|||||||
t.AssertEQ(gconv.Uint32(123.456), uint32(123))
|
t.AssertEQ(gconv.Uint32(123.456), uint32(123))
|
||||||
t.AssertEQ(gconv.Uint32(boolStruct{}), uint32(0))
|
t.AssertEQ(gconv.Uint32(boolStruct{}), uint32(0))
|
||||||
t.AssertEQ(gconv.Uint32(&boolStruct{}), uint32(0))
|
t.AssertEQ(gconv.Uint32(&boolStruct{}), uint32(0))
|
||||||
|
t.AssertEQ(gconv.Uint32("NaN"), uint32(0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -549,6 +560,7 @@ func Test_Uint64_All(t *testing.T) {
|
|||||||
t.AssertEQ(gconv.Uint64(123.456), uint64(123))
|
t.AssertEQ(gconv.Uint64(123.456), uint64(123))
|
||||||
t.AssertEQ(gconv.Uint64(boolStruct{}), uint64(0))
|
t.AssertEQ(gconv.Uint64(boolStruct{}), uint64(0))
|
||||||
t.AssertEQ(gconv.Uint64(&boolStruct{}), uint64(0))
|
t.AssertEQ(gconv.Uint64(&boolStruct{}), uint64(0))
|
||||||
|
t.AssertEQ(gconv.Uint64("NaN"), uint64(0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,6 +595,7 @@ func Test_Float32_All(t *testing.T) {
|
|||||||
t.AssertEQ(gconv.Float32(123.456), float32(123.456))
|
t.AssertEQ(gconv.Float32(123.456), float32(123.456))
|
||||||
t.AssertEQ(gconv.Float32(boolStruct{}), float32(0))
|
t.AssertEQ(gconv.Float32(boolStruct{}), float32(0))
|
||||||
t.AssertEQ(gconv.Float32(&boolStruct{}), float32(0))
|
t.AssertEQ(gconv.Float32(&boolStruct{}), float32(0))
|
||||||
|
t.AssertEQ(gconv.Float32("NaN"), float32(math.NaN()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -617,6 +630,7 @@ func Test_Float64_All(t *testing.T) {
|
|||||||
t.AssertEQ(gconv.Float64(123.456), float64(123.456))
|
t.AssertEQ(gconv.Float64(123.456), float64(123.456))
|
||||||
t.AssertEQ(gconv.Float64(boolStruct{}), float64(0))
|
t.AssertEQ(gconv.Float64(boolStruct{}), float64(0))
|
||||||
t.AssertEQ(gconv.Float64(&boolStruct{}), float64(0))
|
t.AssertEQ(gconv.Float64(&boolStruct{}), float64(0))
|
||||||
|
t.AssertEQ(gconv.Float64("NaN"), float64(math.NaN()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -42,7 +42,7 @@ func Try(ctx context.Context, try func(ctx context.Context)) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TryCatch implements try...catch... logistics using internal panic...recover.
|
// TryCatch implements try...catch... logistics using internal panic...recover.
|
||||||
// It automatically calls function `catch` if any exception occurs ans passes the exception as an error.
|
// It automatically calls function `catch` if any exception occurs and passes the exception as an error.
|
||||||
func TryCatch(ctx context.Context, try func(ctx context.Context), catch ...func(ctx context.Context, exception error)) {
|
func TryCatch(ctx context.Context, try func(ctx context.Context), catch ...func(ctx context.Context, exception error)) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if exception := recover(); exception != nil && len(catch) > 0 {
|
if exception := recover(); exception != nil && len(catch) > 0 {
|
||||||
|
|||||||
@ -93,7 +93,8 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// It checks the struct recursively if its attribute is an embedded struct.
|
// It checks the struct recursively if its attribute is an embedded struct.
|
||||||
// Ignore inputParamMap, rules and messages from parent.
|
// Ignore inputParamMap, assoc, rules and messages from parent.
|
||||||
|
validator.assoc = nil
|
||||||
validator.rules = nil
|
validator.rules = nil
|
||||||
validator.messages = nil
|
validator.messages = nil
|
||||||
for _, item := range inputParamMap {
|
for _, item := range inputParamMap {
|
||||||
|
|||||||
@ -557,11 +557,20 @@ func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValue
|
|||||||
})
|
})
|
||||||
|
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
// Ignore data, rules and messages from parent.
|
// Ignore data, assoc, rules and messages from parent.
|
||||||
validator := v.Clone()
|
var (
|
||||||
|
validator = v.Clone()
|
||||||
|
toBeValidatedObject interface{}
|
||||||
|
)
|
||||||
|
if in.Type.Kind() == reflect.Ptr {
|
||||||
|
toBeValidatedObject = reflect.New(in.Type.Elem()).Interface()
|
||||||
|
} else {
|
||||||
|
toBeValidatedObject = reflect.New(in.Type).Interface()
|
||||||
|
}
|
||||||
|
validator.assoc = nil
|
||||||
validator.rules = nil
|
validator.rules = nil
|
||||||
validator.messages = nil
|
validator.messages = nil
|
||||||
if err := validator.Data(reflect.New(in.Type).Interface()).Assoc(in.Value).Run(ctx); err != nil {
|
if err := validator.Data(toBeValidatedObject).Assoc(in.Value).Run(ctx); err != nil {
|
||||||
// It merges the errors into single error map.
|
// It merges the errors into single error map.
|
||||||
for k, m := range err.(*validationError).errors {
|
for k, m := range err.(*validationError).errors {
|
||||||
in.ErrorMaps[k] = m
|
in.ErrorMaps[k] = m
|
||||||
|
|||||||
@ -341,3 +341,83 @@ func Test_CheckStruct_Recursively_MapAttribute(t *testing.T) {
|
|||||||
t.Assert(err, `Student Name is required`)
|
t.Assert(err, `Student Name is required`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/gogf/gf/issues/1983
|
||||||
|
func Test_Issue1983(t *testing.T) {
|
||||||
|
// Error as the attribute Student in Teacher is a initialized struct, which has default value.
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
type Student struct {
|
||||||
|
Name string `v:"required"`
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
type Teacher struct {
|
||||||
|
Students Student
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
teacher = Teacher{}
|
||||||
|
data = g.Map{
|
||||||
|
"students": nil,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
|
||||||
|
t.Assert(err, `The Name field is required`)
|
||||||
|
})
|
||||||
|
// The same as upper, it is not affected by association values.
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
type Student struct {
|
||||||
|
Name string `v:"required"`
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
type Teacher struct {
|
||||||
|
Students Student
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
teacher = Teacher{}
|
||||||
|
data = g.Map{
|
||||||
|
"name": "john",
|
||||||
|
"students": nil,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
|
||||||
|
t.Assert(err, `The Name field is required`)
|
||||||
|
})
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
type Student struct {
|
||||||
|
Name string `v:"required"`
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
type Teacher struct {
|
||||||
|
Students *Student
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
teacher = Teacher{}
|
||||||
|
data = g.Map{
|
||||||
|
"students": nil,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
err := g.Validator().Assoc(data).Data(teacher).Run(ctx)
|
||||||
|
t.AssertNil(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/gogf/gf/issues/1921
|
||||||
|
func Test_Issue1921(t *testing.T) {
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
type SearchOption struct {
|
||||||
|
Size int `v:"max:100"`
|
||||||
|
}
|
||||||
|
type SearchReq struct {
|
||||||
|
Option *SearchOption `json:"option,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
req = SearchReq{
|
||||||
|
Option: &SearchOption{
|
||||||
|
Size: 10000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
err := g.Validator().Data(req).Run(ctx)
|
||||||
|
t.Assert(err, "The Size value `10000` must be equal or lesser than 100")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package gf
|
package gf
|
||||||
|
|
||||||
const VERSION = "v2.1.2"
|
const VERSION = "v2.1.4"
|
||||||
const AUTHORS = "john<john@goframe.org>"
|
const AUTHORS = "john<john@goframe.org>"
|
||||||
|
|||||||
Reference in New Issue
Block a user