improve package gerror, add HasCode/HasError function for package gerror (#2006)

This commit is contained in:
John Guo 2022-07-15 10:49:04 +08:00 committed by GitHub
parent 2c70bb6a00
commit 5d51e9fa2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 512 additions and 424 deletions

View File

@ -13,6 +13,10 @@ import (
"database/sql/driver"
"errors"
"fmt"
"net/url"
"strings"
"time"
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/database/gdb"
@ -24,9 +28,6 @@ import (
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/google/uuid"
"net/url"
"strings"
"time"
)
// Driver is the driver for postgresql database.
@ -148,7 +149,13 @@ func (d *Driver) TableFields(
if link, err = d.SlaveLink(useSchema); err != 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 database = '%s' and `table` = '%s'",
columns, d.GetConfig().Name, table,
)
)
result, err = d.DoSelect(ctx, link, getColumnsSql)
if err != nil {
return nil

View File

@ -218,6 +218,7 @@ ORDER BY
c.relname`,
querySchema,
)
query, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(query))
result, err = d.DoSelect(ctx, link, query)
if err != nil {
return

View File

@ -36,7 +36,6 @@ type Driver struct {
var (
// tableFieldsMap caches the table information retrieved from database.
tableFieldsMap = gmap.New(true)
// Error
ErrorSave = gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by sqlite driver`)
)

View File

@ -4,316 +4,55 @@
// If a copy of the MIT was not distributed with this file,
// 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.
package gerror
import (
"fmt"
"strings"
"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,
}
// IIs is the interface for Is feature.
type IIs interface {
Error() string
Is(target error) bool
}
// 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,
}
// IEqual is the interface for Equal feature.
type IEqual interface {
Error() string
Equal(target error) bool
}
// 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,
}
// ICode is the interface for Code feature.
type ICode interface {
Error() string
Code() gcode.Code
}
// 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,
}
// IStack is the interface for Stack feature.
type IStack interface {
Error() string
Stack() string
}
// 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),
}
// ICause is the interface for Cause feature.
type ICause interface {
Error() string
Cause() error
}
// 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),
}
// ICurrent is the interface for Current feature.
type ICurrent interface {
Error() string
Current() error
}
// 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),
}
}
// 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
// IUnwrap is the interface for Unwrap feature.
type IUnwrap interface {
Error() string
Unwrap() error
}

110
errors/gerror/gerror_api.go Normal file
View 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),
}
}

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

View 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[:])]
}

View File

@ -68,7 +68,7 @@ func (err *Error) Cause() error {
if e, ok := loop.error.(*Error); ok {
// Internal Error struct.
loop = e
} else if e, ok := loop.error.(iCause); ok {
} else if e, ok := loop.error.(ICause); ok {
// Other Error that implements ApiCause interface.
return e.Cause()
} else {
@ -76,6 +76,7 @@ func (err *Error) Cause() error {
}
} else {
// return loop
//
// To be compatible with Case of https://github.com/pkg/errors.
return errors.New(loop.text)
}
@ -97,21 +98,15 @@ func (err *Error) Current() error {
}
}
// Next returns the next level error.
// It returns nil if current level error or the next level error is nil.
func (err *Error) Next() 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 {
if err == nil {
return nil
}
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`.
// 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.
@ -132,19 +127,19 @@ func (err *Error) Equal(target error) bool {
}
// 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 {
if Equal(err, target) {
return true
}
nextErr := err.Next()
nextErr := err.Unwrap()
if nextErr == nil {
return false
}
if Equal(nextErr, target) {
return true
}
if e, ok := nextErr.(iIs); ok {
if e, ok := nextErr.(IIs); ok {
return e.Is(target)
}
return false

View File

@ -17,7 +17,7 @@ func (err *Error) Code() gcode.Code {
return gcode.CodeNil
}
if err.code == gcode.CodeNil {
return Code(err.Next())
return Code(err.Unwrap())
}
return err.code
}

View File

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

View File

@ -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[:])]
}

View File

@ -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) {
gtest.C(t, func(t *gtest.T) {
err := errors.New("1")
@ -381,3 +363,25 @@ func Test_Is(t *testing.T) {
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)
})
}

View File

@ -94,12 +94,12 @@ func TestDelayAdd(t *testing.T) {
func TestDelayAddEntry(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
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)
}, false, 2, gtimer.StatusReady)
time.Sleep(300 * time.Millisecond)
time.Sleep(500 * time.Millisecond)
t.Assert(array.Len(), 0)
time.Sleep(1000 * time.Millisecond)
time.Sleep(2000 * time.Millisecond)
t.Assert(array.Len(), 2)
})
}

View File

@ -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.
// Ignore inputParamMap, rules and messages from parent.
// Ignore inputParamMap, assoc, rules and messages from parent.
validator.assoc = nil
validator.rules = nil
validator.messages = nil
for _, item := range inputParamMap {

View File

@ -557,11 +557,15 @@ func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValue
})
case reflect.Struct:
// Ignore data, rules and messages from parent.
validator := v.Clone()
// Ignore data, assoc, rules and messages from parent.
var (
validator = v.Clone()
toBeValidatedObject = reflect.New(in.Type).Interface()
)
validator.assoc = nil
validator.rules = 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.
for k, m := range err.(*validationError).errors {
in.ErrorMaps[k] = m

View File

@ -341,3 +341,61 @@ func Test_CheckStruct_Recursively_MapAttribute(t *testing.T) {
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)
})
}